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 | |||
| 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 | |||
| ===================================================== | |||
| @@ -135,6 +135,26 @@ href="../CoreTypes/propertyset.html">propertyset</a>s.</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> | |||
| <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> | |||
| <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> | |||
| <pre> | |||
| <target name="default"> | |||
| @@ -192,4 +192,26 @@ | |||
| </ant> | |||
| </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> | |||
| @@ -50,4 +50,27 @@ | |||
| <param name="multi" value="SET"/> | |||
| </antcall> | |||
| </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 | |||
| // graph. | |||
| 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(); | |||
| BuildException buildException = null; // first build exception | |||
| for (Enumeration iter = sortedTargets.elements(); | |||
| @@ -1245,9 +1254,6 @@ public class Project { | |||
| } | |||
| } | |||
| } | |||
| if (curtarget.getName().equals(targetName)) { // old exit condition | |||
| break; | |||
| } | |||
| } | |||
| if (buildException != null) { | |||
| throw buildException; | |||
| @@ -1564,21 +1570,48 @@ public class Project { | |||
| * targets, or if a named target does not exist. | |||
| */ | |||
| 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 { | |||
| Vector ret = new Vector(); | |||
| Hashtable state = new Hashtable(); | |||
| 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. | |||
| // This is unnecessary for doing our build, but it catches | |||
| // circular dependencies or missing Targets on the entire | |||
| // dependency tree, not just on the Targets that depend on the | |||
| // 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();) { | |||
| String curTarget = (String) en.nextElement(); | |||
| String st = (String) state.get(curTarget); | |||
| @@ -71,9 +71,6 @@ public class Ant extends Task { | |||
| */ | |||
| private String antFile = null; | |||
| /** the target to call if any */ | |||
| private String target = null; | |||
| /** the output */ | |||
| private String output = null; | |||
| @@ -98,6 +95,12 @@ public class Ant extends Task { | |||
| /** the sets of properties to pass to the new project */ | |||
| 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. | |||
| * Defaults to true. | |||
| @@ -285,7 +288,7 @@ public class Ant extends Task { | |||
| public void execute() throws BuildException { | |||
| File savedDir = dir; | |||
| String savedAntFile = antFile; | |||
| String savedTarget = target; | |||
| Vector locals = new Vector(targets); | |||
| try { | |||
| if (newProject == null) { | |||
| reinit(); | |||
| @@ -317,8 +320,9 @@ public class Ant extends Task { | |||
| File file = FileUtils.newFileUtils().resolveFile(dir, antFile); | |||
| 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); | |||
| String thisAntFile = getProject().getProperty("ant.file"); | |||
| @@ -348,8 +352,11 @@ public class Ant extends Task { | |||
| 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") | |||
| @@ -358,13 +365,18 @@ public class Ant extends Task { | |||
| String owningTargetName = getOwningTarget().getName(); | |||
| if (owningTargetName.equals(target)) { | |||
| if (locals.contains(owningTargetName)) { | |||
| throw new BuildException(getTaskName() + " task calling " | |||
| + "its own parent target."); | |||
| } 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() | |||
| + " task calling a target" | |||
| + " that depends on" | |||
| @@ -377,12 +389,20 @@ public class Ant extends Task { | |||
| addReferences(); | |||
| if (target != null && !"".equals(target)) { | |||
| if (locals.size() > 0 && !(locals.size() == 1 && locals.get(0) == "")) { | |||
| Throwable t = null; | |||
| try { | |||
| log("Entering " + antFile + "...", Project.MSG_VERBOSE); | |||
| 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) { | |||
| t = ProjectHelper | |||
| .addLocationToBuildException(ex, getLocation()); | |||
| @@ -410,7 +430,6 @@ public class Ant extends Task { | |||
| } | |||
| dir = savedDir; | |||
| antFile = savedAntFile; | |||
| target = savedTarget; | |||
| } | |||
| } | |||
| @@ -601,7 +620,8 @@ public class Ant extends Task { | |||
| 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); | |||
| } | |||
| /** | |||
| * 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. | |||
| * | |||
| @@ -691,4 +728,34 @@ public class Ant extends Task { | |||
| 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 { | |||
| private Ant callee; | |||
| private String subTarget; | |||
| // must match the default value of Ant#inheritAll | |||
| private boolean inheritAll = true; | |||
| // must match the default value of Ant#inheritRefs | |||
| private boolean inheritRefs = false; | |||
| private boolean targetSet = false; | |||
| /** | |||
| * If true, pass all properties to the new Ant project. | |||
| * Defaults to true. | |||
| @@ -93,13 +94,13 @@ public class CallTarget extends Task { | |||
| 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.setTarget(subTarget); | |||
| callee.setInheritAll(inheritAll); | |||
| callee.setInheritRefs(inheritRefs); | |||
| callee.execute(); | |||
| @@ -143,7 +144,24 @@ public class CallTarget extends Task { | |||
| * Target to execute, required. | |||
| */ | |||
| 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); | |||
| } | |||
| 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 String[] expectedBasedirs; | |||
| private int calls = 0; | |||
| @@ -55,6 +55,14 @@ public class CallTargetTest extends BuildFileTest { | |||
| 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() { | |||
| project.executeTarget("cleanup"); | |||
| } | |||