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