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