1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.PrintStream;
25 import java.lang.reflect.Method;
26 import java.util.Enumeration;
27 import java.util.Hashtable;
28 import java.util.Iterator;
29 import java.util.Vector;
30 import java.util.Set;
31 import java.util.HashSet;
32 import org.apache.tools.ant.BuildException;
33 import org.apache.tools.ant.BuildListener;
34 import org.apache.tools.ant.DefaultLogger;
35 import org.apache.tools.ant.Project;
36 import org.apache.tools.ant.ProjectComponent;
37 import org.apache.tools.ant.ProjectHelper;
38 import org.apache.tools.ant.Target;
39 import org.apache.tools.ant.Task;
40 import org.apache.tools.ant.MagicNames;
41 import org.apache.tools.ant.Main;
42 import org.apache.tools.ant.types.PropertySet;
43 import org.apache.tools.ant.util.FileUtils;
44
45 /**
46 * Build a sub-project.
47 *
48 * <pre>
49 * <target name="foo" depends="init">
50 * <ant antfile="build.xml" target="bar" >
51 * <property name="property1" value="aaaaa" />
52 * <property name="foo" value="baz" />
53 * </ant></span>
54 * </target></span>
55 *
56 * <target name="bar" depends="init">
57 * <echo message="prop is ${property1} ${foo}" />
58 * </target>
59 * </pre>
60 *
61 *
62 * @since Ant 1.1
63 *
64 * @ant.task category="control"
65 */
66 public class Ant extends Task {
67
68 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
69
70 /** the basedir where is executed the build file */
71 private File dir = null;
72
73 /**
74 * the build.xml file (can be absolute) in this case dir will be
75 * ignored
76 */
77 private String antFile = null;
78
79 /** the output */
80 private String output = null;
81
82 /** should we inherit properties from the parent ? */
83 private boolean inheritAll = true;
84
85 /** should we inherit references from the parent ? */
86 private boolean inheritRefs = false;
87
88 /** the properties to pass to the new project */
89 private Vector properties = new Vector();
90
91 /** the references to pass to the new project */
92 private Vector references = new Vector();
93
94 /** the temporary project created to run the build file */
95 private Project newProject;
96
97 /** The stream to which output is to be written. */
98 private PrintStream out = null;
99
100 /** the sets of properties to pass to the new project */
101 private Vector propertySets = new Vector();
102
103 /** the targets to call on the new project */
104 private Vector targets = new Vector();
105
106 /** whether the target attribute was specified **/
107 private boolean targetAttributeSet = false;
108
109 /**
110 * simple constructor
111 */
112 public Ant() {
113 //default
114 }
115
116 /**
117 * create a task bound to its creator
118 * @param owner owning task
119 */
120 public Ant(Task owner) {
121 bindToOwner(owner);
122 }
123
124
125 /**
126 * If true, pass all properties to the new Ant project.
127 * Defaults to true.
128 * @param value if true pass all properties to the new Ant project.
129 */
130 public void setInheritAll(boolean value) {
131 inheritAll = value;
132 }
133
134 /**
135 * If true, pass all references to the new Ant project.
136 * Defaults to false.
137 * @param value if true, pass all references to the new Ant project
138 */
139 public void setInheritRefs(boolean value) {
140 inheritRefs = value;
141 }
142
143 /**
144 * Creates a Project instance for the project to call.
145 */
146 public void init() {
147 newProject = getProject().createSubProject();
148 newProject.setJavaVersionProperty();
149 }
150
151 /**
152 * Called in execute or createProperty (via getNewProject())
153 * if newProject is null.
154 *
155 * <p>This can happen if the same instance of this task is run
156 * twice as newProject is set to null at the end of execute (to
157 * save memory and help the GC).</p>
158 * <p>calls init() again</p>
159 *
160 */
161 private void reinit() {
162 init();
163 }
164
165 /**
166 * Attaches the build listeners of the current project to the new
167 * project, configures a possible logfile, transfers task and
168 * data-type definitions, transfers properties (either all or just
169 * the ones specified as user properties to the current project,
170 * depending on inheritall), transfers the input handler.
171 */
172 private void initializeProject() {
173 newProject.setInputHandler(getProject().getInputHandler());
174
175 Iterator iter = getBuildListeners();
176 while (iter.hasNext()) {
177 newProject.addBuildListener((BuildListener) iter.next());
178 }
179
180 if (output != null) {
181 File outfile = null;
182 if (dir != null) {
183 outfile = FILE_UTILS.resolveFile(dir, output);
184 } else {
185 outfile = getProject().resolveFile(output);
186 }
187 try {
188 out = new PrintStream(new FileOutputStream(outfile));
189 DefaultLogger logger = new DefaultLogger();
190 logger.setMessageOutputLevel(Project.MSG_INFO);
191 logger.setOutputPrintStream(out);
192 logger.setErrorPrintStream(out);
193 newProject.addBuildListener(logger);
194 } catch (IOException ex) {
195 log("Ant: Can't set output to " + output);
196 }
197 }
198 // set user-defined properties
199 getProject().copyUserProperties(newProject);
200
201 if (!inheritAll) {
202 // set Ant's built-in properties separately,
203 // because they are not being inherited.
204 newProject.initProperties();
205
206 } else {
207 // set all properties from calling project
208 addAlmostAll(getProject().getProperties());
209 }
210
211 Enumeration e = propertySets.elements();
212 while (e.hasMoreElements()) {
213 PropertySet ps = (PropertySet) e.nextElement();
214 addAlmostAll(ps.getProperties());
215 }
216 }
217
218 /**
219 * Handles output.
220 * Send it the the new project if is present, otherwise
221 * call the super class.
222 * @param outputToHandle The string output to output.
223 * @see Task#handleOutput(String)
224 * @since Ant 1.5
225 */
226 public void handleOutput(String outputToHandle) {
227 if (newProject != null) {
228 newProject.demuxOutput(outputToHandle, false);
229 } else {
230 super.handleOutput(outputToHandle);
231 }
232 }
233
234 /**
235 * Handles input.
236 * Deleate to the created project, if present, otherwise
237 * call the super class.
238 * @param buffer the buffer into which data is to be read.
239 * @param offset the offset into the buffer at which data is stored.
240 * @param length the amount of data to read.
241 *
242 * @return the number of bytes read.
243 *
244 * @exception IOException if the data cannot be read.
245 * @see Task#handleInput(byte[], int, int)
246 * @since Ant 1.6
247 */
248 public int handleInput(byte[] buffer, int offset, int length)
249 throws IOException {
250 if (newProject != null) {
251 return newProject.demuxInput(buffer, offset, length);
252 }
253 return super.handleInput(buffer, offset, length);
254 }
255
256 /**
257 * Handles output.
258 * Send it the the new project if is present, otherwise
259 * call the super class.
260 * @param toFlush The string to output.
261 * @see Task#handleFlush(String)
262 * @since Ant 1.5.2
263 */
264 public void handleFlush(String toFlush) {
265 if (newProject != null) {
266 newProject.demuxFlush(toFlush, false);
267 } else {
268 super.handleFlush(toFlush);
269 }
270 }
271
272 /**
273 * Handle error output.
274 * Send it the the new project if is present, otherwise
275 * call the super class.
276 * @param errorOutputToHandle The string to output.
277 *
278 * @see Task#handleErrorOutput(String)
279 * @since Ant 1.5
280 */
281 public void handleErrorOutput(String errorOutputToHandle) {
282 if (newProject != null) {
283 newProject.demuxOutput(errorOutputToHandle, true);
284 } else {
285 super.handleErrorOutput(errorOutputToHandle);
286 }
287 }
288
289 /**
290 * Handle error output.
291 * Send it the the new project if is present, otherwise
292 * call the super class.
293 * @param errorOutputToFlush The string to output.
294 * @see Task#handleErrorFlush(String)
295 * @since Ant 1.5.2
296 */
297 public void handleErrorFlush(String errorOutputToFlush) {
298 if (newProject != null) {
299 newProject.demuxFlush(errorOutputToFlush, true);
300 } else {
301 super.handleErrorFlush(errorOutputToFlush);
302 }
303 }
304
305 /**
306 * Do the execution.
307 * @throws BuildException if a target tries to call itself;
308 * probably also if a BuildException is thrown by the new project.
309 */
310 public void execute() throws BuildException {
311 File savedDir = dir;
312 String savedAntFile = antFile;
313 Vector locals = new Vector(targets);
314 try {
315 getNewProject();
316
317 if (dir == null && inheritAll) {
318 dir = getProject().getBaseDir();
319 }
320
321 initializeProject();
322
323 if (dir != null) {
324 newProject.setBaseDir(dir);
325 if (savedDir != null) {
326 // has been set explicitly
327 newProject.setInheritedProperty(MagicNames.PROJECT_BASEDIR,
328 dir.getAbsolutePath());
329 }
330 } else {
331 dir = getProject().getBaseDir();
332 }
333
334 overrideProperties();
335
336 if (antFile == null) {
337 antFile = Main.DEFAULT_BUILD_FILENAME;
338 }
339
340 File file = FILE_UTILS.resolveFile(dir, antFile);
341 antFile = file.getAbsolutePath();
342
343 log("calling target(s) "
344 + ((locals.size() > 0) ? locals.toString() : "[default]")
345 + " in build file " + antFile, Project.MSG_VERBOSE);
346 newProject.setUserProperty(MagicNames.ANT_FILE , antFile);
347
348 String thisAntFile = getProject().getProperty(MagicNames.ANT_FILE);
349 // Are we trying to call the target in which we are defined (or
350 // the build file if this is a top level task)?
351 if (thisAntFile != null
352 && file.equals(getProject().resolveFile(thisAntFile))
353 && getOwningTarget() != null) {
354
355 if (getOwningTarget().getName().equals("")) {
356 if (getTaskName().equals("antcall")) {
357 throw new BuildException("antcall must not be used at"
358 + " the top level.");
359 }
360 throw new BuildException(getTaskName() + " task at the"
361 + " top level must not invoke"
362 + " its own build file.");
363 }
364 }
365
366 try {
367 ProjectHelper.configureProject(newProject, file);
368 } catch (BuildException ex) {
369 throw ProjectHelper.addLocationToBuildException(
370 ex, getLocation());
371 }
372
373 if (locals.size() == 0) {
374 String defaultTarget = newProject.getDefaultTarget();
375 if (defaultTarget != null) {
376 locals.add(defaultTarget);
377 }
378 }
379
380 if (newProject.getProperty(MagicNames.ANT_FILE)
381 .equals(getProject().getProperty(MagicNames.ANT_FILE))
382 && getOwningTarget() != null) {
383
384 String owningTargetName = getOwningTarget().getName();
385
386 if (locals.contains(owningTargetName)) {
387 throw new BuildException(getTaskName() + " task calling "
388 + "its own parent target.");
389 }
390 boolean circular = false;
391 for (Iterator it = locals.iterator();
392 !circular && it.hasNext();) {
393 Target other =
394 (Target) (getProject().getTargets().get(it.next()));
395 circular |= (other != null
396 && other.dependsOn(owningTargetName));
397 }
398 if (circular) {
399 throw new BuildException(getTaskName()
400 + " task calling a target"
401 + " that depends on"
402 + " its parent target \'"
403 + owningTargetName
404 + "\'.");
405 }
406 }
407
408 addReferences();
409
410 if (locals.size() > 0 && !(locals.size() == 1
411 && "".equals(locals.get(0)))) {
412 BuildException be = null;
413 try {
414 log("Entering " + antFile + "...", Project.MSG_VERBOSE);
415 newProject.fireSubBuildStarted();
416 newProject.executeTargets(locals);
417 } catch (BuildException ex) {
418 be = ProjectHelper
419 .addLocationToBuildException(ex, getLocation());
420 throw be;
421 } finally {
422 log("Exiting " + antFile + ".", Project.MSG_VERBOSE);
423 newProject.fireSubBuildFinished(be);
424 }
425 }
426 } finally {
427 // help the gc
428 newProject = null;
429 Enumeration e = properties.elements();
430 while (e.hasMoreElements()) {
431 Property p = (Property) e.nextElement();
432 p.setProject(null);
433 }
434
435 if (output != null && out != null) {
436 try {
437 out.close();
438 } catch (final Exception ex) {
439 //ignore
440 }
441 }
442 dir = savedDir;
443 antFile = savedAntFile;
444 }
445 }
446
447 /**
448 * Override the properties in the new project with the one
449 * explicitly defined as nested elements here.
450 * @throws BuildException under unknown circumstances.
451 */
452 private void overrideProperties() throws BuildException {
453 // remove duplicate properties - last property wins
454 // Needed for backward compatibility
455 Set set = new HashSet();
456 for (int i = properties.size() - 1; i >= 0; --i) {
457 Property p = (Property) properties.get(i);
458 if (p.getName() != null && !p.getName().equals("")) {
459 if (set.contains(p.getName())) {
460 properties.remove(i);
461 } else {
462 set.add(p.getName());
463 }
464 }
465 }
466 Enumeration e = properties.elements();
467 while (e.hasMoreElements()) {
468 Property p = (Property) e.nextElement();
469 p.setProject(newProject);
470 p.execute();
471 }
472 getProject().copyInheritedProperties(newProject);
473 }
474
475 /**
476 * Add the references explicitly defined as nested elements to the
477 * new project. Also copy over all references that don't override
478 * existing references in the new project if inheritrefs has been
479 * requested.
480 * @throws BuildException if a reference does not have a refid.
481 */
482 private void addReferences() throws BuildException {
483 Hashtable thisReferences
484 = (Hashtable) getProject().getReferences().clone();
485 Hashtable newReferences = newProject.getReferences();
486 Enumeration e;
487 if (references.size() > 0) {
488 for (e = references.elements(); e.hasMoreElements();) {
489 Reference ref = (Reference) e.nextElement();
490 String refid = ref.getRefId();
491 if (refid == null) {
492 throw new BuildException("the refid attribute is required"
493 + " for reference elements");
494 }
495 if (!thisReferences.containsKey(refid)) {
496 log("Parent project doesn't contain any reference '"
497 + refid + "'",
498 Project.MSG_WARN);
499 continue;
500 }
501
502 thisReferences.remove(refid);
503 String toRefid = ref.getToRefid();
504 if (toRefid == null) {
505 toRefid = refid;
506 }
507 copyReference(refid, toRefid);
508 }
509 }
510
511 // Now add all references that are not defined in the
512 // subproject, if inheritRefs is true
513 if (inheritRefs) {
514 for (e = thisReferences.keys(); e.hasMoreElements();) {
515 String key = (String) e.nextElement();
516 if (newReferences.containsKey(key)) {
517 continue;
518 }
519 copyReference(key, key);
520 newProject.inheritIDReferences(getProject());
521 }
522 }
523 }
524
525 /**
526 * Try to clone and reconfigure the object referenced by oldkey in
527 * the parent project and add it to the new project with the key newkey.
528 *
529 * <p>If we cannot clone it, copy the referenced object itself and
530 * keep our fingers crossed.</p>
531 * @param oldKey the reference id in the current project.
532 * @param newKey the reference id in the new project.
533 */
534 private void copyReference(String oldKey, String newKey) {
535 Object orig = getProject().getReference(oldKey);
536 if (orig == null) {
537 log("No object referenced by " + oldKey + ". Can't copy to "
538 + newKey,
539 Project.MSG_WARN);
540 return;
541 }
542
543 Class c = orig.getClass();
544 Object copy = orig;
545 try {
546 Method cloneM = c.getMethod("clone", new Class[0]);
547 if (cloneM != null) {
548 copy = cloneM.invoke(orig, new Object[0]);
549 log("Adding clone of reference " + oldKey, Project.MSG_DEBUG);
550 }
551 } catch (Exception e) {
552 // not Clonable
553 }
554
555
556 if (copy instanceof ProjectComponent) {
557 ((ProjectComponent) copy).setProject(newProject);
558 } else {
559 try {
560 Method setProjectM =
561 c.getMethod("setProject", new Class[] {Project.class});
562 if (setProjectM != null) {
563 setProjectM.invoke(copy, new Object[] {newProject});
564 }
565 } catch (NoSuchMethodException e) {
566 // ignore this if the class being referenced does not have
567 // a set project method.
568 } catch (Exception e2) {
569 String msg = "Error setting new project instance for "
570 + "reference with id " + oldKey;
571 throw new BuildException(msg, e2, getLocation());
572 }
573 }
574 newProject.addReference(newKey, copy);
575 }
576
577 /**
578 * Copies all properties from the given table to the new project -
579 * omitting those that have already been set in the new project as
580 * well as properties named basedir or ant.file.
581 * @param props properties <code>Hashtable</code> to copy to the
582 * new project.
583 * @since Ant 1.6
584 */
585 private void addAlmostAll(Hashtable props) {
586 Enumeration e = props.keys();
587 while (e.hasMoreElements()) {
588 String key = e.nextElement().toString();
589 if (MagicNames.PROJECT_BASEDIR.equals(key) || MagicNames.ANT_FILE.equals(key)) {
590 // basedir and ant.file get special treatment in execute()
591 continue;
592 }
593
594 String value = props.get(key).toString();
595 // don't re-set user properties, avoid the warning message
596 if (newProject.getProperty(key) == null) {
597 // no user property
598 newProject.setNewProperty(key, value);
599 }
600 }
601 }
602
603 /**
604 * The directory to use as a base directory for the new Ant project.
605 * Defaults to the current project's basedir, unless inheritall
606 * has been set to false, in which case it doesn't have a default
607 * value. This will override the basedir setting of the called project.
608 * @param dir new directory as <code>File</code>.
609 */
610 public void setDir(File dir) {
611 this.dir = dir;
612 }
613
614 /**
615 * The build file to use. Defaults to "build.xml". This file is expected
616 * to be a filename relative to the dir attribute given.
617 * @param antFile the <code>String</code> build file name.
618 */
619 public void setAntfile(String antFile) {
620 // @note: it is a string and not a file to handle relative/absolute
621 // otherwise a relative file will be resolved based on the current
622 // basedir.
623 this.antFile = antFile;
624 }
625
626 /**
627 * The target of the new Ant project to execute.
628 * Defaults to the new project's default target.
629 * @param targetToAdd the name of the target to invoke.
630 */
631 public void setTarget(String targetToAdd) {
632 if (targetToAdd.equals("")) {
633 throw new BuildException("target attribute must not be empty");
634 }
635 targets.add(targetToAdd);
636 targetAttributeSet = true;
637 }
638
639 /**
640 * Set the filename to write the output to. This is relative to the value
641 * of the dir attribute if it has been set or to the base directory of the
642 * current project otherwise.
643 * @param outputFile the name of the file to which the output should go.
644 */
645 public void setOutput(String outputFile) {
646 this.output = outputFile;
647 }
648
649 /**
650 * Property to pass to the new project.
651 * The property is passed as a 'user property'.
652 * @return the created <code>Property</code> object.
653 */
654 public Property createProperty() {
655 Property p = new Property(true, getProject());
656 p.setProject(getNewProject());
657 p.setTaskName("property");
658 properties.addElement(p);
659 return p;
660 }
661
662 /**
663 * Add a Reference element identifying a data type to carry
664 * over to the new project.
665 * @param ref <code>Reference</code> to add.
666 */
667 public void addReference(Reference ref) {
668 references.addElement(ref);
669 }
670
671 /**
672 * Add a target to this Ant invocation.
673 * @param t the <code>TargetElement</code> to add.
674 * @since Ant 1.6.3
675 */
676 public void addConfiguredTarget(TargetElement t) {
677 if (targetAttributeSet) {
678 throw new BuildException(
679 "nested target is incompatible with the target attribute");
680 }
681 String name = t.getName();
682 if (name.equals("")) {
683 throw new BuildException("target name must not be empty");
684 }
685 targets.add(name);
686 }
687
688 /**
689 * Add a set of properties to pass to the new project.
690 *
691 * @param ps <code>PropertySet</code> to add.
692 * @since Ant 1.6
693 */
694 public void addPropertyset(PropertySet ps) {
695 propertySets.addElement(ps);
696 }
697
698 /**
699 * Get the (sub)-Project instance currently in use.
700 * @return Project
701 * @since Ant 1.7
702 */
703 protected Project getNewProject() {
704 if (newProject == null) {
705 reinit();
706 }
707 return newProject;
708 }
709
710 /**
711 * @since Ant 1.6.2
712 */
713 private Iterator getBuildListeners() {
714 return getProject().getBuildListeners().iterator();
715 }
716
717 /**
718 * Helper class that implements the nested <reference>
719 * element of <ant> and <antcall>.
720 */
721 public static class Reference
722 extends org.apache.tools.ant.types.Reference {
723
724 /** Creates a reference to be configured by Ant. */
725 public Reference() {
726 super();
727 }
728
729 private String targetid = null;
730
731 /**
732 * Set the id that this reference to be stored under in the
733 * new project.
734 *
735 * @param targetid the id under which this reference will be passed to
736 * the new project. */
737 public void setToRefid(String targetid) {
738 this.targetid = targetid;
739 }
740
741 /**
742 * Get the id under which this reference will be stored in the new
743 * project.
744 *
745 * @return the id of the reference in the new project.
746 */
747 public String getToRefid() {
748 return targetid;
749 }
750 }
751
752 /**
753 * Helper class that implements the nested <target>
754 * element of <ant> and <antcall>.
755 * @since Ant 1.6.3
756 */
757 public static class TargetElement {
758 private String name;
759
760 /**
761 * Default constructor.
762 */
763 public TargetElement() {
764 //default
765 }
766
767 /**
768 * Set the name of this TargetElement.
769 * @param name the <code>String</code> target name.
770 */
771 public void setName(String name) {
772 this.name = name;
773 }
774
775 /**
776 * Get the name of this TargetElement.
777 * @return <code>String</code>.
778 */
779 public String getName() {
780 return name;
781 }
782 }
783 }