Passed
Push — master ( afa2e3...eb32d2 )
by Siad
18:11
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 854
    public function __construct()
124
    {
125 854
        $this->fileUtils = new FileUtils();
126 854
    }
127
128
    /**
129
     * Sets the input handler
130
     *
131
     * @param InputHandler $handler
132
     */
133 33
    public function setInputHandler($handler)
134
    {
135 33
        $this->inputHandler = $handler;
136 33
    }
137
138
    /**
139
     * Retrieves the current input handler.
140
     *
141
     * @return InputHandler
142
     */
143 33
    public function getInputHandler()
144
    {
145 33
        return $this->inputHandler;
146
    }
147
148
    /**
149
     * inits the project, called from main app
150
     */
151 819
    public function init()
152
    {
153
        // set builtin properties
154 819
        $this->setSystemProperties();
155
156 819
        $componentHelper = ComponentHelper::getComponentHelper($this);
157
158 819
        $componentHelper->initDefaultDefinitions();
159 819
    }
160
161
    /**
162
     * Create and initialize a subproject. By default the subproject will be of
163
     * the same type as its parent. If a no-arg constructor is unavailable, the
164
     * <code>Project</code> class will be used.
165
     * @return Project instance configured as a subproject of this Project.
166
     */
167 36
    public function createSubProject(): \Project
168
    {
169
        try {
170 36
            $ref = new ReflectionObject($this);
171 36
            $subProject = $ref->newInstance();
172
        } catch (ReflectionException $e) {
173
            $subProject = new Project();
174
        }
175 36
        $this->initSubProject($subProject);
176 36
        return $subProject;
177
    }
178
179
    /**
180
     * Initialize a subproject.
181
     * @param Project $subProject the subproject to initialize.
182
     */
183 36
    public function initSubProject(Project $subProject): void
184
    {
185 36
        ComponentHelper::getComponentHelper($subProject)
186 36
            ->initSubProject(ComponentHelper::getComponentHelper($this));
187 36
        $subProject->setKeepGoingMode($this->isKeepGoingMode());
188 36
        $subProject->setStrictMode($this->strictMode);
189 36
    }
190
191
    /**
192
     * returns the global filterset (future use)
193
     */
194
    public function getGlobalFilterSet()
195
    {
196
        return $this->globalFilterSet;
197
    }
198
199
    // ---------------------------------------------------------
200
    // Property methods
201
    // ---------------------------------------------------------
202
203
    /**
204
     * Sets a property. Any existing property of the same name
205
     * is overwritten, unless it is a user property.
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
     */
212 208
    public function setProperty($name, $value)
213
    {
214 208
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, true);
215 208
    }
216
217
    /**
218
     * Sets a property if no value currently exists. If the property
219
     * exists already, a message is logged and the method returns with
220
     * no other effect.
221
     *
222
     * @param string $name The name of property to set.
223
     *                      Must not be <code>null</code>.
224
     * @param string $value The new value of the property.
225
     *                      Must not be <code>null</code>.
226
     * @since 2.0
227
     */
228 82
    public function setNewProperty($name, $value)
229
    {
230 82
        PropertyHelper::getPropertyHelper($this)->setNewProperty(null, $name, $value);
231 82
    }
232
233
    /**
234
     * Sets a user property, which cannot be overwritten by
235
     * set/unset property calls. Any previous value is overwritten.
236
     *
237
     * @param string $name The name of property to set.
238
     *                      Must not be <code>null</code>.
239
     * @param string $value The new value of the property.
240
     *                      Must not be <code>null</code>.
241
     * @see   setProperty()
242
     */
243 819
    public function setUserProperty($name, $value)
244
    {
245 819
        PropertyHelper::getPropertyHelper($this)->setUserProperty(null, $name, $value);
246 819
    }
247
248
    /**
249
     * Sets a user property, which cannot be overwritten by set/unset
250
     * property calls. Any previous value is overwritten. Also marks
251
     * these properties as properties that have not come from the
252
     * command line.
253
     *
254
     * @param string $name The name of property to set.
255
     *                      Must not be <code>null</code>.
256
     * @param string $value The new value of the property.
257
     *                      Must not be <code>null</code>.
258
     * @see   setProperty()
259
     */
260 12
    public function setInheritedProperty($name, $value)
261
    {
262 12
        PropertyHelper::getPropertyHelper($this)->setInheritedProperty(null, $name, $value);
263 12
    }
264
265
    /**
266
     * Sets a property unless it is already defined as a user property
267
     * (in which case the method returns silently).
268
     *
269
     * @param string $name The name of the property.
270
     *                      Must not be
271
     *                      <code>null</code>.
272
     * @param string $value The property value. Must not be <code>null</code>.
273
     */
274 835
    private function setPropertyInternal($name, $value)
275
    {
276 835
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, false);
277 835
    }
278
279
    /**
280
     * Returns the value of a property, if it is set.
281
     *
282
     * @param  string $name The name of the property.
283
     *                      May be <code>null</code>, in which case
284
     *                      the return value is also <code>null</code>.
285
     * @return string The property value, or <code>null</code> for no match
286
     *                     or if a <code>null</code> name is provided.
287
     */
288 819
    public function getProperty($name)
289
    {
290 819
        return PropertyHelper::getPropertyHelper($this)->getProperty(null, $name);
291
    }
292
293
    /**
294
     * Replaces ${} style constructions in the given value with the
295
     * string value of the corresponding data types.
296
     *
297
     * @param string $value The value string to be scanned for property references.
298
     *                      May be <code>null</code>.
299
     *
300
     * @return string the given string with embedded property names replaced
301
     *                by values, or <code>null</code> if the given string is
302
     *                <code>null</code>.
303
     *
304
     * @throws BuildException if the given value has an unclosed
305
     *                           property name, e.g. <code>${xxx</code>
306
     */
307 671
    public function replaceProperties($value)
308
    {
309 671
        return PropertyHelper::getPropertyHelper($this)->replaceProperties($value, $this->getProperties());
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 1
    public function getUserProperty($name)
322
    {
323 1
        return PropertyHelper::getPropertyHelper($this)->getUserProperty(null, $name);
324
    }
325
326
    /**
327
     * Returns a copy of the properties table.
328
     *
329
     * @return array A hashtable containing all properties
330
     *               (including user properties).
331
     */
332 820
    public function getProperties()
333
    {
334 820
        return PropertyHelper::getPropertyHelper($this)->getProperties();
335
    }
336
337
    /**
338
     * Returns a copy of the user property hashtable
339
     *
340
     * @return array a hashtable containing just the user properties
341
     */
342
    public function getUserProperties()
343
    {
344
        return PropertyHelper::getPropertyHelper($this)->getUserProperties();
345
    }
346
347
    public function getInheritedProperties()
348
    {
349
        return PropertyHelper::getPropertyHelper($this)->getInheritedProperties();
350
    }
351
352
    /**
353
     * Copies all user properties that have been set on the command
354
     * line or a GUI tool from this instance to the Project instance
355
     * given as the argument.
356
     *
357
     * <p>To copy all "user" properties, you will also have to call
358
     * {@link #copyInheritedProperties copyInheritedProperties}.</p>
359
     *
360
     * @param  Project $other the project to copy the properties to.  Must not be null.
361
     * @return void
362
     * @since  phing 2.0
363
     */
364 33
    public function copyUserProperties(Project $other)
365
    {
366 33
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
367 33
    }
368
369
    /**
370
     * Copies all user properties that have not been set on the
371
     * command line or a GUI tool from this instance to the Project
372
     * instance given as the argument.
373
     *
374
     * <p>To copy all "user" properties, you will also have to call
375
     * {@link #copyUserProperties copyUserProperties}.</p>
376
     *
377
     * @param Project $other the project to copy the properties to.  Must not be null.
378
     *
379
     * @since phing 2.0
380
     */
381 33
    public function copyInheritedProperties(Project $other)
382
    {
383 33
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
384 33
    }
385
386
    // ---------------------------------------------------------
387
    //  END Properties methods
388
    // ---------------------------------------------------------
389
390
    /**
391
     * Sets default target
392
     *
393
     * @param string $targetName
394
     */
395 819
    public function setDefaultTarget($targetName)
396
    {
397 819
        $this->defaultTarget = (string) trim($targetName);
398 819
    }
399
400
    /**
401
     * Returns default target
402
     *
403
     * @return string
404
     */
405 33
    public function getDefaultTarget()
406
    {
407 33
        return (string) $this->defaultTarget;
408
    }
409
410
    /**
411
     * Sets the name of the current project
412
     *
413
     * @param  string $name name of project
414
     * @return void
415
     * @author Andreas Aderhold, [email protected]
416
     */
417 819
    public function setName($name)
418
    {
419 819
        $this->name = (string) trim($name);
420 819
        $this->setUserProperty("phing.project.name", $this->name);
421 819
    }
422
423
    /**
424
     * Returns the name of this project
425
     *
426
     * @return string projectname
427
     * @author Andreas Aderhold, [email protected]
428
     */
429 2
    public function getName()
430
    {
431 2
        return (string) $this->name;
432
    }
433
434
    /**
435
     * Set the projects description
436
     *
437
     * @param string $description
438
     */
439 6
    public function setDescription($description)
440
    {
441 6
        $this->description = $description;
442 6
    }
443
444
    /**
445
     * return the description, null otherwise
446
     *
447
     * @return string|null
448
     */
449 6
    public function getDescription()
450
    {
451 6
        if ($this->description === null) {
452 5
            $this->description = Description::getAll($this);
453
        }
454 6
        return $this->description;
455
    }
456
457
    /**
458
     * Set the minimum required phing version
459
     *
460
     * @param string $version
461
     */
462 5
    public function setPhingVersion($version)
463
    {
464 5
        $version = str_replace('phing', '', strtolower($version));
465 5
        $this->phingVersion = (string) trim($version);
466 5
    }
467
468
    /**
469
     * Get the minimum required phing version
470
     *
471
     * @return string
472
     */
473 5
    public function getPhingVersion()
474
    {
475 5
        if ($this->phingVersion === null) {
476 5
            $this->setPhingVersion(Phing::getPhingVersion());
477
        }
478
479 5
        return $this->phingVersion;
480
    }
481
482
    /**
483
     * Sets the strict-mode (status) for the current project
484
     * (If strict mode is On, all the warnings would be converted to an error
485
     * (and the build will be stopped/aborted)
486
     *
487
     * @param bool $strictmode
488
     * @return void
489
     * @access public
490
     * @author Utsav Handa, [email protected]
491
     */
492 36
    public function setStrictMode(bool $strictmode)
493
    {
494 36
        $this->strictMode = $strictmode;
495 36
        $this->setProperty("phing.project.strictmode", $this->strictMode);
496 36
    }
497
498
    /**
499
     * Get the strict-mode status for the project
500
     *
501
     * @return boolean
502
     */
503
    public function getStrictmode()
504
    {
505
        return $this->strictMode;
506
    }
507
508
    /**
509
     * Set basedir object from xm
510
     *
511
     * @param  PhingFile|string $dir
512
     * @throws BuildException
513
     */
514 835
    public function setBasedir($dir)
515
    {
516 835
        if ($dir instanceof PhingFile) {
517 98
            $dir = $dir->getAbsolutePath();
518
        }
519
520 835
        $dir = $this->fileUtils->normalize($dir);
521 835
        $dir = FileSystem::getFileSystem()->canonicalize($dir);
522
523 835
        $dir = new PhingFile((string) $dir);
524 835
        if (!$dir->exists()) {
525
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " does not exist");
526
        }
527 835
        if (!$dir->isDirectory()) {
528
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " is not a directory");
529
        }
530 835
        $this->basedir = $dir;
531 835
        $this->setPropertyInternal("project.basedir", $this->basedir->getPath());
532 835
        $this->log("Project base dir set to: " . $this->basedir->getPath(), Project::MSG_VERBOSE);
533
534
        // [HL] added this so that ./ files resolve correctly.  This may be a mistake ... or may be in wrong place.
535 835
        chdir($dir->getAbsolutePath());
536 835
    }
537
538
    /**
539
     * Returns the basedir of this project
540
     *
541
     * @return PhingFile      Basedir PhingFile object
542
     *
543
     * @throws BuildException
544
     *
545
     * @author Andreas Aderhold, [email protected]
546
     */
547 177
    public function getBasedir()
548
    {
549 177
        if ($this->basedir === null) {
550
            try { // try to set it
551
                $this->setBasedir(".");
552
            } catch (BuildException $exc) {
553
                throw new BuildException("Can not set default basedir. " . $exc->getMessage());
554
            }
555
        }
556
557 177
        return $this->basedir;
558
    }
559
560
    /**
561
     * Set &quot;keep-going&quot; mode. In this mode Ant will try to execute
562
     * as many targets as possible. All targets that do not depend
563
     * on failed target(s) will be executed.  If the keepGoing settor/getter
564
     * methods are used in conjunction with the <code>ant.executor.class</code>
565
     * property, they will have no effect.
566
     *
567
     * @param bool $keepGoingMode &quot;keep-going&quot; mode
568
     */
569 36
    public function setKeepGoingMode($keepGoingMode)
570
    {
571 36
        $this->keepGoingMode = $keepGoingMode;
572 36
    }
573
574
    /**
575
     * Return the keep-going mode.  If the keepGoing settor/getter
576
     * methods are used in conjunction with the <code>phing.executor.class</code>
577
     * property, they will have no effect.
578
     *
579
     * @return bool &quot;keep-going&quot; mode
580
     */
581 36
    public function isKeepGoingMode()
582
    {
583 36
        return $this->keepGoingMode;
584
    }
585
586
    /**
587
     * Sets system properties and the environment variables for this project.
588
     *
589
     * @return void
590
     */
591 819
    public function setSystemProperties()
592
    {
593
594
        // first get system properties
595 819
        $systemP = array_merge($this->getProperties(), Phing::getProperties());
596 819
        foreach ($systemP as $name => $value) {
597 819
            $this->setPropertyInternal($name, $value);
598
        }
599
600
        // and now the env vars
601 819
        foreach ($_SERVER as $name => $value) {
602
            // skip arrays
603 819
            if (is_array($value)) {
604 819
                continue;
605
            }
606 819
            $this->setPropertyInternal('env.' . $name, $value);
607
        }
608 819
    }
609
610
    /**
611
     * Adds a task definition.
612
     *
613
     * @param string $name Name of tag.
614
     * @param string $class The class path to use.
615
     * @param string $classpath The classpat to use.
616
     */
617 72
    public function addTaskDefinition($name, $class, $classpath = null)
618
    {
619 72
        ComponentHelper::getComponentHelper($this)->addTaskDefinition($name, $class, $classpath);
620 72
    }
621
622
    /**
623
     * Returns the task definitions
624
     *
625
     * @return array
626
     */
627 43
    public function getTaskDefinitions()
628
    {
629 43
        return ComponentHelper::getComponentHelper($this)->getTaskDefinitions();
630
    }
631
632
    /**
633
     * Adds a data type definition.
634
     *
635
     * @param string $typeName Name of the type.
636
     * @param string $typeClass The class to use.
637
     * @param string $classpath The classpath to use.
638
     */
639 40
    public function addDataTypeDefinition($typeName, $typeClass, $classpath = null)
640
    {
641 40
        ComponentHelper::getComponentHelper($this)->addDataTypeDefinition($typeName, $typeClass, $classpath);
642 40
    }
643
644
    /**
645
     * Returns the data type definitions
646
     *
647
     * @return array
648
     */
649 820
    public function getDataTypeDefinitions()
650
    {
651 820
        return ComponentHelper::getComponentHelper($this)->getDataTypeDefinitions();
652
    }
653
654
    /**
655
     * Add a new target to the project
656
     *
657
     * @param  string $targetName
658
     * @param  Target $target
659
     * @throws BuildException
660
     */
661 819
    public function addTarget($targetName, $target)
662
    {
663 819
        if (isset($this->targets[$targetName])) {
664
            throw new BuildException("Duplicate target: $targetName");
665
        }
666 819
        $this->addOrReplaceTarget($targetName, $target);
667 819
    }
668
669
    /**
670
     * Adds or replaces a target in the project
671
     *
672
     * @param string $targetName
673
     * @param Target $target
674
     */
675 819
    public function addOrReplaceTarget($targetName, &$target)
676
    {
677 819
        $this->log("  +Target: $targetName", Project::MSG_DEBUG);
678 819
        $target->setProject($this);
679 819
        $this->targets[$targetName] = $target;
680
681 819
        $ctx = $this->getReference(ProjectConfigurator::PARSING_CONTEXT_REFERENCE);
682 819
        $current = $ctx->getCurrentTargets();
683 819
        $current[$targetName] = $target;
684 819
    }
685
686
    /**
687
     * Returns the available targets
688
     *
689
     * @return Target[]
690
     */
691 819
    public function getTargets()
692
    {
693 819
        return $this->targets;
694
    }
695
696
    /**
697
     * @return string[]
698
     */
699
    public function getExecutedTargetNames()
700
    {
701
        return $this->executedTargetNames;
702
    }
703
704
    /**
705
     * Create a new task instance and return reference to it.
706
     *
707
     * @param string $taskType Task name
708
     * @return Task           A task object
709
     * @throws BuildException
710
     */
711 677
    public function createTask($taskType)
712
    {
713 677
        return ComponentHelper::getComponentHelper($this)->createTask($taskType);
714
    }
715
716
    /**
717
     * Creates a new condition and returns the reference to it
718
     *
719
     * @param  string $conditionType
720
     * @return Condition
721
     * @throws BuildException
722
     */
723 1
    public function createCondition($conditionType)
724
    {
725 1
        return ComponentHelper::getComponentHelper($this)->createCondition($conditionType);
726
    }
727
728
    /**
729
     * Create a datatype instance and return reference to it
730
     * See createTask() for explanation how this works
731
     *
732
     * @param  string $typeName Type name
733
     * @return object         A datatype object
734
     * @throws BuildException
735
     *                                 Exception
736
     */
737 71
    public function createDataType($typeName)
738
    {
739 71
        return ComponentHelper::getComponentHelper($this)->createDataType($typeName);
740
    }
741
742
    /**
743
     * Executes a list of targets
744
     *
745
     * @param  array $targetNames List of target names to execute
746
     * @return void
747
     * @throws BuildException
748
     */
749
    public function executeTargets($targetNames)
750
    {
751
        $this->executedTargetNames = $targetNames;
752
753
        foreach ($targetNames as $tname) {
754
            $this->executeTarget($tname);
755
        }
756
    }
757
758
    /**
759
     * Executes a target
760
     *
761
     * @param  string $targetName Name of Target to execute
762
     * @return void
763
     * @throws BuildException
764
     */
765 627
    public function executeTarget($targetName)
766
    {
767
768
        // complain about executing void
769 627
        if ($targetName === null) {
0 ignored issues
show
introduced by
The condition $targetName === null is always false.
Loading history...
770
            throw new BuildException("No target specified");
771
        }
772
773
        // invoke topological sort of the target tree and run all targets
774
        // until targetName occurs.
775 627
        $sortedTargets = $this->topoSort($targetName);
776
777 627
        $curIndex = (int) 0;
778 627
        $curTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $curTarget is dead and can be removed.
Loading history...
779 627
        $thrownException = null;
780 627
        $buildException = null;
781
        do {
782
            try {
783 627
                $curTarget = $sortedTargets[$curIndex++];
784 627
                $curTarget->performTasks();
785 166
            } catch (BuildException $exc) {
786 166
                if (!($this->keepGoingMode)) {
787 166
                    throw $exc;
788
                }
789
                $thrownException = $exc;
790
            }
791 495
            if ($thrownException != null) {
792
                if ($thrownException instanceof BuildException) {
793
                    $this->log(
794
                        "Target '" . $curTarget->getName()
795
                        . "' failed with message '"
796
                        . $thrownException->getMessage() . "'.",
797
                        Project::MSG_ERR
798
                    );
799
                    // only the first build exception is reported
800
                    if ($buildException === null) {
801
                        $buildException = $thrownException;
802
                    }
803
                } else {
804
                    $this->log(
805
                        "Target '" . $curTarget->getName()
806
                        . "' failed with message '"
807
                        . $thrownException->getMessage() . "'." . PHP_EOL
808
                        . $thrownException->getTraceAsString(),
809
                        Project::MSG_ERR
810
                    );
811
                    if ($buildException === null) {
812
                        $buildException = new BuildException($thrownException);
813
                    }
814
                }
815
            }
816 495
        } while ($curTarget->getName() !== $targetName);
817
818 495
        if ($buildException !== null) {
819
            throw $buildException;
820
        }
821 495
    }
822
823
    /**
824
     * Helper function
825
     *
826
     * @param  string $fileName
827
     * @param  PhingFile $rootDir
828
     * @return \PhingFile
829
     * @throws IOException
830
     */
831 435
    public function resolveFile(string $fileName, PhingFile $rootDir = null): PhingFile
832
    {
833 435
        if ($rootDir === null) {
834 406
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
835
        }
836 90
        return $this->fileUtils->resolveFile($rootDir, $fileName);
837
    }
838
839
    /**
840
     * Return the boolean equivalent of a string, which is considered
841
     * <code>true</code> if either <code>"on"</code>, <code>"true"</code>,
842
     * or <code>"yes"</code> is found, ignoring case.
843
     *
844
     * @param string $s The string to convert to a boolean value.
845
     *
846
     * @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...
847
     *         <code>"true"</code> or <code>"yes"</code>, or
848
     *         <code>false</code> otherwise.
849
     */
850 7
    public static function toBoolean($s)
851
    {
852
        return (
853 7
            strcasecmp($s, 'on') === 0
854 7
            || strcasecmp($s, 'true') === 0
855 5
            || strcasecmp($s, 'yes') === 0
856
            // FIXME next condition should be removed if the boolean behavior for properties will be solved
857 7
            || strcasecmp($s, 1) === 0
858
        );
859
    }
860
861
    /**
862
     * Topologically sort a set of Targets.
863
     *
864
     * @param  string $rootTarget is the (String) name of the root Target. The sort is
865
     *                         created in such a way that the sequence of Targets until the root
866
     *                         target is the minimum possible such sequence.
867
     * @return Target[] targets in sorted order
868
     * @throws Exception
869
     * @throws BuildException
870
     */
871 627
    public function topoSort($rootTarget)
872
    {
873 627
        $rootTarget = (string) $rootTarget;
874 627
        $ret = [];
875 627
        $state = [];
876 627
        $visiting = [];
877
878
        // We first run a DFS based sort using the root as the starting node.
879
        // This creates the minimum sequence of Targets to the root node.
880
        // We then do a sort on any remaining unVISITED targets.
881
        // This is unnecessary for doing our build, but it catches
882
        // circular dependencies or missing Targets on the entire
883
        // dependency tree, not just on the Targets that depend on the
884
        // build Target.
885
886 627
        $this->_tsort($rootTarget, $state, $visiting, $ret);
887
888 627
        $retHuman = "";
889 627
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
890 627
            $retHuman .= (string) $ret[$i] . " ";
891
        }
892 627
        $this->log("Build sequence for target '$rootTarget' is: $retHuman", Project::MSG_VERBOSE);
893
894 627
        $keys = array_keys($this->targets);
895 627
        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...
896 627
            $curTargetName = (string) array_shift($keys);
897 627
            if (!isset($state[$curTargetName])) {
898 627
                $st = null;
899
            } else {
900 627
                $st = (string) $state[$curTargetName];
901
            }
902
903 627
            if ($st === null) {
904 627
                $this->_tsort($curTargetName, $state, $visiting, $ret);
905 627
            } elseif ($st === "VISITING") {
906
                throw new Exception("Unexpected node in visiting state: $curTargetName");
907
            }
908
        }
909
910 627
        $retHuman = "";
911 627
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
912 627
            $retHuman .= (string) $ret[$i] . " ";
913
        }
914 627
        $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE);
915
916 627
        return $ret;
917
    }
918
919
    // one step in a recursive DFS traversal of the target dependency tree.
920
    // - The array "state" contains the state (VISITED or VISITING or null)
921
    //   of all the target names.
922
    // - The stack "visiting" contains a stack of target names that are
923
    //   currently on the DFS stack. (NB: the target names in "visiting" are
924
    //    exactly the target names in "state" that are in the VISITING state.)
925
    // 1. Set the current target to the VISITING state, and push it onto
926
    //    the "visiting" stack.
927
    // 2. Throw a BuildException if any child of the current node is
928
    //    in the VISITING state (implies there is a cycle.) It uses the
929
    //    "visiting" Stack to construct the cycle.
930
    // 3. If any children have not been VISITED, tsort() the child.
931
    // 4. Add the current target to the Vector "ret" after the children
932
    //    have been visited. Move the current target to the VISITED state.
933
    //    "ret" now contains the sorted sequence of Targets up to the current
934
    //    Target.
935
936
    /**
937
     * @param $root
938
     * @param $state
939
     * @param $visiting
940
     * @param $ret
941
     * @throws BuildException
942
     * @throws Exception
943
     */
944 627
    public function _tsort($root, &$state, &$visiting, &$ret)
945
    {
946 627
        $state[$root] = "VISITING";
947 627
        $visiting[] = $root;
948
949 627
        if (!isset($this->targets[$root]) || !($this->targets[$root] instanceof Target)) {
950 1
            $target = null;
951
        } else {
952 627
            $target = $this->targets[$root];
953
        }
954
955
        // make sure we exist
956 627
        if ($target === null) {
957 1
            $sb = "Target '$root' does not exist in this project.";
958 1
            array_pop($visiting);
959 1
            if (!empty($visiting)) {
960
                $parent = (string) $visiting[count($visiting) - 1];
961
                $sb .= " It is a dependency of target '$parent'.";
962
            }
963 1
            throw new BuildException($sb);
964
        }
965
966 627
        $deps = $target->getDependencies();
967
968 627
        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...
969 130
            $cur = (string) array_shift($deps);
970 130
            if (!isset($state[$cur])) {
971 78
                $m = null;
972
            } else {
973 113
                $m = (string) $state[$cur];
974
            }
975 130
            if ($m === null) {
976
                // not been visited
977 78
                $this->_tsort($cur, $state, $visiting, $ret);
978 113
            } elseif ($m == "VISITING") {
979
                // currently visiting this node, so have a cycle
980
                throw $this->_makeCircularException($cur, $visiting);
981
            }
982
        }
983
984 627
        $p = (string) array_pop($visiting);
985 627
        if ($root !== $p) {
986
            throw new Exception("Unexpected internal error: expected to pop $root but got $p");
987
        }
988
989 627
        $state[$root] = "VISITED";
990 627
        $ret[] = $target;
991 627
    }
992
993
    /**
994
     * @param string $end
995
     * @param array $stk
996
     * @return BuildException
997
     */
998
    public function _makeCircularException($end, $stk)
999
    {
1000
        $sb = "Circular dependency: $end";
1001
        do {
1002
            $c = (string) array_pop($stk);
1003
            $sb .= " <- " . $c;
1004
        } while ($c != $end);
1005
1006
        return new BuildException($sb);
1007
    }
1008
1009
    /**
1010
     * Adds a reference to an object. This method is called when the parser
1011
     * detects a id="foo" attribute. It passes the id as $name and a reference
1012
     * to the object assigned to this id as $value
1013
     *
1014
     * @param string $name
1015
     * @param object $object
1016
     */
1017 839
    public function addReference($name, $object)
1018
    {
1019 839
        $ref = $this->references[$name] ?? null;
1020 839
        if ($ref === $object) {
1021
            return;
1022
        }
1023 839
        if ($ref !== null && !$ref instanceof UnknownElement) {
1024 37
            $this->log("Overriding previous definition of reference to $name", Project::MSG_VERBOSE);
1025
        }
1026 839
        $refName = (is_scalar($object) || $object instanceof PropertyValue) ? (string) $object : get_class($object);
1027 839
        $this->log("Adding reference: $name -> " . $refName, Project::MSG_DEBUG);
1028 839
        $this->references[$name] = $object;
1029 839
    }
1030
1031
    /**
1032
     * Returns the references array.
1033
     *
1034
     * @return array
1035
     */
1036 32
    public function getReferences()
1037
    {
1038 32
        return $this->references;
1039
    }
1040
1041
    /**
1042
     * Returns a specific reference.
1043
     *
1044
     * @param  string $key The reference id/key.
1045
     * @return object Reference or null if not defined
1046
     */
1047 840
    public function getReference($key)
1048
    {
1049 840
        return $this->references[$key] ?? null; // just to be explicit
1050
    }
1051
1052
    /**
1053
     * Does the project know this reference?
1054
     *
1055
     * @param  string $key The reference id/key.
1056
     * @return bool
1057
     */
1058 5
    public function hasReference(string $key): bool
1059
    {
1060 5
        return isset($this->references[$key]);
1061
    }
1062
1063
    /**
1064
     * Abstracting and simplifyling Logger calls for project messages
1065
     *
1066
     * @param string $msg
1067
     * @param int $level
1068
     */
1069 839
    public function log($msg, $level = Project::MSG_INFO)
1070
    {
1071 839
        $this->logObject($this, $msg, $level);
1072 839
    }
1073
1074
    /**
1075
     * @param mixed $obj
1076
     * @param string $msg
1077
     * @param int $level
1078
     * @param Exception|null $t
1079
     */
1080 840
    public function logObject($obj, $msg, $level, Exception $t = null)
1081
    {
1082 840
        $this->fireMessageLogged($obj, $msg, $level, $t);
1083
1084
        // Checking whether the strict-mode is On, then consider all the warnings
1085
        // as errors.
1086 840
        if (($this->strictMode) && (Project::MSG_WARN == $level)) {
1087
            throw new BuildException('Build contains warnings, considered as errors in strict mode', null);
1088
        }
1089 840
    }
1090
1091
    /**
1092
     * @param BuildListener $listener
1093
     */
1094 819
    public function addBuildListener(BuildListener $listener)
1095
    {
1096 819
        $this->listeners[] = $listener;
1097 819
    }
1098
1099
    /**
1100
     * @param BuildListener $listener
1101
     */
1102 9
    public function removeBuildListener(BuildListener $listener)
1103
    {
1104 9
        $newarray = [];
1105 9
        for ($i = 0, $size = count($this->listeners); $i < $size; $i++) {
1106 9
            if ($this->listeners[$i] !== $listener) {
1107 9
                $newarray[] = $this->listeners[$i];
1108
            }
1109
        }
1110 9
        $this->listeners = $newarray;
1111 9
    }
1112
1113
    /**
1114
     * @return array
1115
     */
1116 33
    public function getBuildListeners()
1117
    {
1118 33
        return $this->listeners;
1119
    }
1120
1121
    public function fireBuildStarted()
1122
    {
1123
        $event = new BuildEvent($this);
1124
        foreach ($this->listeners as $listener) {
1125
            $listener->buildStarted($event);
1126
        }
1127
1128
        $this->log((string) $event, Project::MSG_DEBUG);
1129
    }
1130
1131
    /**
1132
     * @param Exception $exception
1133
     */
1134
    public function fireBuildFinished($exception)
1135
    {
1136
        $event = new BuildEvent($this);
1137
        $event->setException($exception);
1138
        foreach ($this->listeners as $listener) {
1139
            $listener->buildFinished($event);
1140
        }
1141
1142
        $this->log((string) $event, Project::MSG_DEBUG);
1143
    }
1144
1145
    /**
1146
     * @param $target
1147
     */
1148 627
    public function fireTargetStarted($target)
1149
    {
1150 627
        $event = new BuildEvent($target);
1151 627
        foreach ($this->listeners as $listener) {
1152 627
            $listener->targetStarted($event);
1153
        }
1154
1155 627
        $this->log((string) $event, Project::MSG_DEBUG);
1156 627
    }
1157
1158
    /**
1159
     * @param $target
1160
     * @param $exception
1161
     */
1162 627
    public function fireTargetFinished($target, $exception)
1163
    {
1164 627
        $event = new BuildEvent($target);
1165 627
        $event->setException($exception);
1166 627
        foreach ($this->listeners as $listener) {
1167 627
            $listener->targetFinished($event);
1168
        }
1169
1170 627
        $this->log((string) $event, Project::MSG_DEBUG);
1171 627
    }
1172
1173
    /**
1174
     * @param $task
1175
     */
1176 680
    public function fireTaskStarted($task)
1177
    {
1178 680
        $event = new BuildEvent($task);
1179 680
        foreach ($this->listeners as $listener) {
1180 680
            $listener->taskStarted($event);
1181
        }
1182
1183 680
        $this->log((string) $event, Project::MSG_DEBUG);
1184 680
    }
1185
1186
    /**
1187
     * @param $task
1188
     * @param $exception
1189
     */
1190 680
    public function fireTaskFinished($task, $exception)
1191
    {
1192 680
        $event = new BuildEvent($task);
1193 680
        $event->setException($exception);
1194 680
        foreach ($this->listeners as $listener) {
1195 680
            $listener->taskFinished($event);
1196
        }
1197
1198 680
        $this->log((string) $event, Project::MSG_DEBUG);
1199 680
    }
1200
1201
    /**
1202
     * @param $event
1203
     * @param $message
1204
     * @param $priority
1205
     */
1206 840
    public function fireMessageLoggedEvent(BuildEvent $event, $message, $priority)
1207
    {
1208 840
        $event->setMessage($message, $priority);
1209 840
        foreach ($this->listeners as $listener) {
1210 819
            $listener->messageLogged($event);
1211
        }
1212 840
    }
1213
1214
    /**
1215
     * @param mixed $object
1216
     * @param string $message
1217
     * @param int $priority
1218
     * @param Exception $t
1219
     * @throws \Exception
1220
     */
1221 840
    public function fireMessageLogged($object, $message, $priority, Exception $t = null)
1222
    {
1223 840
        $event = new BuildEvent($object);
1224 840
        if ($t !== null) {
1225
            $event->setException($t);
1226
        }
1227 840
        $this->fireMessageLoggedEvent($event, $message, $priority);
1228 840
    }
1229
}
1230