Completed
Push — master ( 9cf089...a49cfd )
by Siad
12:42
created

Project::_tsort()   B

Complexity

Conditions 10
Paths 28

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.2918

Importance

Changes 0
Metric Value
cc 10
eloc 29
c 0
b 0
f 0
nc 28
nop 4
dl 0
loc 47
ccs 24
cts 28
cp 0.8571
crap 10.2918
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
/**
21
 *  The Phing project class. Represents a completely configured Phing project.
22
 *  The class defines the project and all tasks/targets. It also contains
23
 *  methods to start a build as well as some properties and FileSystem
24
 *  abstraction.
25
 *
26
 * @author Andreas Aderhold <[email protected]>
27
 * @author Hans Lellelid <[email protected]>
28
 *
29
 * @package phing
30
 */
31
class Project
32
{
33
34
    // Logging level constants.
35
    public const MSG_DEBUG = 4;
36
    public const MSG_VERBOSE = 3;
37
    public const MSG_INFO = 2;
38
    public const MSG_WARN = 1;
39
    public const MSG_ERR = 0;
40
41
    /**
42
     * contains the targets
43
     *
44
     * @var Target[]
45
     */
46
    private $targets = [];
47
    /**
48
     * global filterset (future use)
49
     */
50
    private $globalFilterSet = [];
51
    /**
52
     * all globals filters (future use)
53
     */
54
    private $globalFilters = [];
0 ignored issues
show
introduced by
The private property $globalFilters is not used, and could be removed.
Loading history...
55
56
    /**
57
     * holds ref names and a reference to the referred object
58
     */
59
    private $references = [];
60
61
    /**
62
     * The InputHandler being used by this project.
63
     *
64
     * @var InputHandler
65
     */
66
    private $inputHandler;
67
68
    /* -- properties that come in via xml attributes -- */
69
70
    /**
71
     * basedir (PhingFile object)
72
     */
73
    private $basedir;
74
75
    /**
76
     * the default target name
77
     */
78
    private $defaultTarget = 'all';
79
80
    /**
81
     * project name (required)
82
     */
83
    private $name;
84
85
    /**
86
     * project description
87
     */
88
    private $description;
89
90
    /**
91
     * require phing version
92
     */
93
    private $phingVersion;
94
95
    /**
96
     * project strict mode
97
     */
98
    private $strictMode = false;
99
100
    /**
101
     * a FileUtils object
102
     */
103
    private $fileUtils;
104
105
    /**
106
     * Build listeneers
107
     */
108
    private $listeners = [];
109
110
    /**
111
     * Keep going flag.
112
     */
113
    private $keepGoingMode = false;
114
115
    /**
116
     * @var string[]
117
     */
118
    private $executedTargetNames = [];
119
120
    /**
121
     *  Constructor, sets any default vars.
122
     */
123 815
    public function __construct()
124
    {
125 815
        $this->fileUtils = new FileUtils();
126 815
    }
127
128
    /**
129
     * Sets the input handler
130
     *
131
     * @param InputHandler $handler
132
     */
133 11
    public function setInputHandler($handler)
134
    {
135 11
        $this->inputHandler = $handler;
136 11
    }
137
138
    /**
139
     * Retrieves the current input handler.
140
     *
141
     * @return InputHandler
142
     */
143 11
    public function getInputHandler()
144
    {
145 11
        return $this->inputHandler;
146
    }
147
148
    /**
149
     * inits the project, called from main app
150
     */
151 780
    public function init()
152
    {
153
        // set builtin properties
154 780
        $this->setSystemProperties();
155
156 780
        $componentHelper = ComponentHelper::getComponentHelper($this);
157
158 780
        $componentHelper->initDefaultDefinitions();
159 780
    }
160
161
    /**
162
     * returns the global filterset (future use)
163
     */
164
    public function getGlobalFilterSet()
165
    {
166
        return $this->globalFilterSet;
167
    }
168
169
    // ---------------------------------------------------------
170
    // Property methods
171
    // ---------------------------------------------------------
172
173
    /**
174
     * Sets a property. Any existing property of the same name
175
     * is overwritten, unless it is a user property.
176
     *
177
     * @param string $name The name of property to set.
178
     *                       Must not be <code>null</code>.
179
     * @param string $value The new value of the property.
180
     *                       Must not be <code>null</code>.
181
     */
182 171
    public function setProperty($name, $value)
183
    {
184 171
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, true);
185 171
    }
186
187
    /**
188
     * Sets a property if no value currently exists. If the property
189
     * exists already, a message is logged and the method returns with
190
     * no other effect.
191
     *
192
     * @param string $name The name of property to set.
193
     *                      Must not be <code>null</code>.
194
     * @param string $value The new value of the property.
195
     *                      Must not be <code>null</code>.
196
     * @since 2.0
197
     */
198 65
    public function setNewProperty($name, $value)
199
    {
200 65
        PropertyHelper::getPropertyHelper($this)->setNewProperty(null, $name, $value);
201 65
    }
202
203
    /**
204
     * Sets a user property, which cannot be overwritten by
205
     * set/unset property calls. Any previous value is overwritten.
206
     *
207
     * @param string $name The name of property to set.
208
     *                      Must not be <code>null</code>.
209
     * @param string $value The new value of the property.
210
     *                      Must not be <code>null</code>.
211
     * @see   setProperty()
212
     */
213 780
    public function setUserProperty($name, $value)
214
    {
215 780
        PropertyHelper::getPropertyHelper($this)->setUserProperty(null, $name, $value);
216 780
    }
217
218
    /**
219
     * Sets a user property, which cannot be overwritten by set/unset
220
     * property calls. Any previous value is overwritten. Also marks
221
     * these properties as properties that have not come from the
222
     * command line.
223
     *
224
     * @param string $name The name of property to set.
225
     *                      Must not be <code>null</code>.
226
     * @param string $value The new value of the property.
227
     *                      Must not be <code>null</code>.
228
     * @see   setProperty()
229
     */
230 1
    public function setInheritedProperty($name, $value)
231
    {
232 1
        PropertyHelper::getPropertyHelper($this)->setInheritedProperty(null, $name, $value);
233 1
    }
234
235
    /**
236
     * Sets a property unless it is already defined as a user property
237
     * (in which case the method returns silently).
238
     *
239
     * @param string $name The name of the property.
240
     *                      Must not be
241
     *                      <code>null</code>.
242
     * @param string $value The property value. Must not be <code>null</code>.
243
     */
244 796
    private function setPropertyInternal($name, $value)
245
    {
246 796
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, false);
247 796
    }
248
249
    /**
250
     * Returns the value of a property, if it is set.
251
     *
252
     * @param  string $name The name of the property.
253
     *                      May be <code>null</code>, in which case
254
     *                      the return value is also <code>null</code>.
255
     * @return string The property value, or <code>null</code> for no match
256
     *                     or if a <code>null</code> name is provided.
257
     */
258 780
    public function getProperty($name)
259
    {
260 780
        return PropertyHelper::getPropertyHelper($this)->getProperty(null, $name);
261
    }
262
263
    /**
264
     * Replaces ${} style constructions in the given value with the
265
     * string value of the corresponding data types.
266
     *
267
     * @param string $value The value string to be scanned for property references.
268
     *                      May be <code>null</code>.
269
     *
270
     * @return string the given string with embedded property names replaced
271
     *                by values, or <code>null</code> if the given string is
272
     *                <code>null</code>.
273
     *
274
     * @throws BuildException if the given value has an unclosed
275
     *                           property name, e.g. <code>${xxx</code>
276
     */
277 632
    public function replaceProperties($value)
278
    {
279 632
        return PropertyHelper::getPropertyHelper($this)->replaceProperties($value, $this->getProperties());
280
    }
281
282
    /**
283
     * Returns the value of a user property, if it is set.
284
     *
285
     * @param  string $name The name of the property.
286
     *                      May be <code>null</code>, in which case
287
     *                      the return value is also <code>null</code>.
288
     * @return string The property value, or <code>null</code> for no match
289
     *                     or if a <code>null</code> name is provided.
290
     */
291 1
    public function getUserProperty($name)
292
    {
293 1
        return PropertyHelper::getPropertyHelper($this)->getUserProperty(null, $name);
294
    }
295
296
    /**
297
     * Returns a copy of the properties table.
298
     *
299
     * @return array A hashtable containing all properties
300
     *               (including user properties).
301
     */
302 781
    public function getProperties()
303
    {
304 781
        return PropertyHelper::getPropertyHelper($this)->getProperties();
305
    }
306
307
    /**
308
     * Returns a copy of the user property hashtable
309
     *
310
     * @return array a hashtable containing just the user properties
311
     */
312
    public function getUserProperties()
313
    {
314
        return PropertyHelper::getPropertyHelper($this)->getUserProperties();
315
    }
316
317
    public function getInheritedProperties()
318
    {
319
        return PropertyHelper::getPropertyHelper($this)->getInheritedProperties();
320
    }
321
322
    /**
323
     * Copies all user properties that have been set on the command
324
     * line or a GUI tool from this instance to the Project instance
325
     * given as the argument.
326
     *
327
     * <p>To copy all "user" properties, you will also have to call
328
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
329
     *
330
     * @param  Project $other the project to copy the properties to.  Must not be null.
331
     * @return void
332
     * @since  phing 2.0
333
     */
334 11
    public function copyUserProperties(Project $other)
335
    {
336 11
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
337 11
    }
338
339
    /**
340
     * Copies all user properties that have not been set on the
341
     * command line or a GUI tool from this instance to the Project
342
     * instance given as the argument.
343
     *
344
     * <p>To copy all "user" properties, you will also have to call
345
     * {@link #copyUserProperties copyUserProperties}.</p>
346
     *
347
     * @param Project $other the project to copy the properties to.  Must not be null.
348
     *
349
     * @since phing 2.0
350
     */
351 11
    public function copyInheritedProperties(Project $other)
352
    {
353 11
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
354 11
    }
355
356
    // ---------------------------------------------------------
357
    //  END Properties methods
358
    // ---------------------------------------------------------
359
360
    /**
361
     * Sets default target
362
     *
363
     * @param string $targetName
364
     */
365 780
    public function setDefaultTarget($targetName)
366
    {
367 780
        $this->defaultTarget = (string) trim($targetName);
368 780
    }
369
370
    /**
371
     * Returns default target
372
     *
373
     * @return string
374
     */
375
    public function getDefaultTarget()
376
    {
377
        return (string) $this->defaultTarget;
378
    }
379
380
    /**
381
     * Sets the name of the current project
382
     *
383
     * @param  string $name name of project
384
     * @return void
385
     * @author Andreas Aderhold, [email protected]
386
     */
387 780
    public function setName($name)
388
    {
389 780
        $this->name = (string) trim($name);
390 780
        $this->setUserProperty("phing.project.name", $this->name);
391 780
    }
392
393
    /**
394
     * Returns the name of this project
395
     *
396
     * @return string projectname
397
     * @author Andreas Aderhold, [email protected]
398
     */
399 1
    public function getName()
400
    {
401 1
        return (string) $this->name;
402
    }
403
404
    /**
405
     * Set the projects description
406
     *
407
     * @param string $description
408
     */
409 6
    public function setDescription($description)
410
    {
411 6
        $this->description = $description;
412 6
    }
413
414
    /**
415
     * return the description, null otherwise
416
     *
417
     * @return string|null
418
     */
419 6
    public function getDescription()
420
    {
421 6
        if ($this->description === null) {
422 5
            $this->description = Description::getAll($this);
423
        }
424 6
        return $this->description;
425
    }
426
427
    /**
428
     * Set the minimum required phing version
429
     *
430
     * @param string $version
431
     */
432 5
    public function setPhingVersion($version)
433
    {
434 5
        $version = str_replace('phing', '', strtolower($version));
435 5
        $this->phingVersion = (string) trim($version);
436 5
    }
437
438
    /**
439
     * Get the minimum required phing version
440
     *
441
     * @return string
442
     */
443 5
    public function getPhingVersion()
444
    {
445 5
        if ($this->phingVersion === null) {
446 5
            $this->setPhingVersion(Phing::getPhingVersion());
447
        }
448
449 5
        return $this->phingVersion;
450
    }
451
452
    /**
453
     * Sets the strict-mode (status) for the current project
454
     * (If strict mode is On, all the warnings would be converted to an error
455
     * (and the build will be stopped/aborted)
456
     *
457
     * @param bool $strictmode
458
     * @return void
459
     * @access public
460
     * @author Utsav Handa, [email protected]
461
     */
462
    public function setStrictMode(bool $strictmode)
463
    {
464
        $this->strictMode = $strictmode;
465
        $this->setProperty("phing.project.strictmode", $this->strictMode);
466
    }
467
468
    /**
469
     * Get the strict-mode status for the project
470
     *
471
     * @return boolean
472
     */
473
    public function getStrictmode()
474
    {
475
        return $this->strictMode;
476
    }
477
478
    /**
479
     * Set basedir object from xm
480
     *
481
     * @param  PhingFile|string $dir
482
     * @throws BuildException
483
     */
484 796
    public function setBasedir($dir)
485
    {
486 796
        if ($dir instanceof PhingFile) {
487 73
            $dir = $dir->getAbsolutePath();
488
        }
489
490 796
        $dir = $this->fileUtils->normalize($dir);
491 796
        $dir = FileSystem::getFileSystem()->canonicalize($dir);
492
493 796
        $dir = new PhingFile((string) $dir);
494 796
        if (!$dir->exists()) {
495
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " does not exist");
496
        }
497 796
        if (!$dir->isDirectory()) {
498
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " is not a directory");
499
        }
500 796
        $this->basedir = $dir;
501 796
        $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath());
502 796
        $this->log("Project base dir set to: " . $this->basedir->getPath(), Project::MSG_VERBOSE);
503
504
        // [HL] added this so that ./ files resolve correctly.  This may be a mistake ... or may be in wrong place.
505 796
        chdir($dir->getAbsolutePath());
506 796
    }
507
508
    /**
509
     * Returns the basedir of this project
510
     *
511
     * @return PhingFile      Basedir PhingFile object
512
     *
513
     * @throws BuildException
514
     *
515
     * @author Andreas Aderhold, [email protected]
516
     */
517 156
    public function getBasedir()
518
    {
519 156
        if ($this->basedir === null) {
520
            try { // try to set it
521
                $this->setBasedir(".");
522
            } catch (BuildException $exc) {
523
                throw new BuildException("Can not set default basedir. " . $exc->getMessage());
524
            }
525
        }
526
527 156
        return $this->basedir;
528
    }
529
530
    /**
531
     * Set &quot;keep-going&quot; mode. In this mode Ant will try to execute
532
     * as many targets as possible. All targets that do not depend
533
     * on failed target(s) will be executed.  If the keepGoing settor/getter
534
     * methods are used in conjunction with the <code>ant.executor.class</code>
535
     * property, they will have no effect.
536
     *
537
     * @param bool $keepGoingMode &quot;keep-going&quot; mode
538
     */
539
    public function setKeepGoingMode($keepGoingMode)
540
    {
541
        $this->keepGoingMode = $keepGoingMode;
542
    }
543
544
    /**
545
     * Return the keep-going mode.  If the keepGoing settor/getter
546
     * methods are used in conjunction with the <code>phing.executor.class</code>
547
     * property, they will have no effect.
548
     *
549
     * @return bool &quot;keep-going&quot; mode
550
     */
551
    public function isKeepGoingMode()
552
    {
553
        return $this->keepGoingMode;
554
    }
555
556
    /**
557
     * Sets system properties and the environment variables for this project.
558
     *
559
     * @return void
560
     */
561 780
    public function setSystemProperties()
562
    {
563
564
        // first get system properties
565 780
        $systemP = array_merge($this->getProperties(), Phing::getProperties());
566 780
        foreach ($systemP as $name => $value) {
567 780
            $this->setPropertyInternal($name, $value);
568
        }
569
570
        // and now the env vars
571 780
        foreach ($_SERVER as $name => $value) {
572
            // skip arrays
573 780
            if (is_array($value)) {
574 780
                continue;
575
            }
576 780
            $this->setPropertyInternal('env.' . $name, $value);
577
        }
578 780
    }
579
580
    /**
581
     * Adds a task definition.
582
     *
583
     * @param string $name Name of tag.
584
     * @param string $class The class path to use.
585
     * @param string $classpath The classpat to use.
586
     */
587 51
    public function addTaskDefinition($name, $class, $classpath = null)
588
    {
589 51
        ComponentHelper::getComponentHelper($this)->addTaskDefinition($name, $class, $classpath);
590 51
    }
591
592
    /**
593
     * Returns the task definitions
594
     *
595
     * @return array
596
     */
597 20
    public function getTaskDefinitions()
598
    {
599 20
        return ComponentHelper::getComponentHelper($this)->getTaskDefinitions();
600
    }
601
602
    /**
603
     * Adds a data type definition.
604
     *
605
     * @param string $typeName Name of the type.
606
     * @param string $typeClass The class to use.
607
     * @param string $classpath The classpath to use.
608
     */
609 18
    public function addDataTypeDefinition($typeName, $typeClass, $classpath = null)
610
    {
611 18
        ComponentHelper::getComponentHelper($this)->addDataTypeDefinition($typeName, $typeClass, $classpath);
612 18
    }
613
614
    /**
615
     * Returns the data type definitions
616
     *
617
     * @return array
618
     */
619 781
    public function getDataTypeDefinitions()
620
    {
621 781
        return ComponentHelper::getComponentHelper($this)->getDataTypeDefinitions();
622
    }
623
624
    /**
625
     * Add a new target to the project
626
     *
627
     * @param  string $targetName
628
     * @param  Target $target
629
     * @throws BuildException
630
     */
631 780
    public function addTarget($targetName, $target)
632
    {
633 780
        if (isset($this->targets[$targetName])) {
634
            throw new BuildException("Duplicate target: $targetName");
635
        }
636 780
        $this->addOrReplaceTarget($targetName, $target);
637 780
    }
638
639
    /**
640
     * Adds or replaces a target in the project
641
     *
642
     * @param string $targetName
643
     * @param Target $target
644
     */
645 780
    public function addOrReplaceTarget($targetName, &$target)
646
    {
647 780
        $this->log("  +Target: $targetName", Project::MSG_DEBUG);
648 780
        $target->setProject($this);
649 780
        $this->targets[$targetName] = $target;
650
651 780
        $ctx = $this->getReference(ProjectConfigurator::PARSING_CONTEXT_REFERENCE);
652 780
        $current = $ctx->getCurrentTargets();
653 780
        $current[$targetName] = $target;
654 780
    }
655
656
    /**
657
     * Returns the available targets
658
     *
659
     * @return Target[]
660
     */
661 780
    public function getTargets()
662
    {
663 780
        return $this->targets;
664
    }
665
666
    /**
667
     * @return string[]
668
     */
669
    public function getExecutedTargetNames()
670
    {
671
        return $this->executedTargetNames;
672
    }
673
674
    /**
675
     * Create a new task instance and return reference to it.
676
     *
677
     * @param  string $taskType Task name
678
     * @return Task           A task object
679
     * @throws BuildException
680
     */
681 638
    public function createTask($taskType)
682
    {
683 638
        return ComponentHelper::getComponentHelper($this)->createTask($taskType);
684
    }
685
686
    /**
687
     * Creates a new condition and returns the reference to it
688
     *
689
     * @param  string $conditionType
690
     * @return Condition
691
     * @throws BuildException
692
     */
693 1
    public function createCondition($conditionType)
694
    {
695 1
        return ComponentHelper::getComponentHelper($this)->createCondition($conditionType);
696
    }
697
698
    /**
699
     * Create a datatype instance and return reference to it
700
     * See createTask() for explanation how this works
701
     *
702
     * @param  string $typeName Type name
703
     * @return object         A datatype object
704
     * @throws BuildException
705
     *                                 Exception
706
     */
707 49
    public function createDataType($typeName)
708
    {
709 49
        return ComponentHelper::getComponentHelper($this)->createDataType($typeName);
710
    }
711
712
    /**
713
     * Executes a list of targets
714
     *
715
     * @param  array $targetNames List of target names to execute
716
     * @return void
717
     * @throws BuildException
718
     */
719
    public function executeTargets($targetNames)
720
    {
721
        $this->executedTargetNames = $targetNames;
722
723
        foreach ($targetNames as $tname) {
724
            $this->executeTarget($tname);
725
        }
726
    }
727
728
    /**
729
     * Executes a target
730
     *
731
     * @param  string $targetName Name of Target to execute
732
     * @return void
733
     * @throws BuildException
734
     */
735 588
    public function executeTarget($targetName)
736
    {
737
738
        // complain about executing void
739 588
        if ($targetName === null) {
0 ignored issues
show
introduced by
The condition $targetName === null is always false.
Loading history...
740
            throw new BuildException("No target specified");
741
        }
742
743
        // invoke topological sort of the target tree and run all targets
744
        // until targetName occurs.
745 588
        $sortedTargets = $this->topoSort($targetName);
746
747 588
        $curIndex = (int) 0;
748 588
        $curTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $curTarget is dead and can be removed.
Loading history...
749 588
        $thrownException = null;
750 588
        $buildException = null;
751
        do {
752
            try {
753 588
                $curTarget = $sortedTargets[$curIndex++];
754 588
                $curTarget->performTasks();
755 155
            } catch (BuildException $exc) {
756 155
                if (!($this->keepGoingMode)) {
757 155
                    throw $exc;
758
                }
759
                $thrownException = $exc;
760
            }
761 463
            if ($thrownException != null) {
762
                if ($thrownException instanceof BuildException) {
763
                    $this->log(
764
                        "Target '" . $curTarget->getName()
765
                        . "' failed with message '"
766
                        . $thrownException->getMessage() . "'.",
767
                        Project::MSG_ERR
768
                    );
769
                    // only the first build exception is reported
770
                    if ($buildException === null) {
771
                        $buildException = $thrownException;
772
                    }
773
                } else {
774
                    $this->log(
775
                        "Target '" . $curTarget->getName()
776
                        . "' failed with message '"
777
                        . $thrownException->getMessage() . "'." . PHP_EOL
778
                        . $thrownException->getTraceAsString(),
779
                        Project::MSG_ERR
780
                    );
781
                    if ($buildException === null) {
782
                        $buildException = new BuildException($thrownException);
783
                    }
784
                }
785
            }
786 463
        } while ($curTarget->getName() !== $targetName);
787
788 463
        if ($buildException !== null) {
789
            throw $buildException;
790
        }
791 463
    }
792
793
    /**
794
     * Helper function
795
     *
796
     * @param  string $fileName
797
     * @param  PhingFile $rootDir
798
     * @throws IOException
799
     * @return \PhingFile
800
     */
801 394
    public function resolveFile(string $fileName, PhingFile $rootDir = null): PhingFile
802
    {
803 394
        if ($rootDir === null) {
804 365
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
805
        }
806 73
        return $this->fileUtils->resolveFile($rootDir, $fileName);
807
    }
808
809
    /**
810
     * Return the boolean equivalent of a string, which is considered
811
     * <code>true</code> if either <code>"on"</code>, <code>"true"</code>,
812
     * or <code>"yes"</code> is found, ignoring case.
813
     *
814
     * @param string $s The string to convert to a boolean value.
815
     *
816
     * @return <code>true</code> if the given string is <code>"on"</code>,
0 ignored issues
show
Documentation Bug introduced by
The doc comment <code>true</code> at position 0 could not be parsed: Unknown type name '<' at position 0 in <code>true</code>.
Loading history...
817
     *         <code>"true"</code> or <code>"yes"</code>, or
818
     *         <code>false</code> otherwise.
819
     */
820 7
    public static function toBoolean($s)
821
    {
822
        return (
823 7
            strcasecmp($s, 'on') === 0
824 7
            || strcasecmp($s, 'true') === 0
825 5
            || strcasecmp($s, 'yes') === 0
826
            // FIXME next condition should be removed if the boolean behavior for properties will be solved
827 7
            || strcasecmp($s, 1) === 0
828
        );
829
    }
830
831
    /**
832
     * Topologically sort a set of Targets.
833
     *
834
     * @param  string $rootTarget is the (String) name of the root Target. The sort is
835
     *                         created in such a way that the sequence of Targets until the root
836
     *                         target is the minimum possible such sequence.
837
     * @throws BuildException
838
     * @throws Exception
839
     * @return Target[] targets in sorted order
840
     */
841 588
    public function topoSort($rootTarget)
842
    {
843 588
        $rootTarget = (string) $rootTarget;
844 588
        $ret = [];
845 588
        $state = [];
846 588
        $visiting = [];
847
848
        // We first run a DFS based sort using the root as the starting node.
849
        // This creates the minimum sequence of Targets to the root node.
850
        // We then do a sort on any remaining unVISITED targets.
851
        // This is unnecessary for doing our build, but it catches
852
        // circular dependencies or missing Targets on the entire
853
        // dependency tree, not just on the Targets that depend on the
854
        // build Target.
855
856 588
        $this->_tsort($rootTarget, $state, $visiting, $ret);
857
858 588
        $retHuman = "";
859 588
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
860 588
            $retHuman .= (string) $ret[$i] . " ";
861
        }
862 588
        $this->log("Build sequence for target '$rootTarget' is: $retHuman", Project::MSG_VERBOSE);
863
864 588
        $keys = array_keys($this->targets);
865 588
        while ($keys) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $keys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
866 588
            $curTargetName = (string) array_shift($keys);
867 588
            if (!isset($state[$curTargetName])) {
868 588
                $st = null;
869
            } else {
870 588
                $st = (string) $state[$curTargetName];
871
            }
872
873 588
            if ($st === null) {
874 588
                $this->_tsort($curTargetName, $state, $visiting, $ret);
875 588
            } elseif ($st === "VISITING") {
876
                throw new Exception("Unexpected node in visiting state: $curTargetName");
877
            }
878
        }
879
880 588
        $retHuman = "";
881 588
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
882 588
            $retHuman .= (string) $ret[$i] . " ";
883
        }
884 588
        $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE);
885
886 588
        return $ret;
887
    }
888
889
    // one step in a recursive DFS traversal of the target dependency tree.
890
    // - The array "state" contains the state (VISITED or VISITING or null)
891
    //   of all the target names.
892
    // - The stack "visiting" contains a stack of target names that are
893
    //   currently on the DFS stack. (NB: the target names in "visiting" are
894
    //    exactly the target names in "state" that are in the VISITING state.)
895
    // 1. Set the current target to the VISITING state, and push it onto
896
    //    the "visiting" stack.
897
    // 2. Throw a BuildException if any child of the current node is
898
    //    in the VISITING state (implies there is a cycle.) It uses the
899
    //    "visiting" Stack to construct the cycle.
900
    // 3. If any children have not been VISITED, tsort() the child.
901
    // 4. Add the current target to the Vector "ret" after the children
902
    //    have been visited. Move the current target to the VISITED state.
903
    //    "ret" now contains the sorted sequence of Targets up to the current
904
    //    Target.
905
906
    /**
907
     * @param $root
908
     * @param $state
909
     * @param $visiting
910
     * @param $ret
911
     * @throws BuildException
912
     * @throws Exception
913
     */
914 588
    public function _tsort($root, &$state, &$visiting, &$ret)
915
    {
916 588
        $state[$root] = "VISITING";
917 588
        $visiting[] = $root;
918
919 588
        if (!isset($this->targets[$root]) || !($this->targets[$root] instanceof Target)) {
920 1
            $target = null;
921
        } else {
922 588
            $target = $this->targets[$root];
923
        }
924
925
        // make sure we exist
926 588
        if ($target === null) {
927 1
            $sb = "Target '$root' does not exist in this project.";
928 1
            array_pop($visiting);
929 1
            if (!empty($visiting)) {
930
                $parent = (string) $visiting[count($visiting) - 1];
931
                $sb .= " It is a dependency of target '$parent'.";
932
            }
933 1
            throw new BuildException($sb);
934
        }
935
936 588
        $deps = $target->getDependencies();
937
938 588
        while ($deps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $deps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
939 106
            $cur = (string) array_shift($deps);
940 106
            if (!isset($state[$cur])) {
941 54
                $m = null;
942
            } else {
943 96
                $m = (string) $state[$cur];
944
            }
945 106
            if ($m === null) {
946
                // not been visited
947 54
                $this->_tsort($cur, $state, $visiting, $ret);
948 96
            } elseif ($m == "VISITING") {
949
                // currently visiting this node, so have a cycle
950
                throw $this->_makeCircularException($cur, $visiting);
951
            }
952
        }
953
954 588
        $p = (string) array_pop($visiting);
955 588
        if ($root !== $p) {
956
            throw new Exception("Unexpected internal error: expected to pop $root but got $p");
957
        }
958
959 588
        $state[$root] = "VISITED";
960 588
        $ret[] = $target;
961 588
    }
962
963
    /**
964
     * @param string $end
965
     * @param array $stk
966
     * @return BuildException
967
     */
968
    public function _makeCircularException($end, $stk)
969
    {
970
        $sb = "Circular dependency: $end";
971
        do {
972
            $c = (string) array_pop($stk);
973
            $sb .= " <- " . $c;
974
        } while ($c != $end);
975
976
        return new BuildException($sb);
977
    }
978
979
    /**
980
     * Adds a reference to an object. This method is called when the parser
981
     * detects a id="foo" attribute. It passes the id as $name and a reference
982
     * to the object assigned to this id as $value
983
     *
984
     * @param string $name
985
     * @param object $object
986
     */
987 800
    public function addReference($name, $object)
988
    {
989 800
        $ref = $this->references[$name] ?? null;
990 800
        if ($ref === $object) {
991 6
            return;
992
        }
993 800
        if ($ref !== null && !$ref instanceof UnknownElement) {
994 15
            $this->log("Overriding previous definition of reference to $name", Project::MSG_VERBOSE);
995
        }
996 800
        $refName = (is_scalar($object) || $object instanceof PropertyValue) ? (string) $object : get_class($object);
997 800
        $this->log("Adding reference: $name -> " . $refName, Project::MSG_DEBUG);
998 800
        $this->references[$name] = $object;
999 800
    }
1000
1001
    /**
1002
     * Returns the references array.
1003
     *
1004
     * @return array
1005
     */
1006 15
    public function getReferences()
1007
    {
1008 15
        return $this->references;
1009
    }
1010
1011
    /**
1012
     * Returns a specific reference.
1013
     *
1014
     * @param  string $key The reference id/key.
1015
     * @return object Reference or null if not defined
1016
     */
1017 801
    public function getReference($key)
1018
    {
1019 801
        return $this->references[$key] ?? null; // just to be explicit
1020
    }
1021
1022
    /**
1023
     * Abstracting and simplifyling Logger calls for project messages
1024
     *
1025
     * @param string $msg
1026
     * @param int $level
1027
     */
1028 800
    public function log($msg, $level = Project::MSG_INFO)
1029
    {
1030 800
        $this->logObject($this, $msg, $level);
1031 800
    }
1032
1033
    /**
1034
     * @param mixed $obj
1035
     * @param string $msg
1036
     * @param int $level
1037
     * @param Exception|null $t
1038
     */
1039 801
    public function logObject($obj, $msg, $level, Exception $t = null)
1040
    {
1041 801
        $this->fireMessageLogged($obj, $msg, $level, $t);
1042
1043
        // Checking whether the strict-mode is On, then consider all the warnings
1044
        // as errors.
1045 801
        if (($this->strictMode) && (Project::MSG_WARN == $level)) {
1046
            throw new BuildException('Build contains warnings, considered as errors in strict mode', null);
1047
        }
1048 801
    }
1049
1050
    /**
1051
     * @param BuildListener $listener
1052
     */
1053 780
    public function addBuildListener(BuildListener $listener)
1054
    {
1055 780
        $this->listeners[] = $listener;
1056 780
    }
1057
1058
    /**
1059
     * @param BuildListener $listener
1060
     */
1061
    public function removeBuildListener(BuildListener $listener)
1062
    {
1063
        $newarray = [];
1064
        for ($i = 0, $size = count($this->listeners); $i < $size; $i++) {
1065
            if ($this->listeners[$i] !== $listener) {
1066
                $newarray[] = $this->listeners[$i];
1067
            }
1068
        }
1069
        $this->listeners = $newarray;
1070
    }
1071
1072
    /**
1073
     * @return array
1074
     */
1075 11
    public function getBuildListeners()
1076
    {
1077 11
        return $this->listeners;
1078
    }
1079
1080
    public function fireBuildStarted()
1081
    {
1082
        $event = new BuildEvent($this);
1083
        foreach ($this->listeners as $listener) {
1084
            $listener->buildStarted($event);
1085
        }
1086
1087
        $this->log((string) $event, Project::MSG_DEBUG);
1088
    }
1089
1090
    /**
1091
     * @param Exception $exception
1092
     */
1093
    public function fireBuildFinished($exception)
1094
    {
1095
        $event = new BuildEvent($this);
1096
        $event->setException($exception);
1097
        foreach ($this->listeners as $listener) {
1098
            $listener->buildFinished($event);
1099
        }
1100
1101
        $this->log((string) $event, Project::MSG_DEBUG);
1102
    }
1103
1104
    /**
1105
     * @param $target
1106
     */
1107 588
    public function fireTargetStarted($target)
1108
    {
1109 588
        $event = new BuildEvent($target);
1110 588
        foreach ($this->listeners as $listener) {
1111 588
            $listener->targetStarted($event);
1112
        }
1113
1114 588
        $this->log((string) $event, Project::MSG_DEBUG);
1115 588
    }
1116
1117
    /**
1118
     * @param $target
1119
     * @param $exception
1120
     */
1121 588
    public function fireTargetFinished($target, $exception)
1122
    {
1123 588
        $event = new BuildEvent($target);
1124 588
        $event->setException($exception);
1125 588
        foreach ($this->listeners as $listener) {
1126 588
            $listener->targetFinished($event);
1127
        }
1128
1129 588
        $this->log((string) $event, Project::MSG_DEBUG);
1130 588
    }
1131
1132
    /**
1133
     * @param $task
1134
     */
1135 641
    public function fireTaskStarted($task)
1136
    {
1137 641
        $event = new BuildEvent($task);
1138 641
        foreach ($this->listeners as $listener) {
1139 641
            $listener->taskStarted($event);
1140
        }
1141
1142 641
        $this->log((string) $event, Project::MSG_DEBUG);
1143 641
    }
1144
1145
    /**
1146
     * @param $task
1147
     * @param $exception
1148
     */
1149 641
    public function fireTaskFinished($task, $exception)
1150
    {
1151 641
        $event = new BuildEvent($task);
1152 641
        $event->setException($exception);
1153 641
        foreach ($this->listeners as $listener) {
1154 641
            $listener->taskFinished($event);
1155
        }
1156
1157 641
        $this->log((string) $event, Project::MSG_DEBUG);
1158 641
    }
1159
1160
    /**
1161
     * @param $event
1162
     * @param $message
1163
     * @param $priority
1164
     */
1165 801
    public function fireMessageLoggedEvent(BuildEvent $event, $message, $priority)
1166
    {
1167 801
        $event->setMessage($message, $priority);
1168 801
        foreach ($this->listeners as $listener) {
1169 780
            $listener->messageLogged($event);
1170
        }
1171 801
    }
1172
1173
    /**
1174
     * @param mixed $object
1175
     * @param string $message
1176
     * @param int $priority
1177
     * @param Exception $t
1178
     * @throws \Exception
1179
     */
1180 801
    public function fireMessageLogged($object, $message, $priority, Exception $t = null)
1181
    {
1182 801
        $event = new BuildEvent($object);
1183 801
        if ($t !== null) {
1184
            $event->setException($t);
1185
        }
1186 801
        $this->fireMessageLoggedEvent($event, $message, $priority);
1187 801
    }
1188
}
1189