1. /*
  2. * Copyright 2000-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.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.PrintStream;
  22. import java.lang.reflect.Method;
  23. import java.util.Enumeration;
  24. import java.util.Hashtable;
  25. import java.util.Iterator;
  26. import java.util.Vector;
  27. import java.util.Set;
  28. import java.util.HashSet;
  29. import org.apache.tools.ant.BuildException;
  30. import org.apache.tools.ant.BuildListener;
  31. import org.apache.tools.ant.DefaultLogger;
  32. import org.apache.tools.ant.Project;
  33. import org.apache.tools.ant.ProjectComponent;
  34. import org.apache.tools.ant.ProjectHelper;
  35. import org.apache.tools.ant.Target;
  36. import org.apache.tools.ant.Task;
  37. import org.apache.tools.ant.types.PropertySet;
  38. import org.apache.tools.ant.util.FileUtils;
  39. /**
  40. * Build a sub-project.
  41. *
  42. * <pre>
  43. * <target name="foo" depends="init">
  44. * <ant antfile="build.xml" target="bar" >
  45. * <property name="property1" value="aaaaa" />
  46. * <property name="foo" value="baz" />
  47. * </ant></SPAN>
  48. * </target></SPAN>
  49. *
  50. * <target name="bar" depends="init">
  51. * <echo message="prop is ${property1} ${foo}" />
  52. * </target>
  53. * </pre>
  54. *
  55. *
  56. *
  57. * @since Ant 1.1
  58. *
  59. * @ant.task category="control"
  60. */
  61. public class Ant extends Task {
  62. /** the basedir where is executed the build file */
  63. private File dir = null;
  64. /**
  65. * the build.xml file (can be absolute) in this case dir will be
  66. * ignored
  67. */
  68. private String antFile = null;
  69. /** the target to call if any */
  70. private String target = null;
  71. /** the output */
  72. private String output = null;
  73. /** should we inherit properties from the parent ? */
  74. private boolean inheritAll = true;
  75. /** should we inherit references from the parent ? */
  76. private boolean inheritRefs = false;
  77. /** the properties to pass to the new project */
  78. private Vector properties = new Vector();
  79. /** the references to pass to the new project */
  80. private Vector references = new Vector();
  81. /** the temporary project created to run the build file */
  82. private Project newProject;
  83. /** The stream to which output is to be written. */
  84. private PrintStream out = null;
  85. /** the sets of properties to pass to the new project */
  86. private Vector propertySets = new Vector();
  87. /**
  88. * If true, pass all properties to the new Ant project.
  89. * Defaults to true.
  90. * @param value if true pass all properties to the new Ant project.
  91. */
  92. public void setInheritAll(boolean value) {
  93. inheritAll = value;
  94. }
  95. /**
  96. * If true, pass all references to the new Ant project.
  97. * Defaults to false.
  98. * @param value if true, pass all references to the new Ant project
  99. */
  100. public void setInheritRefs(boolean value) {
  101. inheritRefs = value;
  102. }
  103. /**
  104. * Creates a Project instance for the project to call.
  105. */
  106. public void init() {
  107. newProject = new Project();
  108. newProject.setDefaultInputStream(getProject().getDefaultInputStream());
  109. newProject.setJavaVersionProperty();
  110. }
  111. /**
  112. * Called in execute or createProperty if newProject is null.
  113. *
  114. * <p>This can happen if the same instance of this task is run
  115. * twice as newProject is set to null at the end of execute (to
  116. * save memory and help the GC).</p>
  117. * <p>calls init() again</p>
  118. *
  119. */
  120. private void reinit() {
  121. init();
  122. }
  123. /**
  124. * Attaches the build listeners of the current project to the new
  125. * project, configures a possible logfile, transfers task and
  126. * data-type definitions, transfers properties (either all or just
  127. * the ones specified as user properties to the current project,
  128. * depending on inheritall), transfers the input handler.
  129. */
  130. private void initializeProject() {
  131. newProject.setInputHandler(getProject().getInputHandler());
  132. Iterator iter = getBuildListeners();
  133. while (iter.hasNext()) {
  134. newProject.addBuildListener((BuildListener) iter.next());
  135. }
  136. if (output != null) {
  137. File outfile = null;
  138. if (dir != null) {
  139. outfile = FileUtils.newFileUtils().resolveFile(dir, output);
  140. } else {
  141. outfile = getProject().resolveFile(output);
  142. }
  143. try {
  144. out = new PrintStream(new FileOutputStream(outfile));
  145. DefaultLogger logger = new DefaultLogger();
  146. logger.setMessageOutputLevel(Project.MSG_INFO);
  147. logger.setOutputPrintStream(out);
  148. logger.setErrorPrintStream(out);
  149. newProject.addBuildListener(logger);
  150. } catch (IOException ex) {
  151. log("Ant: Can't set output to " + output);
  152. }
  153. }
  154. getProject().initSubProject(newProject);
  155. // set user-defined properties
  156. getProject().copyUserProperties(newProject);
  157. if (!inheritAll) {
  158. // set Java built-in properties separately,
  159. // b/c we won't inherit them.
  160. newProject.setSystemProperties();
  161. } else {
  162. // set all properties from calling project
  163. addAlmostAll(getProject().getProperties());
  164. }
  165. Enumeration e = propertySets.elements();
  166. while (e.hasMoreElements()) {
  167. PropertySet ps = (PropertySet) e.nextElement();
  168. addAlmostAll(ps.getProperties());
  169. }
  170. }
  171. /**
  172. * Pass output sent to System.out to the new project.
  173. *
  174. * @param output a line of output
  175. * @since Ant 1.5
  176. */
  177. public void handleOutput(String output) {
  178. if (newProject != null) {
  179. newProject.demuxOutput(output, false);
  180. } else {
  181. super.handleOutput(output);
  182. }
  183. }
  184. /**
  185. * Process input into the ant task
  186. *
  187. * @param buffer the buffer into which data is to be read.
  188. * @param offset the offset into the buffer at which data is stored.
  189. * @param length the amount of data to read
  190. *
  191. * @return the number of bytes read
  192. *
  193. * @exception IOException if the data cannot be read
  194. *
  195. * @see Task#handleInput(byte[], int, int)
  196. *
  197. * @since Ant 1.6
  198. */
  199. public int handleInput(byte[] buffer, int offset, int length)
  200. throws IOException {
  201. if (newProject != null) {
  202. return newProject.demuxInput(buffer, offset, length);
  203. } else {
  204. return super.handleInput(buffer, offset, length);
  205. }
  206. }
  207. /**
  208. * Pass output sent to System.out to the new project.
  209. *
  210. * @param output The output to log. Should not be <code>null</code>.
  211. *
  212. * @since Ant 1.5.2
  213. */
  214. public void handleFlush(String output) {
  215. if (newProject != null) {
  216. newProject.demuxFlush(output, false);
  217. } else {
  218. super.handleFlush(output);
  219. }
  220. }
  221. /**
  222. * Pass output sent to System.err to the new project.
  223. *
  224. * @param output The error output to log. Should not be <code>null</code>.
  225. *
  226. * @since Ant 1.5
  227. */
  228. public void handleErrorOutput(String output) {
  229. if (newProject != null) {
  230. newProject.demuxOutput(output, true);
  231. } else {
  232. super.handleErrorOutput(output);
  233. }
  234. }
  235. /**
  236. * Pass output sent to System.err to the new project.
  237. *
  238. * @param output The error output to log. Should not be <code>null</code>.
  239. *
  240. * @since Ant 1.5.2
  241. */
  242. public void handleErrorFlush(String output) {
  243. if (newProject != null) {
  244. newProject.demuxFlush(output, true);
  245. } else {
  246. super.handleErrorFlush(output);
  247. }
  248. }
  249. /**
  250. * Do the execution.
  251. * @throws BuildException if a target tries to call itself
  252. * probably also if a BuildException is thrown by the new project
  253. */
  254. public void execute() throws BuildException {
  255. File savedDir = dir;
  256. String savedAntFile = antFile;
  257. String savedTarget = target;
  258. try {
  259. if (newProject == null) {
  260. reinit();
  261. }
  262. if ((dir == null) && (inheritAll)) {
  263. dir = getProject().getBaseDir();
  264. }
  265. initializeProject();
  266. if (dir != null) {
  267. newProject.setBaseDir(dir);
  268. if (savedDir != null) {
  269. // has been set explicitly
  270. newProject.setInheritedProperty("basedir" ,
  271. dir.getAbsolutePath());
  272. }
  273. } else {
  274. dir = getProject().getBaseDir();
  275. }
  276. overrideProperties();
  277. if (antFile == null) {
  278. antFile = "build.xml";
  279. }
  280. File file = FileUtils.newFileUtils().resolveFile(dir, antFile);
  281. antFile = file.getAbsolutePath();
  282. log("calling target " + (target != null ? target : "[default]")
  283. + " in build file " + antFile, Project.MSG_VERBOSE);
  284. newProject.setUserProperty("ant.file" , antFile);
  285. String thisAntFile = getProject().getProperty("ant.file");
  286. // Are we trying to call the target in which we are defined (or
  287. // the build file if this is a top level task)?
  288. if (thisAntFile != null
  289. && newProject.resolveFile(newProject.getProperty("ant.file"))
  290. .equals(getProject().resolveFile(thisAntFile))
  291. && getOwningTarget() != null) {
  292. if (getOwningTarget().getName().equals("")) {
  293. if (getTaskName().equals("antcall")) {
  294. throw new BuildException("antcall must not be used at"
  295. + " the top level.");
  296. } else {
  297. throw new BuildException(getTaskName() + " task at the"
  298. + " top level must not invoke"
  299. + " its own build file.");
  300. }
  301. }
  302. }
  303. try {
  304. ProjectHelper.configureProject(newProject, new File(antFile));
  305. } catch (BuildException ex) {
  306. throw ProjectHelper.addLocationToBuildException(
  307. ex, getLocation());
  308. }
  309. if (target == null) {
  310. target = newProject.getDefaultTarget();
  311. }
  312. if (newProject.getProperty("ant.file")
  313. .equals(getProject().getProperty("ant.file"))
  314. && getOwningTarget() != null) {
  315. String owningTargetName = getOwningTarget().getName();
  316. if (owningTargetName.equals(target)) {
  317. throw new BuildException(getTaskName() + " task calling "
  318. + "its own parent target.");
  319. } else {
  320. Target other =
  321. (Target) getProject().getTargets().get(target);
  322. if (other != null && other.dependsOn(owningTargetName)) {
  323. throw new BuildException(getTaskName()
  324. + " task calling a target"
  325. + " that depends on"
  326. + " its parent target \'"
  327. + owningTargetName
  328. + "\'.");
  329. }
  330. }
  331. }
  332. addReferences();
  333. if (target != null && !"".equals(target)) {
  334. Throwable t = null;
  335. try {
  336. log("Entering " + antFile + "...", Project.MSG_VERBOSE);
  337. newProject.fireSubBuildStarted();
  338. newProject.executeTarget(target);
  339. } catch (BuildException ex) {
  340. t = ProjectHelper
  341. .addLocationToBuildException(ex, getLocation());
  342. throw (BuildException) t;
  343. } finally {
  344. log("Exiting " + antFile + ".", Project.MSG_VERBOSE);
  345. newProject.fireSubBuildFinished(t);
  346. }
  347. }
  348. } finally {
  349. // help the gc
  350. newProject = null;
  351. Enumeration e = properties.elements();
  352. while (e.hasMoreElements()) {
  353. Property p = (Property) e.nextElement();
  354. p.setProject(null);
  355. }
  356. if (output != null && out != null) {
  357. try {
  358. out.close();
  359. } catch (final Exception ex) {
  360. //ignore
  361. }
  362. }
  363. dir = savedDir;
  364. antFile = savedAntFile;
  365. target = savedTarget;
  366. }
  367. }
  368. /**
  369. * Override the properties in the new project with the one
  370. * explicitly defined as nested elements here.
  371. * @throws BuildException under unknown circumstances
  372. */
  373. private void overrideProperties() throws BuildException {
  374. // remove duplicate properties - last property wins
  375. // Needed for backward compatibility
  376. Set set = new HashSet();
  377. for (int i = properties.size() - 1; i >= 0; --i) {
  378. Property p = (Property) properties.get(i);
  379. if (p.getName() != null && !p.getName().equals("")) {
  380. if (set.contains(p.getName())) {
  381. properties.remove(i);
  382. } else {
  383. set.add(p.getName());
  384. }
  385. }
  386. }
  387. Enumeration e = properties.elements();
  388. while (e.hasMoreElements()) {
  389. Property p = (Property) e.nextElement();
  390. p.setProject(newProject);
  391. p.execute();
  392. }
  393. getProject().copyInheritedProperties(newProject);
  394. }
  395. /**
  396. * Add the references explicitly defined as nested elements to the
  397. * new project. Also copy over all references that don't override
  398. * existing references in the new project if inheritrefs has been
  399. * requested.
  400. * @throws BuildException if a reference does not have a refid
  401. */
  402. private void addReferences() throws BuildException {
  403. Hashtable thisReferences
  404. = (Hashtable) getProject().getReferences().clone();
  405. Hashtable newReferences = newProject.getReferences();
  406. Enumeration e;
  407. if (references.size() > 0) {
  408. for (e = references.elements(); e.hasMoreElements();) {
  409. Reference ref = (Reference) e.nextElement();
  410. String refid = ref.getRefId();
  411. if (refid == null) {
  412. throw new BuildException("the refid attribute is required"
  413. + " for reference elements");
  414. }
  415. if (!thisReferences.containsKey(refid)) {
  416. log("Parent project doesn't contain any reference '"
  417. + refid + "'",
  418. Project.MSG_WARN);
  419. continue;
  420. }
  421. thisReferences.remove(refid);
  422. String toRefid = ref.getToRefid();
  423. if (toRefid == null) {
  424. toRefid = refid;
  425. }
  426. copyReference(refid, toRefid);
  427. }
  428. }
  429. // Now add all references that are not defined in the
  430. // subproject, if inheritRefs is true
  431. if (inheritRefs) {
  432. for (e = thisReferences.keys(); e.hasMoreElements();) {
  433. String key = (String) e.nextElement();
  434. if (newReferences.containsKey(key)) {
  435. continue;
  436. }
  437. copyReference(key, key);
  438. }
  439. }
  440. }
  441. /**
  442. * Try to clone and reconfigure the object referenced by oldkey in
  443. * the parent project and add it to the new project with the key
  444. * newkey.
  445. *
  446. * <p>If we cannot clone it, copy the referenced object itself and
  447. * keep our fingers crossed.</p>
  448. */
  449. private void copyReference(String oldKey, String newKey) {
  450. Object orig = getProject().getReference(oldKey);
  451. if (orig == null) {
  452. log("No object referenced by " + oldKey + ". Can't copy to "
  453. + newKey,
  454. Project.MSG_WARN);
  455. return;
  456. }
  457. Class c = orig.getClass();
  458. Object copy = orig;
  459. try {
  460. Method cloneM = c.getMethod("clone", new Class[0]);
  461. if (cloneM != null) {
  462. copy = cloneM.invoke(orig, new Object[0]);
  463. log("Adding clone of reference " + oldKey, Project.MSG_DEBUG);
  464. }
  465. } catch (Exception e) {
  466. // not Clonable
  467. }
  468. if (copy instanceof ProjectComponent) {
  469. ((ProjectComponent) copy).setProject(newProject);
  470. } else {
  471. try {
  472. Method setProjectM =
  473. c.getMethod("setProject", new Class[] {Project.class});
  474. if (setProjectM != null) {
  475. setProjectM.invoke(copy, new Object[] {newProject});
  476. }
  477. } catch (NoSuchMethodException e) {
  478. // ignore this if the class being referenced does not have
  479. // a set project method.
  480. } catch (Exception e2) {
  481. String msg = "Error setting new project instance for "
  482. + "reference with id " + oldKey;
  483. throw new BuildException(msg, e2, getLocation());
  484. }
  485. }
  486. newProject.addReference(newKey, copy);
  487. }
  488. /**
  489. * Copies all properties from the given table to the new project -
  490. * omitting those that have already been set in the new project as
  491. * well as properties named basedir or ant.file.
  492. * @param props properties to copy to the new project
  493. * @since Ant 1.6
  494. */
  495. private void addAlmostAll(Hashtable props) {
  496. Enumeration e = props.keys();
  497. while (e.hasMoreElements()) {
  498. String key = e.nextElement().toString();
  499. if ("basedir".equals(key) || "ant.file".equals(key)) {
  500. // basedir and ant.file get special treatment in execute()
  501. continue;
  502. }
  503. String value = props.get(key).toString();
  504. // don't re-set user properties, avoid the warning message
  505. if (newProject.getProperty(key) == null) {
  506. // no user property
  507. newProject.setNewProperty(key, value);
  508. }
  509. }
  510. }
  511. /**
  512. * The directory to use as a base directory for the new Ant project.
  513. * Defaults to the current project's basedir, unless inheritall
  514. * has been set to false, in which case it doesn't have a default
  515. * value. This will override the basedir setting of the called project.
  516. * @param d new directory
  517. */
  518. public void setDir(File d) {
  519. this.dir = d;
  520. }
  521. /**
  522. * The build file to use.
  523. * Defaults to "build.xml". This file is expected to be a filename relative
  524. * to the dir attribute given.
  525. * @param s build file to use
  526. */
  527. public void setAntfile(String s) {
  528. // @note: it is a string and not a file to handle relative/absolute
  529. // otherwise a relative file will be resolved based on the current
  530. // basedir.
  531. this.antFile = s;
  532. }
  533. /**
  534. * The target of the new Ant project to execute.
  535. * Defaults to the new project's default target.
  536. * @param s target to invoke
  537. */
  538. public void setTarget(String s) {
  539. if (s.equals("")) {
  540. throw new BuildException("target attribute must not be empty");
  541. }
  542. this.target = s;
  543. }
  544. /**
  545. * Filename to write the output to.
  546. * This is relative to the value of the dir attribute
  547. * if it has been set or to the base directory of the
  548. * current project otherwise.
  549. * @param s file to which the output should go to
  550. */
  551. public void setOutput(String s) {
  552. this.output = s;
  553. }
  554. /**
  555. * Property to pass to the new project.
  556. * The property is passed as a 'user property'
  557. * @return new property created
  558. */
  559. public Property createProperty() {
  560. if (newProject == null) {
  561. reinit();
  562. }
  563. Property p = new Property(true, getProject());
  564. p.setProject(newProject);
  565. p.setTaskName("property");
  566. properties.addElement(p);
  567. return p;
  568. }
  569. /**
  570. * Reference element identifying a data type to carry
  571. * over to the new project.
  572. * @param r reference to add
  573. */
  574. public void addReference(Reference r) {
  575. references.addElement(r);
  576. }
  577. /**
  578. * Set of properties to pass to the new project.
  579. *
  580. * @param ps property set to add
  581. * @since Ant 1.6
  582. */
  583. public void addPropertyset(PropertySet ps) {
  584. propertySets.addElement(ps);
  585. }
  586. /**
  587. * @since Ant 1.6.2
  588. */
  589. private Iterator getBuildListeners() {
  590. return getProject().getBuildListeners().iterator();
  591. }
  592. /**
  593. * Helper class that implements the nested <reference>
  594. * element of <ant> and <antcall>.
  595. */
  596. public static class Reference
  597. extends org.apache.tools.ant.types.Reference {
  598. /** Creates a reference to be configured by Ant */
  599. public Reference() {
  600. super();
  601. }
  602. private String targetid = null;
  603. /**
  604. * Set the id that this reference to be stored under in the
  605. * new project.
  606. *
  607. * @param targetid the id under which this reference will be passed to
  608. * the new project */
  609. public void setToRefid(String targetid) {
  610. this.targetid = targetid;
  611. }
  612. /**
  613. * Get the id under which this reference will be stored in the new
  614. * project
  615. *
  616. * @return the id of the reference in the new project.
  617. */
  618. public String getToRefid() {
  619. return targetid;
  620. }
  621. }
  622. }