Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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