Passed
Push — master ( a0b722...864342 )
by Michiel
11:10
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 826
    public function __construct()
124
    {
125 826
        $this->fileUtils = new FileUtils();
126 826
    }
127
128
    /**
129
     * Sets the input handler
130
     *
131
     * @param InputHandler $handler
132
     */
133 25
    public function setInputHandler($handler)
134
    {
135 25
        $this->inputHandler = $handler;
136 25
    }
137
138
    /**
139
     * Retrieves the current input handler.
140
     *
141
     * @return InputHandler
142
     */
143 25
    public function getInputHandler()
144
    {
145 25
        return $this->inputHandler;
146
    }
147
148
    /**
149
     * inits the project, called from main app
150
     */
151 791
    public function init()
152
    {
153
        // set builtin properties
154 791
        $this->setSystemProperties();
155
156 791
        $componentHelper = ComponentHelper::getComponentHelper($this);
157
158 791
        $componentHelper->initDefaultDefinitions();
159 791
    }
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 28
    public function createSubProject(): \Project
168
    {
169
        try {
170 28
            $ref = new ReflectionObject($this);
171 28
            $subProject = $ref->newInstance();
172
        } catch (ReflectionException $e) {
173
            $subProject = new Project();
174
        }
175 28
        $this->initSubProject($subProject);
176 28
        return $subProject;
177
    }
178
179
    /**
180
     * Initialize a subproject.
181
     * @param Project $subProject the subproject to initialize.
182
     */
183 28
    public function initSubProject(Project $subProject): void
184
    {
185 28
        ComponentHelper::getComponentHelper($subProject)
186 28
            ->initSubProject(ComponentHelper::getComponentHelper($this));
187 28
        $subProject->setKeepGoingMode($this->isKeepGoingMode());
188 28
        $subProject->setStrictMode($this->strictMode);
189 28
    }
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 200
    public function setProperty($name, $value)
213
    {
214 200
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, true);
215 200
    }
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 74
    public function setNewProperty($name, $value)
229
    {
230 74
        PropertyHelper::getPropertyHelper($this)->setNewProperty(null, $name, $value);
231 74
    }
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 791
    public function setUserProperty($name, $value)
244
    {
245 791
        PropertyHelper::getPropertyHelper($this)->setUserProperty(null, $name, $value);
246 791
    }
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 4
    public function setInheritedProperty($name, $value)
261
    {
262 4
        PropertyHelper::getPropertyHelper($this)->setInheritedProperty(null, $name, $value);
263 4
    }
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 807
    private function setPropertyInternal($name, $value)
275
    {
276 807
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, false);
277 807
    }
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 791
    public function getProperty($name)
289
    {
290 791
        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 643
    public function replaceProperties($value)
308
    {
309 643
        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 792
    public function getProperties()
333
    {
334 792
        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 25
    public function copyUserProperties(Project $other)
365
    {
366 25
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
367 25
    }
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 25
    public function copyInheritedProperties(Project $other)
382
    {
383 25
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
384 25
    }
385
386
    // ---------------------------------------------------------
387
    //  END Properties methods
388
    // ---------------------------------------------------------
389
390
    /**
391
     * Sets default target
392
     *
393
     * @param string $targetName
394
     */
395 791
    public function setDefaultTarget($targetName)
396
    {
397 791
        $this->defaultTarget = (string) trim($targetName);
398 791
    }
399
400
    /**
401
     * Returns default target
402
     *
403
     * @return string
404
     */
405 25
    public function getDefaultTarget()
406
    {
407 25
        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 791
    public function setName($name)
418
    {
419 791
        $this->name = (string) trim($name);
420 791
        $this->setUserProperty("phing.project.name", $this->name);
421 791
    }
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 28
    public function setStrictMode(bool $strictmode)
493
    {
494 28
        $this->strictMode = $strictmode;
495 28
        $this->setProperty("phing.project.strictmode", $this->strictMode);
496 28
    }
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 807
    public function setBasedir($dir)
515
    {
516 807
        if ($dir instanceof PhingFile) {
517 87
            $dir = $dir->getAbsolutePath();
518
        }
519
520 807
        $dir = $this->fileUtils->normalize($dir);
521 807
        $dir = FileSystem::getFileSystem()->canonicalize($dir);
522
523 807
        $dir = new PhingFile((string) $dir);
524 807
        if (!$dir->exists()) {
525
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " does not exist");
526
        }
527 807
        if (!$dir->isDirectory()) {
528
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " is not a directory");
529
        }
530 807
        $this->basedir = $dir;
531 807
        $this->setPropertyInternal("project.basedir", $this->basedir->getPath());
532 807
        $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 807
        chdir($dir->getAbsolutePath());
536 807
    }
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 170
    public function getBasedir()
548
    {
549 170
        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 170
        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 28
    public function setKeepGoingMode($keepGoingMode)
570
    {
571 28
        $this->keepGoingMode = $keepGoingMode;
572 28
    }
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 28
    public function isKeepGoingMode()
582
    {
583 28
        return $this->keepGoingMode;
584
    }
585
586
    /**
587
     * Sets system properties and the environment variables for this project.
588
     *
589
     * @return void
590
     */
591 791
    public function setSystemProperties()
592
    {
593
594
        // first get system properties
595 791
        $systemP = array_merge($this->getProperties(), Phing::getProperties());
596 791
        foreach ($systemP as $name => $value) {
597 791
            $this->setPropertyInternal($name, $value);
598
        }
599
600
        // and now the env vars
601 791
        foreach ($_SERVER as $name => $value) {
602
            // skip arrays
603 791
            if (is_array($value)) {
604 791
                continue;
605
            }
606 791
            $this->setPropertyInternal('env.' . $name, $value);
607
        }
608 791
    }
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 65
    public function addTaskDefinition($name, $class, $classpath = null)
618
    {
619 65
        ComponentHelper::getComponentHelper($this)->addTaskDefinition($name, $class, $classpath);
620 65
    }
621
622
    /**
623
     * Returns the task definitions
624
     *
625
     * @return array
626
     */
627 34
    public function getTaskDefinitions()
628
    {
629 34
        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 32
    public function addDataTypeDefinition($typeName, $typeClass, $classpath = null)
640
    {
641 32
        ComponentHelper::getComponentHelper($this)->addDataTypeDefinition($typeName, $typeClass, $classpath);
642 32
    }
643
644
    /**
645
     * Returns the data type definitions
646
     *
647
     * @return array
648
     */
649 792
    public function getDataTypeDefinitions()
650
    {
651 792
        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 791
    public function addTarget($targetName, $target)
662
    {
663 791
        if (isset($this->targets[$targetName])) {
664
            throw new BuildException("Duplicate target: $targetName");
665
        }
666 791
        $this->addOrReplaceTarget($targetName, $target);
667 791
    }
668
669
    /**
670
     * Adds or replaces a target in the project
671
     *
672
     * @param string $targetName
673
     * @param Target $target
674
     */
675 791
    public function addOrReplaceTarget($targetName, &$target)
676
    {
677 791
        $this->log("  +Target: $targetName", Project::MSG_DEBUG);
678 791
        $target->setProject($this);
679 791
        $this->targets[$targetName] = $target;
680
681 791
        $ctx = $this->getReference(ProjectConfigurator::PARSING_CONTEXT_REFERENCE);
682 791
        $current = $ctx->getCurrentTargets();
683 791
        $current[$targetName] = $target;
684 791
    }
685
686
    /**
687
     * Returns the available targets
688
     *
689
     * @return Target[]
690
     */
691 791
    public function getTargets()
692
    {
693 791
        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 649
    public function createTask($taskType)
712
    {
713 649
        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 64
    public function createDataType($typeName)
738
    {
739 64
        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 599
    public function executeTarget($targetName)
766
    {
767
768
        // complain about executing void
769 599
        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 599
        $sortedTargets = $this->topoSort($targetName);
776
777 599
        $curIndex = (int) 0;
778 599
        $curTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $curTarget is dead and can be removed.
Loading history...
779 599
        $thrownException = null;
780 599
        $buildException = null;
781
        do {
782
            try {
783 599
                $curTarget = $sortedTargets[$curIndex++];
784 599
                $curTarget->performTasks();
785 158
            } catch (BuildException $exc) {
786 158
                if (!($this->keepGoingMode)) {
787 158
                    throw $exc;
788
                }
789
                $thrownException = $exc;
790
            }
791 471
            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 471
        } while ($curTarget->getName() !== $targetName);
817
818 471
        if ($buildException !== null) {
819
            throw $buildException;
820
        }
821 471
    }
822
823
    /**
824
     * Helper function
825
     *
826
     * @param  string $fileName
827
     * @param  PhingFile $rootDir
828
     * @return \PhingFile
829
     * @throws IOException
830
     */
831 408
    public function resolveFile(string $fileName, PhingFile $rootDir = null): PhingFile
832
    {
833 408
        if ($rootDir === null) {
834 379
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
835
        }
836 87
        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 599
    public function topoSort($rootTarget)
872
    {
873 599
        $rootTarget = (string) $rootTarget;
874 599
        $ret = [];
875 599
        $state = [];
876 599
        $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 599
        $this->_tsort($rootTarget, $state, $visiting, $ret);
887
888 599
        $retHuman = "";
889 599
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
890 599
            $retHuman .= (string) $ret[$i] . " ";
891
        }
892 599
        $this->log("Build sequence for target '$rootTarget' is: $retHuman", Project::MSG_VERBOSE);
893
894 599
        $keys = array_keys($this->targets);
895 599
        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 599
            $curTargetName = (string) array_shift($keys);
897 599
            if (!isset($state[$curTargetName])) {
898 599
                $st = null;
899
            } else {
900 599
                $st = (string) $state[$curTargetName];
901
            }
902
903 599
            if ($st === null) {
904 599
                $this->_tsort($curTargetName, $state, $visiting, $ret);
905 599
            } elseif ($st === "VISITING") {
906
                throw new Exception("Unexpected node in visiting state: $curTargetName");
907
            }
908
        }
909
910 599
        $retHuman = "";
911 599
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
912 599
            $retHuman .= (string) $ret[$i] . " ";
913
        }
914 599
        $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE);
915
916 599
        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 599
    public function _tsort($root, &$state, &$visiting, &$ret)
945
    {
946 599
        $state[$root] = "VISITING";
947 599
        $visiting[] = $root;
948
949 599
        if (!isset($this->targets[$root]) || !($this->targets[$root] instanceof Target)) {
950 1
            $target = null;
951
        } else {
952 599
            $target = $this->targets[$root];
953
        }
954
955
        // make sure we exist
956 599
        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 599
        $deps = $target->getDependencies();
967
968 599
        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 120
            $cur = (string) array_shift($deps);
970 120
            if (!isset($state[$cur])) {
971 68
                $m = null;
972
            } else {
973 110
                $m = (string) $state[$cur];
974
            }
975 120
            if ($m === null) {
976
                // not been visited
977 68
                $this->_tsort($cur, $state, $visiting, $ret);
978 110
            } elseif ($m == "VISITING") {
979
                // currently visiting this node, so have a cycle
980
                throw $this->_makeCircularException($cur, $visiting);
981
            }
982
        }
983
984 599
        $p = (string) array_pop($visiting);
985 599
        if ($root !== $p) {
986
            throw new Exception("Unexpected internal error: expected to pop $root but got $p");
987
        }
988
989 599
        $state[$root] = "VISITED";
990 599
        $ret[] = $target;
991 599
    }
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 811
    public function addReference($name, $object)
1018
    {
1019 811
        $ref = $this->references[$name] ?? null;
1020 811
        if ($ref === $object) {
1021 6
            return;
1022
        }
1023 811
        if ($ref !== null && !$ref instanceof UnknownElement) {
1024 29
            $this->log("Overriding previous definition of reference to $name", Project::MSG_VERBOSE);
1025
        }
1026 811
        $refName = (is_scalar($object) || $object instanceof PropertyValue) ? (string) $object : get_class($object);
1027 811
        $this->log("Adding reference: $name -> " . $refName, Project::MSG_DEBUG);
1028 811
        $this->references[$name] = $object;
1029 811
    }
1030
1031
    /**
1032
     * Returns the references array.
1033
     *
1034
     * @return array
1035
     */
1036 27
    public function getReferences()
1037
    {
1038 27
        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 812
    public function getReference($key)
1048
    {
1049 812
        return $this->references[$key] ?? null; // just to be explicit
1050
    }
1051
1052
    /**
1053
     * Abstracting and simplifyling Logger calls for project messages
1054
     *
1055
     * @param string $msg
1056
     * @param int $level
1057
     */
1058 811
    public function log($msg, $level = Project::MSG_INFO)
1059
    {
1060 811
        $this->logObject($this, $msg, $level);
1061 811
    }
1062
1063
    /**
1064
     * @param mixed $obj
1065
     * @param string $msg
1066
     * @param int $level
1067
     * @param Exception|null $t
1068
     */
1069 812
    public function logObject($obj, $msg, $level, Exception $t = null)
1070
    {
1071 812
        $this->fireMessageLogged($obj, $msg, $level, $t);
1072
1073
        // Checking whether the strict-mode is On, then consider all the warnings
1074
        // as errors.
1075 812
        if (($this->strictMode) && (Project::MSG_WARN == $level)) {
1076
            throw new BuildException('Build contains warnings, considered as errors in strict mode', null);
1077
        }
1078 812
    }
1079
1080
    /**
1081
     * @param BuildListener $listener
1082
     */
1083 791
    public function addBuildListener(BuildListener $listener)
1084
    {
1085 791
        $this->listeners[] = $listener;
1086 791
    }
1087
1088
    /**
1089
     * @param BuildListener $listener
1090
     */
1091 7
    public function removeBuildListener(BuildListener $listener)
1092
    {
1093 7
        $newarray = [];
1094 7
        for ($i = 0, $size = count($this->listeners); $i < $size; $i++) {
1095 7
            if ($this->listeners[$i] !== $listener) {
1096 7
                $newarray[] = $this->listeners[$i];
1097
            }
1098
        }
1099 7
        $this->listeners = $newarray;
1100 7
    }
1101
1102
    /**
1103
     * @return array
1104
     */
1105 25
    public function getBuildListeners()
1106
    {
1107 25
        return $this->listeners;
1108
    }
1109
1110
    public function fireBuildStarted()
1111
    {
1112
        $event = new BuildEvent($this);
1113
        foreach ($this->listeners as $listener) {
1114
            $listener->buildStarted($event);
1115
        }
1116
1117
        $this->log((string) $event, Project::MSG_DEBUG);
1118
    }
1119
1120
    /**
1121
     * @param Exception $exception
1122
     */
1123
    public function fireBuildFinished($exception)
1124
    {
1125
        $event = new BuildEvent($this);
1126
        $event->setException($exception);
1127
        foreach ($this->listeners as $listener) {
1128
            $listener->buildFinished($event);
1129
        }
1130
1131
        $this->log((string) $event, Project::MSG_DEBUG);
1132
    }
1133
1134
    /**
1135
     * @param $target
1136
     */
1137 599
    public function fireTargetStarted($target)
1138
    {
1139 599
        $event = new BuildEvent($target);
1140 599
        foreach ($this->listeners as $listener) {
1141 599
            $listener->targetStarted($event);
1142
        }
1143
1144 599
        $this->log((string) $event, Project::MSG_DEBUG);
1145 599
    }
1146
1147
    /**
1148
     * @param $target
1149
     * @param $exception
1150
     */
1151 599
    public function fireTargetFinished($target, $exception)
1152
    {
1153 599
        $event = new BuildEvent($target);
1154 599
        $event->setException($exception);
1155 599
        foreach ($this->listeners as $listener) {
1156 599
            $listener->targetFinished($event);
1157
        }
1158
1159 599
        $this->log((string) $event, Project::MSG_DEBUG);
1160 599
    }
1161
1162
    /**
1163
     * @param $task
1164
     */
1165 652
    public function fireTaskStarted($task)
1166
    {
1167 652
        $event = new BuildEvent($task);
1168 652
        foreach ($this->listeners as $listener) {
1169 652
            $listener->taskStarted($event);
1170
        }
1171
1172 652
        $this->log((string) $event, Project::MSG_DEBUG);
1173 652
    }
1174
1175
    /**
1176
     * @param $task
1177
     * @param $exception
1178
     */
1179 652
    public function fireTaskFinished($task, $exception)
1180
    {
1181 652
        $event = new BuildEvent($task);
1182 652
        $event->setException($exception);
1183 652
        foreach ($this->listeners as $listener) {
1184 652
            $listener->taskFinished($event);
1185
        }
1186
1187 652
        $this->log((string) $event, Project::MSG_DEBUG);
1188 652
    }
1189
1190
    /**
1191
     * @param $event
1192
     * @param $message
1193
     * @param $priority
1194
     */
1195 812
    public function fireMessageLoggedEvent(BuildEvent $event, $message, $priority)
1196
    {
1197 812
        $event->setMessage($message, $priority);
1198 812
        foreach ($this->listeners as $listener) {
1199 791
            $listener->messageLogged($event);
1200
        }
1201 812
    }
1202
1203
    /**
1204
     * @param mixed $object
1205
     * @param string $message
1206
     * @param int $priority
1207
     * @param Exception $t
1208
     * @throws \Exception
1209
     */
1210 812
    public function fireMessageLogged($object, $message, $priority, Exception $t = null)
1211
    {
1212 812
        $event = new BuildEvent($object);
1213 812
        if ($t !== null) {
1214
            $event->setException($t);
1215
        }
1216 812
        $this->fireMessageLoggedEvent($event, $message, $priority);
1217 812
    }
1218
}
1219