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