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