PageRenderTime 55ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/install/phing/classes/phing/Project.php

https://github.com/chregu/fluxcms
PHP | 950 lines | 499 code | 117 blank | 334 comment | 57 complexity | 8f0d47b82bd92dab4b4fe9b230d34c54 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
  1. <?php
  2. /*
  3. * $Id$
  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. define('PROJECT_MSG_DEBUG', 4);
  22. define('PROJECT_MSG_VERBOSE', 3);
  23. define('PROJECT_MSG_INFO', 2);
  24. define('PROJECT_MSG_WARN', 1);
  25. define('PROJECT_MSG_ERR', 0);
  26. include_once 'phing/util/FileUtils.php';
  27. include_once 'phing/TaskAdapter.php';
  28. include_once 'phing/util/StringHelper.php';
  29. include_once 'phing/BuildEvent.php';
  30. include_once 'phing/input/DefaultInputHandler.php';
  31. /**
  32. * The Phing project class. Represents a completely configured Phing project.
  33. * The class defines the project and all tasks/targets. It also contains
  34. * methods to start a build as well as some properties and FileSystem
  35. * abstraction.
  36. *
  37. * @author Andreas Aderhold <andi@binarycloud.com>
  38. * @author Hans Lellelid <hans@xmpl.org>
  39. * @version $Revision: 1.24 $
  40. * @package phing
  41. */
  42. class Project {
  43. /** contains the targets */
  44. private $targets = array();
  45. /** global filterset (future use) */
  46. private $globalFilterSet = array();
  47. /** all globals filters (future use) */
  48. private $globalFilters = array();
  49. /** Project properties map (usually String to String). */
  50. private $properties = array();
  51. /**
  52. * Map of "user" properties (as created in the Ant task, for example).
  53. * Note that these key/value pairs are also always put into the
  54. * project properties, so only the project properties need to be queried.
  55. * Mapping is String to String.
  56. */
  57. private $userProperties = array();
  58. /**
  59. * Map of inherited "user" properties - that are those "user"
  60. * properties that have been created by tasks and not been set
  61. * from the command line or a GUI tool.
  62. * Mapping is String to String.
  63. */
  64. private $inheritedProperties = array();
  65. /** task definitions for this project*/
  66. private $taskdefs = array();
  67. /** type definitions for this project */
  68. private $typedefs = array();
  69. /** holds ref names and a reference to the referred object*/
  70. private $references = array();
  71. /** The InputHandler being used by this project. */
  72. private $inputHandler;
  73. /* -- properties that come in via xml attributes -- */
  74. /** basedir (File object) */
  75. private $basedir;
  76. /** the default target name */
  77. private $defaultTarget = 'all';
  78. /** project name (required) */
  79. private $name;
  80. /** project description */
  81. private $description;
  82. /** a FileUtils object */
  83. private $fileUtils;
  84. /** Build listeneers */
  85. private $listeners = array();
  86. /**
  87. * Constructor, sets any default vars.
  88. */
  89. function __construct() {
  90. $this->fileUtils = new FileUtils();
  91. $this->inputHandler = new DefaultInputHandler();
  92. }
  93. /**
  94. * Sets the input handler
  95. */
  96. public function setInputHandler(InputHandler $handler) {
  97. $this->inputHandler = $handler;
  98. }
  99. /**
  100. * Retrieves the current input handler.
  101. */
  102. public function getInputHandler() {
  103. return $this->inputHandler;
  104. }
  105. /** inits the project, called from main app */
  106. function init() {
  107. // set builtin properties
  108. $this->setSystemProperties();
  109. // load default tasks
  110. $taskdefs = Phing::getResourcePath("phing/tasks/defaults.properties");
  111. try { // try to load taskdefs
  112. $props = new Properties();
  113. $in = new File((string)$taskdefs);
  114. if ($in === null) {
  115. throw new BuildException("Can't load default task list");
  116. }
  117. $props->load($in);
  118. $enum = $props->propertyNames();
  119. foreach($enum as $key) {
  120. $value = $props->getProperty($key);
  121. $this->addTaskDefinition($key, $value);
  122. }
  123. } catch (IOException $ioe) {
  124. throw new BuildException("Can't load default task list");
  125. }
  126. // load default tasks
  127. $typedefs = Phing::getResourcePath("phing/types/defaults.properties");
  128. try { // try to load typedefs
  129. $props = new Properties();
  130. $in = new File((string)$typedefs);
  131. if ($in === null) {
  132. throw new BuildException("Can't load default datatype list");
  133. }
  134. $props->load($in);
  135. $enum = $props->propertyNames();
  136. foreach($enum as $key) {
  137. $value = $props->getProperty($key);
  138. $this->addDataTypeDefinition($key, $value);
  139. }
  140. } catch(IOException $ioe) {
  141. throw new BuildException("Can't load default datatype list");
  142. }
  143. }
  144. /** returns the global filterset (future use) */
  145. function getGlobalFilterSet() {
  146. return $this->globalFilterSet;
  147. }
  148. // ---------------------------------------------------------
  149. // Property methods
  150. // ---------------------------------------------------------
  151. /**
  152. * Sets a property. Any existing property of the same name
  153. * is overwritten, unless it is a user property.
  154. * @param string $name The name of property to set.
  155. * Must not be <code>null</code>.
  156. * @param string $value The new value of the property.
  157. * Must not be <code>null</code>.
  158. * @return void
  159. */
  160. public function setProperty($name, $value) {
  161. // command line properties take precedence
  162. if (null !== @$this->userProperties[$name]) {
  163. $this->log("Override ignored for user property " . $name, PROJECT_MSG_DEBUG);
  164. return;
  165. }
  166. if (null !== @$this->properties[$name]) {
  167. $this->log("Overriding previous definition of property " . $name, PROJECT_MSG_VERBOSE);
  168. }
  169. $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  170. $this->properties[$name] = $value;
  171. }
  172. /**
  173. * Sets a property if no value currently exists. If the property
  174. * exists already, a message is logged and the method returns with
  175. * no other effect.
  176. *
  177. * @param string $name The name of property to set.
  178. * Must not be <code>null</code>.
  179. * @param string $value The new value of the property.
  180. * Must not be <code>null</code>.
  181. * @since 2.0
  182. */
  183. public function setNewProperty($name, $value) {
  184. if (null !== @$this->properties[$name]) {
  185. $this->log("Override ignored for property " . $name, PROJECT_MSG_VERBOSE);
  186. return;
  187. }
  188. $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  189. $this->properties[$name] = $value;
  190. }
  191. /**
  192. * Sets a user property, which cannot be overwritten by
  193. * set/unset property calls. Any previous value is overwritten.
  194. * @param string $name The name of property to set.
  195. * Must not be <code>null</code>.
  196. * @param string $value The new value of the property.
  197. * Must not be <code>null</code>.
  198. * @see #setProperty()
  199. */
  200. public function setUserProperty($name, $value) {
  201. $this->log("Setting ro project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  202. $this->userProperties[$name] = $value;
  203. $this->properties[$name] = $value;
  204. }
  205. /**
  206. * Sets a user property, which cannot be overwritten by set/unset
  207. * property calls. Any previous value is overwritten. Also marks
  208. * these properties as properties that have not come from the
  209. * command line.
  210. *
  211. * @param string $name The name of property to set.
  212. * Must not be <code>null</code>.
  213. * @param string $value The new value of the property.
  214. * Must not be <code>null</code>.
  215. * @see #setProperty()
  216. */
  217. public function setInheritedProperty($name, $value) {
  218. $this->inheritedProperties[$name] = $value;
  219. $this->setUserProperty($name, $value);
  220. }
  221. /**
  222. * Sets a property unless it is already defined as a user property
  223. * (in which case the method returns silently).
  224. *
  225. * @param name The name of the property.
  226. * Must not be <code>null</code>.
  227. * @param value The property value. Must not be <code>null</code>.
  228. */
  229. private function setPropertyInternal($name, $value) {
  230. if (null !== @$this->userProperties[$name]) {
  231. return;
  232. }
  233. $this->properties[$name] = $value;
  234. }
  235. /**
  236. * Returns the value of a property, if it is set.
  237. *
  238. * @param string $name The name of the property.
  239. * May be <code>null</code>, in which case
  240. * the return value is also <code>null</code>.
  241. * @return string The property value, or <code>null</code> for no match
  242. * or if a <code>null</code> name is provided.
  243. */
  244. public function getProperty($name) {
  245. if (!isset($this->properties[$name])) {
  246. return null;
  247. }
  248. return $this->properties[$name];
  249. }
  250. /**
  251. * Replaces ${} style constructions in the given value with the
  252. * string value of the corresponding data types.
  253. *
  254. * @param value The string to be scanned for property references.
  255. * May be <code>null</code>.
  256. *
  257. * @return the given string with embedded property names replaced
  258. * by values, or <code>null</code> if the given string is
  259. * <code>null</code>.
  260. *
  261. * @exception BuildException if the given value has an unclosed
  262. * property name, e.g. <code>${xxx</code>
  263. */
  264. public function replaceProperties($value) {
  265. return ProjectConfigurator::replaceProperties($this, $value, $this->properties);
  266. }
  267. /**
  268. * Returns the value of a user property, if it is set.
  269. *
  270. * @param string $name The name of the property.
  271. * May be <code>null</code>, in which case
  272. * the return value is also <code>null</code>.
  273. * @return string The property value, or <code>null</code> for no match
  274. * or if a <code>null</code> name is provided.
  275. */
  276. public function getUserProperty($name) {
  277. if (!isset($this->userProperties[$name])) {
  278. return null;
  279. }
  280. return $this->userProperties[$name];
  281. }
  282. /**
  283. * Returns a copy of the properties table.
  284. * @return array A hashtable containing all properties
  285. * (including user properties).
  286. */
  287. public function getProperties() {
  288. return $this->properties;
  289. }
  290. /**
  291. * Returns a copy of the user property hashtable
  292. * @return a hashtable containing just the user properties
  293. */
  294. public function getUserProperties() {
  295. return $this->userProperties;
  296. }
  297. /**
  298. * Copies all user properties that have been set on the command
  299. * line or a GUI tool from this instance to the Project instance
  300. * given as the argument.
  301. *
  302. * <p>To copy all "user" properties, you will also have to call
  303. * {@link #copyInheritedProperties copyInheritedProperties}.</p>
  304. *
  305. * @param Project $other the project to copy the properties to. Must not be null.
  306. * @return void
  307. * @since phing 2.0
  308. */
  309. public function copyUserProperties(Project $other) {
  310. foreach($this->userProperties as $arg => $value) {
  311. if (isset($this->inheritedProperties[$arg])) {
  312. continue;
  313. }
  314. $other->setUserProperty($arg, $value);
  315. }
  316. }
  317. /**
  318. * Copies all user properties that have not been set on the
  319. * command line or a GUI tool from this instance to the Project
  320. * instance given as the argument.
  321. *
  322. * <p>To copy all "user" properties, you will also have to call
  323. * {@link #copyUserProperties copyUserProperties}.</p>
  324. *
  325. * @param other the project to copy the properties to. Must not be null.
  326. *
  327. * @since phing 2.0
  328. */
  329. public function copyInheritedProperties(Project $other) {
  330. foreach($this->userProperties as $arg => $value) {
  331. if ($other->getUserProperty($arg) !== null) {
  332. continue;
  333. }
  334. $other->setInheritedProperty($arg, $value);
  335. }
  336. }
  337. // ---------------------------------------------------------
  338. // END Properties methods
  339. // ---------------------------------------------------------
  340. function setDefaultTarget($targetName) {
  341. $this->defaultTarget = (string) trim($targetName);
  342. }
  343. function getDefaultTarget() {
  344. return (string) $this->defaultTarget;
  345. }
  346. /**
  347. * Sets the name of the current project
  348. *
  349. * @param string name of project
  350. * @return void
  351. * @access public
  352. * @author Andreas Aderhold, andi@binarycloud.com
  353. */
  354. function setName($name) {
  355. $this->name = (string) trim($name);
  356. $this->setProperty("phing.project.name", $this->name);
  357. }
  358. /**
  359. * Returns the name of this project
  360. *
  361. * @returns string projectname
  362. * @access public
  363. * @author Andreas Aderhold, andi@binarycloud.com
  364. */
  365. function getName() {
  366. return (string) $this->name;
  367. }
  368. /** Set the projects description */
  369. function setDescription($description) {
  370. $this->description = (string) trim($description);
  371. }
  372. /** return the description, null otherwise */
  373. function getDescription() {
  374. return $this->description;
  375. }
  376. /** Set basedir object from xml*/
  377. function setBasedir($dir) {
  378. if ($dir instanceof File) {
  379. $dir = $dir->getAbsolutePath();
  380. }
  381. $dir = $this->fileUtils->normalize($dir);
  382. $dir = new File((string) $dir);
  383. if (!$dir->exists()) {
  384. throw new BuildException("Basedir ".$dir->getAbsolutePath()." does not exist");
  385. }
  386. if (!$dir->isDirectory()) {
  387. throw new BuildException("Basedir ".$dir->getAbsolutePath()." is not a directory");
  388. }
  389. $this->basedir = $dir;
  390. $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath());
  391. $this->log("Project base dir set to: " . $this->basedir->getPath(), PROJECT_MSG_VERBOSE);
  392. // [HL] added this so that ./ files resolve correctly. This may be a mistake ... or may be in wrong place.
  393. chdir($dir->getAbsolutePath());
  394. }
  395. /**
  396. * Returns the basedir of this project
  397. *
  398. * @returns File Basedir File object
  399. * @access public
  400. * @throws BuildException
  401. * @author Andreas Aderhold, andi@binarycloud.com
  402. */
  403. function getBasedir() {
  404. if ($this->basedir === null) {
  405. try { // try to set it
  406. $this->setBasedir(".");
  407. } catch (BuildException $exc) {
  408. throw new BuildException("Can not set default basedir. ".$exc->getMessage());
  409. }
  410. }
  411. return $this->basedir;
  412. }
  413. /**
  414. * Sets system properties and the environment variables for this project.
  415. *
  416. * @return void
  417. */
  418. function setSystemProperties() {
  419. // first get system properties
  420. $systemP = array_merge( self::getProperties(), Phing::getProperties() );
  421. foreach($systemP as $name => $value) {
  422. $this->setPropertyInternal($name, $value);
  423. }
  424. // and now the env vars
  425. foreach($_SERVER as $name => $value) {
  426. // skip arrays
  427. if (is_array($value)) {
  428. continue;
  429. }
  430. $this->setPropertyInternal('env.' . $name, $value);
  431. }
  432. return true;
  433. }
  434. /**
  435. * Adds a task definition.
  436. * @param string $name Name of tag.
  437. * @param string $class The class path to use.
  438. * @param string $classpath The classpat to use.
  439. */
  440. function addTaskDefinition($name, $class, $classpath = null) {
  441. $name = $name;
  442. $class = $class;
  443. if ($class === "") {
  444. $this->log("Task $name has no class defined.", PROJECT_MSG_ERR);
  445. } elseif (!isset($this->taskdefs[$name])) {
  446. Phing::import($class, $classpath);
  447. $this->taskdefs[$name] = $class;
  448. $this->log(" +Task definiton: $name ($class)", PROJECT_MSG_DEBUG);
  449. } else {
  450. $this->log("Task $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
  451. }
  452. }
  453. function &getTaskDefinitions() {
  454. return $this->taskdefs;
  455. }
  456. /**
  457. * Adds a data type definition.
  458. * @param string $name Name of tag.
  459. * @param string $class The class path to use.
  460. * @param string $classpath The classpat to use.
  461. */
  462. function addDataTypeDefinition($typeName, $typeClass, $classpath = null) {
  463. if (!isset($this->taskdefs[$typeName])) {
  464. Phing::import($typeClass, $classpath);
  465. $this->typedefs[$typeName] = $typeClass;
  466. $this->log(" +User datatype: $typeName ($typeClass)", PROJECT_MSG_DEBUG);
  467. } else {
  468. $this->log("Type $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
  469. }
  470. }
  471. function getDataTypeDefinitions() {
  472. return $this->typedefs;
  473. }
  474. /** add a new target to the project */
  475. function addTarget($targetName, &$target) {
  476. if (isset($this->targets[$targetName])) {
  477. throw new BuildException("Duplicate target: $targetName");
  478. }
  479. $this->addOrReplaceTarget($targetName, $target);
  480. }
  481. function addOrReplaceTarget($targetName, &$target) {
  482. $this->log(" +Target: $targetName", PROJECT_MSG_DEBUG);
  483. $target->setProject($this);
  484. $this->targets[$targetName] = $target;
  485. }
  486. function getTargets() {
  487. return $this->targets;
  488. }
  489. /**
  490. * Create a new task instance and return reference to it. This method is
  491. * sorta factory like. A _local_ instance is created and a reference returned to
  492. * that instance. Usually PHP destroys local variables when the function call
  493. * ends. But not if you return a reference to that variable.
  494. * This is kinda error prone, because if no reference exists to the variable
  495. * it is destroyed just like leaving the local scope with primitive vars. There's no
  496. * central place where the instance is stored as in other OOP like languages.
  497. *
  498. * [HL] Well, ZE2 is here now, and this is still working. We'll leave this alone
  499. * unless there's any good reason not to.
  500. *
  501. * @param string $taskType Task name
  502. * @returns Task A task object
  503. * @throws BuildException
  504. * Exception
  505. */
  506. function createTask($taskType) {
  507. try {
  508. $cls = "";
  509. $tasklwr = strtolower($taskType);
  510. foreach ($this->taskdefs as $name => $class) {
  511. if (strtolower($name) === $tasklwr) {
  512. $cls = StringHelper::unqualify($class);
  513. break;
  514. }
  515. }
  516. if ($cls === "") {
  517. return null;
  518. }
  519. if (!class_exists($cls)) {
  520. throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)");
  521. }
  522. $o = new $cls();
  523. if ($o instanceof Task) {
  524. $task = $o;
  525. } else {
  526. $this->log (" (Using TaskAdapter for: $taskType)", PROJECT_MSG_DEBUG);
  527. // not a real task, try adapter
  528. $taskA = new TaskAdapter();
  529. $taskA->setProxy($o);
  530. $task = $taskA;
  531. }
  532. $task->setProject($this);
  533. $task->setTaskType($taskType);
  534. // set default value, can be changed by the user
  535. $task->setTaskName($taskType);
  536. $this->log (" +Task: " . $taskType, PROJECT_MSG_DEBUG);
  537. } catch (Exception $t) {
  538. throw new BuildException("Could not create task of type: " . $taskType, $t);
  539. }
  540. // everything fine return reference
  541. return $task;
  542. }
  543. /**
  544. * Create a task instance and return reference to it
  545. * See createTask() for explanation how this works
  546. *
  547. * @param string Type name
  548. * @returns object A datatype object
  549. * @throws BuildException
  550. * Exception
  551. */
  552. function createDataType($typeName) {
  553. try {
  554. $cls = "";
  555. $typelwr = strtolower($typeName);
  556. foreach ($this->typedefs as $name => $class) {
  557. if (strtolower($name) === $typelwr) {
  558. $cls = StringHelper::unqualify($class);
  559. break;
  560. }
  561. }
  562. if ($cls === "") {
  563. return null;
  564. }
  565. if (!class_exists($cls)) {
  566. throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)");
  567. }
  568. $type = new $cls();
  569. $this->log(" +Type: $typeName", PROJECT_MSG_DEBUG);
  570. if (!($type instanceof DataType)) {
  571. throw new Exception("$class is not an instance of phing.types.DataType");
  572. }
  573. if ($type instanceof ProjectComponent) {
  574. $type->setProject($this);
  575. }
  576. } catch (Exception $t) {
  577. throw new BuildException("Could not create type: $typeName", $t);
  578. }
  579. // everything fine return reference
  580. return $type;
  581. }
  582. /**
  583. * Executes a list of targets
  584. *
  585. * @param array List of target names to execute
  586. * @returns void
  587. * @throws BuildException
  588. */
  589. function executeTargets($targetNames) {
  590. foreach($targetNames as $tname) {
  591. $this->executeTarget($tname);
  592. }
  593. }
  594. /**
  595. * Executes a target
  596. *
  597. * @param string Name of Target to execute
  598. * @returns void
  599. * @throws BuildException
  600. */
  601. function executeTarget($targetName) {
  602. // complain about executing void
  603. if ($targetName === null) {
  604. throw new BuildException("No target specified");
  605. }
  606. // invoke topological sort of the target tree and run all targets
  607. // until targetName occurs.
  608. $sortedTargets = $this->_topoSort($targetName, $this->targets);
  609. $curIndex = (int) 0;
  610. $curTarget = null;
  611. do {
  612. try {
  613. $curTarget = $sortedTargets[$curIndex++];
  614. $curTarget->performTasks();
  615. } catch (BuildException $exc) {
  616. $this->log("Execution of target \"".$curTarget->getName()."\" failed for the following reason: ".$exc->getMessage(), PROJECT_MSG_ERR);
  617. throw $exc;
  618. }
  619. } while ($curTarget->getName() !== $targetName);
  620. }
  621. function resolveFile($fileName, $rootDir = null) {
  622. if ($rootDir === null) {
  623. return $this->fileUtils->resolveFile($this->basedir, $fileName);
  624. } else {
  625. return $this->fileUtils->resolveFile($rootDir, $fileName);
  626. }
  627. }
  628. /**
  629. * Topologically sort a set of Targets.
  630. * @param $root is the (String) name of the root Target. The sort is
  631. * created in such a way that the sequence of Targets until the root
  632. * target is the minimum possible such sequence.
  633. * @param $targets is a array representing a "name to Target" mapping
  634. * @return An array of Strings with the names of the targets in
  635. * sorted order.
  636. */
  637. function _topoSort($root, &$targets) {
  638. $root = (string) $root;
  639. $ret = array();
  640. $state = array();
  641. $visiting = array();
  642. // We first run a DFS based sort using the root as the starting node.
  643. // This creates the minimum sequence of Targets to the root node.
  644. // We then do a sort on any remaining unVISITED targets.
  645. // This is unnecessary for doing our build, but it catches
  646. // circular dependencies or missing Targets on the entire
  647. // dependency tree, not just on the Targets that depend on the
  648. // build Target.
  649. $this->_tsort($root, $targets, $state, $visiting, $ret);
  650. $retHuman = "";
  651. for ($i=0, $_i=count($ret); $i < $_i; $i++) {
  652. $retHuman .= $ret[$i]->toString()." ";
  653. }
  654. $this->log("Build sequence for target '$root' is: $retHuman", PROJECT_MSG_VERBOSE);
  655. $keys = array_keys($targets);
  656. while($keys) {
  657. $curTargetName = (string) array_shift($keys);
  658. if (!isset($state[$curTargetName])) {
  659. $st = null;
  660. } else {
  661. $st = (string) $state[$curTargetName];
  662. }
  663. if ($st === null) {
  664. $this->_tsort($curTargetName, $targets, $state, $visiting, $ret);
  665. } elseif ($st === "VISITING") {
  666. throw new Exception("Unexpected node in visiting state: $curTargetName");
  667. }
  668. }
  669. $retHuman = "";
  670. for ($i=0,$_i=count($ret); $i < $_i; $i++) {
  671. $retHuman .= $ret[$i]->toString()." ";
  672. }
  673. $this->log("Complete build sequence is: $retHuman", PROJECT_MSG_VERBOSE);
  674. return $ret;
  675. }
  676. // one step in a recursive DFS traversal of the target dependency tree.
  677. // - The array "state" contains the state (VISITED or VISITING or null)
  678. // of all the target names.
  679. // - The stack "visiting" contains a stack of target names that are
  680. // currently on the DFS stack. (NB: the target names in "visiting" are
  681. // exactly the target names in "state" that are in the VISITING state.)
  682. // 1. Set the current target to the VISITING state, and push it onto
  683. // the "visiting" stack.
  684. // 2. Throw a BuildException if any child of the current node is
  685. // in the VISITING state (implies there is a cycle.) It uses the
  686. // "visiting" Stack to construct the cycle.
  687. // 3. If any children have not been VISITED, tsort() the child.
  688. // 4. Add the current target to the Vector "ret" after the children
  689. // have been visited. Move the current target to the VISITED state.
  690. // "ret" now contains the sorted sequence of Targets upto the current
  691. // Target.
  692. function _tsort($root, &$targets, &$state, &$visiting, &$ret) {
  693. $state[$root] = "VISITING";
  694. $visiting[] = $root;
  695. if (!isset($targets[$root]) || !($targets[$root] instanceof Target)) {
  696. $target = null;
  697. } else {
  698. $target = $targets[$root];
  699. }
  700. // make sure we exist
  701. if ($target === null) {
  702. $sb = "Target '$root' does not exist in this project.";
  703. array_pop($visiting);
  704. if (!empty($visiting)) {
  705. $parent = (string) $visiting[count($visiting)-1];
  706. $sb .= "It is used from target '$parent'.";
  707. }
  708. throw new BuildException($sb);
  709. }
  710. $deps = $target->getDependencies();
  711. while($deps) {
  712. $cur = (string) array_shift($deps);
  713. if (!isset($state[$cur])) {
  714. $m = null;
  715. } else {
  716. $m = (string) $state[$cur];
  717. }
  718. if ($m === null) {
  719. // not been visited
  720. $this->_tsort($cur, $targets, $state, $visiting, $ret);
  721. } elseif ($m == "VISITING") {
  722. // currently visiting this node, so have a cycle
  723. throw $this->_makeCircularException($cur, $visiting);
  724. }
  725. }
  726. $p = (string) array_pop($visiting);
  727. if ($root !== $p) {
  728. throw new Exception("Unexpected internal error: expected to pop $root but got $p");
  729. }
  730. $state[$root] = "VISITED";
  731. $ret[] = $target;
  732. }
  733. function _makeCircularException($end, $stk) {
  734. $sb = "Circular dependency: $end";
  735. do {
  736. $sb .= " <- ".(string) array_pop($stk);
  737. } while($c != $end);
  738. return new BuildException($sb);
  739. }
  740. /**
  741. * Adds a reference to an object. This method is called when the parser
  742. * detects a id="foo" attribute. It passes the id as $name and a reference
  743. * to the object assigned to this id as $value
  744. */
  745. function addReference($name, $object) {
  746. if (isset($this->references[$name])) {
  747. $this->log("Overriding previous definition of reference to $name", PROJECT_MSG_WARN);
  748. }
  749. $this->log("Adding reference: $name -> ".get_class($object), PROJECT_MSG_DEBUG);
  750. $this->references[$name] = $object;
  751. }
  752. /**
  753. * Returns a reference to references array ;)
  754. *
  755. */
  756. function getReferences() {
  757. return $this->references;
  758. }
  759. /**
  760. * Abstracting and simplifyling Logger calls for project messages
  761. */
  762. function log($msg, $level = PROJECT_MSG_INFO) {
  763. $this->logObject($this, $msg, $level);
  764. }
  765. function logObject($obj, $msg, $level) {
  766. $this->fireMessageLogged($obj, $msg, $level);
  767. }
  768. function addBuildListener(BuildListener $listener) {
  769. $this->listeners[] = $listener;
  770. }
  771. function removeBuildListener(BuildListener $listener) {
  772. $newarray = array();
  773. for ($i=0, $size=count($this->listeners); $i < $size; $i++) {
  774. if ($this->listeners[$i] !== $listener) {
  775. $newarray[] = $this->listeners[$i];
  776. }
  777. }
  778. $this->listeners = $newarray;
  779. }
  780. function getBuildListeners() {
  781. return $this->listeners;
  782. }
  783. function fireBuildStarted() {
  784. $event = new BuildEvent($this);
  785. foreach($this->listeners as $listener) {
  786. $listener->buildStarted($event);
  787. }
  788. }
  789. function fireBuildFinished($exception) {
  790. $event = new BuildEvent($this);
  791. $event->setException($exception);
  792. foreach($this->listeners as $listener) {
  793. $listener->buildFinished($event);
  794. }
  795. }
  796. function fireTargetStarted($target) {
  797. $event = new BuildEvent($target);
  798. foreach($this->listeners as $listener) {
  799. $listener->targetStarted($event);
  800. }
  801. }
  802. function fireTargetFinished($target, $exception) {
  803. $event = new BuildEvent($target);
  804. $event->setException($exception);
  805. foreach($this->listeners as $listener) {
  806. $listener->targetFinished($event);
  807. }
  808. }
  809. function fireTaskStarted($task) {
  810. $event = new BuildEvent($task);
  811. foreach($this->listeners as $listener) {
  812. $listener->taskStarted($event);
  813. }
  814. }
  815. function fireTaskFinished($task, $exception) {
  816. $event = new BuildEvent($task);
  817. $event->setException($exception);
  818. foreach($this->listeners as $listener) {
  819. $listener->taskFinished($event);
  820. }
  821. }
  822. function fireMessageLoggedEvent($event, $message, $priority) {
  823. $event->setMessage($message, $priority);
  824. foreach($this->listeners as $listener) {
  825. $listener->messageLogged($event);
  826. }
  827. }
  828. function fireMessageLogged($object, $message, $priority) {
  829. $this->fireMessageLoggedEvent(new BuildEvent($object), $message, $priority);
  830. }
  831. }