Passed
Push — master ( a862c8...fef60d )
by Siad
05:54
created

Phing::explodeIncludePath()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 4
nop 1
dl 0
loc 16
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 10
c 0
b 0
f 0
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
use SebastianBergmann\Version;
21
use Symfony\Component\Console\Output\ConsoleOutput;
22
23
/**
24
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
25
 * parsing & handling commandline arguments to assembling the project to shutting down
26
 * and cleaning up in the end.
27
 *
28
 * If you are invoking Phing from an external application, this is still
29
 * the class to use.  Your application can invoke the start() method, passing
30
 * any commandline arguments or additional properties.
31
 *
32
 * @author Andreas Aderhold <[email protected]>
33
 * @author Hans Lellelid <[email protected]>
34
 *
35
 * @package phing
36
 */
37
class Phing
38
{
39
    /**
40
     * Alias for phar file
41
     */
42
    public const PHAR_ALIAS = 'phing.phar';
43
44
    /**
45
     * The default build file name
46
     */
47
    public const DEFAULT_BUILD_FILENAME = "build.xml";
48
    public const PHING_HOME = 'phing.home';
49
    public const PHP_VERSION = 'php.version';
50
    public const PHP_INTERPRETER = 'php.interpreter';
51
52
    /**
53
     * Our current message output status. Follows Project::MSG_XXX
54
     */
55
    private static $msgOutputLevel = Project::MSG_INFO;
56
57
    /**
58
     * PhingFile that we are using for configuration
59
     */
60
    private $buildFile = null;
61
62
    /**
63
     * The build targets
64
     */
65
    private $targets = [];
66
67
    /**
68
     * Set of properties that are passed in from commandline or invoking code.
69
     *
70
     * @var Properties
71
     */
72
    private static $definedProps;
73
74
    /**
75
     * Names of classes to add as listeners to project
76
     */
77
    private $listeners = [];
78
79
    /**
80
     * keep going mode
81
     *
82
     * @var bool $keepGoingMode
83
     */
84
    private $keepGoingMode = false;
85
86
    private $loggerClassname = null;
87
88
    /**
89
     * The class to handle input (can be only one).
90
     */
91
    private $inputHandlerClassname;
92
93
    /**
94
     * Whether or not log output should be reduced to the minimum.
95
     *
96
     * @var bool $silent
97
     */
98
    private $silent = false;
99
100
    /**
101
     * Indicates whether phing should run in strict mode
102
     */
103
    private $strictMode = false;
104
105
    /**
106
     * Indicates if this phing should be run
107
     */
108
    private $readyToRun = false;
109
110
    /**
111
     * Indicates we should only parse and display the project help information
112
     */
113
    private $projectHelp = false;
114
115
    /**
116
     * Used by utility function getResourcePath()
117
     */
118
    private static $importPaths;
119
120
    /**
121
     * System-wide static properties (moved from System)
122
     */
123
    private static $properties = [];
124
125
    /**
126
     * Static system timer.
127
     */
128
    private static $timer;
129
130
    /**
131
     * The current Project
132
     */
133
    private static $currentProject;
134
135
    /**
136
     * Whether to capture PHP errors to buffer.
137
     */
138
    private static $phpErrorCapture = false;
139
140
    /**
141
     * Whether to values in a property file should override existing values.
142
     */
143
    private $propertyFileOverride = false;
144
145
    /**
146
     * Array of captured PHP errors
147
     */
148
    private static $capturedPhpErrors = [];
149
150
    /**
151
     * @var OUtputStream Stream for standard output.
152
     */
153
    private static $out;
154
155
    /**
156
     * @var OutputStream Stream for error output.
157
     */
158
    private static $err;
159
160
    /**
161
     * @var boolean Whether we are using a logfile.
162
     */
163
    private static $isLogFileUsed = false;
164
165
    /**
166
     * Array to hold original ini settings that Phing changes (and needs
167
     * to restore in restoreIni() method).
168
     *
169
     * @var array Struct of array(setting-name => setting-value)
170
     * @see restoreIni()
171
     */
172
    private static $origIniSettings = [];
173
174
    /**
175
     * Whether or not output to the log is to be unadorned.
176
     */
177
    private $emacsMode = false;
178
179
    /**
180
     * @var string
181
     */
182
    private $searchForThis;
183
    private $propertyFiles = [];
184
185
    /**
186
     * Entry point allowing for more options from other front ends.
187
     *
188
     * This method encapsulates the complete build lifecycle.
189
     *
190
     * @param  array $args The commandline args passed to phing shell script.
191
     * @param  array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
192
     *                                         These additional properties will be available using the getDefinedProperty() method and will
193
     *                                         be added to the project's "user" properties
194
     * @see    execute()
195
     * @see    runBuild()
196
     * @throws Exception - if there is an error during build
197
     */
198
    public static function start($args, array $additionalUserProperties = null)
199
    {
200
        try {
201
            $m = new self();
202
            $m->execute($args);
203
        } catch (Exception $exc) {
204
            self::handleLogfile();
205
            self::printMessage($exc);
206
            self::statusExit(1);
207
            return;
208
        }
209
210
        if ($additionalUserProperties !== null) {
211
            foreach ($additionalUserProperties as $key => $value) {
212
                $m::setDefinedProperty($key, $value);
213
            }
214
        }
215
216
        // expect the worst
217
        $exitCode = 1;
218
        try {
219
            try {
220
                $m->runBuild();
221
                $exitCode = 0;
222
            } catch (ExitStatusException $ese) {
223
                $exitCode = $ese->getCode();
224
                if ($exitCode !== 0) {
225
                    self::handleLogfile();
226
                    throw $ese;
227
                }
228
            }
229
        } catch (BuildException $exc) {
230
            // avoid printing output twice: self::printMessage($exc);
231
        } catch (Throwable $exc) {
232
            self::printMessage($exc);
233
        } finally {
234
            self::handleLogfile();
235
        }
236
        self::statusExit($exitCode);
237
    }
238
239
    /**
240
     * This operation is expected to call `exit($int)`, which
241
     * is what the base version does.
242
     * However, it is possible to do something else.
243
     *
244
     * @param int $exitCode code to exit with
245
     */
246
    protected static function statusExit($exitCode)
247
    {
248
        Phing::shutdown();
249
        exit($exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
250
    }
251
252
    /**
253
     * Prints the message of the Exception if it's not null.
254
     *
255
     * @param Throwable $t
256
     */
257
    public static function printMessage(Throwable $t)
258
    {
259
        if (self::$err === null) { // Make sure our error output is initialized
260
            self::initializeOutputStreams();
261
        }
262
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
263
            self::$err->write((string) $t . PHP_EOL);
264
        } else {
265
            self::$err->write($t->getMessage() . PHP_EOL);
266
        }
267
    }
268
269
    /**
270
     * Sets the stdout and stderr streams if they are not already set.
271
     */
272 1
    private static function initializeOutputStreams()
273
    {
274 1
        if (self::$out === null) {
275
            if (!defined('STDOUT')) {
276
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://stdout', 'w') can also be of type false; however, parameter $stream of OutputStream::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

276
                self::$out = new OutputStream(/** @scrutinizer ignore-type */ fopen('php://stdout', 'w'));
Loading history...
277
            } else {
278
                self::$out = new OutputStream(STDOUT);
279
            }
280
        }
281 1
        if (self::$err === null) {
282
            if (!defined('STDERR')) {
283
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
284
            } else {
285
                self::$err = new OutputStream(STDERR);
286
            }
287
        }
288 1
    }
289
290
    /**
291
     * Sets the stream to use for standard (non-error) output.
292
     *
293
     * @param OutputStream $stream The stream to use for standard output.
294
     */
295 1
    public static function setOutputStream(OutputStream $stream)
296
    {
297 1
        self::$out = $stream;
298 1
    }
299
300
    /**
301
     * Gets the stream to use for standard (non-error) output.
302
     *
303
     * @return OutputStream
304
     */
305
    public static function getOutputStream()
306
    {
307
        return self::$out;
308
    }
309
310
    /**
311
     * Sets the stream to use for error output.
312
     *
313
     * @param OutputStream $stream The stream to use for error output.
314
     */
315 1
    public static function setErrorStream(OutputStream $stream)
316
    {
317 1
        self::$err = $stream;
318 1
    }
319
320
    /**
321
     * Gets the stream to use for error output.
322
     *
323
     * @return OutputStream
324
     */
325
    public static function getErrorStream()
326
    {
327
        return self::$err;
328
    }
329
330
    /**
331
     * Close logfiles, if we have been writing to them.
332
     *
333
     * @since Phing 2.3.0
334
     *
335
     * @return void
336
     */
337
    private static function handleLogfile()
338
    {
339
        if (self::$isLogFileUsed) {
340
            self::$err->close();
341
            self::$out->close();
342
        }
343
    }
344
345
    /**
346
     * Making output level a static property so that this property
347
     * can be accessed by other parts of the system, enabling
348
     * us to display more information -- e.g. backtraces -- for "debug" level.
349
     *
350
     * @return int
351
     */
352 7
    public static function getMsgOutputLevel()
353
    {
354 7
        return self::$msgOutputLevel;
355
    }
356
357
    /**
358
     * Command line entry point. This method kicks off the building
359
     * of a project object and executes a build using either a given
360
     * target or the default target.
361
     *
362
     * @param array $args Command line args.
363
     *
364
     * @return void
365
     */
366
    public static function fire($args)
367
    {
368
        self::start($args, null);
369
    }
370
371
    /**
372
     * Setup/initialize Phing environment from commandline args.
373
     *
374
     * @param array $args commandline args passed to phing shell.
375
     *
376
     * @throws ConfigurationException
377
     *
378
     * @return void
379
     */
380
    public function execute($args)
381
    {
382
        self::$definedProps = new Properties();
383
        $this->searchForThis = null;
384
385
        // 1) First handle any options which should always
386
        // Note: The order in which these are executed is important (if multiple of these options are specified)
387
388
        if (in_array('-help', $args) || in_array('-h', $args)) {
389
            static::printUsage();
390
391
            return;
392
        }
393
394
        if (in_array('-version', $args) || in_array('-v', $args)) {
395
            static::printVersion();
396
397
            return;
398
        }
399
400
        if (in_array('-init', $args) || in_array('-i', $args)) {
401
            $key = array_search('-init', $args) ?: array_search('-i', $args);
402
            $path = $args[$key + 1] ?? null;
403
404
            self::init($path);
405
406
            return;
407
        }
408
409
        if (in_array('-diagnostics', $args)) {
410
            Diagnostics::doReport(new PrintStream(self::$out));
411
412
            return;
413
        }
414
415
        // 2) Next pull out stand-alone args.
416
        // Note: The order in which these are executed is important (if multiple of these options are specified)
417
418
        if (
419
            false !== ($key = array_search('-quiet', $args, true)) ||
420
            false !== ($key = array_search(
421
                '-q',
422
                $args,
423
                true
424
            ))
425
        ) {
426
            self::$msgOutputLevel = Project::MSG_WARN;
427
            unset($args[$key]);
428
        }
429
430
        if (
431
            false !== ($key = array_search('-emacs', $args, true))
432
            || false !== ($key = array_search('-e', $args, true))
433
        ) {
434
            $this->emacsMode = true;
435
            unset($args[$key]);
436
        }
437
438
        if (false !== ($key = array_search('-verbose', $args, true))) {
439
            self::$msgOutputLevel = Project::MSG_VERBOSE;
440
            unset($args[$key]);
441
        }
442
443
        if (false !== ($key = array_search('-debug', $args, true))) {
444
            self::$msgOutputLevel = Project::MSG_DEBUG;
445
            unset($args[$key]);
446
        }
447
448
        if (
449
            false !== ($key = array_search('-silent', $args, true))
450
            || false !== ($key = array_search('-S', $args, true))
451
        ) {
452
            $this->silent = true;
453
            unset($args[$key]);
454
        }
455
456
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
457
            $this->propertyFileOverride = true;
458
            unset($args[$key]);
459
        }
460
461
        // 3) Finally, cycle through to parse remaining args
462
        //
463
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
464
        $max = $keys ? max($keys) : -1;
465
        for ($i = 0; $i <= $max; $i++) {
466
            if (!array_key_exists($i, $args)) {
467
                // skip this argument, since it must have been removed above.
468
                continue;
469
            }
470
471
            $arg = $args[$i];
472
473
            if ($arg == "-logfile") {
474
                try {
475
                    // see: http://phing.info/trac/ticket/65
476
                    if (!isset($args[$i + 1])) {
477
                        $msg = "You must specify a log file when using the -logfile argument\n";
478
                        throw new ConfigurationException($msg);
479
                    }
480
481
                    $logFile = new PhingFile($args[++$i]);
482
                    $out = new FileOutputStream($logFile); // overwrite
483
                    self::setOutputStream($out);
484
                    self::setErrorStream($out);
485
                    self::$isLogFileUsed = true;
486
                } catch (IOException $ioe) {
487
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
488
                    throw new ConfigurationException($msg, $ioe);
489
                }
490
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
491
                if (!isset($args[$i + 1])) {
492
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
493
                    throw new ConfigurationException($msg);
494
                }
495
496
                $this->buildFile = new PhingFile($args[++$i]);
497
            } elseif ($arg == "-listener") {
498
                if (!isset($args[$i + 1])) {
499
                    $msg = "You must specify a listener class when using the -listener argument";
500
                    throw new ConfigurationException($msg);
501
                }
502
503
                $this->listeners[] = $args[++$i];
504
            } elseif (StringHelper::startsWith("-D", $arg)) {
505
                // Evaluating the property information //
506
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
507
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
508
                    $name = $args[++$i];
509
                } else {
510
                    $name = substr($arg, 2);
511
                }
512
513
                $value = null;
514
                $posEq = strpos($name, "=");
515
                if ($posEq !== false) {
516
                    $value = substr($name, $posEq + 1);
517
                    $name = substr($name, 0, $posEq);
518
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
519
                    $value = $args[++$i];
520
                }
521
                self::$definedProps->setProperty($name, $value);
522
            } elseif ($arg == "-logger") {
523
                if (!isset($args[$i + 1])) {
524
                    $msg = "You must specify a classname when using the -logger argument";
525
                    throw new ConfigurationException($msg);
526
                }
527
528
                $this->loggerClassname = $args[++$i];
529
            } elseif ($arg == "-no-strict") {
530
                $this->strictMode = false;
531
            } elseif ($arg == "-strict") {
532
                $this->strictMode = true;
533
            } elseif ($arg == "-inputhandler") {
534
                if ($this->inputHandlerClassname !== null) {
535
                    throw new ConfigurationException("Only one input handler class may be specified.");
536
                }
537
                if (!isset($args[$i + 1])) {
538
                    $msg = "You must specify a classname when using the -inputhandler argument";
539
                    throw new ConfigurationException($msg);
540
                }
541
542
                $this->inputHandlerClassname = $args[++$i];
543
            } elseif ($arg === "-propertyfile") {
544
                $i = $this->handleArgPropertyFile($args, $i);
545
            } elseif ($arg === "-keep-going" || $arg === "-k") {
546
                $this->keepGoingMode = true;
547
            } elseif ($arg == "-longtargets") {
548
                self::$definedProps->setProperty('phing.showlongtargets', 1);
549
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
550
                // set the flag to display the targets and quit
551
                $this->projectHelp = true;
552
            } elseif ($arg == "-find") {
553
                // eat up next arg if present, default to build.xml
554
                if ($i < count($args) - 1) {
555
                    $this->searchForThis = $args[++$i];
556
                } else {
557
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
558
                }
559
            } elseif (substr($arg, 0, 1) == "-") {
560
                // we don't have any more args
561
                self::printUsage();
562
                self::$err->write(PHP_EOL);
563
                throw new ConfigurationException("Unknown argument: " . $arg);
564
            } else {
565
                // if it's no other arg, it may be the target
566
                $this->targets[] = $arg;
567
            }
568
        }
569
570
        // if buildFile was not specified on the command line,
571
        if ($this->buildFile === null) {
572
            // but -find then search for it
573
            if ($this->searchForThis !== null) {
574
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
575
            } else {
576
                $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
577
            }
578
        }
579
580
        try {
581
            // make sure buildfile (or buildfile.dist) exists
582
            if (!$this->buildFile->exists()) {
583
                $distFile = new PhingFile($this->buildFile->getAbsolutePath() . ".dist");
584
                if (!$distFile->exists()) {
585
                    throw new ConfigurationException(
586
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
587
                    );
588
                }
589
                $this->buildFile = $distFile;
590
            }
591
592
            // make sure it's not a directory
593
            if ($this->buildFile->isDirectory()) {
594
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
595
            }
596
        } catch (IOException $e) {
597
            // something else happened, buildfile probably not readable
598
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
599
        }
600
601
        $this->loadPropertyFiles();
602
603
        $this->readyToRun = true;
604
    }
605
606
    /**
607
     * Handle the -propertyfile argument.
608
     *
609
     * @param array $args
610
     * @param int $pos
611
     *
612
     * @return int
613
     *
614
     * @throws ConfigurationException
615
     * @throws IOException
616
     */
617
    private function handleArgPropertyFile(array $args, int $pos): int
618
    {
619
        if (!isset($args[$pos + 1])) {
620
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
621
        }
622
623
        $this->propertyFiles[] = $args[++$pos];
624
625
        return $pos;
626
    }
627
628
    /**
629
     * @throws IOException
630
     */
631
    private function loadPropertyFiles()
632
    {
633
        foreach ($this->propertyFiles as $filename) {
634
            $fileParserFactory = new FileParserFactory();
635
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
636
            $p = new Properties(null, $fileParser);
637
            try {
638
                $p->load(new PhingFile($filename));
639
            } catch (IOException $e) {
640
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
641
            }
642
            foreach ($p->getProperties() as $prop => $value) {
643
                self::$definedProps->setProperty($prop, $value);
644
            }
645
        }
646
    }
647
648
    /**
649
     * Search parent directories for the build file.
650
     *
651
     * Takes the given target as a suffix to append to each
652
     * parent directory in search of a build file.  Once the
653
     * root of the file-system has been reached an exception
654
     * is thrown.
655
     *
656
     * @param string $start Start file path.
657
     * @param string $suffix Suffix filename to look for in parents.
658
     *
659
     * @throws ConfigurationException
660
     *
661
     * @return PhingFile A handle to the build file
662
     */
663
    private function _findBuildFile($start, $suffix)
664
    {
665
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
666
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
667
        }
668
669
        $parent = new PhingFile((new PhingFile($start))->getAbsolutePath());
670
        $file = new PhingFile($parent, $suffix);
671
672
        // check if the target file exists in the current directory
673
        while (!$file->exists()) {
674
            // change to parent directory
675
            $parent = $parent->getParentFile();
676
677
            // if parent is null, then we are at the root of the fs,
678
            // complain that we can't find the build file.
679
            if ($parent === null) {
680
                throw new ConfigurationException("Could not locate a build file!");
681
            }
682
            // refresh our file handle
683
            $file = new PhingFile($parent, $suffix);
684
        }
685
686
        return $file;
687
    }
688
689
    /**
690
     * Executes the build.
691
     *
692
     * @throws IOException
693
     * @throws Throwable
694
     */
695
    public function runBuild(): void
696
    {
697
        if (!$this->readyToRun) {
698
            return;
699
        }
700
701
        $project = new Project();
702
703
        self::setCurrentProject($project);
704
        set_error_handler(['Phing', 'handlePhpError']);
705
706
        $error = null;
707
708
        try {
709
            $this->addBuildListeners($project);
710
            $this->addInputHandler($project);
711
712
            // set this right away, so that it can be used in logging.
713
            $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
714
            $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
715
            $project->setUserProperty("phing.version", static::getPhingVersion());
716
            $project->fireBuildStarted();
717
            $project->init();
718
            $project->setKeepGoingMode($this->keepGoingMode);
719
720
            $e = self::$definedProps->keys();
721
            while (count($e)) {
722
                $arg = (string) array_shift($e);
723
                $value = (string) self::$definedProps->getProperty($arg);
724
                $project->setUserProperty($arg, $value);
725
            }
726
            unset($e);
727
728
            // first use the Configurator to create the project object
729
            // from the given build file.
730
731
            ProjectConfigurator::configureProject($project, $this->buildFile);
732
733
            // Set the project mode
734
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
735
736
            // make sure that minimum required phing version is satisfied
737
            $this->comparePhingVersion($project->getPhingVersion());
738
739
            if ($this->projectHelp) {
740
                $this->printDescription($project);
741
                $this->printTargets($project);
742
                return;
743
            }
744
745
            // make sure that we have a target to execute
746
            if (count($this->targets) === 0) {
747
                $this->targets[] = $project->getDefaultTarget();
748
            }
749
750
            $project->executeTargets($this->targets);
751
        } catch (Throwable $t) {
752
            $error = $t;
753
            throw $t;
754
        } finally {
755
            if (!$this->projectHelp) {
756
                try {
757
                    $project->fireBuildFinished($error);
758
                } catch (Exception $e) {
759
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
760
                    self::$err->write($e->getTraceAsString());
761
                    if ($error !== null) {
762
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
763
                        self::$err->write($error->getTraceAsString());
764
                    }
765
                    throw new BuildException($t);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t does not seem to be defined for all execution paths leading up to this point.
Loading history...
766
                }
767
            } elseif ($error !== null) {
768
                $project->log($error->getMessage(), Project::MSG_ERR);
769
            }
770
771
            restore_error_handler();
772
            self::unsetCurrentProject();
773
        }
774
    }
775
776
    /**
777
     * @param string $version
778
     *
779
     * @return int
780
     *
781
     * @throws BuildException
782
     * @throws ConfigurationException
783
     */
784
    private function comparePhingVersion($version)
785
    {
786
        $current = strtolower(self::getPhingVersion());
787
        $current = trim(str_replace('phing', '', $current));
788
789
        // make sure that version checks are not applied to trunk
790
        if ('dev' === $current) {
791
            return 1;
792
        }
793
794
        if (-1 == version_compare($current, $version)) {
795
            throw new BuildException(
796
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
797
            );
798
        }
799
    }
800
801
    /**
802
     * Bind any registered build listeners to this project.
803
     *
804
     * This means adding the logger and any build listeners that were specified
805
     * with -listener arg.
806
     *
807
     * @param  Project $project
808
     * @throws BuildException
809
     * @throws ConfigurationException
810
     * @return void
811
     */
812
    private function addBuildListeners(Project $project)
813
    {
814
        // Add the default listener
815
        $project->addBuildListener($this->createLogger());
816
817
        foreach ($this->listeners as $listenerClassname) {
818
            try {
819
                $clz = Phing::import($listenerClassname);
820
            } catch (Exception $e) {
821
                $msg = "Unable to instantiate specified listener "
822
                    . "class " . $listenerClassname . " : "
823
                    . $e->getMessage();
824
                throw new ConfigurationException($msg);
825
            }
826
827
            $listener = new $clz();
828
829
            if ($listener instanceof StreamRequiredBuildLogger) {
830
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
831
            }
832
            $project->addBuildListener($listener);
833
        }
834
    }
835
836
    /**
837
     * Creates the InputHandler and adds it to the project.
838
     *
839
     * @param Project $project the project instance.
840
     *
841
     * @throws ConfigurationException
842
     */
843
    private function addInputHandler(Project $project)
844
    {
845
        if ($this->inputHandlerClassname === null) {
846
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
847
        } else {
848
            try {
849
                $clz = Phing::import($this->inputHandlerClassname);
850
                $handler = new $clz();
851
                if ($project !== null && method_exists($handler, 'setProject')) {
852
                    $handler->setProject($project);
853
                }
854
            } catch (Exception $e) {
855
                $msg = "Unable to instantiate specified input handler "
856
                    . "class " . $this->inputHandlerClassname . " : "
857
                    . $e->getMessage();
858
                throw new ConfigurationException($msg);
859
            }
860
        }
861
        $project->setInputHandler($handler);
862
    }
863
864
    /**
865
     * Creates the default build logger for sending build events to the log.
866
     *
867
     * @throws BuildException
868
     * @return BuildLogger The created Logger
869
     */
870
    private function createLogger()
871
    {
872
        if ($this->silent) {
873
            $logger = new SilentLogger();
874
            self::$msgOutputLevel = Project::MSG_WARN;
875
        } elseif ($this->loggerClassname !== null) {
876
            self::import($this->loggerClassname);
877
            // get class name part
878
            $classname = self::import($this->loggerClassname);
879
            $logger = new $classname();
880
            if (!($logger instanceof BuildLogger)) {
881
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
882
            }
883
        } else {
884
            $logger = new DefaultLogger();
885
        }
886
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
887
        $logger->setOutputStream(self::$out);
888
        $logger->setErrorStream(self::$err);
889
        $logger->setEmacsMode($this->emacsMode);
890
891
        return $logger;
892
    }
893
894
    /**
895
     * Sets the current Project
896
     *
897
     * @param Project $p
898
     */
899 1
    public static function setCurrentProject($p)
900
    {
901 1
        self::$currentProject = $p;
902 1
    }
903
904
    /**
905
     * Unsets the current Project
906
     */
907 1
    public static function unsetCurrentProject()
908
    {
909 1
        self::$currentProject = null;
910 1
    }
911
912
    /**
913
     * Gets the current Project.
914
     *
915
     * @return Project Current Project or NULL if none is set yet/still.
916
     */
917 5
    public static function getCurrentProject()
918
    {
919 5
        return self::$currentProject;
920
    }
921
922
    /**
923
     * A static convenience method to send a log to the current (last-setup) Project.
924
     * If there is no currently-configured Project, then this will do nothing.
925
     *
926
     * @param string $message
927
     * @param int $priority Project::MSG_INFO, etc.
928
     */
929
    public static function log($message, $priority = Project::MSG_INFO)
930
    {
931
        $p = self::getCurrentProject();
932
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Project, thus it always evaluated to true.
Loading history...
933
            $p->log($message, $priority);
934
        }
935
    }
936
937
    /**
938
     * Error handler for PHP errors encountered during the build.
939
     * This uses the logging for the currently configured project.
940
     *
941
     * @param $level
942
     * @param string $message
943
     * @param $file
944
     * @param $line
945
     */
946
    public static function handlePhpError($level, $message, $file, $line)
947
    {
948
949
        // don't want to print suppressed errors
950
        if (error_reporting() > 0) {
951
            if (self::$phpErrorCapture) {
952
                self::$capturedPhpErrors[] = [
953
                    'message' => $message,
954
                    'level' => $level,
955
                    'line' => $line,
956
                    'file' => $file
957
                ];
958
            } else {
959
                $message = '[PHP Error] ' . $message;
960
                $message .= ' [line ' . $line . ' of ' . $file . ']';
961
962
                switch ($level) {
963
                    case E_USER_DEPRECATED:
964
                    case E_DEPRECATED:
965
                    case E_STRICT:
966
                    case E_NOTICE:
967
                    case E_USER_NOTICE:
968
                        self::log($message, Project::MSG_VERBOSE);
969
                        break;
970
                    case E_WARNING:
971
                    case E_USER_WARNING:
972
                        self::log($message, Project::MSG_WARN);
973
                        break;
974
                    case E_ERROR:
975
                    case E_USER_ERROR:
976
                    default:
977
                        self::log($message, Project::MSG_ERR);
978
                } // switch
979
            } // if phpErrorCapture
980
        } // if not @
981
    }
982
983
    /**
984
     * Begins capturing PHP errors to a buffer.
985
     * While errors are being captured, they are not logged.
986
     */
987
    public static function startPhpErrorCapture()
988
    {
989
        self::$phpErrorCapture = true;
990
        self::$capturedPhpErrors = [];
991
    }
992
993
    /**
994
     * Stops capturing PHP errors to a buffer.
995
     * The errors will once again be logged after calling this method.
996
     */
997
    public static function stopPhpErrorCapture()
998
    {
999
        self::$phpErrorCapture = false;
1000
    }
1001
1002
    /**
1003
     * Clears the captured errors without affecting the starting/stopping of the capture.
1004
     */
1005
    public static function clearCapturedPhpErrors()
1006
    {
1007
        self::$capturedPhpErrors = [];
1008
    }
1009
1010
    /**
1011
     * Gets any PHP errors that were captured to buffer.
1012
     *
1013
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1014
     */
1015
    public static function getCapturedPhpErrors()
1016
    {
1017
        return self::$capturedPhpErrors;
1018
    }
1019
1020
    /**
1021
     * Prints the usage of how to use this class
1022
     */
1023 1
    public static function printUsage()
1024
    {
1025 1
        $msg = "";
1026 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1027 1
        $msg .= "Options: " . PHP_EOL;
1028 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1029 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1030 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1031 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1032 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1033 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1034 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1035 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1036 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1037 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1038 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1039 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1040 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1041 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1042 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1043 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1044 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1045 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1046 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1047 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1048 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1049 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1050 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1051 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1052 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1053
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1054 1
        $msg .= PHP_EOL;
1055 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1056 1
        self::$err->write($msg);
1057 1
    }
1058
1059
    /**
1060
     * Prints the current Phing version.
1061
     */
1062
    public static function printVersion()
1063
    {
1064
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1065
    }
1066
1067
    /**
1068
     * Creates generic buildfile
1069
     *
1070
     * @param string $path
1071
     */
1072
    public static function init($path)
1073
    {
1074
        if ($buildfilePath = self::initPath($path)) {
1075
            self::initWrite($buildfilePath);
1076
        }
1077
    }
1078
1079
1080
    /**
1081
     * Returns buildfile's path
1082
     *
1083
     * @param $path
1084
     *
1085
     * @return string
1086
     * @throws ConfigurationException
1087
     */
1088
    protected static function initPath($path)
1089
    {
1090
        // Fallback
1091
        if (empty($path)) {
1092
            $defaultDir = self::getProperty('application.startdir');
1093
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1094
        }
1095
1096
        // Adding filename if necessary
1097
        if (is_dir($path)) {
1098
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1099
        }
1100
1101
        // Check if path is available
1102
        $dirname = dirname($path);
1103
        if (is_dir($dirname) && !is_file($path)) {
1104
            return $path;
1105
        }
1106
1107
        // Path is valid, but buildfile already exists
1108
        if (is_file($path)) {
1109
            throw new ConfigurationException('Buildfile already exists.');
1110
        }
1111
1112
        throw new ConfigurationException('Invalid path for sample buildfile.');
1113
    }
1114
1115
1116
    /**
1117
     * Writes sample buildfile
1118
     *
1119
     * If $buildfilePath does not exist, the buildfile is created.
1120
     *
1121
     * @param $buildfilePath buildfile's location
0 ignored issues
show
Documentation Bug introduced by
The doc comment buildfile's at position 0 could not be parsed: Unknown type name 'buildfile's' at position 0 in buildfile's.
Loading history...
1122
     *
1123
     * @return null
1124
     * @throws ConfigurationException
1125
     */
1126
    protected static function initWrite($buildfilePath)
1127
    {
1128
        // Overwriting protection
1129
        if (file_exists($buildfilePath)) {
1130
            throw new ConfigurationException('Cannot overwrite existing file.');
1131
        }
1132
1133
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1134
        $content .= '' . PHP_EOL;
1135
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1136
        $content .= '    ' . PHP_EOL;
1137
        $content .= '    <target name="" description="">' . PHP_EOL;
1138
        $content .= '        ' . PHP_EOL;
1139
        $content .= '    </target>' . PHP_EOL;
1140
        $content .= '    ' . PHP_EOL;
1141
        $content .= '</project>' . PHP_EOL;
1142
1143
        file_put_contents($buildfilePath, $content);
1144
    }
1145
1146
    /**
1147
     * Gets the current Phing version based on VERSION.TXT file.
1148
     *
1149
     * @throws ConfigurationException
1150
     *
1151
     * @return string
1152
     */
1153 6
    public static function getPhingVersion()
1154
    {
1155 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1156 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1157 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1158
        }
1159 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1160
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1161
        }
1162
        try { // try to read file
1163 6
            $file = new PhingFile($versionPath);
1164 6
            $reader = new FileReader($file);
1165 6
            $phingVersion = trim($reader->read());
1166
        } catch (IOException $iox) {
1167
            throw new ConfigurationException("Can't read version information file");
1168
        }
1169
1170 6
        $basePath = dirname(__DIR__, 2);
1171
1172 6
        $version = new Version($phingVersion, $basePath);
1173
1174 6
        return "Phing " . $version->getVersion();
1175
    }
1176
1177
    /**
1178
     * Print the project description, if any
1179
     *
1180
     * @param Project $project
1181
     *
1182
     * @throws IOException
1183
     */
1184
    public function printDescription(Project $project)
1185
    {
1186
        if ($project->getDescription() !== null) {
1187
            $project->log($project->getDescription());
1188
        }
1189
    }
1190
1191
    /**
1192
     * Print out a list of all targets in the current buildfile
1193
     *
1194
     * @param $project
1195
     */
1196 1
    public function printTargets($project)
1197
    {
1198
        // find the target with the longest name
1199 1
        $maxLength = 0;
1200 1
        $targets = $project->getTargets();
1201 1
        $targetName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetName is dead and can be removed.
Loading history...
1202 1
        $targetDescription = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetDescription is dead and can be removed.
Loading history...
1203
        /** @var Target $currentTarget */
1204 1
        $currentTarget = null;
1205
1206
        // split the targets in top-level and sub-targets depending
1207
        // on the presence of a description
1208
1209 1
        $subNames = [];
1210 1
        $subDependencies = [];
1211 1
        $topNameDescMap = [];
1212
1213 1
        foreach ($targets as $currentTarget) {
1214 1
            $targetName = $currentTarget->getName();
1215 1
            $targetDescription = $currentTarget->getDescription();
1216 1
            if ($currentTarget->isHidden()) {
1217
                continue;
1218
            }
1219
1220
            // subtargets are targets w/o descriptions
1221 1
            if ($targetDescription === null) {
1222 1
                $subNames[] = $targetName;
1223 1
                $currentDependencies = $currentTarget->getDependencies();
1224 1
                if (!empty($currentDependencies)) {
1225 1
                    array_push($subDependencies, ...$currentTarget->getDependencies());
1226
                }
1227
            } else {
1228
                // topNames and topDescriptions are handled later
1229
                // here we store in hash map (for sorting purposes)
1230
                $topNameDescMap[$targetName] = $targetDescription;
1231
                if (strlen($targetName) > $maxLength) {
1232
                    $maxLength = strlen($targetName);
1233
                }
1234
            }
1235
        }
1236
1237
        // Sort the arrays
1238 1
        sort($subNames); // sort array values, resetting keys (which are numeric)
1239 1
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1240
1241 1
        $topNames = array_keys($topNameDescMap);
1242 1
        $topDescriptions = array_values($topNameDescMap);
1243 1
        $topDependencies = $currentTarget->getDependencies();
1244
1245 1
        $defaultTarget = $project->getDefaultTarget();
1246
1247 1
        if ($defaultTarget !== null && $defaultTarget !== "") {
1248
            $defaultName = [];
1249
            $defaultDesc = [];
1250
            $defaultName[] = $defaultTarget;
1251
1252
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1253
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1254
                $defaultDesc = [];
1255
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1256
            }
1257
1258
            $this->_printTargets($project, $defaultName, $defaultDesc, [], "Default target:", $maxLength);
1259
        }
1260 1
        $this->_printTargets($project, $topNames, $topDescriptions, $topDependencies, "Main targets:", $maxLength);
1261 1
        $this->_printTargets($project, $subNames, null, $subDependencies, "Subtargets:", 0);
1262 1
    }
1263
1264
    /**
1265
     * Writes a formatted list of target names with an optional description.
1266
     *
1267
     * @param Project $project
1268
     * @param array $names The names to be printed.
1269
     *                             Must not be <code>null</code>.
1270
     * @param array $descriptions The associated target descriptions.
1271
     *                             May be <code>null</code>, in which case
1272
     *                             no descriptions are displayed.
1273
     *                             If non-<code>null</code>, this should have
1274
     *                             as many elements as <code>names</code>.
1275
     * @param $dependencies
1276
     * @param string $heading The heading to display.
1277
     *                             Should not be <code>null</code>.
1278
     * @param int $maxlen The maximum length of the names of the targets.
1279
     *                             If descriptions are given, they are padded to this
1280
     *                             position so they line up (so long as the names really
1281
     *                             <i>are</i> shorter than this).
1282
     */
1283 1
    private function _printTargets(Project $project, $names, $descriptions, $dependencies, $heading, $maxlen)
1284
    {
1285 1
        $spaces = '  ';
1286 1
        while (strlen($spaces) < $maxlen) {
1287
            $spaces .= $spaces;
1288
        }
1289 1
        $msg = "";
1290 1
        $msg .= $heading . PHP_EOL;
1291 1
        $msg .= str_repeat("-", 79) . PHP_EOL;
1292
1293 1
        $total = count($names);
1294 1
        for ($i = 0; $i < $total; $i++) {
1295 1
            $msg .= " ";
1296 1
            $msg .= $names[$i];
1297 1
            if (!empty($descriptions)) {
1298
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1299
                $msg .= $descriptions[$i];
1300
            }
1301 1
            $msg .= PHP_EOL;
1302 1
            if (!empty($dependencies) && isset($dependencies[$i + 1])) {
1303
                $msg .= '   depends on: ' . implode(', ', $dependencies) . PHP_EOL;
1304
            }
1305
        }
1306
1307 1
        $project->log($msg, Project::MSG_WARN);
1308 1
    }
1309
1310
    /**
1311
     * Import a path, supporting the following conventions:
1312
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1313
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1314
     * - dot-path
1315
     *
1316
     * @param string $dotPath Path
1317
     * @param mixed $classpath String or object supporting __toString()
1318
     *
1319
     * @return string         The unqualified classname (which can be instantiated).
1320
     *
1321
     * @throws BuildException - if cannot find the specified file
1322
     */
1323 805
    public static function import($dotPath, $classpath = null)
1324
    {
1325 805
        if (strpos($dotPath, '.') !== false) {
1326 804
            $classname = StringHelper::unqualify($dotPath);
1327
        } else {
1328 803
            $classname = $dotPath;
1329 803
            $dotPath = '';
1330 803
            $shortClassName = $classname;
1331 803
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1332 803
                $namespace = substr($shortClassName, 0, $lastNsPos);
1333 803
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1334 803
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1335
            }
1336 803
            $dotPath .= str_replace('_', '.', $shortClassName);
1337
        }
1338
1339
        // first check to see that the class specified hasn't already been included.
1340
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1341 805
        if (class_exists($classname)) {
1342 803
            return $classname;
1343
        }
1344
1345 7
        $dotClassname = basename($dotPath);
1346 7
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1347
1348
        // 1- temporarily replace escaped '.' with another illegal char (#)
1349 7
        $tmp = str_replace('\.', '##', $dotClassname);
1350
        // 2- swap out the remaining '.' with DIR_SEP
1351 7
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1352
        // 3- swap back the escaped '.'
1353 7
        $tmp = str_replace('##', '.', $tmp);
1354
1355 7
        $classFile = $tmp . ".php";
1356
1357 7
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1358
1359 7
        Phing::importFile($path, $classpath);
1360
1361 5
        return $classname;
1362
    }
1363
1364
    /**
1365
     * Import a PHP file
1366
     *
1367
     * This used to be named __import, however PHP has reserved all method names
1368
     * with a double underscore prefix for future use.
1369
     *
1370
     * @param string $path Path to the PHP file
1371
     * @param mixed $classpath String or object supporting __toString()
1372
     *
1373
     * @throws ConfigurationException
1374
     */
1375 10
    public static function importFile($path, $classpath = null)
1376
    {
1377 10
        if ($classpath) {
1378
            // Apparently casting to (string) no longer invokes __toString() automatically.
1379 2
            if (is_object($classpath)) {
1380
                $classpath = $classpath->__toString();
1381
            }
1382
1383
            // classpaths are currently additive, but we also don't want to just
1384
            // indiscriminantly prepand/append stuff to the include_path.  This means
1385
            // we need to parse current incldue_path, and prepend any
1386
            // specified classpath locations that are not already in the include_path.
1387
            //
1388
            // NOTE:  the reason why we do it this way instead of just changing include_path
1389
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1390
            // include/require class files from within method calls.  This means that not all
1391
            // necessary files will be included in this import() call, and hence we can't
1392
            // change the include_path back without breaking those apps.  While this method could
1393
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1394
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1395
            // really where speed matters more.
1396
1397 2
            $curr_parts = Phing::explodeIncludePath();
1398 2
            $add_parts = Phing::explodeIncludePath($classpath);
1399 2
            $new_parts = array_diff($add_parts, $curr_parts);
1400 2
            if ($new_parts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_parts 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...
1401 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1402
            }
1403
        }
1404
1405 10
        $ret = include_once $path;
1406
1407 8
        if ($ret === false) {
1408
            $msg = "Error importing $path";
1409
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1410
                $x = new Exception("for-path-trace-only");
1411
                $msg .= $x->getTraceAsString();
1412
            }
1413
            throw new ConfigurationException($msg);
1414
        }
1415 8
    }
1416
1417
    /**
1418
     * Looks on include path for specified file.
1419
     *
1420
     * @param string $path
1421
     *
1422
     * @return string File found (null if no file found).
1423
     */
1424 803
    public static function getResourcePath($path)
1425
    {
1426 803
        if (self::$importPaths === null) {
1427
            self::$importPaths = self::explodeIncludePath();
1428
        }
1429
1430 803
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1431
1432 803
        foreach (self::$importPaths as $prefix) {
1433 803
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1434 803
            if (file_exists($testPath)) {
1435 802
                return $testPath;
1436
            }
1437
        }
1438
1439
        // Check for the property phing.home
1440 803
        $homeDir = self::getProperty(self::PHING_HOME);
1441 803
        if ($homeDir) {
1442 803
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1443 803
            if (file_exists($testPath)) {
1444 803
                return $testPath;
1445
            }
1446
        }
1447
1448
        // Check for the phing home of phar archive
1449 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1450
            $testPath = self::$importPaths[0] . '/../' . $path;
1451
            if (file_exists($testPath)) {
1452
                return $testPath;
1453
            }
1454
        }
1455
1456
        // Do one additional check based on path of current file (Phing.php)
1457 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1458 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1459 6
        if (file_exists($testPath)) {
1460
            return $testPath;
1461
        }
1462
1463 6
        return null;
1464
    }
1465
1466
    /**
1467
     * Explode an include path into an array
1468
     *
1469
     * If no path provided, uses current include_path. Works around issues that
1470
     * occur when the path includes stream schemas.
1471
     *
1472
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1473
     *
1474
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1475
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1476
     * @param     string|null $path
1477
     * @return    array
1478
     */
1479 16
    public static function explodeIncludePath($path = null)
1480
    {
1481 16
        if (null === $path) {
1482 16
            $path = get_include_path();
1483
        }
1484
1485 16
        if (PATH_SEPARATOR == ':') {
1486
            // On *nix systems, include_paths which include paths with a stream
1487
            // schema cannot be safely explode'd, so we have to be a bit more
1488
            // intelligent in the approach.
1489 16
            $paths = preg_split('#:(?!//)#', $path);
1490
        } else {
1491
            $paths = explode(PATH_SEPARATOR, $path);
1492
        }
1493
1494 16
        return $paths;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paths could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1495
    }
1496
1497
    /**
1498
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1499
     *
1500
     * @return void
1501
     */
1502 1
    private static function setSystemConstants()
1503
    {
1504
1505
        /*
1506
         * PHP_OS returns on
1507
         *   WindowsNT4.0sp6  => WINNT
1508
         *   Windows2000      => WINNT
1509
         *   Windows ME       => WIN32
1510
         *   Windows 98SE     => WIN32
1511
         *   FreeBSD 4.5p7    => FreeBSD
1512
         *   Redhat Linux     => Linux
1513
         *   Mac OS X         => Darwin
1514
         */
1515 1
        self::setProperty('host.os', PHP_OS);
1516
1517
        // this is used by some tasks too
1518 1
        self::setProperty('os.name', PHP_OS);
1519
1520
        // it's still possible this won't be defined,
1521
        // e.g. if Phing is being included in another app w/o
1522
        // using the phing.php script.
1523 1
        if (!defined('PHP_CLASSPATH')) {
1524
            define('PHP_CLASSPATH', get_include_path());
1525
        }
1526
1527 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1528
1529
        // try to determine the host filesystem and set system property
1530
        // used by Fileself::getFileSystem to instantiate the correct
1531
        // abstraction layer
1532
1533 1
        if (PHP_OS_FAMILY === 'Windows') {
1534
            self::setProperty('host.fstype', 'WINDOWS');
1535
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1536
        } else {
1537 1
            self::setProperty('host.fstype', 'UNIX');
1538 1
            self::setProperty('user.home', getenv('HOME'));
1539
        }
1540 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1541 1
        self::setProperty('file.separator', FileUtils::getSeparator());
1542 1
        self::setProperty('line.separator', PHP_EOL);
1543 1
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1544 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1545 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1546 1
        self::setProperty('application.startdir', getcwd());
1547 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1548
1549
        // try to detect machine dependent information
1550 1
        $sysInfo = [];
1551 1
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1552 1
            $sysInfo = posix_uname();
1553
        } else {
1554
            $sysInfo['nodename'] = php_uname('n');
1555
            $sysInfo['machine'] = php_uname('m');
1556
            //this is a not so ideal substition, but maybe better than nothing
1557
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1558
            $sysInfo['release'] = php_uname('r');
1559
            $sysInfo['version'] = php_uname('v');
1560
        }
1561
1562 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1563 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1564 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1565 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1566 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1567 1
        unset($sysInfo);
1568 1
    }
1569
1570
    /**
1571
     * This gets a property that was set via command line or otherwise passed into Phing.
1572
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1573
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1574
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1575
     * the pear.log.name property.
1576
     *
1577
     * @param  string $name
1578
     * @return string value of found property (or null, if none found).
1579
     */
1580
    public static function getDefinedProperty($name)
1581
    {
1582
        return self::$definedProps->getProperty($name);
1583
    }
1584
1585
    /**
1586
     * This sets a property that was set via command line or otherwise passed into Phing.
1587
     *
1588
     * @param  string $name
1589
     * @param  mixed $value
1590
     * @return mixed value of found property (or null, if none found).
1591
     */
1592
    public static function setDefinedProperty($name, $value)
1593
    {
1594
        return self::$definedProps->setProperty($name, $value);
1595
    }
1596
1597
    /**
1598
     * Returns property value for a System property.
1599
     * System properties are "global" properties like application.startdir,
1600
     * and user.dir.  Many of these correspond to similar properties in Java
1601
     * or Ant.
1602
     *
1603
     * @param  string $propName
1604
     * @return string Value of found property (or null, if none found).
1605
     */
1606 814
    public static function getProperty($propName)
1607
    {
1608
1609
        // some properties are detemined on each access
1610
        // some are cached, see below
1611
1612
        // default is the cached value:
1613 814
        $val = self::$properties[$propName] ?? null;
1614
1615
        // special exceptions
1616
        switch ($propName) {
1617 814
            case 'user.dir':
1618 139
                $val = getcwd();
1619 139
                break;
1620
        }
1621
1622 814
        return $val;
1623
    }
1624
1625
    /**
1626
     * Retuns reference to all properties
1627
     */
1628 802
    public static function &getProperties()
1629
    {
1630 802
        return self::$properties;
1631
    }
1632
1633
    /**
1634
     * @param $propName
1635
     * @param $propValue
1636
     * @return string
1637
     */
1638 7
    public static function setProperty($propName, $propValue)
1639
    {
1640 7
        $propName = (string) $propName;
1641 7
        $oldValue = self::getProperty($propName);
1642 7
        self::$properties[$propName] = $propValue;
1643
1644 7
        return $oldValue;
1645
    }
1646
1647
    /**
1648
     * @return float
1649
     */
1650 68
    public static function currentTimeMillis()
1651
    {
1652 68
        [$usec, $sec] = explode(" ", microtime());
1653
1654 68
        return ((float) $usec + (float) $sec);
1655
    }
1656
1657
    /**
1658
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1659
     *
1660
     * @return void
1661
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1662
     */
1663 1
    private static function setIncludePaths()
1664
    {
1665 1
        if (defined('PHP_CLASSPATH')) {
1666 1
            $result = set_include_path(PHP_CLASSPATH);
1667 1
            if ($result === false) {
1668
                throw new ConfigurationException("Could not set PHP include_path.");
1669
            }
1670 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1671
        }
1672 1
    }
1673
1674
    /**
1675
     * Converts shorthand notation values as returned by ini_get()
1676
     *
1677
     * @see    http://www.php.net/ini_get
1678
     * @param  string|int $val
1679
     * @return int
1680
     */
1681 25
    public static function convertShorthand($val): int
1682
    {
1683 25
        $val = trim($val);
1684 25
        $last = strtolower($val[strlen($val) - 1]);
1685
1686 25
        if (!is_numeric($last)) {
1687 16
            $val = (int) substr($val, 0, -1);
1688
1689 16
            switch ($last) {
1690
                // The 'G' modifier is available since PHP 5.1.0
1691 16
                case 'g':
1692 1
                    $val *= 1024;
1693
                // no break
1694 16
                case 'm':
1695 2
                    $val *= 1024;
1696
                // no break
1697 15
                case 'k':
1698 16
                    $val *= 1024;
1699
            }
1700
        }
1701
1702 25
        return $val;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $val could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
1703
    }
1704
1705
    /**
1706
     * Sets PHP INI values that Phing needs.
1707
     */
1708 1
    private static function setIni(): void
1709
    {
1710 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1711
1712
        // We won't bother storing original max_execution_time, since 1) the value in
1713
        // php.ini may be wrong (and there's no way to get the current value) and
1714
        // 2) it would mean something very strange to set it to a value less than time script
1715
        // has already been running, which would be the likely change.
1716
1717 1
        set_time_limit(0);
1718
1719 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1720 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1721
1722 1
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1723 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1724
            // We do *not* need to save the original value here, since we don't plan to restore
1725
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1726
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1727
        }
1728 1
    }
1729
1730
    /**
1731
     * Restores [most] PHP INI values to their pre-Phing state.
1732
     *
1733
     * Currently the following settings are not restored:
1734
     *  - max_execution_time (because getting current time limit is not possible)
1735
     *  - memory_limit (which may have been increased by Phing)
1736
     */
1737 1
    private static function restoreIni(): void
1738
    {
1739 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1740 1
            switch ($settingName) {
1741 1
                case 'error_reporting':
1742 1
                    error_reporting($settingValue);
1743 1
                    break;
1744
                default:
1745 1
                    ini_set($settingName, $settingValue);
1746
            }
1747
        }
1748 1
    }
1749
1750
    /**
1751
     * Returns reference to Timer object.
1752
     *
1753
     * @return Timer
1754
     */
1755 2
    public static function getTimer(): Timer
1756
    {
1757 2
        if (self::$timer === null) {
1758
            self::$timer = new Timer();
1759
        }
1760
1761 2
        return self::$timer;
1762
    }
1763
1764
    /**
1765
     * Start up Phing.
1766
     * Sets up the Phing environment but does not initiate the build process.
1767
     *
1768
     * @return void
1769
     * @throws Exception - If the Phing environment cannot be initialized.
1770
     */
1771 1
    public static function startup(): void
1772
    {
1773
1774
        // setup STDOUT and STDERR defaults
1775 1
        self::initializeOutputStreams();
1776
1777
        // some init stuff
1778 1
        self::getTimer()->start();
1779
1780 1
        self::setSystemConstants();
1781 1
        self::setIncludePaths();
1782 1
        self::setIni();
1783 1
    }
1784
1785
    /**
1786
     * Performs any shutdown routines, such as stopping timers.
1787
     *
1788
     * @throws IOException
1789
     */
1790 1
    public static function shutdown(): void
1791
    {
1792 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
1793 1
        self::$msgOutputLevel = Project::MSG_INFO;
1794 1
        self::restoreIni();
1795 1
        self::getTimer()->stop();
1796 1
    }
1797
}
1798