This Ant task provides a Gant calling capability. The original intention behind this was to support
+ * continuous integration systems that do not directly support Gant but only Ant. However it also allows
+ * for gradual evolution of an Ant build into a Gant build.
+ *
+ *
Possible attributes are:
+ *
+ *
+ *
file – the path of the Gant script to execute.
+ *
target – the target to execute; must be a single target name. For specifying than a
+ * single target, use nested gantTarget tags.
+ *
+ *
+ *
Both of these are optional. The file 'build.gant' and the default target are used by default. An
+ * error results if there is no default target and no target is specified.
+ *
+ *
Definitions, if needed, are specified using nested definition tags, one for each symbol
+ * to be defined. Each definition tag takes a compulsory name attribute and an
+ * optional value attribute.
+ *
+ * @author Russel Winder
+ */
+public class Gant extends Task {
+ /**
+ * The path to the file to use to drive the Gant build. The default is build.gant. This path is
+ * relative to the basedir of the Ant project if it is set, or the directory in which the job was started
+ * if the basedir is not set.
+ */
+ private String file = "build.gant" ;
+ /**
+ * A class representing a nested definition tag.
+ */
+ public static final class Definition {
+ private String name ;
+ private String value ;
+ public void setName ( final String s ) { name = s ; }
+ public String getName ( ) { return name ; }
+ public void setValue ( final String s ) { value = s ; }
+ public String getValue ( ) { return value ; }
+ }
+ /**
+ * A list of definitions to be set in the Gant instance.
+ */
+ private final List definitions = new ArrayList ( ) ;
+ /**
+ * A class representing a nested target tag.
+ */
+ public static final class GantTarget {
+ private String value ;
+ public void setValue ( final String s ) { value = s ; }
+ public String getValue ( ) { return value ; }
+ }
+ /**
+ * A list of targets to be achieved by the Gant instance.
+ */
+ private final List targets = new ArrayList ( ) ;
+ /**
+ * Set the name of the build file to use. This path is relative to the basedir of the Ant project if it
+ * is set, or the directory in which the job was started if the basedir is not set.
+ *
+ * @param f The name of the file to be used to drive the build.
+ */
+ public void setFile ( final String f ) { file = f ; }
+ /**
+ * Set the target to be achieved.
+ *
+ * @param t The target to achieve.
+ */
+ public void setTarget ( final String t ) {
+ final GantTarget gt = new GantTarget ( ) ;
+ gt.setValue ( t ) ;
+ targets.add ( gt ) ;
+ }
+ /**
+ * Create a node to represent a nested gantTarget tag.
+ *
+ * @return a new GantTarget instance ready for values to be added.
+ */
+ public GantTarget createGantTarget ( ) {
+ final GantTarget gt = new GantTarget ( ) ;
+ targets.add ( gt ) ;
+ return gt ;
+ }
+ /**
+ * Create a node to represent a nested definition tag.
+ *
+ * @return a new Definition instance ready for values to be added.
+ */
+ public Definition createDefinition ( ) {
+ final Definition definition = new Definition ( ) ;
+ definitions.add ( definition ) ;
+ return definition ;
+ }
+ /**
+ * Load the file and then execute it.
+ */
+ @Override public void execute ( ) throws BuildException {
+ //
+ // At first it might seem appropriate to use the Project object from the calling Ant instance as the
+ // Project object used by the AntBuilder object and hence GantBuilder object associated with the Gant
+ // instance we are going to create here. However, if we just use that Project object directly then
+ // there are problems with proper annotation of the lines of output, so it isn't really an option.
+ // Therefore create a new Project instance and set the things appropriately from the original Project
+ // object.
+ //
+ // Issues driving things here are GANT-50 and GANT-80. GANT-50 is about having the correct base
+ // directory for operations, GANT-80 is about ensuring that all output generation actually generated
+ // observable output.
+ //
+ // NB As this class is called Gant, we have to use fully qualified name to get to the Gant main class.
+ //
+ final Project antProject = getOwningTarget ( ).getProject ( ) ;
+ final Project newProject = new Project ( ) ;
+ newProject.init ( ) ;
+ // Deal with GANT-80 by getting all the the loggers from the Ant instance Project object and adding
+ // them to the new Project Object. This was followed up by GANT-91 so the code was amended to copying
+ // over all listeners except the class loader if present.
+ for ( final Object o : antProject.getBuildListeners ( ) ) {
+ final BuildListener listener = (BuildListener) o ;
+ if ( ! ( listener instanceof AntClassLoader ) ) { newProject.addBuildListener ( listener ) ; }
+ }
+ // Deal with GANT-50 by getting the base directory from the Ant instance Project object and use it for
+ // the new Project object.
+ newProject.setBaseDir ( antProject.getBaseDir ( ) ) ;
+ final File gantFile = newProject.resolveFile( file ) ;
+ if ( ! gantFile.exists ( ) ) { throw new BuildException ( "Gantfile does not exist." , getLocation ( ) ) ; }
+ final GantBuilder ant = new GantBuilder ( newProject ) ;
+ final Map environmentParameter = new HashMap ( ) ;
+ environmentParameter.put ( "environment" , "environment" ) ;
+ ant.invokeMethod ( "property" , new Object[] { environmentParameter } ) ;
+ final GantBinding binding = new GantBinding ( ) ;
+ binding.forcedSettingOfVariable ( "ant" , ant ) ;
+ for ( final Definition definition : definitions ) {
+ final Map definitionParameter = new HashMap ( ) ;
+ definitionParameter.put ( "name" , definition.getName ( ) ) ;
+ definitionParameter.put ( "value" , definition.getValue ( ) ) ;
+ ant.invokeMethod ( "property" , new Object[] { definitionParameter } ) ;
+ }
+ final gant.Gant gant = new gant.Gant ( binding ) ;
+ gant.loadScript ( gantFile ) ;
+ final List targetsAsStrings = new ArrayList ( ) ;
+ for ( final GantTarget g : targets ) { targetsAsStrings.add ( g.getValue ( ) ) ; }
+ final int returnCode = gant.processTargets ( targetsAsStrings ) ;
+ if ( returnCode != 0 ) { throw new BuildException ( "Gant execution failed with return code " + returnCode + '.' , getLocation ( ) ) ; }
+ }
+}
diff --git a/jps/jps.iml b/jps/jps.iml
index 3d516e1bcecb..d98f68373730 100644
--- a/jps/jps.iml
+++ b/jps/jps.iml
@@ -2,7 +2,7 @@
-
+
@@ -13,8 +13,8 @@
-
+
diff --git a/jps/lib/javac2-all.jar b/jps/lib/javac2-all.jar
index 188a75d7afe5..cc05f8577aea 100644
Binary files a/jps/lib/javac2-all.jar and b/jps/lib/javac2-all.jar differ
diff --git a/jps/src/org/jetbrains/jps/Jps.groovy b/jps/src/org/jetbrains/jps/Jps.groovy
index b93002f4bb80..2041e4784da9 100644
--- a/jps/src/org/jetbrains/jps/Jps.groovy
+++ b/jps/src/org/jetbrains/jps/Jps.groovy
@@ -51,7 +51,7 @@ final class Jps {
})
- binding.ant.taskdef(name: "layout", classname: "jetbrains.antlayout.tasks.LayoutTask")
+ project.taskdef(name: "layout", classname: "jetbrains.antlayout.tasks.LayoutTask")
}
def Jps(GantBinding binding, Map map) {
diff --git a/jps/src/org/jetbrains/jps/Module.groovy b/jps/src/org/jetbrains/jps/Module.groovy
index 86cd635f052d..ba37cbb50373 100644
--- a/jps/src/org/jetbrains/jps/Module.groovy
+++ b/jps/src/org/jetbrains/jps/Module.groovy
@@ -13,6 +13,7 @@ class Module extends LazyInitializeableObject implements ClasspathItem {
List excludes = []
Map props = [:]
+ Map sourceRootPrefixes = [:]
def Module(project, name, initializer) {
this.project = project;
@@ -66,6 +67,18 @@ class Module extends LazyInitializeableObject implements ClasspathItem {
project.builder.makeModule(this)
}
+ def getOutput() {
+ make()
+ }
+
+ List runtimeClasspath() {
+ project.builder.moduleRuntimeClasspath(this, false)
+ }
+
+ List testRuntimeClasspath() {
+ project.builder.moduleRuntimeClasspath(this, true)
+ }
+
def makeTests() {
project.builder.makeModuleTests(this)
}
diff --git a/jps/src/org/jetbrains/jps/Project.groovy b/jps/src/org/jetbrains/jps/Project.groovy
index a1a1274091b0..05f984f91168 100644
--- a/jps/src/org/jetbrains/jps/Project.groovy
+++ b/jps/src/org/jetbrains/jps/Project.groovy
@@ -28,8 +28,12 @@ class Project {
final Map modules = [:]
final Map libraries = [:]
+ Closure stagePrinter;
+
String targetFolder = "."
+ boolean dryRun = false
+
def Project(GantBinding binding) {
builder = new ProjectBuilder(binding, this)
this.binding = binding;
@@ -105,6 +109,14 @@ class Project {
binding.ant.project.log(message, org.apache.tools.ant.Project.MSG_WARN)
}
+ def stage(String message) {
+ if (stagePrinter != null) {
+ stagePrinter(message)
+ }
+
+ info(message)
+ }
+
def info(String message) {
binding.ant.project.log(message, org.apache.tools.ant.Project.MSG_INFO)
}
@@ -113,8 +125,19 @@ class Project {
builder.buildAll()
}
+ def makeProduction() {
+ builder.buildProduction()
+ }
+
def clean() {
- binding.ant.delete(dir: targetFolder)
+ if (!dryRun) {
+ stage("Cleaning $targetFolder")
+ binding.ant.delete(dir: targetFolder)
+ }
+ else {
+ stage("Cleaning $targetFolder skipped as we're running dry")
+ }
+ builder.clean()
}
def ClasspathItem resolve(Object dep) {
@@ -122,28 +145,24 @@ class Project {
return dep
}
- if (dep instanceof String) {
- String path = dep
- List results = []
- resolvers.each {
- def resolved = it.resolve(path)
- if (resolved != null) results.add(resolved)
- }
+ String path = dep.toString()
- if (results.isEmpty()) {
- if (new File(path).exists()) return new PathEntry(path: path)
-
- error("Cannot resolve $path")
- }
- else if (results.size() > 1) {
- error("Ambigous resolve for $path. All of $results match")
- }
-
- return results[0]
+ List results = []
+ resolvers.each {
+ def resolved = it.resolve(path)
+ if (resolved != null) results.add(resolved)
}
- error("cannot resolve $dep")
- return null
+ if (results.isEmpty()) {
+ if (new File(path).exists()) return new PathEntry(path: path)
+
+ error("Cannot resolve $path")
+ }
+ else if (results.size() > 1) {
+ error("Ambigous resolve for $path. All of $results match")
+ }
+
+ return results[0]
}
def getAt(String key) {
@@ -159,4 +178,36 @@ class Project {
def putAt(String key, Object value) {
props[key] = value
}
+
+ String getPropertyIfDefined(String name) {
+ try {
+ binding[name]
+ }
+ catch (MissingPropertyException mpe) {
+ return null
+ }
+ }
+
+ boolean isDefined(String prop) {
+ try {
+ binding[prop]
+ return true
+ }
+ catch (MissingPropertyException mpe) {
+ return false
+ }
+ }
+
+ def exportProperty(String name, String value) {
+ binding.ant.project.setProperty(name, value)
+ }
+
+ def taskdef(Map args) {
+ binding.ant.taskdef(name: args.name, classname: args.classname) {
+ String additionalClasspathId = getPropertyIfDefined("additional.classpath.id")
+ if (additionalClasspathId != null) {
+ classpath (refid: additionalClasspathId)
+ }
+ }
+ }
}
diff --git a/jps/src/org/jetbrains/jps/ProjectBuilder.groovy b/jps/src/org/jetbrains/jps/ProjectBuilder.groovy
index d444dce3fa1b..49e53965b679 100644
--- a/jps/src/org/jetbrains/jps/ProjectBuilder.groovy
+++ b/jps/src/org/jetbrains/jps/ProjectBuilder.groovy
@@ -34,6 +34,13 @@ class ProjectBuilder {
}
}
+ public def clean() {
+ outputs.clear()
+ testOutputs.clear()
+ cp.clear()
+ testCp.clear()
+ }
+
public def buildAll() {
buildChunks()
chunks.each {
@@ -42,6 +49,13 @@ class ProjectBuilder {
}
}
+ public def buildProduction() {
+ buildChunks()
+ chunks.each {
+ makeChunk(it)
+ }
+ }
+
private ModuleChunk chunkForModule(Module m) {
buildChunks();
mapping[m]
@@ -55,7 +69,7 @@ class ProjectBuilder {
String currentOutput = outputs[chunk]
if (currentOutput != null) return currentOutput
- project.info("Making module ${chunk.name}")
+ project.stage("Making module ${chunk.name}")
def dst = folderForChunkOutput(chunk, classesDir(binding.project), false)
outputs[chunk] = dst
compile(chunk, dst, false)
@@ -71,7 +85,7 @@ class ProjectBuilder {
String currentOutput = testOutputs[chunk]
if (currentOutput != null) return currentOutput
- project.info("Making tests for ${chunk.name}")
+ project.stage("Making tests for ${chunk.name}")
def dst = folderForChunkOutput(chunk, testClassesDir(binding.project), true)
testOutputs[chunk] = dst
compile(chunk, dst, true)
@@ -80,11 +94,11 @@ class ProjectBuilder {
}
private String classesDir(Project project) {
- return new File(project.targetFolder, "classes").absolutePath
+ return new File(project.targetFolder, "production").absolutePath
}
private String testClassesDir(Project project) {
- return new File(project.targetFolder, "testClasses").absolutePath
+ return new File(project.targetFolder, "test").absolutePath
}
private String folderForChunkOutput(ModuleChunk chunk, String basePath, boolean tests) {
@@ -108,25 +122,26 @@ class ProjectBuilder {
(
sourceRoots: sources,
excludes: chunk.excludes,
- classpath: moduleClasspath(chunk, tests),
+ classpath: moduleCompileClasspath(chunk, tests),
targetFolder: dst,
tempRootsToDelete: []
)
- state.print()
-
- project.builders().each {
- it.processModule(chunk, state)
+ if (!project.dryRun) {
+ project.builders().each {
+ it.processModule(chunk, state)
+ }
+ state.tempRootsToDelete.each {
+ binding.ant.delete(dir: it)
+ }
}
- state.tempRootsToDelete.each {
- binding.ant.delete(dir: it)
+ chunk.modules.each {
+ project.exportProperty("module.${it.name}.output.${tests ? "test" : "main"}", dst)
}
-
- binding.ant.project.setProperty("module.${chunk.name}.output.${tests ? "test" : "main"}", dst)
}
- List moduleClasspath(ModuleChunk chunk, boolean test) {
+ List moduleCompileClasspath(ModuleChunk chunk, boolean test) {
Map> map = test ? testCp : cp
if (map[chunk] != null) return map[chunk]
@@ -143,6 +158,18 @@ class ProjectBuilder {
map[chunk] = set.asList()
}
+ List moduleRuntimeClasspath(Module module, boolean test) {
+ return chunkRuntimeClasspath(chunkForModule(module), test)
+ }
+
+ List chunkRuntimeClasspath(ModuleChunk chunk, boolean test) {
+ Set set = new LinkedHashSet()
+ set.addAll(moduleCompileClasspath(chunk, test))
+ set.add(chunkOutput(chunk))
+
+ return set.asList()
+ }
+
private def transitiveClasspath(Object chunkOrModule, boolean test, Set set, Set