001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs;
019:
020: import java.io.File;
021: import java.io.IOException;
022:
023: import java.util.Vector;
024: import java.util.Enumeration;
025:
026: import org.apache.tools.ant.Task;
027: import org.apache.tools.ant.Project;
028: import org.apache.tools.ant.BuildException;
029:
030: import org.apache.tools.ant.types.Path;
031: import org.apache.tools.ant.types.DirSet;
032: import org.apache.tools.ant.types.FileSet;
033: import org.apache.tools.ant.types.FileList;
034: import org.apache.tools.ant.types.PropertySet;
035: import org.apache.tools.ant.types.Reference;
036: import org.apache.tools.ant.types.ResourceCollection;
037:
038: import org.apache.tools.ant.taskdefs.Ant.TargetElement;
039:
040: /**
041: * Calls a given target for all defined sub-builds. This is an extension
042: * of ant for bulk project execution.
043: * <p>
044: * <h2> Use with directories </h2>
045: * <p>
046: * subant can be used with directory sets to execute a build from different directories.
047: * 2 different options are offered
048: * </p>
049: * <ul>
050: * <li>
051: * run the same build file /somepath/otherpath/mybuild.xml
052: * with different base directories use the genericantfile attribute
053: * </li>
054: * <li>if you want to run directory1/build.xml, directory2/build.xml, ....
055: * use the antfile attribute. The base directory does not get set by the subant task in this case,
056: * because you can specify it in each build file.
057: * </li>
058: * </ul>
059: * @since Ant1.6
060: * @ant.task name="subant" category="control"
061: */
062: public class SubAnt extends Task {
063:
064: private Path buildpath;
065:
066: private Ant ant = null;
067: private String subTarget = null;
068: private String antfile = "build.xml";
069: private File genericantfile = null;
070: private boolean verbose = false;
071: private boolean inheritAll = false;
072: private boolean inheritRefs = false;
073: private boolean failOnError = true;
074: private String output = null;
075:
076: private Vector properties = new Vector();
077: private Vector references = new Vector();
078: private Vector propertySets = new Vector();
079:
080: /** the targets to call on the new project */
081: private Vector/*<TargetElement>*/targets = new Vector();
082:
083: /**
084: * Pass output sent to System.out to the new project.
085: *
086: * @param output a line of output
087: * @since Ant 1.6.2
088: */
089: public void handleOutput(String output) {
090: if (ant != null) {
091: ant.handleOutput(output);
092: } else {
093: super .handleOutput(output);
094: }
095: }
096:
097: /**
098: * Process input into the ant task
099: *
100: * @param buffer the buffer into which data is to be read.
101: * @param offset the offset into the buffer at which data is stored.
102: * @param length the amount of data to read
103: *
104: * @return the number of bytes read
105: *
106: * @exception IOException if the data cannot be read
107: *
108: * @see Task#handleInput(byte[], int, int)
109: *
110: * @since Ant 1.6.2
111: */
112: public int handleInput(byte[] buffer, int offset, int length)
113: throws IOException {
114: if (ant != null) {
115: return ant.handleInput(buffer, offset, length);
116: } else {
117: return super .handleInput(buffer, offset, length);
118: }
119: }
120:
121: /**
122: * Pass output sent to System.out to the new project.
123: *
124: * @param output The output to log. Should not be <code>null</code>.
125: *
126: * @since Ant 1.6.2
127: */
128: public void handleFlush(String output) {
129: if (ant != null) {
130: ant.handleFlush(output);
131: } else {
132: super .handleFlush(output);
133: }
134: }
135:
136: /**
137: * Pass output sent to System.err to the new project.
138: *
139: * @param output The error output to log. Should not be <code>null</code>.
140: *
141: * @since Ant 1.6.2
142: */
143: public void handleErrorOutput(String output) {
144: if (ant != null) {
145: ant.handleErrorOutput(output);
146: } else {
147: super .handleErrorOutput(output);
148: }
149: }
150:
151: /**
152: * Pass output sent to System.err to the new project.
153: *
154: * @param output The error output to log. Should not be <code>null</code>.
155: *
156: * @since Ant 1.6.2
157: */
158: public void handleErrorFlush(String output) {
159: if (ant != null) {
160: ant.handleErrorFlush(output);
161: } else {
162: super .handleErrorFlush(output);
163: }
164: }
165:
166: /**
167: * Runs the various sub-builds.
168: */
169: public void execute() {
170: if (buildpath == null) {
171: throw new BuildException("No buildpath specified");
172: }
173:
174: final String[] filenames = buildpath.list();
175: final int count = filenames.length;
176: if (count < 1) {
177: log("No sub-builds to iterate on", Project.MSG_WARN);
178: return;
179: }
180: /*
181: //REVISIT: there must be cleaner way of doing this, if it is merited at all
182: if (subTarget == null) {
183: subTarget = getOwningTarget().getName();
184: }
185: */
186: BuildException buildException = null;
187: for (int i = 0; i < count; ++i) {
188: File file = null;
189: String subdirPath = null;
190: Throwable thrownException = null;
191: try {
192: File directory = null;
193: file = new File(filenames[i]);
194: if (file.isDirectory()) {
195: if (verbose) {
196: subdirPath = file.getPath();
197: log("Entering directory: " + subdirPath + "\n",
198: Project.MSG_INFO);
199: }
200: if (genericantfile != null) {
201: directory = file;
202: file = genericantfile;
203: } else {
204: file = new File(file, antfile);
205: }
206: }
207: execute(file, directory);
208: if (verbose && subdirPath != null) {
209: log("Leaving directory: " + subdirPath + "\n",
210: Project.MSG_INFO);
211: }
212: } catch (RuntimeException ex) {
213: if (!(getProject().isKeepGoingMode())) {
214: if (verbose && subdirPath != null) {
215: log("Leaving directory: " + subdirPath + "\n",
216: Project.MSG_INFO);
217: }
218: throw ex; // throw further
219: }
220: thrownException = ex;
221: } catch (Throwable ex) {
222: if (!(getProject().isKeepGoingMode())) {
223: if (verbose && subdirPath != null) {
224: log("Leaving directory: " + subdirPath + "\n",
225: Project.MSG_INFO);
226: }
227: throw new BuildException(ex);
228: }
229: thrownException = ex;
230: }
231: if (thrownException != null) {
232: if (thrownException instanceof BuildException) {
233: log("File '" + file + "' failed with message '"
234: + thrownException.getMessage() + "'.",
235: Project.MSG_ERR);
236: // only the first build exception is reported
237: if (buildException == null) {
238: buildException = (BuildException) thrownException;
239: }
240: } else {
241: log("Target '" + file + "' failed with message '"
242: + thrownException.getMessage() + "'.",
243: Project.MSG_ERR);
244: thrownException.printStackTrace(System.err);
245: if (buildException == null) {
246: buildException = new BuildException(
247: thrownException);
248: }
249: }
250: if (verbose && subdirPath != null) {
251: log("Leaving directory: " + subdirPath + "\n",
252: Project.MSG_INFO);
253: }
254: }
255: }
256: // check if one of the builds failed in keep going mode
257: if (buildException != null) {
258: throw buildException;
259: }
260: }
261:
262: /**
263: * Runs the given target on the provided build file.
264: *
265: * @param file the build file to execute
266: * @param directory the directory of the current iteration
267: * @throws BuildException is the file cannot be found, read, is
268: * a directory, or the target called failed, but only if
269: * <code>failOnError</code> is <code>true</code>. Otherwise,
270: * a warning log message is simply output.
271: */
272: private void execute(File file, File directory)
273: throws BuildException {
274: if (!file.exists() || file.isDirectory() || !file.canRead()) {
275: String msg = "Invalid file: " + file;
276: if (failOnError) {
277: throw new BuildException(msg);
278: }
279: log(msg, Project.MSG_WARN);
280: return;
281: }
282:
283: ant = createAntTask(directory);
284: String antfilename = file.getAbsolutePath();
285: ant.setAntfile(antfilename);
286: for (int i = 0; i < targets.size(); i++) {
287: TargetElement targetElement = (TargetElement) targets
288: .get(i);
289: ant.addConfiguredTarget(targetElement);
290: }
291:
292: try {
293: ant.execute();
294: } catch (BuildException e) {
295: if (failOnError) {
296: throw e;
297: }
298: log("Failure for target '" + subTarget + "' of: "
299: + antfilename + "\n" + e.getMessage(),
300: Project.MSG_WARN);
301: } catch (Throwable e) {
302: if (failOnError) {
303: throw new BuildException(e);
304: }
305: log("Failure for target '" + subTarget + "' of: "
306: + antfilename + "\n" + e.toString(),
307: Project.MSG_WARN);
308: } finally {
309: ant = null;
310: }
311: }
312:
313: /**
314: * This method builds the file name to use in conjunction with directories.
315: *
316: * <p>Defaults to "build.xml".
317: * If <code>genericantfile</code> is set, this attribute is ignored.</p>
318: *
319: * @param antfile the short build file name. Defaults to "build.xml".
320: */
321: public void setAntfile(String antfile) {
322: this .antfile = antfile;
323: }
324:
325: /**
326: * This method builds a file path to use in conjunction with directories.
327: *
328: * <p>Use <code>genericantfile</code>, in order to run the same build file
329: * with different basedirs.</p>
330: * If this attribute is set, <code>antfile</code> is ignored.
331: *
332: * @param afile (path of the generic ant file, absolute or relative to
333: * project base directory)
334: * */
335: public void setGenericAntfile(File afile) {
336: this .genericantfile = afile;
337: }
338:
339: /**
340: * Sets whether to fail with a build exception on error, or go on.
341: *
342: * @param failOnError the new value for this boolean flag.
343: */
344: public void setFailonerror(boolean failOnError) {
345: this .failOnError = failOnError;
346: }
347:
348: /**
349: * The target to call on the different sub-builds. Set to "" to execute
350: * the default target.
351: * @param target the target
352: * <p>
353: */
354: // REVISIT: Defaults to the target name that contains this task if not specified.
355: public void setTarget(String target) {
356: this .subTarget = target;
357: }
358:
359: /**
360: * Add a target to this Ant invocation.
361: * @param t the <code>TargetElement</code> to add.
362: * @since Ant 1.7
363: */
364: public void addConfiguredTarget(TargetElement t) {
365: String name = t.getName();
366: if ("".equals(name)) {
367: throw new BuildException("target name must not be empty");
368: }
369: targets.add(t);
370: }
371:
372: /**
373: * Enable/ disable verbose log messages showing when each sub-build path is entered/ exited.
374: * The default value is "false".
375: * @param on true to enable verbose mode, false otherwise (default).
376: */
377: public void setVerbose(boolean on) {
378: this .verbose = on;
379: }
380:
381: /**
382: * Corresponds to <code><ant></code>'s
383: * <code>output</code> attribute.
384: *
385: * @param s the filename to write the output to.
386: */
387: public void setOutput(String s) {
388: this .output = s;
389: }
390:
391: /**
392: * Corresponds to <code><ant></code>'s
393: * <code>inheritall</code> attribute.
394: *
395: * @param b the new value for this boolean flag.
396: */
397: public void setInheritall(boolean b) {
398: this .inheritAll = b;
399: }
400:
401: /**
402: * Corresponds to <code><ant></code>'s
403: * <code>inheritrefs</code> attribute.
404: *
405: * @param b the new value for this boolean flag.
406: */
407: public void setInheritrefs(boolean b) {
408: this .inheritRefs = b;
409: }
410:
411: /**
412: * Corresponds to <code><ant></code>'s
413: * nested <code><property></code> element.
414: *
415: * @param p the property to pass on explicitly to the sub-build.
416: */
417: public void addProperty(Property p) {
418: properties.addElement(p);
419: }
420:
421: /**
422: * Corresponds to <code><ant></code>'s
423: * nested <code><reference></code> element.
424: *
425: * @param r the reference to pass on explicitly to the sub-build.
426: */
427: public void addReference(Ant.Reference r) {
428: references.addElement(r);
429: }
430:
431: /**
432: * Corresponds to <code><ant></code>'s
433: * nested <code><propertyset></code> element.
434: * @param ps the propertset
435: */
436: public void addPropertyset(PropertySet ps) {
437: propertySets.addElement(ps);
438: }
439:
440: /**
441: * Adds a directory set to the implicit build path.
442: * <p>
443: * <em>Note that the directories will be added to the build path
444: * in no particular order, so if order is significant, one should
445: * use a file list instead!</em>
446: *
447: * @param set the directory set to add.
448: */
449: public void addDirset(DirSet set) {
450: add(set);
451: }
452:
453: /**
454: * Adds a file set to the implicit build path.
455: * <p>
456: * <em>Note that the directories will be added to the build path
457: * in no particular order, so if order is significant, one should
458: * use a file list instead!</em>
459: *
460: * @param set the file set to add.
461: */
462: public void addFileset(FileSet set) {
463: add(set);
464: }
465:
466: /**
467: * Adds an ordered file list to the implicit build path.
468: * <p>
469: * <em>Note that contrary to file and directory sets, file lists
470: * can reference non-existent files or directories!</em>
471: *
472: * @param list the file list to add.
473: */
474: public void addFilelist(FileList list) {
475: add(list);
476: }
477:
478: /**
479: * Adds a resource collection to the implicit build path.
480: *
481: * @param rc the resource collection to add.
482: * @since Ant 1.7
483: */
484: public void add(ResourceCollection rc) {
485: getBuildpath().add(rc);
486: }
487:
488: /**
489: * Set the buildpath to be used to find sub-projects.
490: *
491: * @param s an Ant Path object containing the buildpath.
492: */
493: public void setBuildpath(Path s) {
494: getBuildpath().append(s);
495: }
496:
497: /**
498: * Creates a nested build path, and add it to the implicit build path.
499: *
500: * @return the newly created nested build path.
501: */
502: public Path createBuildpath() {
503: return getBuildpath().createPath();
504: }
505:
506: /**
507: * Creates a nested <code><buildpathelement></code>,
508: * and add it to the implicit build path.
509: *
510: * @return the newly created nested build path element.
511: */
512: public Path.PathElement createBuildpathElement() {
513: return getBuildpath().createPathElement();
514: }
515:
516: /**
517: * Gets the implicit build path, creating it if <code>null</code>.
518: *
519: * @return the implicit build path.
520: */
521: private Path getBuildpath() {
522: if (buildpath == null) {
523: buildpath = new Path(getProject());
524: }
525: return buildpath;
526: }
527:
528: /**
529: * Buildpath to use, by reference.
530: *
531: * @param r a reference to an Ant Path object containing the buildpath.
532: */
533: public void setBuildpathRef(Reference r) {
534: createBuildpath().setRefid(r);
535: }
536:
537: /**
538: * Creates the <ant> task configured to run a specific target.
539: *
540: * @param directory : if not null the directory where the build should run
541: *
542: * @return the ant task, configured with the explicit properties and
543: * references necessary to run the sub-build.
544: */
545: private Ant createAntTask(File directory) {
546: Ant antTask = new Ant(this );
547: antTask.init();
548: if (subTarget != null && subTarget.length() > 0) {
549: antTask.setTarget(subTarget);
550: }
551:
552: if (output != null) {
553: antTask.setOutput(output);
554: }
555:
556: if (directory != null) {
557: antTask.setDir(directory);
558: }
559:
560: antTask.setInheritAll(inheritAll);
561: for (Enumeration i = properties.elements(); i.hasMoreElements();) {
562: copyProperty(antTask.createProperty(), (Property) i
563: .nextElement());
564: }
565:
566: for (Enumeration i = propertySets.elements(); i
567: .hasMoreElements();) {
568: antTask.addPropertyset((PropertySet) i.nextElement());
569: }
570:
571: antTask.setInheritRefs(inheritRefs);
572: for (Enumeration i = references.elements(); i.hasMoreElements();) {
573: antTask.addReference((Ant.Reference) i.nextElement());
574: }
575:
576: return antTask;
577: }
578:
579: /**
580: * Assigns an Ant property to another.
581: *
582: * @param to the destination property whose content is modified.
583: * @param from the source property whose content is copied.
584: */
585: private static void copyProperty(Property to, Property from) {
586: to.setName(from.getName());
587:
588: if (from.getValue() != null) {
589: to.setValue(from.getValue());
590: }
591: if (from.getFile() != null) {
592: to.setFile(from.getFile());
593: }
594: if (from.getResource() != null) {
595: to.setResource(from.getResource());
596: }
597: if (from.getPrefix() != null) {
598: to.setPrefix(from.getPrefix());
599: }
600: if (from.getRefid() != null) {
601: to.setRefid(from.getRefid());
602: }
603: if (from.getEnvironment() != null) {
604: to.setEnvironment(from.getEnvironment());
605: }
606: if (from.getClasspath() != null) {
607: to.setClasspath(from.getClasspath());
608: }
609: }
610:
611: } // END class SubAnt
|