PR: 5270, 8148, 17071, 6368, 29623 git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@276629 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -21,6 +21,10 @@ Other changes: | |||||
| * A new base class DispatchTask has been added to facilitate elegant | * A new base class DispatchTask has been added to facilitate elegant | ||||
| creation of tasks with multiple actions. | creation of tasks with multiple actions. | ||||
| * Added <target> nested elements to <ant> and <antcall> to allow | |||||
| specification of multiple sub-build targets, which are executed | |||||
| with a single dependency analysis. | |||||
| Changes from Ant 1.6.1 to current Ant 1.6 CVS version | Changes from Ant 1.6.1 to current Ant 1.6 CVS version | ||||
| ===================================================== | ===================================================== | ||||
| @@ -135,6 +135,26 @@ href="../CoreTypes/propertyset.html">propertyset</a>s.</p> | |||||
| <p><em>since Ant 1.6</em>.</p> | <p><em>since Ant 1.6</em>.</p> | ||||
| <h4>target</h4> | |||||
| <p>You can specify multiple targets using nested <target> elements | |||||
| instead of using the target attribute. These will be executed as if | |||||
| Ant had been invoked with a single target whose dependencies are the | |||||
| targets so specified, in the order specified.</p> | |||||
| <table border="1" cellpadding="2" cellspacing="0"> | |||||
| <tr> | |||||
| <td valign="top"><b>Attribute</b></td> | |||||
| <td valign="top"><b>Description</b></td> | |||||
| <td align="center" valign="top"><b>Required</b></td> | |||||
| </tr> | |||||
| <tr> | |||||
| <td valign="top">name</td> | |||||
| <td valign="top">The name of the called target.</td> | |||||
| <td valign="top" align="center">Yes</td> | |||||
| </tr> | |||||
| </table> | |||||
| <p><em>since Ant 1.6.2</em>.</p> | |||||
| <h3>Basedir of the new project</h3> | <h3>Basedir of the new project</h3> | ||||
| <p>The basedir value of the new project is affected by the two | <p>The basedir value of the new project is affected by the two | ||||
| @@ -120,6 +120,26 @@ href="../CoreTypes/propertyset.html">propertyset</a>s.</p> | |||||
| <p><em>since Ant 1.6</em>.</p> | <p><em>since Ant 1.6</em>.</p> | ||||
| <h4>target</h4> | |||||
| <p>You can specify multiple targets using nested <target> elements | |||||
| instead of using the target attribute. These will be executed as if | |||||
| Ant had been invoked with a single target whose dependencies are the | |||||
| targets so specified, in the order specified.</p> | |||||
| <table border="1" cellpadding="2" cellspacing="0"> | |||||
| <tr> | |||||
| <td valign="top"><b>Attribute</b></td> | |||||
| <td valign="top"><b>Description</b></td> | |||||
| <td align="center" valign="top"><b>Required</b></td> | |||||
| </tr> | |||||
| <tr> | |||||
| <td valign="top">name</td> | |||||
| <td valign="top">The name of the called target.</td> | |||||
| <td valign="top" align="center">Yes</td> | |||||
| </tr> | |||||
| </table> | |||||
| <p><em>since Ant 1.6.2</em>.</p> | |||||
| <h3>Examples</h3> | <h3>Examples</h3> | ||||
| <pre> | <pre> | ||||
| <target name="default"> | <target name="default"> | ||||
| @@ -192,4 +192,26 @@ | |||||
| </ant> | </ant> | ||||
| </target> | </target> | ||||
| <target name="blank-target"> | |||||
| <ant antfile="ant.topleveltest.xml"> | |||||
| <target name="" /> | |||||
| </ant> | |||||
| </target> | |||||
| <target name="multiple-targets"> | |||||
| <ant antfile="ant.xml"> | |||||
| <target name="ta" /> | |||||
| <target name="tb" /> | |||||
| <target name="tc" /> | |||||
| </ant> | |||||
| </target> | |||||
| <target name="ta"><echo>ta</echo></target> | |||||
| <target name="tb" depends="da,dc"><echo>tb</echo></target> | |||||
| <target name="tc" depends="db,dc"><echo>tc</echo></target> | |||||
| <target name="da"><echo>da</echo></target> | |||||
| <target name="db"><echo>db</echo></target> | |||||
| <target name="dc"><echo>dc</echo></target> | |||||
| </project> | </project> | ||||
| @@ -50,4 +50,27 @@ | |||||
| <param name="multi" value="SET"/> | <param name="multi" value="SET"/> | ||||
| </antcall> | </antcall> | ||||
| </target> | </target> | ||||
| </project> | |||||
| <target name="blank-target"> | |||||
| <antcall> | |||||
| <target name="" /> | |||||
| </antcall> | |||||
| </target> | |||||
| <target name="multiple-targets"> | |||||
| <antcall> | |||||
| <target name="ta" /> | |||||
| <target name="tb" /> | |||||
| <target name="tc" /> | |||||
| </antcall> | |||||
| </target> | |||||
| <target name="ta"><echo>ta</echo></target> | |||||
| <target name="tb" depends="da,dc"><echo>tb</echo></target> | |||||
| <target name="tc" depends="db,dc"><echo>tc</echo></target> | |||||
| <target name="da"><echo>da</echo></target> | |||||
| <target name="db"><echo>db</echo></target> | |||||
| <target name="dc"><echo>dc</echo></target> | |||||
| </project> | |||||
| @@ -1187,7 +1187,16 @@ public class Project { | |||||
| // exist, and if there is any cycle in the dependency | // exist, and if there is any cycle in the dependency | ||||
| // graph. | // graph. | ||||
| Vector sortedTargets = topoSort(targetName, targets); | Vector sortedTargets = topoSort(targetName, targets); | ||||
| sortedTargets.setSize(sortedTargets.indexOf(targets.get(targetName)) + 1); | |||||
| executeSortedTargets(sortedTargets); | |||||
| } | |||||
| /** | |||||
| * Executes a <CODE>Vector</CODE> of sorted targets. | |||||
| * @param sortedTargets the aforementioned <CODE>Vector</CODE>. | |||||
| */ | |||||
| public void executeSortedTargets(Vector sortedTargets) | |||||
| throws BuildException { | |||||
| Set succeededTargets = new HashSet(); | Set succeededTargets = new HashSet(); | ||||
| BuildException buildException = null; // first build exception | BuildException buildException = null; // first build exception | ||||
| for (Enumeration iter = sortedTargets.elements(); | for (Enumeration iter = sortedTargets.elements(); | ||||
| @@ -1245,9 +1254,6 @@ public class Project { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (curtarget.getName().equals(targetName)) { // old exit condition | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| if (buildException != null) { | if (buildException != null) { | ||||
| throw buildException; | throw buildException; | ||||
| @@ -1564,21 +1570,48 @@ public class Project { | |||||
| * targets, or if a named target does not exist. | * targets, or if a named target does not exist. | ||||
| */ | */ | ||||
| public final Vector topoSort(String root, Hashtable targets) | public final Vector topoSort(String root, Hashtable targets) | ||||
| throws BuildException { | |||||
| return topoSort(new String[] {root}, targets); | |||||
| } | |||||
| /** | |||||
| * Topologically sorts a set of targets. | |||||
| * | |||||
| * @param root <CODE>String[]</CODE> containing the names of the root targets. | |||||
| * The sort is created in such a way that the sequence of Targets | |||||
| * up to the root target is the minimum possible such sequence. | |||||
| * Must not be <code>null</code>. | |||||
| * @param targets A map of names to targets (String to Target). | |||||
| * Must not be <code>null</code>. | |||||
| * @return a vector of Target objects in sorted order. | |||||
| * @exception BuildException if there is a cyclic dependency among the | |||||
| * targets, or if a named target does not exist. | |||||
| */ | |||||
| public final Vector topoSort(String[] root, Hashtable targets) | |||||
| throws BuildException { | throws BuildException { | ||||
| Vector ret = new Vector(); | Vector ret = new Vector(); | ||||
| Hashtable state = new Hashtable(); | Hashtable state = new Hashtable(); | ||||
| Stack visiting = new Stack(); | Stack visiting = new Stack(); | ||||
| // We first run a DFS based sort using the root as the starting node. | |||||
| // This creates the minimum sequence of Targets to the root node. | |||||
| // We first run a DFS based sort using each root as a starting node. | |||||
| // This creates the minimum sequence of Targets to the root node(s). | |||||
| // We then do a sort on any remaining unVISITED targets. | // We then do a sort on any remaining unVISITED targets. | ||||
| // This is unnecessary for doing our build, but it catches | // This is unnecessary for doing our build, but it catches | ||||
| // circular dependencies or missing Targets on the entire | // circular dependencies or missing Targets on the entire | ||||
| // dependency tree, not just on the Targets that depend on the | // dependency tree, not just on the Targets that depend on the | ||||
| // build Target. | // build Target. | ||||
| tsort(root, targets, state, visiting, ret); | |||||
| log("Build sequence for target `" + root + "' is " + ret, MSG_VERBOSE); | |||||
| for (int i = 0; i < root.length; i++) { | |||||
| tsort(root[i], targets, state, visiting, ret); | |||||
| } | |||||
| StringBuffer buf = new StringBuffer("Build sequence for target(s)"); | |||||
| for (int j = 0; j < root.length; j++) { | |||||
| buf.append((j == 0) ? " `" : ", `").append(root[j]).append('\''); | |||||
| } | |||||
| buf.append(" is " + ret); | |||||
| log(buf.toString(), MSG_VERBOSE); | |||||
| for (Enumeration en = targets.keys(); en.hasMoreElements();) { | for (Enumeration en = targets.keys(); en.hasMoreElements();) { | ||||
| String curTarget = (String) en.nextElement(); | String curTarget = (String) en.nextElement(); | ||||
| String st = (String) state.get(curTarget); | String st = (String) state.get(curTarget); | ||||
| @@ -71,9 +71,6 @@ public class Ant extends Task { | |||||
| */ | */ | ||||
| private String antFile = null; | private String antFile = null; | ||||
| /** the target to call if any */ | |||||
| private String target = null; | |||||
| /** the output */ | /** the output */ | ||||
| private String output = null; | private String output = null; | ||||
| @@ -98,6 +95,12 @@ public class Ant extends Task { | |||||
| /** the sets of properties to pass to the new project */ | /** the sets of properties to pass to the new project */ | ||||
| private Vector propertySets = new Vector(); | private Vector propertySets = new Vector(); | ||||
| /** the targets to call on the new project */ | |||||
| private Vector targets = new Vector(); | |||||
| /** whether the target attribute was specified **/ | |||||
| private boolean targetAttributeSet = false; | |||||
| /** | /** | ||||
| * If true, pass all properties to the new Ant project. | * If true, pass all properties to the new Ant project. | ||||
| * Defaults to true. | * Defaults to true. | ||||
| @@ -285,7 +288,7 @@ public class Ant extends Task { | |||||
| public void execute() throws BuildException { | public void execute() throws BuildException { | ||||
| File savedDir = dir; | File savedDir = dir; | ||||
| String savedAntFile = antFile; | String savedAntFile = antFile; | ||||
| String savedTarget = target; | |||||
| Vector locals = new Vector(targets); | |||||
| try { | try { | ||||
| if (newProject == null) { | if (newProject == null) { | ||||
| reinit(); | reinit(); | ||||
| @@ -317,8 +320,9 @@ public class Ant extends Task { | |||||
| File file = FileUtils.newFileUtils().resolveFile(dir, antFile); | File file = FileUtils.newFileUtils().resolveFile(dir, antFile); | ||||
| antFile = file.getAbsolutePath(); | antFile = file.getAbsolutePath(); | ||||
| log("calling target " + (target != null ? target : "[default]") | |||||
| + " in build file " + antFile, Project.MSG_VERBOSE); | |||||
| log("calling target(s) " | |||||
| + ((locals.size() == 0) ? locals.toString() : "[default]") | |||||
| + " in build file " + antFile, Project.MSG_VERBOSE); | |||||
| newProject.setUserProperty("ant.file" , antFile); | newProject.setUserProperty("ant.file" , antFile); | ||||
| String thisAntFile = getProject().getProperty("ant.file"); | String thisAntFile = getProject().getProperty("ant.file"); | ||||
| @@ -348,8 +352,11 @@ public class Ant extends Task { | |||||
| ex, getLocation()); | ex, getLocation()); | ||||
| } | } | ||||
| if (target == null) { | |||||
| target = newProject.getDefaultTarget(); | |||||
| if (locals.size() == 0) { | |||||
| String defaultTarget = newProject.getDefaultTarget(); | |||||
| if (defaultTarget != null) { | |||||
| locals.add(defaultTarget); | |||||
| } | |||||
| } | } | ||||
| if (newProject.getProperty("ant.file") | if (newProject.getProperty("ant.file") | ||||
| @@ -358,13 +365,18 @@ public class Ant extends Task { | |||||
| String owningTargetName = getOwningTarget().getName(); | String owningTargetName = getOwningTarget().getName(); | ||||
| if (owningTargetName.equals(target)) { | |||||
| if (locals.contains(owningTargetName)) { | |||||
| throw new BuildException(getTaskName() + " task calling " | throw new BuildException(getTaskName() + " task calling " | ||||
| + "its own parent target."); | + "its own parent target."); | ||||
| } else { | } else { | ||||
| Target other = | |||||
| (Target) getProject().getTargets().get(target); | |||||
| if (other != null && other.dependsOn(owningTargetName)) { | |||||
| boolean circular = false; | |||||
| for (Iterator it = locals.iterator(); !circular && it.hasNext();) { | |||||
| Target other = (Target)(getProject().getTargets().get( | |||||
| (String)(it.next()))); | |||||
| circular |= (other != null | |||||
| && other.dependsOn(owningTargetName)); | |||||
| } | |||||
| if (circular) { | |||||
| throw new BuildException(getTaskName() | throw new BuildException(getTaskName() | ||||
| + " task calling a target" | + " task calling a target" | ||||
| + " that depends on" | + " that depends on" | ||||
| @@ -377,12 +389,20 @@ public class Ant extends Task { | |||||
| addReferences(); | addReferences(); | ||||
| if (target != null && !"".equals(target)) { | |||||
| if (locals.size() > 0 && !(locals.size() == 1 && locals.get(0) == "")) { | |||||
| Throwable t = null; | Throwable t = null; | ||||
| try { | try { | ||||
| log("Entering " + antFile + "...", Project.MSG_VERBOSE); | log("Entering " + antFile + "...", Project.MSG_VERBOSE); | ||||
| newProject.fireSubBuildStarted(); | newProject.fireSubBuildStarted(); | ||||
| newProject.executeTarget(target); | |||||
| String[] nameArray = | |||||
| (String[])(locals.toArray(new String[locals.size()])); | |||||
| Hashtable targets = newProject.getTargets(); | |||||
| Vector sortedTargets = newProject.topoSort(nameArray, targets); | |||||
| sortedTargets.setSize(sortedTargets.indexOf(targets.get( | |||||
| locals.lastElement())) + 1); | |||||
| newProject.executeSortedTargets(sortedTargets); | |||||
| } catch (BuildException ex) { | } catch (BuildException ex) { | ||||
| t = ProjectHelper | t = ProjectHelper | ||||
| .addLocationToBuildException(ex, getLocation()); | .addLocationToBuildException(ex, getLocation()); | ||||
| @@ -410,7 +430,6 @@ public class Ant extends Task { | |||||
| } | } | ||||
| dir = savedDir; | dir = savedDir; | ||||
| antFile = savedAntFile; | antFile = savedAntFile; | ||||
| target = savedTarget; | |||||
| } | } | ||||
| } | } | ||||
| @@ -601,7 +620,8 @@ public class Ant extends Task { | |||||
| throw new BuildException("target attribute must not be empty"); | throw new BuildException("target attribute must not be empty"); | ||||
| } | } | ||||
| this.target = s; | |||||
| targets.add(s); | |||||
| targetAttributeSet = true; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -640,6 +660,23 @@ public class Ant extends Task { | |||||
| references.addElement(r); | references.addElement(r); | ||||
| } | } | ||||
| /** | |||||
| * Add a target to this Ant invocation. | |||||
| * @param target the <CODE>TargetElement</CODE> to add. | |||||
| * @since Ant 1.7 | |||||
| */ | |||||
| public void addConfiguredTarget(TargetElement t) { | |||||
| if (targetAttributeSet) { | |||||
| throw new BuildException( | |||||
| "nested target is incompatible with the target attribute"); | |||||
| } | |||||
| String name = t.getName(); | |||||
| if (name.equals("")) { | |||||
| throw new BuildException("target name must not be empty"); | |||||
| } | |||||
| targets.add(name); | |||||
| } | |||||
| /** | /** | ||||
| * Set of properties to pass to the new project. | * Set of properties to pass to the new project. | ||||
| * | * | ||||
| @@ -691,4 +728,34 @@ public class Ant extends Task { | |||||
| return targetid; | return targetid; | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Helper class that implements the nested <target> | |||||
| * element of <ant> and <antcall>. | |||||
| * @since Ant 1.7 | |||||
| */ | |||||
| public static class TargetElement { | |||||
| private String name; | |||||
| /** | |||||
| * Default constructor. | |||||
| */ | |||||
| public TargetElement() {} | |||||
| /** | |||||
| * Set the name of this TargetElement. | |||||
| * @param name the <CODE>String</CODE> target name. | |||||
| */ | |||||
| public void setName(String name) { | |||||
| this.name = name; | |||||
| } | |||||
| /** | |||||
| * Get the name of this TargetElement. | |||||
| * @return <CODE>String</CODE>. | |||||
| */ | |||||
| public String getName() { | |||||
| return name; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -48,12 +48,13 @@ import java.io.IOException; | |||||
| public class CallTarget extends Task { | public class CallTarget extends Task { | ||||
| private Ant callee; | private Ant callee; | ||||
| private String subTarget; | |||||
| // must match the default value of Ant#inheritAll | // must match the default value of Ant#inheritAll | ||||
| private boolean inheritAll = true; | private boolean inheritAll = true; | ||||
| // must match the default value of Ant#inheritRefs | // must match the default value of Ant#inheritRefs | ||||
| private boolean inheritRefs = false; | private boolean inheritRefs = false; | ||||
| private boolean targetSet = false; | |||||
| /** | /** | ||||
| * If true, pass all properties to the new Ant project. | * If true, pass all properties to the new Ant project. | ||||
| * Defaults to true. | * Defaults to true. | ||||
| @@ -93,13 +94,13 @@ public class CallTarget extends Task { | |||||
| init(); | init(); | ||||
| } | } | ||||
| if (subTarget == null) { | |||||
| throw new BuildException("Attribute target is required.", | |||||
| getLocation()); | |||||
| if (!targetSet) { | |||||
| throw new BuildException( | |||||
| "Attribute target or at least one nested target is required.", | |||||
| getLocation()); | |||||
| } | } | ||||
| callee.setAntfile(getProject().getProperty("ant.file")); | callee.setAntfile(getProject().getProperty("ant.file")); | ||||
| callee.setTarget(subTarget); | |||||
| callee.setInheritAll(inheritAll); | callee.setInheritAll(inheritAll); | ||||
| callee.setInheritRefs(inheritRefs); | callee.setInheritRefs(inheritRefs); | ||||
| callee.execute(); | callee.execute(); | ||||
| @@ -143,7 +144,24 @@ public class CallTarget extends Task { | |||||
| * Target to execute, required. | * Target to execute, required. | ||||
| */ | */ | ||||
| public void setTarget(String target) { | public void setTarget(String target) { | ||||
| subTarget = target; | |||||
| if (callee == null) { | |||||
| init(); | |||||
| } | |||||
| callee.setTarget(target); | |||||
| targetSet = true; | |||||
| } | |||||
| /** | |||||
| * Target element identifying a data type to carry | |||||
| * over to the invoked target. | |||||
| * @since Ant 1.6.2 | |||||
| */ | |||||
| public void addConfiguredTarget(Ant.TargetElement t) { | |||||
| if (callee == null) { | |||||
| init(); | |||||
| } | |||||
| callee.addConfiguredTarget(t); | |||||
| targetSet = true; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -295,6 +295,14 @@ public class AntTest extends BuildFileTest { | |||||
| project.removeBuildListener(pcFoo); | project.removeBuildListener(pcFoo); | ||||
| } | } | ||||
| public void testBlankTarget() { | |||||
| expectBuildException("blank-target", "target name must not be empty"); | |||||
| } | |||||
| public void testMultipleTargets() { | |||||
| expectLog("multiple-targets", "tadadctbdbtc"); | |||||
| } | |||||
| private class BasedirChecker implements BuildListener { | private class BasedirChecker implements BuildListener { | ||||
| private String[] expectedBasedirs; | private String[] expectedBasedirs; | ||||
| private int calls = 0; | private int calls = 0; | ||||
| @@ -55,6 +55,14 @@ public class CallTargetTest extends BuildFileTest { | |||||
| assertLogContaining("multi is SETmulti is SET"); | assertLogContaining("multi is SETmulti is SET"); | ||||
| } | } | ||||
| public void testBlankTarget() { | |||||
| expectBuildException("blank-target", "target name must not be empty"); | |||||
| } | |||||
| public void testMultipleTargets() { | |||||
| expectLog("multiple-targets", "tadadctbdbtc"); | |||||
| } | |||||
| public void tearDown() { | public void tearDown() { | ||||
| project.executeTarget("cleanup"); | project.executeTarget("cleanup"); | ||||
| } | } | ||||