summaryrefslogtreecommitdiff
path: root/buildscripts/phing/classes/phing/Project.php
blob: 8123d91e1352036700aba7635bb8871eff545486 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
<?php
/*
 *  $Id: Project.php,v 1.29 2006/02/02 20:27:10 hlellelid Exp $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information please see
 * <http://phing.info>.
 */

define('PROJECT_MSG_DEBUG', 4);
define('PROJECT_MSG_VERBOSE', 3);
define('PROJECT_MSG_INFO', 2);
define('PROJECT_MSG_WARN', 1);
define('PROJECT_MSG_ERR', 0);

include_once 'phing/system/io/PhingFile.php';
include_once 'phing/util/FileUtils.php';
include_once 'phing/TaskAdapter.php';
include_once 'phing/util/StringHelper.php';
include_once 'phing/BuildEvent.php';
include_once 'phing/input/DefaultInputHandler.php';

/**
 *  The Phing project class. Represents a completely configured Phing project.
 *  The class defines the project and all tasks/targets. It also contains
 *  methods to start a build as well as some properties and FileSystem
 *  abstraction.
 *
 * @author    Andreas Aderhold <andi@binarycloud.com>
 * @author    Hans Lellelid <hans@xmpl.org>
 * @version   $Revision: 1.29 $
 * @package   phing
 */
class Project {

    /** contains the targets */
    private $targets         = array();
    /** global filterset (future use) */
    private $globalFilterSet = array();
    /**  all globals filters (future use) */
    private $globalFilters   = array();
    
    /** Project properties map (usually String to String). */
    private $properties = array();
    
    /**
     * Map of "user" properties (as created in the Ant task, for example).
     * Note that these key/value pairs are also always put into the
     * project properties, so only the project properties need to be queried.
     * Mapping is String to String.
     */
    private $userProperties = array();
    
    /**
     * Map of inherited "user" properties - that are those "user"
     * properties that have been created by tasks and not been set
     * from the command line or a GUI tool.
     * Mapping is String to String.
     */
    private $inheritedProperties = array();
    
    /** task definitions for this project*/
    private $taskdefs = array();
    
    /** type definitions for this project */
    private $typedefs = array();
    
    /** holds ref names and a reference to the referred object*/
    private $references = array();
    
    /** The InputHandler being used by this project. */
    private $inputHandler;
    
    /* -- properties that come in via xml attributes -- */
    
    /** basedir (PhingFile object) */
    private $basedir;
    
    /** the default target name */
    private $defaultTarget = 'all';
    
    /** project name (required) */
    private $name;
    
    /** project description */
    private $description;

    /** a FileUtils object */
    private $fileUtils;
    
    /**  Build listeneers */
    private $listeners = array();

    /**
     *  Constructor, sets any default vars.
     */
    function __construct() {
        $this->fileUtils = new FileUtils();
        $this->inputHandler = new DefaultInputHandler();
    }

    /**
     * Sets the input handler
     */
    public function setInputHandler(InputHandler $handler) {
        $this->inputHandler = $handler;
    }

    /**
     * Retrieves the current input handler.
     */
    public function getInputHandler() {
        return $this->inputHandler;
    }

    /** inits the project, called from main app */
    function init() {
        // set builtin properties
        $this->setSystemProperties();
        
        // load default tasks
        $taskdefs = Phing::getResourcePath("phing/tasks/defaults.properties");
        
        try { // try to load taskdefs
            $props = new Properties();
            $in = new PhingFile((string)$taskdefs);

            if ($in === null) {
                throw new BuildException("Can't load default task list");
            }
            $props->load($in);

            $enum = $props->propertyNames();
            foreach($enum as $key) {
                $value = $props->getProperty($key);
                $this->addTaskDefinition($key, $value);
            }
        } catch (IOException $ioe) {
            throw new BuildException("Can't load default task list");
        }

        // load default tasks
        $typedefs = Phing::getResourcePath("phing/types/defaults.properties");

        try { // try to load typedefs
            $props = new Properties();
            $in    = new PhingFile((string)$typedefs);
            if ($in === null) {
                throw new BuildException("Can't load default datatype list");
            }
            $props->load($in);

            $enum = $props->propertyNames();
            foreach($enum as $key) {
                $value = $props->getProperty($key);
                $this->addDataTypeDefinition($key, $value);
            }
        } catch(IOException $ioe) {
            throw new BuildException("Can't load default datatype list");
        }
    }

    /** returns the global filterset (future use) */
    function getGlobalFilterSet() {
        return $this->globalFilterSet;
    }

    // ---------------------------------------------------------
    // Property methods
    // ---------------------------------------------------------
    
    /**
     * Sets a property. Any existing property of the same name
     * is overwritten, unless it is a user property.
     * @param string $name The name of property to set.
     *             Must not be <code>null</code>.
     * @param string $value The new value of the property.
     *              Must not be <code>null</code>.
     * @return void
     */
    public function setProperty($name, $value) {
	
        // command line properties take precedence
        if (isset($this->userProperties[$name])) {
            $this->log("Override ignored for user property " . $name, PROJECT_MSG_VERBOSE);
            return;
        }

        if (isset($this->properties[$name])) {
            $this->log("Overriding previous definition of property " . $name, PROJECT_MSG_VERBOSE);
        }

        $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
        $this->properties[$name] = $value;
    }

    /**
     * Sets a property if no value currently exists. If the property
     * exists already, a message is logged and the method returns with
     * no other effect.
     *
     * @param string $name The name of property to set.
     *             Must not be <code>null</code>.
     * @param string $value The new value of the property.
     *              Must not be <code>null</code>.
     * @since 2.0
     */
    public function setNewProperty($name, $value) {
        if (isset($this->properties[$name])) {
            $this->log("Override ignored for property " . $name, PROJECT_MSG_DEBUG);
            return;
        }
        $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
        $this->properties[$name] = $value;
    }

    /**
     * Sets a user property, which cannot be overwritten by
     * set/unset property calls. Any previous value is overwritten.
     * @param string $name The name of property to set.
     *             Must not be <code>null</code>.
     * @param string $value The new value of the property.
     *              Must not be <code>null</code>.
     * @see #setProperty()
     */
    public function setUserProperty($name, $value) {
        $this->log("Setting ro project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
        $this->userProperties[$name] = $value;
        $this->properties[$name] = $value;
    }

    /**
     * Sets a user property, which cannot be overwritten by set/unset
     * property calls. Any previous value is overwritten. Also marks
     * these properties as properties that have not come from the
     * command line.
     *
     * @param string $name The name of property to set.
     *             Must not be <code>null</code>.
     * @param string $value The new value of the property.
     *              Must not be <code>null</code>.
     * @see #setProperty()
     */
    public function setInheritedProperty($name, $value) {
        $this->inheritedProperties[$name] = $value;
        $this->setUserProperty($name, $value);
    }

    /**
     * Sets a property unless it is already defined as a user property
     * (in which case the method returns silently).
     *
     * @param name The name of the property.
     *             Must not be <code>null</code>.
     * @param value The property value. Must not be <code>null</code>.
     */
    private function setPropertyInternal($name, $value) {
        if (isset($this->userProperties[$name])) {
			$this->log("Override ignored for user property " . $name, PROJECT_MSG_VERBOSE);
            return;
        }
        $this->properties[$name] = $value;
    }

    /**
     * Returns the value of a property, if it is set.
     *
     * @param string $name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return string The property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     */
    public function getProperty($name) {
        if (!isset($this->properties[$name])) {
            return null;
        }
        return $this->properties[$name];
    }

    /**
     * Replaces ${} style constructions in the given value with the
     * string value of the corresponding data types.
     *
     * @param value The string to be scanned for property references.
     *              May be <code>null</code>.
     *
     * @return the given string with embedded property names replaced
     *         by values, or <code>null</code> if the given string is
     *         <code>null</code>.
     *
     * @exception BuildException if the given value has an unclosed
     *                           property name, e.g. <code>${xxx</code>
     */
    public function replaceProperties($value) {
        return ProjectConfigurator::replaceProperties($this, $value, $this->properties);
    }

    /**
     * Returns the value of a user property, if it is set.
     *
     * @param string $name The name of the property.
     *             May be <code>null</code>, in which case
     *             the return value is also <code>null</code>.
     * @return string  The property value, or <code>null</code> for no match
     *         or if a <code>null</code> name is provided.
     */
     public function getUserProperty($name) {
        if (!isset($this->userProperties[$name])) {
            return null;
        }
        return $this->userProperties[$name];
    }

    /**
     * Returns a copy of the properties table.
     * @return array A hashtable containing all properties
     *         (including user properties).
     */
    public function getProperties() {
        return $this->properties;
    }

    /**
     * Returns a copy of the user property hashtable
     * @return a hashtable containing just the user properties
     */
    public function getUserProperties() {
        return $this->userProperties;
    }

    /**
     * Copies all user properties that have been set on the command
     * line or a GUI tool from this instance to the Project instance
     * given as the argument.
     *
     * <p>To copy all "user" properties, you will also have to call
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
     *
     * @param Project $other the project to copy the properties to.  Must not be null.
     * @return void
     * @since phing 2.0
     */
    public function copyUserProperties(Project $other) {        
        foreach($this->userProperties as $arg => $value) {
            if (isset($this->inheritedProperties[$arg])) {
                continue;
            }
            $other->setUserProperty($arg, $value);
        }
    }

    /**
     * Copies all user properties that have not been set on the
     * command line or a GUI tool from this instance to the Project
     * instance given as the argument.
     *
     * <p>To copy all "user" properties, you will also have to call
     * {@link #copyUserProperties copyUserProperties}.</p>
     *
     * @param other the project to copy the properties to.  Must not be null.
     *
     * @since phing 2.0
     */
    public function copyInheritedProperties(Project $other) {
        foreach($this->userProperties as $arg => $value) {
            if ($other->getUserProperty($arg) !== null) {
                continue;
            }
            $other->setInheritedProperty($arg, $value);
        }        
    }
    
    // ---------------------------------------------------------
    //  END Properties methods
    // ---------------------------------------------------------


    function setDefaultTarget($targetName) {
        $this->defaultTarget = (string) trim($targetName);
    }

    function getDefaultTarget() {
        return (string) $this->defaultTarget;
    }

    /**
     * Sets the name of the current project
     *
     * @param    string   name of project
     * @return   void
     * @access   public
     * @author   Andreas Aderhold, andi@binarycloud.com
     */

    function setName($name) {
        $this->name = (string) trim($name);
        $this->setProperty("phing.project.name", $this->name);
    }

    /**
     * Returns the name of this project
     *
     * @returns  string  projectname
     * @access   public
     * @author   Andreas Aderhold, andi@binarycloud.com
     */
    function getName() {
        return (string) $this->name;
    }

    /** Set the projects description */
    function setDescription($description) {
        $this->description = (string) trim($description);
    }

    /** return the description, null otherwise */
    function getDescription() {
        return $this->description;
    }

    /** Set basedir object from xml*/
    function setBasedir($dir) {
        if ($dir instanceof PhingFile) {
            $dir = $dir->getAbsolutePath();
        }

        $dir = $this->fileUtils->normalize($dir);

        $dir = new PhingFile((string) $dir);
        if (!$dir->exists()) {
            throw new BuildException("Basedir ".$dir->getAbsolutePath()." does not exist");
        }
        if (!$dir->isDirectory()) {
            throw new BuildException("Basedir ".$dir->getAbsolutePath()." is not a directory");
        }
        $this->basedir = $dir;
        $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath());
        $this->log("Project base dir set to: " . $this->basedir->getPath(), PROJECT_MSG_VERBOSE);
        
        // [HL] added this so that ./ files resolve correctly.  This may be a mistake ... or may be in wrong place.                
        chdir($dir->getAbsolutePath());
    }

    /**
     * Returns the basedir of this project
     *
     * @returns  PhingFile  Basedir PhingFile object
     * @access   public
     * @throws   BuildException
     * @author   Andreas Aderhold, andi@binarycloud.com
     */
    function getBasedir() {
        if ($this->basedir === null) {            
            try { // try to set it
                $this->setBasedir(".");
            } catch (BuildException $exc) {
                throw new BuildException("Can not set default basedir. ".$exc->getMessage());
            }
        }
        return $this->basedir;
    }

    /**
     * Sets system properties and the environment variables for this project.
     * 
     * @return void
     */
    function setSystemProperties() {
        
        // first get system properties
        $systemP = array_merge( self::getProperties(), Phing::getProperties() );
        foreach($systemP as $name => $value) {
            $this->setPropertyInternal($name, $value);
        }
        
        // and now the env vars
        foreach($_SERVER as $name => $value) {
            // skip arrays
            if (is_array($value)) {
                continue;
            }
            $this->setPropertyInternal('env.' . $name, $value);
        }
        return true;
    }


    /**
     * Adds a task definition.
     * @param string $name Name of tag.
     * @param string $class The class path to use.
     * @param string $classpath The classpat to use.
     */
    function addTaskDefinition($name, $class, $classpath = null) {
        $name  = $name;
        $class = $class;
        if ($class === "") {
            $this->log("Task $name has no class defined.", PROJECT_MSG_ERR);
        }  elseif (!isset($this->taskdefs[$name])) {
            Phing::import($class, $classpath);
            $this->taskdefs[$name] = $class;
            $this->log("  +Task definiton: $name ($class)", PROJECT_MSG_DEBUG);
        } else {
            $this->log("Task $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
        }
    }

    function &getTaskDefinitions() {
        return $this->taskdefs;
    }

    /**
     * Adds a data type definition.
     * @param string $name Name of tag.
     * @param string $class The class path to use.
     * @param string $classpath The classpat to use.
     */
    function addDataTypeDefinition($typeName, $typeClass, $classpath = null) {    
        if (!isset($this->typedefs[$typeName])) {        
            Phing::import($typeClass, $classpath);
            $this->typedefs[$typeName] = $typeClass;
            $this->log("  +User datatype: $typeName ($typeClass)", PROJECT_MSG_DEBUG);
        } else {
            $this->log("Type $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
        }
    }

    function getDataTypeDefinitions() {
        return $this->typedefs;
    }

    /** add a new target to the project */
    function addTarget($targetName, &$target) {
        if (isset($this->targets[$targetName])) {
            throw new BuildException("Duplicate target: $targetName");
        }
        $this->addOrReplaceTarget($targetName, $target);
    }

    function addOrReplaceTarget($targetName, &$target) {
        $this->log("  +Target: $targetName", PROJECT_MSG_DEBUG);
        $target->setProject($this);
        $this->targets[$targetName] = $target;
    }

    function getTargets() {
        return $this->targets;
    }

    /**
     * Create a new task instance and return reference to it. This method is
     * sorta factory like. A _local_ instance is created and a reference returned to
     * that instance. Usually PHP destroys local variables when the function call
     * ends. But not if you return a reference to that variable.
     * This is kinda error prone, because if no reference exists to the variable
     * it is destroyed just like leaving the local scope with primitive vars. There's no
     * central place where the instance is stored as in other OOP like languages.
     *
     * [HL] Well, ZE2 is here now, and this is  still working. We'll leave this alone
     * unless there's any good reason not to.
     *
     * @param    string    $taskType    Task name
     * @returns  Task                A task object
     * @throws   BuildException
     *           Exception
     */
    function createTask($taskType) {
        try {
            $cls = "";
            $tasklwr = strtolower($taskType);
            foreach ($this->taskdefs as $name => $class) {
                if (strtolower($name) === $tasklwr) {
                    $cls = StringHelper::unqualify($class);                                    
                    break;
                }
            }
            
            if ($cls === "") {
                return null;
            }
            
            if (!class_exists($cls)) {
                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.)");
            }
            
            $o = new $cls();        
    
            if ($o instanceof Task) {
                $task = $o;
            } else {
                $this->log ("  (Using TaskAdapter for: $taskType)", PROJECT_MSG_DEBUG);
                // not a real task, try adapter
                $taskA = new TaskAdapter();
                $taskA->setProxy($o);
                $task = $taskA;
            }
            $task->setProject($this);
            $task->setTaskType($taskType);
            // set default value, can be changed by the user
            $task->setTaskName($taskType);
            $this->log ("  +Task: " . $taskType, PROJECT_MSG_DEBUG);
        } catch (Exception $t) {
            throw new BuildException("Could not create task of type: " . $taskType, $t);
        }
        // everything fine return reference
        return $task;
    }

    /**
     * Create a task instance and return reference to it
     * See createTask() for explanation how this works
     *
     * @param    string   Type name
     * @returns  object   A datatype object
     * @throws   BuildException
     *           Exception
     */
    function createDataType($typeName) {        
        try {
            $cls = "";
            $typelwr = strtolower($typeName);
            foreach ($this->typedefs as $name => $class) {
                if (strtolower($name) === $typelwr) {
                    $cls = StringHelper::unqualify($class);                                    
                    break;
                }
            }
            
            if ($cls === "") {
                return null;
            }
            
            if (!class_exists($cls)) {
                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.)");
            }
            
            $type = new $cls();
            $this->log("  +Type: $typeName", PROJECT_MSG_DEBUG);
            if (!($type instanceof DataType)) {
                throw new Exception("$class is not an instance of phing.types.DataType");
            }
            if ($type instanceof ProjectComponent) {
                $type->setProject($this);
            }
        } catch (Exception $t) {
            throw new BuildException("Could not create type: $typeName", $t);
        }
        // everything fine return reference
        return $type;
    }

    /**
     * Executes a list of targets
     *
     * @param    array  List of target names to execute
     * @returns  void
     * @throws   BuildException
     */
    function executeTargets($targetNames) {
        foreach($targetNames as $tname) {
            $this->executeTarget($tname);
        }
    }

    /**
     * Executes a target
     *
     * @param    string  Name of Target to execute
     * @returns  void
     * @throws   BuildException
     */
    function executeTarget($targetName) {

        // complain about executing void
        if ($targetName === null) {
            throw new BuildException("No target specified");
        }

        // invoke topological sort of the target tree and run all targets
        // until targetName occurs.
        $sortedTargets = $this->_topoSort($targetName, $this->targets);        

        $curIndex = (int) 0;
        $curTarget = null;
        do {
            try {
                $curTarget = $sortedTargets[$curIndex++];
                $curTarget->performTasks();
            } catch (BuildException $exc) {
                $this->log("Execution of target \"".$curTarget->getName()."\" failed for the following reason: ".$exc->getMessage(), PROJECT_MSG_ERR);
                throw $exc;
            }
        } while ($curTarget->getName() !== $targetName);
    }


    function resolveFile($fileName, $rootDir = null) {
        if ($rootDir === null) {
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
        } else {
            return $this->fileUtils->resolveFile($rootDir, $fileName);
        }
    }    

    /**
     * Topologically sort a set of Targets.
     * @param  $root is the (String) name of the root Target. The sort is
     *         created in such a way that the sequence of Targets until the root
     *         target is the minimum possible such sequence.
     * @param  $targets is a array representing a "name to Target" mapping
     * @return An array of Strings with the names of the targets in
     *         sorted order.
     */
    function _topoSort($root, &$targets) {

        $root     = (string) $root;
        $ret      = array();
        $state    = array();
        $visiting = array();

        // We first run a DFS based sort using the root as the starting node.
        // This creates the minimum sequence of Targets to the root node.
        // We then do a sort on any remaining unVISITED targets.
        // This is unnecessary for doing our build, but it catches
        // circular dependencies or missing Targets on the entire
        // dependency tree, not just on the Targets that depend on the
        // build Target.

        $this->_tsort($root, $targets, $state, $visiting, $ret);

        $retHuman = "";
        for ($i=0, $_i=count($ret); $i < $_i; $i++) {
            $retHuman .= $ret[$i]->toString()." ";
        }
        $this->log("Build sequence for target '$root' is: $retHuman", PROJECT_MSG_VERBOSE);

        $keys = array_keys($targets);
        while($keys) {
            $curTargetName = (string) array_shift($keys);
            if (!isset($state[$curTargetName])) {
                $st = null;
            } else {
                $st = (string) $state[$curTargetName];
            }

            if ($st === null) {
                $this->_tsort($curTargetName, $targets, $state, $visiting, $ret);
            } elseif ($st === "VISITING") {
                throw new Exception("Unexpected node in visiting state: $curTargetName");
            }
        }

        $retHuman = "";
        for ($i=0,$_i=count($ret); $i < $_i; $i++) {
            $retHuman .= $ret[$i]->toString()." ";
        }
        $this->log("Complete build sequence is: $retHuman", PROJECT_MSG_VERBOSE);

        return $ret;
    }

    // one step in a recursive DFS traversal of the target dependency tree.
    // - The array "state" contains the state (VISITED or VISITING or null)
    //   of all the target names.
    // - The stack "visiting" contains a stack of target names that are
    //   currently on the DFS stack. (NB: the target names in "visiting" are
    //    exactly the target names in "state" that are in the VISITING state.)
    // 1. Set the current target to the VISITING state, and push it onto
    //    the "visiting" stack.
    // 2. Throw a BuildException if any child of the current node is
    //    in the VISITING state (implies there is a cycle.) It uses the
    //    "visiting" Stack to construct the cycle.
    // 3. If any children have not been VISITED, tsort() the child.
    // 4. Add the current target to the Vector "ret" after the children
    //    have been visited. Move the current target to the VISITED state.
    //    "ret" now contains the sorted sequence of Targets upto the current
    //    Target.

    function _tsort($root, &$targets, &$state, &$visiting, &$ret) {
        $state[$root] = "VISITING";
        $visiting[]  = $root;

        if (!isset($targets[$root]) || !($targets[$root] instanceof Target)) {
            $target = null;
        } else {
            $target = $targets[$root];
        }

        // make sure we exist
        if ($target === null) {
            $sb = "Target '$root' does not exist in this project.";
            array_pop($visiting);
            if (!empty($visiting)) {
                $parent = (string) $visiting[count($visiting)-1];
                $sb .= "It is used from target '$parent'.";
            }
            throw new BuildException($sb);
        }

        $deps = $target->getDependencies();

        while($deps) {
            $cur = (string) array_shift($deps);
            if (!isset($state[$cur])) {
                $m = null;
            } else {
                $m = (string) $state[$cur];
            }
            if ($m === null) {
                // not been visited
                $this->_tsort($cur, $targets, $state, $visiting, $ret);
            } elseif ($m == "VISITING") {
                // currently visiting this node, so have a cycle
                throw $this->_makeCircularException($cur, $visiting);
            }
        }

        $p = (string) array_pop($visiting);
        if ($root !== $p) {
            throw new Exception("Unexpected internal error: expected to pop $root but got $p");
        }

        $state[$root] = "VISITED";
        $ret[] = $target;
    }

    function _makeCircularException($end, $stk) {
        $sb = "Circular dependency: $end";
        do {
            $sb .= " <- ".(string) array_pop($stk);
        } while($c != $end);
        return new BuildException($sb);
    }

    /**
     * Adds a reference to an object. This method is called when the parser
     * detects a id="foo" attribute. It passes the id as $name and a reference
     * to the object assigned to this id as $value
     */
    function addReference($name, $object) {
        if (isset($this->references[$name])) {
            $this->log("Overriding previous definition of reference to $name", PROJECT_MSG_WARN);
        }
        $this->log("Adding reference: $name -> ".get_class($object), PROJECT_MSG_DEBUG);
        $this->references[$name] = $object;
    }

    /**
     * Returns the references array.
     * @return array
     */
    function getReferences() {
        return $this->references;
    }
	
	/**
	 * Returns a specific reference.
	 * @param string $key The reference id/key.
	 * @return object or null if not defined
	 */
	function getReference($key)
	{
		if (isset($this->references[$key])) {
		    return $this->references[$key];
		}
		return null; // just to be explicit
	}

    /**
     * Abstracting and simplifyling Logger calls for project messages
     */
    function log($msg, $level = PROJECT_MSG_INFO) {
        $this->logObject($this, $msg, $level);
    }

    function logObject($obj, $msg, $level) {
        $this->fireMessageLogged($obj, $msg, $level);
    }

    function addBuildListener(BuildListener $listener) {
        $this->listeners[] = $listener;
    }

    function removeBuildListener(BuildListener $listener) {
        $newarray = array();
        for ($i=0, $size=count($this->listeners); $i < $size; $i++) {
            if ($this->listeners[$i] !== $listener) {
                $newarray[] = $this->listeners[$i];
            }
        }
        $this->listeners = $newarray;
    }

    function getBuildListeners() {
        return $this->listeners;
    }

    function fireBuildStarted() {
        $event = new BuildEvent($this);        
        foreach($this->listeners as $listener) {
            $listener->buildStarted($event);
        }
    }

    function fireBuildFinished($exception) {        
        $event = new BuildEvent($this);
        $event->setException($exception);
        foreach($this->listeners as $listener) {
            $listener->buildFinished($event);
        }
    }

    function fireTargetStarted($target) {
        $event = new BuildEvent($target);        
           foreach($this->listeners as $listener) {
            $listener->targetStarted($event);
        }
    }

    function fireTargetFinished($target, $exception) {
        $event = new BuildEvent($target);        
        $event->setException($exception);
        foreach($this->listeners as $listener) {
            $listener->targetFinished($event);
        }
    }

    function fireTaskStarted($task) {
        $event = new BuildEvent($task);        
        foreach($this->listeners as $listener) {
            $listener->taskStarted($event);
        }
    }

    function fireTaskFinished($task, $exception) {
        $event = new BuildEvent($task);        
        $event->setException($exception);
        foreach($this->listeners as $listener) {
            $listener->taskFinished($event);
        }
    }

    function fireMessageLoggedEvent($event, $message, $priority) {
        $event->setMessage($message, $priority);
        foreach($this->listeners as $listener) {
            $listener->messageLogged($event);
        }
    }

    function fireMessageLogged($object, $message, $priority) {
        $this->fireMessageLoggedEvent(new BuildEvent($object), $message, $priority);
    }
}