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"); | ||||
} | } | ||||