Phing::printMessage()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

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