Passed
Push — main ( 6dddcc...8fc4c5 )
by Michiel
06:25
created

Phing::handlePhpError()   B

Complexity

Conditions 11
Paths 11

Size

Total Lines 36
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 26
c 1
b 0
f 0
dl 0
loc 36
ccs 0
cts 25
cp 0
rs 7.3166
cc 11
nc 11
nop 4
crap 132

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
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\Exception\ConfigurationException;
26
use Phing\Exception\ExitStatusException;
27
use Phing\Input\ConsoleInputHandler;
28
use Phing\Io\File;
29
use Phing\Io\FileOutputStream;
30
use Phing\Io\FileParserFactory;
31
use Phing\Io\FileReader;
32
use Phing\Io\FileSystem;
33
use Phing\Io\FileUtils;
34
use Phing\Io\IOException;
35
use Phing\Io\OutputStream;
36
use Phing\Io\PrintStream;
37
use Phing\Listener\BuildLogger;
38
use Phing\Listener\DefaultLogger;
39
use Phing\Listener\SilentLogger;
40
use Phing\Listener\StreamRequiredBuildLogger;
41
use Phing\Parser\ProjectConfigurator;
42
use Phing\Util\DefaultClock;
43
use Phing\Util\Diagnostics;
44
use Phing\Util\Properties;
45
use Phing\Util\SizeHelper;
46
use Phing\Util\StringHelper;
47
use SebastianBergmann\Version;
48
use Symfony\Component\Console\Output\ConsoleOutput;
49
use Throwable;
50
51
use function array_filter;
52
use function array_map;
53
use function array_reduce;
54
use function implode;
55
use function method_exists;
56
use function sprintf;
57
use function strlen;
58
use function strval;
59
use function trim;
60
61
use const PHP_EOL;
62
63
/**
64
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
65
 * parsing & handling commandline arguments to assembling the project to shutting down
66
 * and cleaning up in the end.
67
 *
68
 * If you are invoking Phing from an external application, this is still
69
 * the class to use.  Your application can invoke the start() method, passing
70
 * any commandline arguments or additional properties.
71
 *
72
 * @author Andreas Aderhold <[email protected]>
73
 * @author Hans Lellelid <[email protected]>
74
 */
75
class Phing
76
{
77
    /**
78
     * Alias for phar file.
79
     */
80
    public const PHAR_ALIAS = 'phing.phar';
81
82
    /**
83
     * The default build file name.
84
     */
85
    public const DEFAULT_BUILD_FILENAME = 'build.xml';
86
    public const DEFAULT_BUILD_CONTENT = <<<'XML'
87
        <?xml version="1.0" encoding="UTF-8" ?>
88
89
        <project name="" description="" default="">
90
91
            <target name="" description="">
92
93
            </target>
94
95
        </project>
96
        XML;
97
    public const PHING_HOME = 'phing.home';
98
    public const PHP_VERSION = 'php.version';
99
    public const PHP_INTERPRETER = 'php.interpreter';
100
101
    /**
102
     * Our current message output status. Follows Project::MSG_XXX.
103
     */
104
    private static $msgOutputLevel = Project::MSG_INFO;
105
    /**
106
     * Set of properties that are passed in from commandline or invoking code.
107
     *
108
     * @var Properties
109
     */
110
    private static $definedProps;
111
    /**
112
     * Used by utility function getResourcePath().
113
     */
114
    private static $importPaths;
115
    /**
116
     * Cache of the Phing version information when it has been loaded.
117
     */
118
    private static $phingVersion;
119
    /**
120
     * Cache of the short Phing version information when it has been loaded.
121
     */
122
    private static $phingShortVersion;
123
    /**
124
     * System-wide static properties (moved from System).
125
     */
126
    private static $properties = [];
127
    /**
128
     * Static system timer.
129
     */
130
    private static $timer;
131
    /**
132
     * The current Project.
133
     */
134
    private static $currentProject;
135
    /**
136
     * Whether to capture PHP errors to buffer.
137
     */
138
    private static $phpErrorCapture = false;
139
    /**
140
     * Array of captured PHP errors.
141
     */
142
    private static $capturedPhpErrors = [];
143
    /**
144
     * @var OUtputStream stream for standard output
145
     */
146
    private static $out;
147
    /**
148
     * @var OutputStream stream for error output
149
     */
150
    private static $err;
151
    /**
152
     * @var bool whether we are using a logfile
153
     */
154
    private static $isLogFileUsed = false;
155
    /**
156
     * Array to hold original ini settings that Phing changes (and needs
157
     * to restore in restoreIni() method).
158
     *
159
     * @var array Struct of array(setting-name => setting-value)
160
     *
161
     * @see restoreIni()
162
     */
163
    private static $origIniSettings = [];
164
    /**
165
     * PhingFile that we are using for configuration.
166
     */
167
    private $buildFile;
168
    /**
169
     * The build targets.
170
     */
171
    private $targets = [];
172
    /**
173
     * Names of classes to add as listeners to project.
174
     */
175
    private $listeners = [];
176
    /**
177
     * keep going mode.
178
     *
179
     * @var bool
180
     */
181
    private $keepGoingMode = false;
182
    private $loggerClassname;
183
    /**
184
     * The class to handle input (can be only one).
185
     */
186
    private $inputHandlerClassname;
187
    /**
188
     * Whether or not log output should be reduced to the minimum.
189
     *
190
     * @var bool
191
     */
192
    private $silent = false;
193
    /**
194
     * Indicates whether phing should run in strict mode.
195
     */
196
    private $strictMode = false;
197
    /**
198
     * Indicates if this phing should be run.
199
     */
200
    private $readyToRun = false;
201
    /**
202
     * Indicates we should only parse and display the project help information.
203
     */
204
    private $projectHelp = false;
205
    /**
206
     * Whether to values in a property file should override existing values.
207
     */
208
    private $propertyFileOverride = false;
209
    /**
210
     * Whether or not output to the log is to be unadorned.
211
     */
212
    private $emacsMode = false;
213
214
    /**
215
     * @var string
216
     */
217
    private $searchForThis;
218
    private $propertyFiles = [];
219
220
    /**
221
     * Gets the stream to use for standard (non-error) output.
222
     *
223
     * @return OutputStream
224
     */
225
    public static function getOutputStream()
226
    {
227
        return self::$out;
228
    }
229
230
    /**
231
     * Gets the stream to use for error output.
232
     *
233
     * @return OutputStream
234
     */
235
    public static function getErrorStream()
236
    {
237
        return self::$err;
238
    }
239
240
    /**
241
     * Command line entry point. This method kicks off the building
242
     * of a project object and executes a build using either a given
243
     * target or the default target.
244
     *
245
     * @param array $args command line args
246
     *
247
     * @throws Exception
248
     */
249
    public static function fire($args): void
250
    {
251
        self::start($args);
252
    }
253
254
    /**
255
     * Entry point allowing for more options from other front ends.
256
     *
257
     * This method encapsulates the complete build lifecycle.
258
     *
259
     * @param array      $args                     the commandline args passed to phing shell script
260
     * @param array|null $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
261
     *                                             These additional properties will be available using the getDefinedProperty() method and will
262
     *                                             be added to the project's "user" properties
263
     *
264
     * @throws Exception - if there is an error during build
265
     *
266
     * @see    runBuild()
267
     * @see    execute()
268
     */
269
    public static function start($args, ?array $additionalUserProperties = null)
270
    {
271
        try {
272
            $m = new self();
273
            $m->execute($args);
274
        } catch (Exception $exc) {
275
            self::handleLogfile();
276
            self::printMessage($exc);
277
            self::statusExit(1);
278
279
            return;
280
        }
281
282
        if (null !== $additionalUserProperties) {
283
            foreach ($additionalUserProperties as $key => $value) {
284
                $m::setDefinedProperty($key, $value);
285
            }
286
        }
287
288
        // expect the worst
289
        $exitCode = 1;
290
291
        try {
292
            try {
293
                $m->runBuild();
294
                $exitCode = 0;
295
            } catch (ExitStatusException $ese) {
296
                $exitCode = $ese->getCode();
297
                if (0 !== $exitCode) {
298
                    self::handleLogfile();
299
300
                    throw $ese;
301
                }
302
            }
303
        } catch (BuildException $exc) {
304
            // avoid printing output twice: self::printMessage($exc);
305
        } catch (Throwable $exc) {
306
            self::printMessage($exc);
307
        } finally {
308
            self::handleLogfile();
309
        }
310
        self::statusExit($exitCode);
311
    }
312
313
    /**
314
     * Setup/initialize Phing environment from commandline args.
315
     *
316
     * @param array $args commandline args passed to phing shell
317
     *
318
     * @throws ConfigurationException
319
     */
320
    public function execute($args): void
321
    {
322
        self::$definedProps = new Properties();
323
        $this->searchForThis = null;
324
325
        // 1) First handle any options which should always
326
        // Note: The order in which these are executed is important (if multiple of these options are specified)
327
328
        if (in_array('-help', $args) || in_array('-h', $args)) {
329
            static::printUsage();
330
331
            return;
332
        }
333
334
        if (in_array('-version', $args) || in_array('-v', $args)) {
335
            static::printVersion();
336
337
            return;
338
        }
339
340
        if (in_array('-init', $args) || in_array('-i', $args)) {
341
            $key = array_search('-init', $args) ?: array_search('-i', $args);
342
            $path = $args[$key + 1] ?? null;
343
344
            self::init($path);
345
346
            return;
347
        }
348
349
        if (in_array('-diagnostics', $args)) {
350
            Diagnostics::doReport(new PrintStream(self::$out));
351
352
            return;
353
        }
354
355
        // 2) Next pull out stand-alone args.
356
        // Note: The order in which these are executed is important (if multiple of these options are specified)
357
358
        if (
359
            false !== ($key = array_search('-quiet', $args, true))
360
            || false !== ($key = array_search(
361
                '-q',
362
                $args,
363
                true
364
            ))
365
        ) {
366
            self::$msgOutputLevel = Project::MSG_WARN;
367
            unset($args[$key]);
368
        }
369
370
        if (
371
            false !== ($key = array_search('-emacs', $args, true))
372
            || false !== ($key = array_search('-e', $args, true))
373
        ) {
374
            $this->emacsMode = true;
375
            unset($args[$key]);
376
        }
377
378
        if (false !== ($key = array_search('-verbose', $args, true))) {
379
            self::$msgOutputLevel = Project::MSG_VERBOSE;
380
            unset($args[$key]);
381
        }
382
383
        if (false !== ($key = array_search('-debug', $args, true))) {
384
            self::$msgOutputLevel = Project::MSG_DEBUG;
385
            unset($args[$key]);
386
        }
387
388
        if (
389
            false !== ($key = array_search('-silent', $args, true))
390
            || false !== ($key = array_search('-S', $args, true))
391
        ) {
392
            $this->silent = true;
393
            unset($args[$key]);
394
        }
395
396
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
397
            $this->propertyFileOverride = true;
398
            unset($args[$key]);
399
        }
400
401
        // 3) Finally, cycle through to parse remaining args
402
        //
403
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
404
        $max = $keys ? max($keys) : -1;
405
        for ($i = 0; $i <= $max; ++$i) {
406
            if (!array_key_exists($i, $args)) {
407
                // skip this argument, since it must have been removed above.
408
                continue;
409
            }
410
411
            $arg = $args[$i];
412
413
            if ('-logfile' == $arg) {
414
                try {
415
                    // see: http://phing.info/trac/ticket/65
416
                    if (!isset($args[$i + 1])) {
417
                        $msg = "You must specify a log file when using the -logfile argument\n";
418
419
                        throw new ConfigurationException($msg);
420
                    }
421
422
                    $logFile = new File($args[++$i]);
423
                    $out = new FileOutputStream($logFile); // overwrite
424
                    self::setOutputStream($out);
425
                    self::setErrorStream($out);
426
                    self::$isLogFileUsed = true;
427
                } catch (IOException $ioe) {
428
                    $msg = 'Cannot write on the specified log file. Make sure the path exists and you have write permissions.';
429
430
                    throw new ConfigurationException($msg, $ioe);
431
                }
432
            } elseif ('-buildfile' == $arg || '-file' == $arg || '-f' == $arg) {
433
                if (!isset($args[$i + 1])) {
434
                    $msg = 'You must specify a buildfile when using the -buildfile argument.';
435
436
                    throw new ConfigurationException($msg);
437
                }
438
439
                $this->buildFile = new File($args[++$i]);
440
            } elseif ('-listener' == $arg) {
441
                if (!isset($args[$i + 1])) {
442
                    $msg = 'You must specify a listener class when using the -listener argument';
443
444
                    throw new ConfigurationException($msg);
445
                }
446
447
                $this->listeners[] = $args[++$i];
448
            } elseif (StringHelper::startsWith('-D', $arg)) {
449
                // Evaluating the property information //
450
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
451
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
452
                    $name = $args[++$i];
453
                } else {
454
                    $name = substr($arg, 2);
455
                }
456
457
                $value = null;
458
                $posEq = strpos($name, '=');
459
                if (false !== $posEq) {
460
                    $value = substr($name, $posEq + 1);
461
                    $name = substr($name, 0, $posEq);
462
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith('-D', $args[$i + 1])) {
463
                    $value = $args[++$i];
464
                }
465
                self::$definedProps->setProperty($name, $value);
466
            } elseif ('-logger' == $arg) {
467
                if (!isset($args[$i + 1])) {
468
                    $msg = 'You must specify a classname when using the -logger argument';
469
470
                    throw new ConfigurationException($msg);
471
                }
472
473
                $this->loggerClassname = $args[++$i];
474
            } elseif ('-no-strict' == $arg) {
475
                $this->strictMode = false;
476
            } elseif ('-strict' == $arg) {
477
                $this->strictMode = true;
478
            } elseif ('-inputhandler' == $arg) {
479
                if (null !== $this->inputHandlerClassname) {
480
                    throw new ConfigurationException('Only one input handler class may be specified.');
481
                }
482
                if (!isset($args[$i + 1])) {
483
                    $msg = 'You must specify a classname when using the -inputhandler argument';
484
485
                    throw new ConfigurationException($msg);
486
                }
487
488
                $this->inputHandlerClassname = $args[++$i];
489
            } elseif ('-propertyfile' === $arg) {
490
                $i = $this->handleArgPropertyFile($args, $i);
491
            } elseif ('-keep-going' === $arg || '-k' === $arg) {
492
                $this->keepGoingMode = true;
493
            } elseif ('-longtargets' == $arg) {
494
                self::$definedProps->setProperty('phing.showlongtargets', 1);
495
            } elseif ('-projecthelp' == $arg || '-targets' == $arg || '-list' == $arg || '-l' == $arg || '-p' == $arg) {
496
                // set the flag to display the targets and quit
497
                $this->projectHelp = true;
498
            } elseif ('-find' == $arg) {
499
                // eat up next arg if present, default to build.xml
500
                if ($i < count($args) - 1) {
501
                    $this->searchForThis = $args[++$i];
502
                } else {
503
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
504
                }
505
            } elseif ('-' == substr($arg, 0, 1)) {
506
                // we don't have any more args
507
                self::printUsage();
508
                self::$err->write(PHP_EOL);
509
510
                throw new ConfigurationException('Unknown argument: ' . $arg);
511
            } else {
512
                // if it's no other arg, it may be the target
513
                $this->targets[] = $arg;
514
            }
515
        }
516
517
        // if buildFile was not specified on the command line,
518
        if (null === $this->buildFile) {
519
            // but -find then search for it
520
            if (null !== $this->searchForThis) {
521
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), $this->searchForThis);
0 ignored issues
show
Bug introduced by
$this->searchForThis of type void is incompatible with the type string expected by parameter $suffix of Phing\Phing::findBuildFile(). ( Ignorable by Annotation )

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

521
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), /** @scrutinizer ignore-type */ $this->searchForThis);
Loading history...
522
            } else {
523
                $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
524
            }
525
        }
526
527
        try {
528
            // make sure buildfile (or buildfile.dist) exists
529
            if (!$this->buildFile->exists()) {
530
                $distFile = new File($this->buildFile->getAbsolutePath() . '.dist');
531
                if (!$distFile->exists()) {
532
                    throw new ConfigurationException(
533
                        'Buildfile: ' . $this->buildFile->__toString() . ' does not exist!'
534
                    );
535
                }
536
                $this->buildFile = $distFile;
537
            }
538
539
            // make sure it's not a directory
540
            if ($this->buildFile->isDirectory()) {
541
                throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is a dir!');
542
            }
543
        } catch (IOException $e) {
544
            // something else happened, buildfile probably not readable
545
            throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is not readable!');
546
        }
547
548
        $this->loadPropertyFiles();
549
550
        $this->readyToRun = true;
551
    }
552
553
    /**
554
     * Prints the usage of how to use this class.
555
     */
556 1
    public static function printUsage()
557
    {
558 1
        $msg = <<<'TEXT'
559
            phing [options] [target [target2 [target3] ...]]
560
            Options:
561
              -h -help                 print this message
562
              -l -list                 list available targets in this project
563
              -i -init [file]          generates an initial buildfile
564
              -v -version              print the version information and exit
565
              -q -quiet                be extra quiet
566
              -S -silent               print nothing but task outputs and build failures
567
                 -verbose              be extra verbose
568
                 -debug                print debugging information
569
              -e -emacs                produce logging information without adornments
570
                 -diagnostics          print diagnostics information
571
                 -strict               runs build in strict mode, considering a warning as error
572
                 -no-strict            runs build normally (overrides buildfile attribute)
573
                 -longtargets          show target descriptions during build
574
                 -logfile <file>       use given file for log
575
                 -logger <classname>   the class which is to perform logging
576
                 -listener <classname> add an instance of class as a project listener
577
              -f -buildfile <file>     use given buildfile
578
                 -D<property>=<value>  use value for given property
579
              -k -keep-going           execute all targets that do not depend on failed target(s)
580
                 -propertyfile <file>  load all properties from file
581
                 -propertyfileoverride values in property file override existing values
582
                 -find <file>          search for buildfile towards the root of the filesystem and use it
583
                 -inputhandler <file>  the class to use to handle user input
584
585
            Report bugs to https://github.com/phingofficial/phing/issues
586
587 1
            TEXT;
588
589 1
        self::$err->write($msg);
590
    }
591
592
    /**
593
     * Prints the current Phing version.
594
     */
595
    public static function printVersion()
596
    {
597
        self::$out->write(self::getPhingVersion() . PHP_EOL);
598
    }
599
600
    /**
601
     * Gets the current Phing version based on VERSION.TXT file. Once the information
602
     * has been loaded once, it's cached and returned from the cache on future
603
     * calls.
604
     *
605
     * @throws ConfigurationException
606
     */
607 6
    public static function getPhingVersion(): string
608
    {
609 6
        if (self::$phingVersion === null) {
610
            $versionPath = self::getResourcePath('phing/etc/VERSION.TXT');
611
            if (null === $versionPath) {
612
                $versionPath = self::getResourcePath('etc/VERSION.TXT');
613
            }
614
            if (null === $versionPath) {
615
                throw new ConfigurationException('No VERSION.TXT file found; try setting phing.home environment variable.');
616
            }
617
618
            try { // try to read file
619
                $file = new File($versionPath);
620
                $reader = new FileReader($file);
621
                $phingVersion = trim($reader->read());
622
            } catch (IOException $iox) {
623
                throw new ConfigurationException("Can't read version information file");
624
            }
625
626
            $basePath = dirname(__DIR__, 2);
627
628
            $version = new Version($phingVersion, $basePath);
629
            self::$phingShortVersion = (method_exists($version, 'asString') ? $version->asString() : $version->getVersion());
630
631
            self::$phingVersion = 'Phing ' . self::$phingShortVersion;
632
        }
633 6
        return self::$phingVersion;
634
    }
635
636
    /**
637
     * Returns the short Phing version information, if available. Once the information
638
     * has been loaded once, it's cached and returned from the cache on future
639
     * calls.
640
     *
641
     * @return the short Phing version information as a string
0 ignored issues
show
Bug introduced by
The type Phing\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
642
     *
643
     * @throws ConfigurationException
644
     */
645
    public static function getPhingShortVersion(): string
646
    {
647
        if (self::$phingShortVersion === null) {
648
            self::getPhingVersion();
649
        }
650
        return self::$phingShortVersion;
651
    }
652
653
    /**
654
     * Looks on include path for specified file.
655
     *
656
     * @param string $path
657
     *
658
     * @return null|string file found (null if no file found)
659
     */
660 905
    public static function getResourcePath($path): ?string
661
    {
662 905
        if (null === self::$importPaths) {
663
            self::$importPaths = self::explodeIncludePath();
664
        }
665
666 905
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
667
668 905
        foreach (self::$importPaths as $prefix) {
669 905
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
670 905
            if (file_exists($testPath)) {
671
                return $testPath;
672
            }
673
        }
674
675
        // Check for the property phing.home
676 905
        $homeDir = self::getProperty(self::PHING_HOME);
677 905
        if ($homeDir) {
678 905
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
679 905
            if (file_exists($testPath)) {
680 905
                return $testPath;
681
            }
682
        }
683
684
        // Check for the phing home of phar archive
685
        if (0 === strpos(self::$importPaths[0], 'phar://')) {
686
            $testPath = self::$importPaths[0] . '/../' . $path;
687
            if (file_exists($testPath)) {
688
                return $testPath;
689
            }
690
        }
691
692
        // Do one additional check based on path of current file (Phing.php)
693
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR);
694
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
695
        if (file_exists($testPath)) {
696
            return $testPath;
697
        }
698
699
        return null;
700
    }
701
702
    /**
703
     * Explode an include path into an array.
704
     *
705
     * If no path provided, uses current include_path. Works around issues that
706
     * occur when the path includes stream schemas.
707
     *
708
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
709
     *
710
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
711
     * @license   http://framework.zend.com/license/new-bsd New BSD License
712
     *
713
     * @param null|string $path
714
     *
715
     * @return array
716
     */
717 17
    public static function explodeIncludePath($path = null)
718
    {
719 17
        if (null === $path) {
720 17
            $path = get_include_path();
721
        }
722
723 17
        if (PATH_SEPARATOR === ':') {
724
            // On *nix systems, include_paths which include paths with a stream
725
            // schema cannot be safely explode'd, so we have to be a bit more
726
            // intelligent in the approach.
727 17
            $paths = preg_split('#:(?!//)#', $path);
728
        } else {
729
            $paths = explode(PATH_SEPARATOR, $path);
730
        }
731
732 17
        return $paths;
733
    }
734
735
    /**
736
     * Returns property value for a System property.
737
     * System properties are "global" properties like application.startdir,
738
     * and user.dir.  Many of these correspond to similar properties in Java
739
     * or Ant.
740
     *
741
     * @param string $propName
742
     *
743
     * @return string value of found property (or null, if none found)
744
     */
745 923
    public static function getProperty($propName)
746
    {
747
        // some properties are detemined on each access
748
        // some are cached, see below
749
750
        // default is the cached value:
751 923
        $val = self::$properties[$propName] ?? null;
752
753
        // special exceptions
754
        switch ($propName) {
755 923
            case 'user.dir':
756 152
                $val = getcwd();
757
758 152
                break;
759
        }
760
761 923
        return $val;
762
    }
763
764
    /**
765
     * Creates generic buildfile.
766
     *
767
     * @param string $path
768
     */
769
    public static function init($path)
770
    {
771
        if ($buildfilePath = self::initPath($path)) {
772
            self::initWrite($buildfilePath);
773
        }
774
    }
775
776
    /**
777
     * Sets the stream to use for standard (non-error) output.
778
     *
779
     * @param OutputStream $stream the stream to use for standard output
780
     */
781 1
    public static function setOutputStream(OutputStream $stream)
782
    {
783 1
        self::$out = $stream;
784
    }
785
786
    /**
787
     * Sets the stream to use for error output.
788
     *
789
     * @param OutputStream $stream the stream to use for error output
790
     */
791 1
    public static function setErrorStream(OutputStream $stream): void
792
    {
793 1
        self::$err = $stream;
794
    }
795
796
    /**
797
     * Making output level a static property so that this property
798
     * can be accessed by other parts of the system, enabling
799
     * us to display more information -- e.g. backtraces -- for "debug" level.
800
     *
801
     * @return int
802
     */
803 9
    public static function getMsgOutputLevel()
804
    {
805 9
        return self::$msgOutputLevel;
806
    }
807
808
    /**
809
     * Prints the message of the Exception if it's not null.
810
     */
811
    public static function printMessage(Throwable $t): void
812
    {
813
        if (null === self::$err) { // Make sure our error output is initialized
814
            self::initializeOutputStreams();
815
        }
816
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
817
            self::$err->write((string) $t . PHP_EOL);
818
        } else {
819
            self::$err->write($t->getMessage() . PHP_EOL);
820
        }
821
    }
822
823
    /**
824
     * Performs any shutdown routines, such as stopping timers.
825
     *
826
     * @throws IOException
827
     */
828 1
    public static function shutdown(): void
829
    {
830 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
831 1
        self::$msgOutputLevel = Project::MSG_INFO;
832 1
        self::restoreIni();
833 1
        self::getTimer()->stop();
834
    }
835
836
    /**
837
     * Returns reference to DefaultClock object.
838
     */
839 2
    public static function getTimer(): DefaultClock
840
    {
841 2
        if (null === self::$timer) {
842
            self::$timer = new DefaultClock();
843
        }
844
845 2
        return self::$timer;
846
    }
847
848
    /**
849
     * This sets a property that was set via command line or otherwise passed into Phing.
850
     *
851
     * @param string $name
852
     * @param mixed  $value
853
     *
854
     * @return mixed value of found property (or null, if none found)
855
     */
856
    public static function setDefinedProperty($name, $value)
857
    {
858
        return self::$definedProps->setProperty($name, $value);
859
    }
860
861
    /**
862
     * Executes the build.
863
     *
864
     * @throws IOException
865
     * @throws Throwable
866
     */
867
    public function runBuild(): void
868
    {
869
        if (!$this->readyToRun) {
870
            return;
871
        }
872
873
        $project = new Project();
874
875
        self::setCurrentProject($project);
876
        set_error_handler(['Phing\Phing', 'handlePhpError']);
877
878
        $error = null;
879
880
        try {
881
            $this->addBuildListeners($project);
882
            $this->addInputHandler($project);
883
884
            // set this right away, so that it can be used in logging.
885
            $project->setUserProperty('phing.file', $this->buildFile->getAbsolutePath());
886
            $project->setUserProperty('phing.dir', dirname($this->buildFile->getAbsolutePath()));
887
            $project->setUserProperty('phing.version', static::getPhingVersion());
888
            $project->fireBuildStarted();
889
            $project->init();
890
            $project->setKeepGoingMode($this->keepGoingMode);
891
892
            $e = self::$definedProps->keys();
893
            while (count($e)) {
894
                $arg = (string) array_shift($e);
895
                $value = (string) self::$definedProps->getProperty($arg);
896
                $project->setUserProperty($arg, $value);
897
            }
898
            unset($e);
899
900
            // first use the Configurator to create the project object
901
            // from the given build file.
902
903
            ProjectConfigurator::configureProject($project, $this->buildFile);
904
905
            // Set the project mode
906
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
907
908
            // make sure that minimum required phing version is satisfied
909
            $this->comparePhingVersion($project->getPhingVersion());
910
911
            if ($this->projectHelp) {
912
                $this->printDescription($project);
913
                $this->printTargets($project);
914
915
                return;
916
            }
917
918
            // make sure that we have a target to execute
919
            if (0 === count($this->targets)) {
920
                $this->targets[] = $project->getDefaultTarget();
921
            }
922
923
            $project->executeTargets($this->targets);
924
        } catch (Throwable $t) {
925
            $error = $t;
926
927
            throw $t;
928
        } finally {
929
            if (!$this->projectHelp) {
930
                try {
931
                    $project->fireBuildFinished($error);
932
                } catch (Exception $e) {
933
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
934
                    self::$err->write($e->getTraceAsString());
935
                    if (null !== $error) {
936
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
937
                        self::$err->write($error->getTraceAsString());
938
                    }
939
940
                    throw new BuildException($error);
941
                }
942
            } elseif (null !== $error) {
943
                $project->log($error->getMessage(), Project::MSG_ERR);
944
            }
945
946
            restore_error_handler();
947
            self::unsetCurrentProject();
948
        }
949
    }
950
951
    /**
952
     * Import a class, supporting the following conventions:
953
     * - PEAR style (@see http://pear.php.net/manual/en/standards.naming.php)
954
     * - PSR-0 (@see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md).
955
     *
956
     * @param string $classname Name of class
957
     * @param mixed  $classpath String or object supporting __toString()
958
     *
959
     * @throws BuildException - if cannot find the specified file
960
     *
961
     * @return string the unqualified classname (which can be instantiated)
962
     */
963 908
    public static function import($classname, $classpath = null)
964
    {
965
        // first check to see that the class specified hasn't already been included.
966 908
        if (class_exists($classname)) {
967 906
            return $classname;
968
        }
969
970 7
        $filename = strtr($classname, ['_' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]) . '.php';
971
972 7
        Phing::importFile($filename, $classpath);
973
974 5
        return $classname;
975
    }
976
977
    /**
978
     * Import a PHP file.
979
     *
980
     * This used to be named __import, however PHP has reserved all method names
981
     * with a double underscore prefix for future use.
982
     *
983
     * @param string $path      Path to the PHP file
984
     * @param mixed  $classpath String or object supporting __toString()
985
     *
986
     * @throws ConfigurationException
987
     */
988 10
    public static function importFile($path, $classpath = null)
989
    {
990 10
        if ($classpath) {
991
            // Apparently casting to (string) no longer invokes __toString() automatically.
992 2
            if (is_object($classpath)) {
993
                $classpath = $classpath->__toString();
994
            }
995
996
            // classpaths are currently additive, but we also don't want to just
997
            // indiscriminantly prepand/append stuff to the include_path.  This means
998
            // we need to parse current incldue_path, and prepend any
999
            // specified classpath locations that are not already in the include_path.
1000
            //
1001
            // NOTE:  the reason why we do it this way instead of just changing include_path
1002
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1003
            // include/require class files from within method calls.  This means that not all
1004
            // necessary files will be included in this import() call, and hence we can't
1005
            // change the include_path back without breaking those apps.  While this method could
1006
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1007
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1008
            // really where speed matters more.
1009
1010 2
            $curr_parts = Phing::explodeIncludePath();
1011 2
            $add_parts = Phing::explodeIncludePath($classpath);
1012 2
            $new_parts = array_diff($add_parts, $curr_parts);
1013 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...
1014 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1015
            }
1016
        }
1017
1018 10
        $ret = include_once $path;
1019
1020 8
        if (false === $ret) {
1021
            $msg = "Error importing {$path}";
1022
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1023
                $x = new Exception('for-path-trace-only');
1024
                $msg .= $x->getTraceAsString();
1025
            }
1026
1027
            throw new ConfigurationException($msg);
1028
        }
1029
    }
1030
1031
    /**
1032
     * Print the project description, if any.
1033
     *
1034
     * @throws IOException
1035
     */
1036
    public function printDescription(Project $project): void
1037
    {
1038
        if (null !== $project->getDescription()) {
1039
            $project->log($project->getDescription());
1040
        }
1041
    }
1042
1043
    /**
1044
     * Print out a list of all targets in the current buildfile.
1045
     */
1046 1
    public function printTargets(Project $project)
1047
    {
1048 1
        $visibleTargets = array_filter($project->getTargets(), function (Target $target) {
1049 1
            return !$target->isHidden() && !empty($target->getName());
1050 1
        });
1051 1
        $padding = array_reduce($visibleTargets, function (int $carry, Target $target) {
1052
            return max(strlen($target->getName()), $carry);
1053 1
        }, 0);
1054 1
        $categories = [
1055 1
            'Default target:' => array_filter($visibleTargets, function (Target $target) use ($project) {
1056
                return trim(strval($target)) === $project->getDefaultTarget();
1057 1
            }),
1058 1
            'Main targets:' => array_filter($visibleTargets, function (Target $target) {
1059
                return !empty($target->getDescription());
1060 1
            }),
1061 1
            'Subtargets:' => array_filter($visibleTargets, function (Target $target) {
1062
                return empty($target->getDescription());
1063 1
            }),
1064 1
        ];
1065 1
        foreach ($categories as $title => $targets) {
1066 1
            $targetList = $this->generateTargetList($title, $targets, $padding);
1067 1
            $project->log($targetList, Project::MSG_WARN);
1068
        }
1069
    }
1070
1071
    /**
1072
     * Unsets the current Project.
1073
     */
1074 1
    public static function unsetCurrentProject(): void
1075
    {
1076 1
        self::$currentProject = null;
1077
    }
1078
1079
    /**
1080
     * Error handler for PHP errors encountered during the build.
1081
     * This uses the logging for the currently configured project.
1082
     *
1083
     * @param $level
1084
     * @param string $message
1085
     * @param $file
1086
     * @param $line
1087
     */
1088
    public static function handlePhpError($level, $message, $file, $line)
1089
    {
1090
        // don't want to print suppressed errors
1091
        if (!(error_reporting() & $level)) {
1092
            return true;
1093
        }
1094
        if (self::$phpErrorCapture) {
1095
            self::$capturedPhpErrors[] = [
1096
                'message' => $message,
1097
                'level' => $level,
1098
                'line' => $line,
1099
                'file' => $file,
1100
            ];
1101
        } else {
1102
            $message = '[PHP Error] ' . $message;
1103
            $message .= ' [line ' . $line . ' of ' . $file . ']';
1104
1105
            switch ($level) {
1106
                case E_USER_DEPRECATED:
1107
                case E_DEPRECATED:
1108
                case E_NOTICE:
1109
                case E_USER_NOTICE:
1110
                    self::log($message, Project::MSG_VERBOSE);
1111
1112
                    break;
1113
1114
                case E_WARNING:
1115
                case E_USER_WARNING:
1116
                    self::log($message, Project::MSG_WARN);
1117
1118
                    break;
1119
1120
                case E_ERROR:
1121
                case E_USER_ERROR:
1122
                default:
1123
                    self::log($message, Project::MSG_ERR);
1124
            } // switch
1125
        } // if phpErrorCapture
1126
    }
1127
1128
    /**
1129
     * A static convenience method to send a log to the current (last-setup) Project.
1130
     * If there is no currently-configured Project, then this will do nothing.
1131
     *
1132
     * @param string $message
1133
     * @param int    $priority project::MSG_INFO, etc
1134
     */
1135 1
    public static function log($message, $priority = Project::MSG_INFO): void
1136
    {
1137 1
        $p = self::getCurrentProject();
1138 1
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Phing\Project, thus it always evaluated to true.
Loading history...
1139 1
            $p->log($message, $priority);
1140
        }
1141
    }
1142
1143
    /**
1144
     * Gets the current Project.
1145
     *
1146
     * @return Project current Project or NULL if none is set yet/still
1147
     */
1148 8
    public static function getCurrentProject()
1149
    {
1150 8
        return self::$currentProject;
1151
    }
1152
1153
    /**
1154
     * Sets the current Project.
1155
     *
1156
     * @param Project $p
1157
     */
1158 1
    public static function setCurrentProject($p): void
1159
    {
1160 1
        self::$currentProject = $p;
1161
    }
1162
1163
    /**
1164
     * Begins capturing PHP errors to a buffer.
1165
     * While errors are being captured, they are not logged.
1166
     */
1167
    public static function startPhpErrorCapture(): void
1168
    {
1169
        self::$phpErrorCapture = true;
1170
        self::$capturedPhpErrors = [];
1171
    }
1172
1173
    /**
1174
     * Stops capturing PHP errors to a buffer.
1175
     * The errors will once again be logged after calling this method.
1176
     */
1177
    public static function stopPhpErrorCapture(): void
1178
    {
1179
        self::$phpErrorCapture = false;
1180
    }
1181
1182
    /**
1183
     * Clears the captured errors without affecting the starting/stopping of the capture.
1184
     */
1185
    public static function clearCapturedPhpErrors(): void
1186
    {
1187
        self::$capturedPhpErrors = [];
1188
    }
1189
1190
    /**
1191
     * Gets any PHP errors that were captured to buffer.
1192
     *
1193
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1194
     */
1195
    public static function getCapturedPhpErrors()
1196
    {
1197
        return self::$capturedPhpErrors;
1198
    }
1199
1200
    /**
1201
     * This gets a property that was set via command line or otherwise passed into Phing.
1202
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1203
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1204
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1205
     * the pear.log.name property.
1206
     *
1207
     * @param string $name
1208
     *
1209
     * @return string value of found property (or null, if none found)
1210
     */
1211
    public static function getDefinedProperty($name)
1212
    {
1213
        return self::$definedProps->getProperty($name);
1214
    }
1215
1216
    /**
1217
     * Retuns reference to all properties.
1218
     */
1219 905
    public static function &getProperties()
1220
    {
1221 905
        return self::$properties;
1222
    }
1223
1224
    /**
1225
     * Start up Phing.
1226
     * Sets up the Phing environment but does not initiate the build process.
1227
     *
1228
     * @throws exception - If the Phing environment cannot be initialized
1229
     */
1230 1
    public static function startup(): void
1231
    {
1232
        // setup STDOUT and STDERR defaults
1233 1
        self::initializeOutputStreams();
1234
1235
        // some init stuff
1236 1
        self::getTimer()->start();
1237
1238 1
        self::setSystemConstants();
1239 1
        self::setIncludePaths();
1240 1
        self::setIni();
1241
    }
1242
1243
    /**
1244
     * @param $propName
1245
     * @param $propValue
1246
     *
1247
     * @return string
1248
     */
1249 8
    public static function setProperty($propName, $propValue)
1250
    {
1251 8
        $propName = (string) $propName;
1252 8
        $oldValue = self::getProperty($propName);
1253 8
        self::$properties[$propName] = $propValue;
1254
1255 8
        return $oldValue;
1256
    }
1257
1258
    /**
1259
     * Returns buildfile's path.
1260
     *
1261
     * @param $path
1262
     *
1263
     * @throws ConfigurationException
1264
     *
1265
     * @return string
1266
     */
1267
    protected static function initPath($path)
1268
    {
1269
        // Fallback
1270
        if (empty($path)) {
1271
            $defaultDir = self::getProperty('application.startdir');
1272
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1273
        }
1274
1275
        // Adding filename if necessary
1276
        if (is_dir($path)) {
1277
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1278
        }
1279
1280
        // Check if path is available
1281
        $dirname = dirname($path);
1282
        if (is_dir($dirname) && !is_file($path)) {
1283
            return $path;
1284
        }
1285
1286
        // Path is valid, but buildfile already exists
1287
        if (is_file($path)) {
1288
            throw new ConfigurationException('Buildfile already exists.');
1289
        }
1290
1291
        throw new ConfigurationException('Invalid path for sample buildfile.');
1292
    }
1293
1294
    /**
1295
     * Writes sample buildfile.
1296
     *
1297
     * If $buildfilePath does not exist, the buildfile is created.
1298
     *
1299
     * @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...
1300
     *
1301
     * @throws ConfigurationException
1302
     */
1303
    protected static function initWrite($buildfilePath): void
1304
    {
1305
        // Overwriting protection
1306
        if (file_exists($buildfilePath)) {
1307
            throw new ConfigurationException('Cannot overwrite existing file.');
1308
        }
1309
1310
        file_put_contents($buildfilePath, self::DEFAULT_BUILD_CONTENT);
1311
    }
1312
1313
    /**
1314
     * This operation is expected to call `exit($int)`, which
1315
     * is what the base version does.
1316
     * However, it is possible to do something else.
1317
     *
1318
     * @param int $exitCode code to exit with
1319
     */
1320
    protected static function statusExit($exitCode): void
1321
    {
1322
        Phing::shutdown();
1323
1324
        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...
1325
    }
1326
1327
    /**
1328
     * Handle the -propertyfile argument.
1329
     *
1330
     * @throws ConfigurationException
1331
     * @throws IOException
1332
     */
1333
    private function handleArgPropertyFile(array $args, int $pos): int
1334
    {
1335
        if (!isset($args[$pos + 1])) {
1336
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
1337
        }
1338
1339
        $this->propertyFiles[] = $args[++$pos];
1340
1341
        return $pos;
1342
    }
1343
1344
    /**
1345
     * Search parent directories for the build file.
1346
     *
1347
     * Takes the given target as a suffix to append to each
1348
     * parent directory in search of a build file.  Once the
1349
     * root of the file-system has been reached an exception
1350
     * is thrown.
1351
     *
1352
     * @param string $start  start file path
1353
     * @param string $suffix suffix filename to look for in parents
1354
     *
1355
     * @throws ConfigurationException
1356
     *
1357
     * @return File A handle to the build file
1358
     */
1359
    private function findBuildFile($start, $suffix)
1360
    {
1361
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
1362
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
1363
        }
1364
1365
        $parent = new File((new File($start))->getAbsolutePath());
1366
        $file = new File($parent, $suffix);
1367
1368
        // check if the target file exists in the current directory
1369
        while (!$file->exists()) {
1370
            // change to parent directory
1371
            $parent = $parent->getParentFile();
1372
1373
            // if parent is null, then we are at the root of the fs,
1374
            // complain that we can't find the build file.
1375
            if (null === $parent) {
1376
                throw new ConfigurationException('Could not locate a build file!');
1377
            }
1378
            // refresh our file handle
1379
            $file = new File($parent, $suffix);
1380
        }
1381
1382
        return $file;
1383
    }
1384
1385
    /**
1386
     * @throws IOException
1387
     */
1388
    private function loadPropertyFiles(): void
1389
    {
1390
        foreach ($this->propertyFiles as $filename) {
1391
            $fileParserFactory = new FileParserFactory();
1392
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
1393
            $p = new Properties(null, $fileParser);
1394
1395
            try {
1396
                $p->load(new File($filename));
1397
            } catch (IOException $e) {
1398
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
1399
            }
1400
            foreach ($p->getProperties() as $prop => $value) {
1401
                self::$definedProps->setProperty($prop, $value);
1402
            }
1403
        }
1404
    }
1405
1406
    /**
1407
     * Close logfiles, if we have been writing to them.
1408
     *
1409
     * @since Phing 2.3.0
1410
     */
1411
    private static function handleLogfile(): void
1412
    {
1413
        if (self::$isLogFileUsed) {
1414
            self::$err->close();
1415
            self::$out->close();
1416
        }
1417
    }
1418
1419
    /**
1420
     * Sets the stdout and stderr streams if they are not already set.
1421
     */
1422 1
    private static function initializeOutputStreams()
1423
    {
1424 1
        if (null === self::$out) {
1425
            if (!defined('STDOUT')) {
1426
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
1427
            } else {
1428
                self::$out = new OutputStream(STDOUT);
1429
            }
1430
        }
1431 1
        if (null === self::$err) {
1432
            if (!defined('STDERR')) {
1433
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
1434
            } else {
1435
                self::$err = new OutputStream(STDERR);
1436
            }
1437
        }
1438
    }
1439
1440
    /**
1441
     * Restores [most] PHP INI values to their pre-Phing state.
1442
     *
1443
     * Currently the following settings are not restored:
1444
     *  - max_execution_time (because getting current time limit is not possible)
1445
     *  - memory_limit (which may have been increased by Phing)
1446
     */
1447 1
    private static function restoreIni(): void
1448
    {
1449 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1450
            switch ($settingName) {
1451 1
                case 'error_reporting':
1452 1
                    error_reporting($settingValue);
1453
1454 1
                    break;
1455
1456
                default:
1457 1
                    ini_set($settingName, $settingValue);
1458
            }
1459
        }
1460
    }
1461
1462
    /**
1463
     * Bind any registered build listeners to this project.
1464
     *
1465
     * This means adding the logger and any build listeners that were specified
1466
     * with -listener arg.
1467
     *
1468
     * @throws BuildException
1469
     * @throws ConfigurationException
1470
     */
1471
    private function addBuildListeners(Project $project)
1472
    {
1473
        // Add the default listener
1474
        $project->addBuildListener($this->createLogger());
1475
1476
        foreach ($this->listeners as $listenerClassname) {
1477
            try {
1478
                $clz = Phing::import($listenerClassname);
1479
            } catch (Exception $e) {
1480
                $msg = 'Unable to instantiate specified listener '
1481
                    . 'class ' . $listenerClassname . ' : '
1482
                    . $e->getMessage();
1483
1484
                throw new ConfigurationException($msg);
1485
            }
1486
1487
            $listener = new $clz();
1488
1489
            if ($listener instanceof StreamRequiredBuildLogger) {
1490
                throw new ConfigurationException('Unable to add ' . $listenerClassname . ' as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)');
1491
            }
1492
            $project->addBuildListener($listener);
1493
        }
1494
    }
1495
1496
    /**
1497
     * Creates the default build logger for sending build events to the log.
1498
     *
1499
     * @throws BuildException
1500
     *
1501
     * @return BuildLogger The created Logger
1502
     */
1503
    private function createLogger()
1504
    {
1505
        if ($this->silent) {
1506
            $logger = new SilentLogger();
1507
            self::$msgOutputLevel = Project::MSG_WARN;
1508
        } elseif (null !== $this->loggerClassname) {
1509
            self::import($this->loggerClassname);
1510
            // get class name part
1511
            $classname = self::import($this->loggerClassname);
1512
            $logger = new $classname();
1513
            if (!($logger instanceof BuildLogger)) {
1514
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
1515
            }
1516
        } else {
1517
            $logger = new DefaultLogger();
1518
        }
1519
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
1520
        $logger->setOutputStream(self::$out);
1521
        $logger->setErrorStream(self::$err);
1522
        $logger->setEmacsMode($this->emacsMode);
1523
1524
        return $logger;
1525
    }
1526
1527
    /**
1528
     * Creates the InputHandler and adds it to the project.
1529
     *
1530
     * @param Project $project the project instance
1531
     *
1532
     * @throws ConfigurationException
1533
     */
1534
    private function addInputHandler(Project $project): void
1535
    {
1536
        if (null === $this->inputHandlerClassname) {
1537
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
1538
        } else {
1539
            try {
1540
                $clz = Phing::import($this->inputHandlerClassname);
1541
                $handler = new $clz();
1542
                if (null !== $project && method_exists($handler, 'setProject')) {
1543
                    $handler->setProject($project);
1544
                }
1545
            } catch (Exception $e) {
1546
                $msg = 'Unable to instantiate specified input handler '
1547
                    . 'class ' . $this->inputHandlerClassname . ' : '
1548
                    . $e->getMessage();
1549
1550
                throw new ConfigurationException($msg);
1551
            }
1552
        }
1553
        $project->setInputHandler($handler);
1554
    }
1555
1556
    /**
1557
     * @param string $version
1558
     *
1559
     * @throws BuildException
1560
     * @throws ConfigurationException
1561
     */
1562
    private function comparePhingVersion($version): void
1563
    {
1564
        $current = self::getPhingShortVersion();
1565
1566
        // make sure that version checks are not applied to trunk
1567
        if ('dev' === $current) {
1568
            return;
1569
        }
1570
1571
        if (-1 == version_compare($current, $version)) {
1572
            throw new BuildException(
1573
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
1574
            );
1575
        }
1576
    }
1577
1578
    /**
1579
     * Returns a formatted list of target names with an optional description.
1580
     *
1581
     * @param string   $title   Title for this list
1582
     * @param Target[] $targets Targets in this list
1583
     * @param int      $padding Padding for name column
1584
     */
1585 1
    private function generateTargetList(string $title, array $targets, int $padding): string
1586
    {
1587 1
        usort($targets, function (Target $a, Target $b) {
1588
            return $a->getName() <=> $b->getName();
1589 1
        });
1590
1591 1
        $header = <<<HEADER
1592 1
            {$title}
1593
            -------------------------------------------------------------------------------
1594
1595 1
            HEADER;
1596
1597 1
        $getDetails = function (Target $target) use ($padding): string {
1598
            $details = [];
1599
            if (!empty($target->getDescription())) {
1600
                $details[] = $target->getDescription();
1601
            }
1602
            if (!empty($target->getDependencies())) {
1603
                $details[] = ' - depends on: ' . implode(', ', $target->getDependencies());
1604
            }
1605
            if (!empty($target->getIf())) {
1606
                $details[] = ' - if property: ' . $target->getIf();
1607
            }
1608
            if (!empty($target->getUnless())) {
1609
                $details[] = ' - unless property: ' . $target->getUnless();
1610
            }
1611
            $detailsToString = function (?string $name, ?string $detail) use ($padding): string {
1612
                return sprintf(" %-{$padding}s  %s", $name, $detail);
1613
            };
1614
1615
            return implode(PHP_EOL, array_map($detailsToString, [$target->getName()], $details));
1616 1
        };
1617
1618 1
        return $header . implode(PHP_EOL, array_map($getDetails, $targets)) . PHP_EOL;
1619
    }
1620
1621
    /**
1622
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1623
     */
1624 1
    private static function setSystemConstants(): void
1625
    {
1626
        /*
1627
         * PHP_OS returns on
1628
         *   WindowsNT4.0sp6  => WINNT
1629
         *   Windows2000      => WINNT
1630
         *   Windows ME       => WIN32
1631
         *   Windows 98SE     => WIN32
1632
         *   FreeBSD 4.5p7    => FreeBSD
1633
         *   Redhat Linux     => Linux
1634
         *   Mac OS X         => Darwin
1635
         */
1636 1
        self::setProperty('host.os', PHP_OS);
1637
1638
        // this is used by some tasks too
1639 1
        self::setProperty('os.name', PHP_OS);
1640
1641
        // it's still possible this won't be defined,
1642
        // e.g. if Phing is being included in another app w/o
1643
        // using the phing.php script.
1644 1
        if (!defined('PHP_CLASSPATH')) {
1645
            define('PHP_CLASSPATH', get_include_path());
1646
        }
1647
1648 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1649
1650
        // try to determine the host filesystem and set system property
1651
        // used by Fileself::getFileSystem to instantiate the correct
1652
        // abstraction layer
1653
1654 1
        if (PHP_OS_FAMILY === 'Windows') {
1655
            self::setProperty('host.fstype', 'WINDOWS');
1656
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1657
        } else {
1658 1
            self::setProperty('host.fstype', 'UNIX');
1659 1
            self::setProperty('user.home', getenv('HOME'));
1660
        }
1661 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1662 1
        self::setProperty('file.separator', FileUtils::getSeparator());
1663 1
        self::setProperty('line.separator', PHP_EOL);
1664 1
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1665 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1666 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1667 1
        self::setProperty('application.startdir', getcwd());
1668 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1669
1670
        // try to detect machine dependent information
1671 1
        $sysInfo = [];
1672 1
        if (function_exists('posix_uname') && 0 !== stripos(PHP_OS, 'WIN')) {
1673 1
            $sysInfo = posix_uname();
1674
        } else {
1675
            $sysInfo['nodename'] = php_uname('n');
1676
            $sysInfo['machine'] = php_uname('m');
1677
            //this is a not so ideal substition, but maybe better than nothing
1678
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? 'unknown';
1679
            $sysInfo['release'] = php_uname('r');
1680
            $sysInfo['version'] = php_uname('v');
1681
        }
1682
1683 1
        self::setProperty('host.name', $sysInfo['nodename'] ?? 'unknown');
1684 1
        self::setProperty('host.arch', $sysInfo['machine'] ?? 'unknown');
1685 1
        self::setProperty('host.domain', $sysInfo['domain'] ?? 'unknown');
1686 1
        self::setProperty('host.os.release', $sysInfo['release'] ?? 'unknown');
1687 1
        self::setProperty('host.os.version', $sysInfo['version'] ?? 'unknown');
1688 1
        unset($sysInfo);
1689
    }
1690
1691
    /**
1692
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1693
     *
1694
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1695
     */
1696 1
    private static function setIncludePaths(): void
1697
    {
1698 1
        if (defined('PHP_CLASSPATH')) {
1699 1
            $result = set_include_path(PHP_CLASSPATH);
1700 1
            if (false === $result) {
1701
                throw new ConfigurationException('Could not set PHP include_path.');
1702
            }
1703 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1704
        }
1705
    }
1706
1707
    /**
1708
     * Sets PHP INI values that Phing needs.
1709
     */
1710 1
    private static function setIni(): void
1711
    {
1712 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1713
1714
        // We won't bother storing original max_execution_time, since 1) the value in
1715
        // php.ini may be wrong (and there's no way to get the current value) and
1716
        // 2) it would mean something very strange to set it to a value less than time script
1717
        // has already been running, which would be the likely change.
1718
1719 1
        set_time_limit(0);
1720
1721 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1722 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1723
1724 1
        $mem_limit = (int) SizeHelper::fromHumanToBytes(ini_get('memory_limit'));
1725 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1726
            // We do *not* need to save the original value here, since we don't plan to restore
1727
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1728
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1729
        }
1730
    }
1731
}
1732