1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Vector;
  21. import java.util.Enumeration;
  22. import org.apache.tools.ant.Task;
  23. import org.apache.tools.ant.Project;
  24. import org.apache.tools.ant.BuildException;
  25. import org.apache.tools.ant.types.Path;
  26. import org.apache.tools.ant.types.DirSet;
  27. import org.apache.tools.ant.types.FileSet;
  28. import org.apache.tools.ant.types.FileList;
  29. import org.apache.tools.ant.types.PropertySet;
  30. import org.apache.tools.ant.types.Reference;
  31. /**
  32. * Calls a given target for all defined sub-builds. This is an extension
  33. * of ant for bulk project execution.
  34. * <p>
  35. * <h2> Use with directories </h2>
  36. * <p>
  37. * subant can be used with directory sets to execute a build from different directories.
  38. * 2 different options are offered
  39. * </p>
  40. * <ul>
  41. * <li>
  42. * run the same build file /somepath/otherpath/mybuild.xml
  43. * with different base directories use the genericantfile attribute
  44. * </li>
  45. * <li>if you want to run directory1/build.xml, directory2/build.xml, ....
  46. * use the antfile attribute. The base directory does not get set by the subant task in this case,
  47. * because you can specify it in each build file.
  48. * </li>
  49. * </ul>
  50. * @since Ant1.6
  51. * @ant.task name="subant" category="control"
  52. */
  53. public class SubAnt
  54. extends Task {
  55. private Path buildpath;
  56. private Ant ant = null;
  57. private String target = null;
  58. private String antfile = "build.xml";
  59. private File genericantfile = null;
  60. private boolean inheritAll = false;
  61. private boolean inheritRefs = false;
  62. private boolean failOnError = true;
  63. private String output = null;
  64. private Vector properties = new Vector();
  65. private Vector references = new Vector();
  66. private Vector propertySets = new Vector();
  67. /**
  68. * Pass output sent to System.out to the new project.
  69. *
  70. * @param output a line of output
  71. * @since Ant 1.6.2
  72. */
  73. public void handleOutput(String output) {
  74. if (ant != null) {
  75. ant.handleOutput(output);
  76. } else {
  77. super.handleOutput(output);
  78. }
  79. }
  80. /**
  81. * Process input into the ant task
  82. *
  83. * @param buffer the buffer into which data is to be read.
  84. * @param offset the offset into the buffer at which data is stored.
  85. * @param length the amount of data to read
  86. *
  87. * @return the number of bytes read
  88. *
  89. * @exception IOException if the data cannot be read
  90. *
  91. * @see Task#handleInput(byte[], int, int)
  92. *
  93. * @since Ant 1.6.2
  94. */
  95. public int handleInput(byte[] buffer, int offset, int length)
  96. throws IOException {
  97. if (ant != null) {
  98. return ant.handleInput(buffer, offset, length);
  99. } else {
  100. return super.handleInput(buffer, offset, length);
  101. }
  102. }
  103. /**
  104. * Pass output sent to System.out to the new project.
  105. *
  106. * @param output The output to log. Should not be <code>null</code>.
  107. *
  108. * @since Ant 1.6.2
  109. */
  110. public void handleFlush(String output) {
  111. if (ant != null) {
  112. ant.handleFlush(output);
  113. } else {
  114. super.handleFlush(output);
  115. }
  116. }
  117. /**
  118. * Pass output sent to System.err to the new project.
  119. *
  120. * @param output The error output to log. Should not be <code>null</code>.
  121. *
  122. * @since Ant 1.6.2
  123. */
  124. public void handleErrorOutput(String output) {
  125. if (ant != null) {
  126. ant.handleErrorOutput(output);
  127. } else {
  128. super.handleErrorOutput(output);
  129. }
  130. }
  131. /**
  132. * Pass output sent to System.err to the new project.
  133. *
  134. * @param output The error output to log. Should not be <code>null</code>.
  135. *
  136. * @since Ant 1.6.2
  137. */
  138. public void handleErrorFlush(String output) {
  139. if (ant != null) {
  140. ant.handleErrorFlush(output);
  141. } else {
  142. super.handleErrorFlush(output);
  143. }
  144. }
  145. /**
  146. * Runs the various sub-builds.
  147. */
  148. public void execute() {
  149. if (buildpath == null) {
  150. throw new BuildException("No buildpath specified");
  151. }
  152. final String[] filenames = buildpath.list();
  153. final int count = filenames.length;
  154. if (count < 1) {
  155. log("No sub-builds to iterate on", Project.MSG_WARN);
  156. return;
  157. }
  158. /*
  159. //REVISIT: there must be cleaner way of doing this, if it is merited at all
  160. if (target == null) {
  161. target = getOwningTarget().getName();
  162. }
  163. */
  164. BuildException buildException = null;
  165. for (int i = 0; i < count; ++i) {
  166. File file = null;
  167. Throwable thrownException = null;
  168. try {
  169. File directory = null;
  170. file = new File(filenames[i]);
  171. if (file.isDirectory()) {
  172. if (genericantfile != null) {
  173. directory = file;
  174. file = genericantfile;
  175. } else {
  176. file = new File(file, antfile);
  177. }
  178. }
  179. execute(file, directory);
  180. } catch (RuntimeException ex) {
  181. if (!(getProject().isKeepGoingMode())) {
  182. throw ex; // throw further
  183. }
  184. thrownException = ex;
  185. } catch (Throwable ex) {
  186. if (!(getProject().isKeepGoingMode())) {
  187. throw new BuildException(ex);
  188. }
  189. thrownException = ex;
  190. }
  191. if (thrownException != null) {
  192. if (thrownException instanceof BuildException) {
  193. log("File '" + file
  194. + "' failed with message '"
  195. + thrownException.getMessage() + "'.", Project.MSG_ERR);
  196. // only the first build exception is reported
  197. if (buildException == null) {
  198. buildException = (BuildException) thrownException;
  199. }
  200. } else {
  201. log("Target '" + file
  202. + "' failed with message '"
  203. + thrownException.getMessage() + "'.", Project.MSG_ERR);
  204. thrownException.printStackTrace(System.err);
  205. if (buildException == null) {
  206. buildException =
  207. new BuildException(thrownException);
  208. }
  209. }
  210. }
  211. }
  212. // check if one of the builds failed in keep going mode
  213. if (buildException != null) {
  214. throw buildException;
  215. }
  216. }
  217. /**
  218. * Runs the given target on the provided build file.
  219. *
  220. * @param file the build file to execute
  221. * @param directory the directory of the current iteration
  222. * @throws BuildException is the file cannot be found, read, is
  223. * a directory, or the target called failed, but only if
  224. * <code>failOnError</code> is <code>true</code>. Otherwise,
  225. * a warning log message is simply output.
  226. */
  227. private void execute(File file, File directory)
  228. throws BuildException {
  229. if (!file.exists() || file.isDirectory() || !file.canRead()) {
  230. String msg = "Invalid file: " + file;
  231. if (failOnError) {
  232. throw new BuildException(msg);
  233. }
  234. log(msg, Project.MSG_WARN);
  235. return;
  236. }
  237. ant = createAntTask(directory);
  238. String antfilename = null;
  239. try {
  240. antfilename = file.getCanonicalPath();
  241. } catch (IOException e) {
  242. throw new BuildException(e);
  243. }
  244. ant.setAntfile(antfilename);
  245. try {
  246. ant.execute();
  247. } catch (BuildException e) {
  248. if (failOnError) {
  249. throw e;
  250. }
  251. log("Failure for target '" + target
  252. + "' of: " + antfilename + "\n"
  253. + e.getMessage(), Project.MSG_WARN);
  254. } catch (Throwable e) {
  255. if (failOnError) {
  256. throw new BuildException(e);
  257. }
  258. log("Failure for target '" + target
  259. + "' of: " + antfilename + "\n"
  260. + e.toString(),
  261. Project.MSG_WARN);
  262. } finally {
  263. ant = null;
  264. }
  265. }
  266. /**
  267. * This method builds the file name to use in conjunction with directories.
  268. *
  269. * <p>Defaults to "build.xml".
  270. * If <code>genericantfile</code> is set, this attribute is ignored.</p>
  271. *
  272. * @param antfile the short build file name. Defaults to "build.xml".
  273. */
  274. public void setAntfile(String antfile) {
  275. this.antfile = antfile;
  276. }
  277. /**
  278. * This method builds a file path to use in conjunction with directories.
  279. *
  280. * <p>Use <code>genericantfile</code>, in order to run the same build file
  281. * with different basedirs.</p>
  282. * If this attribute is set, <code>antfile</code> is ignored.
  283. *
  284. * @param afile (path of the generic ant file, absolute or relative to
  285. * project base directory)
  286. * */
  287. public void setGenericAntfile(File afile) {
  288. this.genericantfile = afile;
  289. }
  290. /**
  291. * Sets whether to fail with a build exception on error, or go on.
  292. *
  293. * @param failOnError the new value for this boolean flag.
  294. */
  295. public void setFailonerror(boolean failOnError) {
  296. this.failOnError = failOnError;
  297. }
  298. /**
  299. * The target to call on the different sub-builds. Set to "" to execute
  300. * the default target.
  301. * @param target the target
  302. * <p>
  303. */
  304. // REVISIT: Defaults to the target name that contains this task if not specified.
  305. public void setTarget(String target) {
  306. this.target = target;
  307. }
  308. /**
  309. * Corresponds to <code><ant></code>'s
  310. * <code>output</code> attribute.
  311. *
  312. * @param s the filename to write the output to.
  313. */
  314. public void setOutput(String s) {
  315. this.output = s;
  316. }
  317. /**
  318. * Corresponds to <code><ant></code>'s
  319. * <code>inheritall</code> attribute.
  320. *
  321. * @param b the new value for this boolean flag.
  322. */
  323. public void setInheritall(boolean b) {
  324. this.inheritAll = b;
  325. }
  326. /**
  327. * Corresponds to <code><ant></code>'s
  328. * <code>inheritrefs</code> attribute.
  329. *
  330. * @param b the new value for this boolean flag.
  331. */
  332. public void setInheritrefs(boolean b) {
  333. this.inheritRefs = b;
  334. }
  335. /**
  336. * Corresponds to <code><ant></code>'s
  337. * nested <code><property></code> element.
  338. *
  339. * @param p the property to pass on explicitly to the sub-build.
  340. */
  341. public void addProperty(Property p) {
  342. properties.addElement(p);
  343. }
  344. /**
  345. * Corresponds to <code><ant></code>'s
  346. * nested <code><reference></code> element.
  347. *
  348. * @param r the reference to pass on explicitly to the sub-build.
  349. */
  350. public void addReference(Ant.Reference r) {
  351. references.addElement(r);
  352. }
  353. /**
  354. * Corresponds to <code><ant></code>'s
  355. * nested <code><propertyset></code> element.
  356. * @param ps the propertset
  357. */
  358. public void addPropertyset(PropertySet ps) {
  359. propertySets.addElement(ps);
  360. }
  361. /**
  362. * Adds a directory set to the implicit build path.
  363. * <p>
  364. * <em>Note that the directories will be added to the build path
  365. * in no particular order, so if order is significant, one should
  366. * use a file list instead!</em>
  367. *
  368. * @param set the directory set to add.
  369. */
  370. public void addDirset(DirSet set) {
  371. getBuildpath().addDirset(set);
  372. }
  373. /**
  374. * Adds a file set to the implicit build path.
  375. * <p>
  376. * <em>Note that the directories will be added to the build path
  377. * in no particular order, so if order is significant, one should
  378. * use a file list instead!</em>
  379. *
  380. * @param set the file set to add.
  381. */
  382. public void addFileset(FileSet set) {
  383. getBuildpath().addFileset(set);
  384. }
  385. /**
  386. * Adds an ordered file list to the implicit build path.
  387. * <p>
  388. * <em>Note that contrary to file and directory sets, file lists
  389. * can reference non-existent files or directories!</em>
  390. *
  391. * @param list the file list to add.
  392. */
  393. public void addFilelist(FileList list) {
  394. getBuildpath().addFilelist(list);
  395. }
  396. /**
  397. * Set the buildpath to be used to find sub-projects.
  398. *
  399. * @param s an Ant Path object containing the buildpath.
  400. */
  401. public void setBuildpath(Path s) {
  402. getBuildpath().append(s);
  403. }
  404. /**
  405. * Creates a nested build path, and add it to the implicit build path.
  406. *
  407. * @return the newly created nested build path.
  408. */
  409. public Path createBuildpath() {
  410. return getBuildpath().createPath();
  411. }
  412. /**
  413. * Creates a nested <code><buildpathelement></code>,
  414. * and add it to the implicit build path.
  415. *
  416. * @return the newly created nested build path element.
  417. */
  418. public Path.PathElement createBuildpathElement() {
  419. return getBuildpath().createPathElement();
  420. }
  421. /**
  422. * Gets the implicit build path, creating it if <code>null</code>.
  423. *
  424. * @return the implicit build path.
  425. */
  426. private Path getBuildpath() {
  427. if (buildpath == null) {
  428. buildpath = new Path(getProject());
  429. }
  430. return buildpath;
  431. }
  432. /**
  433. * Buildpath to use, by reference.
  434. *
  435. * @param r a reference to an Ant Path object containing the buildpath.
  436. */
  437. public void setBuildpathRef(Reference r) {
  438. createBuildpath().setRefid(r);
  439. }
  440. /**
  441. * Creates the <ant> task configured to run a specific target.
  442. *
  443. * @param directory : if not null the directory where the build should run
  444. *
  445. * @return the ant task, configured with the explicit properties and
  446. * references necessary to run the sub-build.
  447. */
  448. private Ant createAntTask(File directory) {
  449. Ant ant = (Ant) getProject().createTask("ant");
  450. ant.setOwningTarget(getOwningTarget());
  451. ant.setTaskName(getTaskName());
  452. ant.init();
  453. if (target != null && target.length() > 0) {
  454. ant.setTarget(target);
  455. }
  456. if (output != null) {
  457. ant.setOutput(output);
  458. }
  459. if (directory != null) {
  460. ant.setDir(directory);
  461. }
  462. ant.setInheritAll(inheritAll);
  463. for (Enumeration i = properties.elements(); i.hasMoreElements();) {
  464. copyProperty(ant.createProperty(), (Property) i.nextElement());
  465. }
  466. for (Enumeration i = propertySets.elements(); i.hasMoreElements();) {
  467. ant.addPropertyset((PropertySet) i.nextElement());
  468. }
  469. ant.setInheritRefs(inheritRefs);
  470. for (Enumeration i = references.elements(); i.hasMoreElements();) {
  471. ant.addReference((Ant.Reference) i.nextElement());
  472. }
  473. return ant;
  474. }
  475. /**
  476. * Assigns an Ant property to another.
  477. *
  478. * @param to the destination property whose content is modified.
  479. * @param from the source property whose content is copied.
  480. */
  481. private static void copyProperty(Property to, Property from) {
  482. to.setName(from.getName());
  483. if (from.getValue() != null) {
  484. to.setValue(from.getValue());
  485. }
  486. if (from.getFile() != null) {
  487. to.setFile(from.getFile());
  488. }
  489. if (from.getResource() != null) {
  490. to.setResource(from.getResource());
  491. }
  492. if (from.getPrefix() != null) {
  493. to.setPrefix(from.getPrefix());
  494. }
  495. if (from.getRefid() != null) {
  496. to.setRefid(from.getRefid());
  497. }
  498. if (from.getEnvironment() != null) {
  499. to.setEnvironment(from.getEnvironment());
  500. }
  501. if (from.getClasspath() != null) {
  502. to.setClasspath(from.getClasspath());
  503. }
  504. }
  505. } // END class SubAnt