Test Failed
Push — master ( fd7e86...8c2ebd )
by Siad
12:57
created

Project::getInheritedProperties()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
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 810
    public function __construct()
124
    {
125 810
        $this->fileUtils = new FileUtils();
126 810
    }
127
128
    /**
129
     * Sets the input handler
130
     *
131
     * @param InputHandler $handler
132
     */
133 8
    public function setInputHandler($handler)
134
    {
135 8
        $this->inputHandler = $handler;
136 8
    }
137
138
    /**
139
     * Retrieves the current input handler.
140
     *
141
     * @return InputHandler
142
     */
143 8
    public function getInputHandler()
144
    {
145 8
        return $this->inputHandler;
146
    }
147
148
    /**
149
     * inits the project, called from main app
150
     */
151 775
    public function init()
152
    {
153
        // set builtin properties
154 775
        $this->setSystemProperties();
155
156 775
        $componentHelper = ComponentHelper::getComponentHelper($this);
157
158 775
        $componentHelper->initDefaultDefinitions();
159 775
    }
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 62
    public function setNewProperty($name, $value)
199
    {
200 62
        PropertyHelper::getPropertyHelper($this)->setNewProperty(null, $name, $value);
201 62
    }
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 775
    public function setUserProperty($name, $value)
214
    {
215 775
        PropertyHelper::getPropertyHelper($this)->setUserProperty(null, $name, $value);
216 775
    }
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
    public function setInheritedProperty($name, $value)
231
    {
232
        PropertyHelper::getPropertyHelper($this)->setInheritedProperty(null, $name, $value);
233
    }
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 791
    private function setPropertyInternal($name, $value)
245
    {
246 791
        PropertyHelper::getPropertyHelper($this)->setProperty(null, $name, $value, false);
247 791
    }
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 775
    public function getProperty($name)
259
    {
260 775
        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 627
    public function replaceProperties($value)
278
    {
279 627
        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 776
    public function getProperties()
303
    {
304 776
        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();
0 ignored issues
show
Bug introduced by
The method getInheritedProperties() does not exist on PropertyHelper. Did you maybe mean setInheritedProperty()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        return PropertyHelper::getPropertyHelper($this)->/** @scrutinizer ignore-call */ getInheritedProperties();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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 8
     *
330
     * @param  Project $other the project to copy the properties to.  Must not be null.
331 8
     * @return void
332 8
     * @since  phing 2.0
333
     */
334
    public function copyUserProperties(Project $other)
335
    {
336
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
337
    }
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 8
     *
347
     * @param Project $other the project to copy the properties to.  Must not be null.
348 8
     *
349 8
     * @since phing 2.0
350
     */
351
    public function copyInheritedProperties(Project $other)
352
    {
353
        PropertyHelper::getPropertyHelper($this)->copyUserProperties($other);
354
    }
355
356
    // ---------------------------------------------------------
357
    //  END Properties methods
358
    // ---------------------------------------------------------
359
360 775
    /**
361
     * Sets default target
362 775
     *
363 775
     * @param string $targetName
364
     */
365
    public function setDefaultTarget($targetName)
366
    {
367
        $this->defaultTarget = (string) trim($targetName);
368
    }
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 775
     *
383
     * @param  string $name name of project
384 775
     * @return void
385 775
     * @author Andreas Aderhold, [email protected]
386 775
     */
387
    public function setName($name)
388
    {
389
        $this->name = (string) trim($name);
390
        $this->setUserProperty("phing.project.name", $this->name);
391
    }
392
393
    /**
394 1
     * Returns the name of this project
395
     *
396 1
     * @return string projectname
397
     * @author Andreas Aderhold, [email protected]
398
     */
399
    public function getName()
400
    {
401
        return (string) $this->name;
402
    }
403
404 6
    /**
405
     * Set the projects description
406 6
     *
407 6
     * @param string $description
408
     */
409
    public function setDescription($description)
410
    {
411
        $this->description = $description;
412
    }
413
414 6
    /**
415
     * return the description, null otherwise
416 6
     *
417 5
     * @return string|null
418
     */
419 6
    public function getDescription()
420
    {
421
        if ($this->description === null) {
422
            $this->description = Description::getAll($this);
423
        }
424
        return $this->description;
425
    }
426
427 5
    /**
428
     * Set the minimum required phing version
429 5
     *
430 5
     * @param string $version
431 5
     */
432
    public function setPhingVersion($version)
433
    {
434
        $version = str_replace('phing', '', strtolower($version));
435
        $this->phingVersion = (string) trim($version);
436
    }
437
438 5
    /**
439
     * Get the minimum required phing version
440 5
     *
441 5
     * @return string
442
     */
443
    public function getPhingVersion()
444 5
    {
445
        if ($this->phingVersion === null) {
446
            $this->setPhingVersion(Phing::getPhingVersion());
447
        }
448
449
        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 791
     * Set basedir object from xm
480
     *
481 791
     * @param  PhingFile|string $dir
482 68
     * @throws BuildException
483
     */
484
    public function setBasedir($dir)
485 791
    {
486 791
        if ($dir instanceof PhingFile) {
487
            $dir = $dir->getAbsolutePath();
488 791
        }
489 791
490
        $dir = $this->fileUtils->normalize($dir);
491
        $dir = FileSystem::getFileSystem()->canonicalize($dir);
492 791
493
        $dir = new PhingFile((string) $dir);
494
        if (!$dir->exists()) {
495 791
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " does not exist");
496 791
        }
497 791
        if (!$dir->isDirectory()) {
498
            throw new BuildException("Basedir " . $dir->getAbsolutePath() . " is not a directory");
499
        }
500 791
        $this->basedir = $dir;
501 791
        $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath());
502
        $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
        chdir($dir->getAbsolutePath());
506
    }
507
508
    /**
509
     * Returns the basedir of this project
510
     *
511
     * @return PhingFile      Basedir PhingFile object
512 153
     *
513
     * @throws BuildException
514 153
     *
515
     * @author Andreas Aderhold, [email protected]
516
     */
517
    public function getBasedir()
518
    {
519
        if ($this->basedir === null) {
520
            try { // try to set it
521
                $this->setBasedir(".");
522 153
            } catch (BuildException $exc) {
523
                throw new BuildException("Can not set default basedir. " . $exc->getMessage());
524
            }
525
        }
526
527
        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 775
    /**
557
     * Sets system properties and the environment variables for this project.
558
     *
559
     * @return void
560 775
     */
561 775
    public function setSystemProperties()
562 775
    {
563
564
        // first get system properties
565
        $systemP = array_merge($this->getProperties(), Phing::getProperties());
566 775
        foreach ($systemP as $name => $value) {
567
            $this->setPropertyInternal($name, $value);
568 775
        }
569 775
570
        // and now the env vars
571 775
        foreach ($_SERVER as $name => $value) {
572
            // skip arrays
573 775
            if (is_array($value)) {
574
                continue;
575
            }
576
            $this->setPropertyInternal('env.' . $name, $value);
577
        }
578
    }
579
580
    /**
581
     * Adds a task definition.
582 46
     *
583
     * @param string $name Name of tag.
584 46
     * @param string $class The class path to use.
585 46
     * @param string $classpath The classpat to use.
586
     */
587
    public function addTaskDefinition($name, $class, $classpath = null)
588
    {
589
        ComponentHelper::getComponentHelper($this)->addTaskDefinition($name, $class, $classpath);
590
    }
591
592 15
    /**
593
     * Returns the task definitions
594 15
     *
595
     * @return array
596
     */
597
    public function getTaskDefinitions()
598
    {
599
        return ComponentHelper::getComponentHelper($this)->getTaskDefinitions();
600
    }
601
602
    /**
603
     * Adds a data type definition.
604 15
     *
605
     * @param string $typeName Name of the type.
606 15
     * @param string $typeClass The class to use.
607 15
     * @param string $classpath The classpath to use.
608
     */
609
    public function addDataTypeDefinition($typeName, $typeClass, $classpath = null)
610
    {
611
        ComponentHelper::getComponentHelper($this)->addDataTypeDefinition($typeName, $typeClass, $classpath);
612
    }
613
614 776
    /**
615
     * Returns the data type definitions
616 776
     *
617
     * @return array
618
     */
619
    public function getDataTypeDefinitions()
620
    {
621
        return ComponentHelper::getComponentHelper($this)->getDataTypeDefinitions();
622
    }
623
624
    /**
625
     * Add a new target to the project
626 775
     *
627
     * @param  string $targetName
628 775
     * @param  Target $target
629
     * @throws BuildException
630
     */
631 775
    public function addTarget($targetName, $target)
632 775
    {
633
        if (isset($this->targets[$targetName])) {
634
            throw new BuildException("Duplicate target: $targetName");
635
        }
636
        $this->addOrReplaceTarget($targetName, $target);
637
    }
638
639
    /**
640 775
     * Adds or replaces a target in the project
641
     *
642 775
     * @param string $targetName
643 775
     * @param Target $target
644 775
     */
645
    public function addOrReplaceTarget($targetName, &$target)
646 775
    {
647 775
        $this->log("  +Target: $targetName", Project::MSG_DEBUG);
648 775
        $target->setProject($this);
649 775
        $this->targets[$targetName] = $target;
650
651
        $ctx = $this->getReference(ProjectConfigurator::PARSING_CONTEXT_REFERENCE);
652
        $current = $ctx->getCurrentTargets();
653
        $current[$targetName] = $target;
654
    }
655
656 775
    /**
657
     * Returns the available targets
658 775
     *
659
     * @return Target[]
660
     */
661
    public function getTargets()
662
    {
663
        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 633
     *
677
     * @param  string $taskType Task name
678 633
     * @return Task           A task object
679
     * @throws BuildException
680
     */
681
    public function createTask($taskType)
682
    {
683
        return ComponentHelper::getComponentHelper($this)->createTask($taskType);
684
    }
685
686
    /**
687
     * Creates a new condition and returns the reference to it
688 1
     *
689
     * @param  string $conditionType
690 1
     * @return Condition
691
     * @throws BuildException
692
     */
693
    public function createCondition($conditionType)
694
    {
695
        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 44
     * @param  string $typeName Type name
703
     * @return object         A datatype object
704 44
     * @throws BuildException
705
     *                                 Exception
706
     */
707
    public function createDataType($typeName)
708
    {
709
        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 583
     *
731
     * @param  string $targetName Name of Target to execute
732
     * @return void
733
     * @throws BuildException
734 583
     */
735
    public function executeTarget($targetName)
736
    {
737
738
        // complain about executing void
739
        if ($targetName === null) {
0 ignored issues
show
introduced by
The condition $targetName === null is always false.
Loading history...
740 583
            throw new BuildException("No target specified");
741
        }
742 583
743 583
        // invoke topological sort of the target tree and run all targets
744 583
        // until targetName occurs.
745 583
        $sortedTargets = $this->topoSort($targetName);
746
747
        $curIndex = (int) 0;
748 583
        $curTarget = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $curTarget is dead and can be removed.
Loading history...
749 583
        $thrownException = null;
750 150
        $buildException = null;
751 150
        do {
752 150
            try {
753
                $curTarget = $sortedTargets[$curIndex++];
754
                $curTarget->performTasks();
755
            } catch (BuildException $exc) {
756 458
                if (!($this->keepGoingMode)) {
757
                    throw $exc;
758
                }
759
                $thrownException = $exc;
760
            }
761
            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 458
                    if ($buildException === null) {
782
                        $buildException = new BuildException($thrownException);
783 458
                    }
784
                }
785
            }
786 458
        } while ($curTarget->getName() !== $targetName);
787
788
        if ($buildException !== null) {
789
            throw $buildException;
790
        }
791
    }
792
793
    /**
794
     * Helper function
795
     *
796 389
     * @param  string $fileName
797
     * @param  PhingFile $rootDir
798 389
     * @throws IOException
799 360
     * @return \PhingFile
800
     */
801 68
    public function resolveFile(string $fileName, PhingFile $rootDir = null): PhingFile
802
    {
803
        if ($rootDir === null) {
804
            return $this->fileUtils->resolveFile($this->basedir, $fileName);
805
        }
806
        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 7
     *
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 7
     *         <code>false</code> otherwise.
819 7
     */
820 5
    public static function toBoolean($s)
821
    {
822 7
        return (
823
            strcasecmp($s, 'on') === 0
824
            || strcasecmp($s, 'true') === 0
825
            || strcasecmp($s, 'yes') === 0
826
            // FIXME next condition should be removed if the boolean behavior for properties will be solved
827
            || 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 583
     *                         target is the minimum possible such sequence.
837
     * @throws BuildException
838 583
     * @throws Exception
839 583
     * @return Target[] targets in sorted order
840 583
     */
841 583
    public function topoSort($rootTarget)
842
    {
843
        $rootTarget = (string) $rootTarget;
844
        $ret = [];
845
        $state = [];
846
        $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 583
        // This is unnecessary for doing our build, but it catches
852
        // circular dependencies or missing Targets on the entire
853 583
        // dependency tree, not just on the Targets that depend on the
854 583
        // build Target.
855 583
856
        $this->_tsort($rootTarget, $state, $visiting, $ret);
857 583
858
        $retHuman = "";
859 583
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
860 583
            $retHuman .= (string) $ret[$i] . " ";
861 583
        }
862 583
        $this->log("Build sequence for target '$rootTarget' is: $retHuman", Project::MSG_VERBOSE);
863 583
864
        $keys = array_keys($this->targets);
865 583
        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
            $curTargetName = (string) array_shift($keys);
867
            if (!isset($state[$curTargetName])) {
868 583
                $st = null;
869 583
            } else {
870 583
                $st = (string) $state[$curTargetName];
871
            }
872
873
            if ($st === null) {
874
                $this->_tsort($curTargetName, $state, $visiting, $ret);
875 583
            } elseif ($st === "VISITING") {
876 583
                throw new Exception("Unexpected node in visiting state: $curTargetName");
877 583
            }
878
        }
879 583
880
        $retHuman = "";
881 583
        for ($i = 0, $_i = count($ret); $i < $_i; $i++) {
882
            $retHuman .= (string) $ret[$i] . " ";
883
        }
884
        $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE);
885
886
        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 583
     * @param $visiting
910
     * @param $ret
911 583
     * @throws BuildException
912 583
     * @throws Exception
913
     */
914 583
    public function _tsort($root, &$state, &$visiting, &$ret)
915
    {
916
        $state[$root] = "VISITING";
917 583
        $visiting[] = $root;
918
919
        if (!isset($this->targets[$root]) || !($this->targets[$root] instanceof Target)) {
920
            $target = null;
921 583
        } else {
922
            $target = $this->targets[$root];
923
        }
924
925
        // make sure we exist
926
        if ($target === null) {
927
            $sb = "Target '$root' does not exist in this project.";
928
            array_pop($visiting);
929
            if (!empty($visiting)) {
930
                $parent = (string) $visiting[count($visiting) - 1];
931 583
                $sb .= " It is a dependency of target '$parent'.";
932
            }
933 583
            throw new BuildException($sb);
934 101
        }
935 101
936 49
        $deps = $target->getDependencies();
937
938 91
        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
            $cur = (string) array_shift($deps);
940 101
            if (!isset($state[$cur])) {
941
                $m = null;
942 49
            } else {
943 91
                $m = (string) $state[$cur];
944
            }
945
            if ($m === null) {
946
                // not been visited
947
                $this->_tsort($cur, $state, $visiting, $ret);
948
            } elseif ($m == "VISITING") {
949 583
                // currently visiting this node, so have a cycle
950 583
                throw $this->_makeCircularException($cur, $visiting);
951
            }
952
        }
953
954 583
        $p = (string) array_pop($visiting);
955 583
        if ($root !== $p) {
956 583
            throw new Exception("Unexpected internal error: expected to pop $root but got $p");
957
        }
958
959
        $state[$root] = "VISITED";
960
        $ret[] = $target;
961
    }
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 795
     * to the object assigned to this id as $value
983
     *
984 795
     * @param string $name
985 795
     * @param object $object
986 6
     */
987
    public function addReference($name, $object)
988 795
    {
989 12
        $ref = $this->references[$name] ?? null;
990
        if ($ref === $object) {
991 795
            return;
992 795
        }
993 795
        if ($ref !== null && !$ref instanceof UnknownElement) {
994 795
            $this->log("Overriding previous definition of reference to $name", Project::MSG_VERBOSE);
995
        }
996
        $refName = (is_scalar($object) || $object instanceof PropertyValue) ? (string) $object : get_class($object);
997
        $this->log("Adding reference: $name -> " . $refName, Project::MSG_DEBUG);
998
        $this->references[$name] = $object;
999
    }
1000
1001 13
    /**
1002
     * Returns the references array.
1003 13
     *
1004
     * @return array
1005
     */
1006
    public function getReferences()
1007
    {
1008
        return $this->references;
1009
    }
1010
1011
    /**
1012 796
     * Returns a specific reference.
1013
     *
1014 796
     * @param  string $key The reference id/key.
1015
     * @return object Reference or null if not defined
1016
     */
1017
    public function getReference($key)
1018
    {
1019
        return $this->references[$key] ?? null; // just to be explicit
1020
    }
1021
1022
    /**
1023 795
     * Abstracting and simplifyling Logger calls for project messages
1024
     *
1025 795
     * @param string $msg
1026 795
     * @param int $level
1027
     */
1028
    public function log($msg, $level = Project::MSG_INFO)
1029
    {
1030
        $this->logObject($this, $msg, $level);
1031
    }
1032
1033
    /**
1034 796
     * @param mixed $obj
1035
     * @param string $msg
1036 796
     * @param int $level
1037
     * @param Exception|null $t
1038
     */
1039
    public function logObject($obj, $msg, $level, Exception $t = null)
1040 796
    {
1041
        $this->fireMessageLogged($obj, $msg, $level, $t);
1042
1043 796
        // Checking whether the strict-mode is On, then consider all the warnings
1044
        // as errors.
1045
        if (($this->strictMode) && (Project::MSG_WARN == $level)) {
1046
            throw new BuildException('Build contains warnings, considered as errors in strict mode', null);
1047
        }
1048 775
    }
1049
1050 775
    /**
1051 775
     * @param BuildListener $listener
1052
     */
1053
    public function addBuildListener(BuildListener $listener)
1054
    {
1055
        $this->listeners[] = $listener;
1056
    }
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 8
    }
1071
1072 8
    /**
1073
     * @return array
1074
     */
1075
    public function getBuildListeners()
1076
    {
1077
        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 583
    }
1103
1104 583
    /**
1105 583
     * @param $target
1106 583
     */
1107
    public function fireTargetStarted($target)
1108
    {
1109 583
        $event = new BuildEvent($target);
1110 583
        foreach ($this->listeners as $listener) {
1111
            $listener->targetStarted($event);
1112
        }
1113
1114
        $this->log((string) $event, Project::MSG_DEBUG);
1115
    }
1116 583
1117
    /**
1118 583
     * @param $target
1119 583
     * @param $exception
1120 583
     */
1121 583
    public function fireTargetFinished($target, $exception)
1122
    {
1123
        $event = new BuildEvent($target);
1124 583
        $event->setException($exception);
1125 583
        foreach ($this->listeners as $listener) {
1126
            $listener->targetFinished($event);
1127
        }
1128
1129
        $this->log((string) $event, Project::MSG_DEBUG);
1130 636
    }
1131
1132 636
    /**
1133 636
     * @param $task
1134 636
     */
1135
    public function fireTaskStarted($task)
1136
    {
1137 636
        $event = new BuildEvent($task);
1138 636
        foreach ($this->listeners as $listener) {
1139
            $listener->taskStarted($event);
1140
        }
1141
1142
        $this->log((string) $event, Project::MSG_DEBUG);
1143
    }
1144 636
1145
    /**
1146 636
     * @param $task
1147 636
     * @param $exception
1148 636
     */
1149 636
    public function fireTaskFinished($task, $exception)
1150
    {
1151
        $event = new BuildEvent($task);
1152 636
        $event->setException($exception);
1153 636
        foreach ($this->listeners as $listener) {
1154
            $listener->taskFinished($event);
1155
        }
1156
1157
        $this->log((string) $event, Project::MSG_DEBUG);
1158
    }
1159
1160 796
    /**
1161
     * @param $event
1162 796
     * @param $message
1163 796
     * @param $priority
1164 775
     */
1165
    public function fireMessageLoggedEvent(BuildEvent $event, $message, $priority)
1166 796
    {
1167
        $event->setMessage($message, $priority);
1168
        foreach ($this->listeners as $listener) {
1169
            $listener->messageLogged($event);
1170
        }
1171
    }
1172
1173
    /**
1174
     * @param mixed $object
1175 796
     * @param string $message
1176
     * @param int $priority
1177 796
     * @param Exception $t
1178 796
     * @throws \Exception
1179
     */
1180
    public function fireMessageLogged($object, $message, $priority, Exception $t = null)
1181 796
    {
1182 796
        $event = new BuildEvent($object);
1183
        if ($t !== null) {
1184
            $event->setException($t);
1185
        }
1186
        $this->fireMessageLoggedEvent($event, $message, $priority);
1187
    }
1188
}
1189