Passed
Push — dependabot/add-v2-config-file ( 8c2f3c...9e9ec5 )
by Siad
10:32 queued 02:43
created

Project::getBuildListeners()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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