PhingTask.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. <?php
  2. /*
  3. * $Id: PhingTask.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. include_once 'phing/Task.php';
  22. include_once 'phing/util/FileUtils.php';
  23. include_once 'phing/types/Reference.php';
  24. include_once 'phing/tasks/system/PropertyTask.php';
  25. /**
  26. * Task that invokes phing on another build file.
  27. *
  28. * Use this task, for example, if you have nested buildfiles in your project. Unlike
  29. * AntTask, PhingTask can even support filesets:
  30. *
  31. * <pre>
  32. * <phing>
  33. * <fileset dir="${srcdir}">
  34. * <include name="** /build.xml" /> <!-- space added after ** is there because of PHP comment syntax -->
  35. * <exclude name="build.xml" />
  36. * </fileset>
  37. * </phing>
  38. * </pre>
  39. *
  40. * @author Hans Lellelid <hans@xmpl.org>
  41. * @version $Revision: 905 $
  42. * @package phing.tasks.system
  43. */
  44. class PhingTask extends Task {
  45. /** the basedir where is executed the build file */
  46. private $dir;
  47. /** build.xml (can be absolute) in this case dir will be ignored */
  48. private $phingFile;
  49. /** the target to call if any */
  50. protected $newTarget;
  51. /** should we inherit properties from the parent ? */
  52. private $inheritAll = true;
  53. /** should we inherit references from the parent ? */
  54. private $inheritRefs = false;
  55. /** the properties to pass to the new project */
  56. private $properties = array();
  57. /** the references to pass to the new project */
  58. private $references = array();
  59. /** The filesets that contain the files PhingTask is to be run on. */
  60. private $filesets = array();
  61. /** the temporary project created to run the build file */
  62. private $newProject;
  63. /** Fail the build process when the called build fails? */
  64. private $haltOnFailure = false;
  65. /**
  66. * If true, abort the build process if there is a problem with or in the target build file.
  67. * Defaults to false.
  68. *
  69. * @param boolean new value
  70. */
  71. public function setHaltOnFailure($hof) {
  72. $this->haltOnFailure = (boolean) $hof;
  73. }
  74. /**
  75. * Creates a Project instance for the project to call.
  76. * @return void
  77. */
  78. public function init() {
  79. $this->newProject = new Project();
  80. $tdf = $this->project->getTaskDefinitions();
  81. $this->newProject->addTaskDefinition("property", $tdf["property"]);
  82. }
  83. /**
  84. * Called in execute or createProperty if newProject is null.
  85. *
  86. * <p>This can happen if the same instance of this task is run
  87. * twice as newProject is set to null at the end of execute (to
  88. * save memory and help the GC).</p>
  89. *
  90. * <p>Sets all properties that have been defined as nested
  91. * property elements.</p>
  92. */
  93. private function reinit() {
  94. $this->init();
  95. $count = count($this->properties);
  96. for ($i = 0; $i < $count; $i++) {
  97. $p = $this->properties[$i];
  98. $newP = $this->newProject->createTask("property");
  99. $newP->setName($p->getName());
  100. if ($p->getValue() !== null) {
  101. $newP->setValue($p->getValue());
  102. }
  103. if ($p->getFile() !== null) {
  104. $newP->setFile($p->getFile());
  105. }
  106. if ($p->getPrefix() !== null) {
  107. $newP->setPrefix($p->getPrefix());
  108. }
  109. if ($p->getRefid() !== null) {
  110. $newP->setRefid($p->getRefid());
  111. }
  112. if ($p->getEnvironment() !== null) {
  113. $newP->setEnvironment($p->getEnvironment());
  114. }
  115. if ($p->getUserProperty() !== null) {
  116. $newP->setUserProperty($p->getUserProperty());
  117. }
  118. if ($p->getOverride() !== null) {
  119. $newP->setOverride($p->getOverride());
  120. }
  121. $this->properties[$i] = $newP;
  122. }
  123. }
  124. /**
  125. * Main entry point for the task.
  126. *
  127. * @return void
  128. */
  129. public function main() {
  130. // Call Phing on the file set with the attribute "phingfile"
  131. if ($this->phingFile !== null or $this->dir !== null) {
  132. $this->processFile();
  133. }
  134. // if no filesets are given stop here; else process filesets
  135. if (empty($this->filesets)) {
  136. return;
  137. }
  138. // preserve old settings
  139. $savedDir = $this->dir;
  140. $savedPhingFile = $this->phingFile;
  141. $savedTarget = $this->newTarget;
  142. // set no specific target for files in filesets
  143. // [HL] I'm commenting this out; I don't know why this should not be supported!
  144. // $this->newTarget = null;
  145. foreach($this->filesets as $fs) {
  146. $ds = $fs->getDirectoryScanner($this->project);
  147. $fromDir = $fs->getDir($this->project);
  148. $srcFiles = $ds->getIncludedFiles();
  149. foreach($srcFiles as $fname) {
  150. $f = new PhingFile($ds->getbasedir(), $fname);
  151. $f = $f->getAbsoluteFile();
  152. $this->phingFile = $f->getAbsolutePath();
  153. $this->dir = $f->getParentFile();
  154. $this->processFile(); // run Phing!
  155. }
  156. }
  157. // side effect free programming ;-)
  158. $this->dir = $savedDir;
  159. $this->phingFile = $savedPhingFile;
  160. $this->newTarget = $savedTarget;
  161. // [HL] change back to correct dir
  162. if ($this->dir !== null) {
  163. chdir($this->dir->getAbsolutePath());
  164. }
  165. }
  166. /**
  167. * Execute phing file.
  168. *
  169. * @return void
  170. */
  171. private function processFile() {
  172. $buildFailed = false;
  173. $savedDir = $this->dir;
  174. $savedPhingFile = $this->phingFile;
  175. $savedTarget = $this->newTarget;
  176. $savedBasedirAbsPath = null; // this is used to save the basedir *if* we change it
  177. try {
  178. if ($this->newProject === null) {
  179. $this->reinit();
  180. }
  181. $this->initializeProject();
  182. if ($this->dir !== null) {
  183. $dirAbsPath = $this->dir->getAbsolutePath();
  184. // BE CAREFUL! -- when the basedir is changed for a project,
  185. // all calls to getAbsolutePath() on a relative-path dir will
  186. // be made relative to the project's basedir! This means
  187. // that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!
  188. // We need to save the current project's basedir first.
  189. $savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();
  190. $this->newProject->setBasedir($this->dir);
  191. // Now we must reset $this->dir so that it continues to resolve to the same
  192. // path.
  193. $this->dir = new PhingFile($dirAbsPath);
  194. if ($savedDir !== null) { // has been set explicitly
  195. $this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());
  196. }
  197. } else {
  198. // Since we're not changing the basedir here (for file resolution),
  199. // we don't need to worry about any side-effects in this scanrio.
  200. $this->dir = $this->getProject()->getBasedir();
  201. }
  202. $this->overrideProperties();
  203. if ($this->phingFile === null) {
  204. $this->phingFile = "build.xml";
  205. }
  206. $fu = new FileUtils();
  207. $file = $fu->resolveFile($this->dir, $this->phingFile);
  208. $this->phingFile = $file->getAbsolutePath();
  209. $this->log("Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'");
  210. $this->newProject->setUserProperty("phing.file", $this->phingFile);
  211. ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));
  212. if ($this->newTarget === null) {
  213. $this->newTarget = $this->newProject->getDefaultTarget();
  214. }
  215. // Are we trying to call the target in which we are defined?
  216. if ($this->newProject->getBaseDir() == $this->project->getBaseDir() &&
  217. $this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file") &&
  218. $this->getOwningTarget() !== null &&
  219. $this->newTarget == $this->getOwningTarget()->getName()) {
  220. throw new BuildException("phing task calling its own parent target");
  221. }
  222. $this->addReferences();
  223. $this->newProject->executeTarget($this->newTarget);
  224. } catch (Exception $e) {
  225. $buildFailed = true;
  226. $this->log($e->getMessage(), Project::MSG_ERR);
  227. if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {
  228. $lines = explode("\n", $e->getTraceAsString());
  229. foreach($lines as $line) {
  230. $this->log($line, Project::MSG_DEBUG);
  231. }
  232. }
  233. // important!!! continue on to perform cleanup tasks.
  234. }
  235. // reset environment values to prevent side-effects.
  236. $this->newProject = null;
  237. $pkeys = array_keys($this->properties);
  238. foreach($pkeys as $k) {
  239. $this->properties[$k]->setProject(null);
  240. }
  241. $this->dir = $savedDir;
  242. $this->phingFile = $savedPhingFile;
  243. $this->newTarget = $savedTarget;
  244. // If the basedir for any project was changed, we need to set that back here.
  245. if ($savedBasedirAbsPath !== null) {
  246. chdir($savedBasedirAbsPath);
  247. }
  248. if ($this->haltOnFailure && $buildFailed) {
  249. throw new BuildException("Execution of the target buildfile failed. Aborting.");
  250. }
  251. }
  252. /**
  253. * Configure the Project, i.e. make intance, attach build listeners
  254. * (copy from father project), add Task and Datatype definitions,
  255. * copy properties and references from old project if these options
  256. * are set via the attributes of the XML tag.
  257. *
  258. * Developer note:
  259. * This function replaces the old methods "init", "_reinit" and
  260. * "_initializeProject".
  261. *
  262. * @access protected
  263. */
  264. private function initializeProject() {
  265. $this->newProject->setInputHandler($this->project->getInputHandler());
  266. foreach($this->project->getBuildListeners() as $listener) {
  267. $this->newProject->addBuildListener($listener);
  268. }
  269. /* Copy things from old project. Datatypes and Tasks are always
  270. * copied, properties and references only if specified so/not
  271. * specified otherwise in the XML definition.
  272. */
  273. // Add Datatype definitions
  274. foreach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {
  275. $this->newProject->addDataTypeDefinition($typeName, $typeClass);
  276. }
  277. // Add Task definitions
  278. foreach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {
  279. if ($taskClass == "propertytask") {
  280. // we have already added this taskdef in init()
  281. continue;
  282. }
  283. $this->newProject->addTaskDefinition($taskName, $taskClass);
  284. }
  285. // set user-defined properties
  286. $this->project->copyUserProperties($this->newProject);
  287. if (!$this->inheritAll) {
  288. // set System built-in properties separately,
  289. // b/c we won't inherit them.
  290. $this->newProject->setSystemProperties();
  291. } else {
  292. // set all properties from calling project
  293. $properties = $this->project->getProperties();
  294. foreach ($properties as $name => $value) {
  295. if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {
  296. // basedir and phing.file get special treatment in main()
  297. continue;
  298. }
  299. // don't re-set user properties, avoid the warning message
  300. if ($this->newProject->getProperty($name) === null){
  301. // no user property
  302. $this->newProject->setNewProperty($name, $value);
  303. }
  304. }
  305. }
  306. }
  307. /**
  308. * Override the properties in the new project with the one
  309. * explicitly defined as nested elements here.
  310. * @return void
  311. * @throws BuildException
  312. */
  313. private function overrideProperties() {
  314. foreach(array_keys($this->properties) as $i) {
  315. $p = $this->properties[$i];
  316. $p->setProject($this->newProject);
  317. $p->main();
  318. }
  319. $this->project->copyInheritedProperties($this->newProject);
  320. }
  321. /**
  322. * Add the references explicitly defined as nested elements to the
  323. * new project. Also copy over all references that don't override
  324. * existing references in the new project if inheritrefs has been
  325. * requested.
  326. *
  327. * @return void
  328. * @throws BuildException
  329. */
  330. private function addReferences() {
  331. // parent project references
  332. $projReferences = $this->project->getReferences();
  333. $newReferences = $this->newProject->getReferences();
  334. $subprojRefKeys = array();
  335. if (count($this->references) > 0) {
  336. for ($i=0, $count=count($this->references); $i < $count; $i++) {
  337. $ref = $this->references[$i];
  338. $refid = $ref->getRefId();
  339. if ($refid === null) {
  340. throw new BuildException("the refid attribute is required"
  341. . " for reference elements");
  342. }
  343. if (!isset($projReferences[$refid])) {
  344. $this->log("Parent project doesn't contain any reference '"
  345. . $refid . "'",
  346. Project::MSG_WARN);
  347. continue;
  348. }
  349. $subprojRefKeys[] = $refid;
  350. //thisReferences.remove(refid);
  351. $toRefid = $ref->getToRefid();
  352. if ($toRefid === null) {
  353. $toRefid = $refid;
  354. }
  355. $this->copyReference($refid, $toRefid);
  356. }
  357. }
  358. // Now add all references that are not defined in the
  359. // subproject, if inheritRefs is true
  360. if ($this->inheritRefs) {
  361. // get the keys that are were not used by the subproject
  362. $unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);
  363. foreach($unusedRefKeys as $key) {
  364. if (isset($newReferences[$key])) {
  365. continue;
  366. }
  367. $this->copyReference($key, $key);
  368. }
  369. }
  370. }
  371. /**
  372. * Try to clone and reconfigure the object referenced by oldkey in
  373. * the parent project and add it to the new project with the key
  374. * newkey.
  375. *
  376. * <p>If we cannot clone it, copy the referenced object itself and
  377. * keep our fingers crossed.</p>
  378. *
  379. * @param string $oldKey
  380. * @param string $newKey
  381. * @return void
  382. */
  383. private function copyReference($oldKey, $newKey) {
  384. $orig = $this->project->getReference($oldKey);
  385. if ($orig === null) {
  386. $this->log("No object referenced by " . $oldKey . ". Can't copy to "
  387. .$newKey,
  388. PROJECT_SG_WARN);
  389. return;
  390. }
  391. $copy = clone $orig;
  392. if ($copy instanceof ProjectComponent) {
  393. $copy->setProject($this->newProject);
  394. } elseif (in_array('setProject', get_class_methods(get_class($copy)))) {
  395. $copy->setProject($this->newProject);
  396. } elseif ($copy instanceof Project) {
  397. // don't copy the old "Project" itself
  398. } else {
  399. $msg = "Error setting new project instance for "
  400. . "reference with id " . $oldKey;
  401. throw new BuildException($msg);
  402. }
  403. $this->newProject->addReference($newKey, $copy);
  404. }
  405. /**
  406. * If true, pass all properties to the new phing project.
  407. * Defaults to true.
  408. *
  409. * @access public
  410. */
  411. function setInheritAll($value) {
  412. $this->inheritAll = (boolean) $value;
  413. }
  414. /**
  415. * If true, pass all references to the new phing project.
  416. * Defaults to false.
  417. *
  418. * @access public
  419. */
  420. function setInheritRefs($value) {
  421. $this->inheritRefs = (boolean)$value;
  422. }
  423. /**
  424. * The directory to use as a base directory for the new phing project.
  425. * Defaults to the current project's basedir, unless inheritall
  426. * has been set to false, in which case it doesn't have a default
  427. * value. This will override the basedir setting of the called project.
  428. *
  429. * @access public
  430. */
  431. function setDir($d) {
  432. if ( is_string($d) )
  433. $this->dir = new PhingFile($d);
  434. else
  435. $this->dir = $d;
  436. }
  437. /**
  438. * The build file to use.
  439. * Defaults to "build.xml". This file is expected to be a filename relative
  440. * to the dir attribute given.
  441. *
  442. * @access public
  443. */
  444. function setPhingfile($s) {
  445. // it is a string and not a file to handle relative/absolute
  446. // otherwise a relative file will be resolved based on the current
  447. // basedir.
  448. $this->phingFile = $s;
  449. }
  450. /**
  451. * Alias function for setPhingfile
  452. *
  453. * @access public
  454. */
  455. function setBuildfile($s) {
  456. $this->setPhingFile($s);
  457. }
  458. /**
  459. * The target of the new Phing project to execute.
  460. * Defaults to the new project's default target.
  461. *
  462. * @access public
  463. */
  464. function setTarget($s) {
  465. $this->newTarget = $s;
  466. }
  467. /**
  468. * Support for filesets; This method returns a reference to an instance
  469. * of a FileSet object.
  470. *
  471. * @return FileSet
  472. */
  473. function createFileSet() {
  474. $num = array_push($this->filesets, new FileSet());
  475. return $this->filesets[$num-1];
  476. }
  477. /**
  478. * Property to pass to the new project.
  479. * The property is passed as a 'user property'
  480. *
  481. * @access public
  482. */
  483. function createProperty() {
  484. $p = new PropertyTask();
  485. $p->setFallback($this->newProject);
  486. $p->setUserProperty(true);
  487. $this->properties[] = $p;
  488. return $p;
  489. }
  490. /**
  491. * Reference element identifying a data type to carry
  492. * over to the new project.
  493. *
  494. * @access public
  495. */
  496. function createReference() {
  497. $num = array_push($this->references, new PhingReference());
  498. return $this->references[$num-1];
  499. }
  500. }
  501. /**
  502. * Helper class that implements the nested <reference>
  503. * element of <phing> and <phingcall>.
  504. *
  505. * @package phing.tasks.system
  506. */
  507. class PhingReference extends Reference {
  508. private $targetid = null;
  509. /**
  510. * Set the id that this reference to be stored under in the
  511. * new project.
  512. *
  513. * @param targetid the id under which this reference will be passed to
  514. * the new project */
  515. public function setToRefid($targetid) {
  516. $this->targetid = $targetid;
  517. }
  518. /**
  519. * Get the id under which this reference will be stored in the new
  520. * project
  521. *
  522. * @return the id of the reference in the new project.
  523. */
  524. public function getToRefid() {
  525. return $this->targetid;
  526. }
  527. }