Project.php 34 KB

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