Phing   F
last analyzed

Complexity

Total Complexity 214

Size/Duplication

Total Lines 1624
Duplicated Lines 0 %

Test Coverage

Coverage 28.72%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 214
eloc 618
c 2
b 0
f 0
dl 0
loc 1624
ccs 166
cts 578
cp 0.2872
rs 1.982

54 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 4 2
B getResourcePath() 0 40 9
A explodeIncludePath() 0 16 3
A setIncludePaths() 0 8 3
A setIni() 0 19 3
A getProperty() 0 17 2
A statusExit() 0 5 1
A generateTargetList() 0 34 5
A printUsage() 0 34 1
A restoreIni() 0 11 3
A shutdown() 0 6 1
F execute() 0 231 61
A getPhingVersion() 0 23 5
A createLogger() 0 22 4
A startup() 0 11 1
A stopPhpErrorCapture() 0 3 1
A startPhpErrorCapture() 0 4 1
C handlePhpError() 0 35 12
A addInputHandler() 0 20 5
A comparePhingVersion() 0 13 3
A findBuildFile() 0 24 4
A setErrorStream() 0 3 1
A import() 0 12 2
A clearCapturedPhpErrors() 0 3 1
B start() 0 42 8
A printVersion() 0 3 1
A unsetCurrentProject() 0 3 1
A setProperty() 0 7 1
A getMsgOutputLevel() 0 3 1
A setDefinedProperty() 0 3 1
A initializeOutputStreams() 0 14 5
A fire() 0 3 1
A handleArgPropertyFile() 0 9 2
A importFile() 0 40 6
A getTimer() 0 7 2
A getCapturedPhpErrors() 0 3 1
A handleLogfile() 0 5 2
A printDescription() 0 4 2
A getErrorStream() 0 3 1
A setOutputStream() 0 3 1
A getProperties() 0 3 1
A setSystemConstants() 0 65 5
A printMessage() 0 9 3
A initPath() 0 25 6
A log() 0 5 2
A getCurrentProject() 0 3 1
A printTargets() 0 22 3
A addBuildListeners() 0 22 4
A initWrite() 0 8 2
A setCurrentProject() 0 3 1
A getDefinedProperty() 0 3 1
D runBuild() 0 81 10
A getOutputStream() 0 3 1
A loadPropertyFiles() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like Phing often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Phing, and based on these observations, apply Extract Interface, too.

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
     * System-wide static properties (moved from System).
117
     */
118
    private static $properties = [];
119
    /**
120
     * Static system timer.
121
     */
122
    private static $timer;
123
    /**
124
     * The current Project.
125
     */
126
    private static $currentProject;
127
    /**
128
     * Whether to capture PHP errors to buffer.
129
     */
130
    private static $phpErrorCapture = false;
131
    /**
132
     * Array of captured PHP errors.
133
     */
134
    private static $capturedPhpErrors = [];
135
    /**
136
     * @var OUtputStream stream for standard output
137
     */
138
    private static $out;
139
    /**
140
     * @var OutputStream stream for error output
141
     */
142
    private static $err;
143
    /**
144
     * @var bool whether we are using a logfile
145
     */
146
    private static $isLogFileUsed = false;
147
    /**
148
     * Array to hold original ini settings that Phing changes (and needs
149
     * to restore in restoreIni() method).
150
     *
151
     * @var array Struct of array(setting-name => setting-value)
152
     *
153
     * @see restoreIni()
154
     */
155
    private static $origIniSettings = [];
156
    /**
157
     * PhingFile that we are using for configuration.
158
     */
159
    private $buildFile;
160
    /**
161
     * The build targets.
162
     */
163
    private $targets = [];
164
    /**
165
     * Names of classes to add as listeners to project.
166
     */
167
    private $listeners = [];
168
    /**
169
     * keep going mode.
170
     *
171
     * @var bool
172
     */
173
    private $keepGoingMode = false;
174
    private $loggerClassname;
175
    /**
176
     * The class to handle input (can be only one).
177
     */
178
    private $inputHandlerClassname;
179
    /**
180
     * Whether or not log output should be reduced to the minimum.
181
     *
182
     * @var bool
183
     */
184
    private $silent = false;
185
    /**
186
     * Indicates whether phing should run in strict mode.
187
     */
188
    private $strictMode = false;
189
    /**
190
     * Indicates if this phing should be run.
191
     */
192
    private $readyToRun = false;
193
    /**
194
     * Indicates we should only parse and display the project help information.
195
     */
196
    private $projectHelp = false;
197
    /**
198
     * Whether to values in a property file should override existing values.
199
     */
200
    private $propertyFileOverride = false;
201
    /**
202
     * Whether or not output to the log is to be unadorned.
203
     */
204
    private $emacsMode = false;
205
206
    /**
207
     * @var string
208
     */
209
    private $searchForThis;
210
    private $propertyFiles = [];
211
212
    /**
213
     * Gets the stream to use for standard (non-error) output.
214
     *
215
     * @return OutputStream
216
     */
217
    public static function getOutputStream()
218
    {
219
        return self::$out;
220
    }
221
222
    /**
223
     * Gets the stream to use for error output.
224
     *
225
     * @return OutputStream
226
     */
227
    public static function getErrorStream()
228
    {
229
        return self::$err;
230
    }
231
232
    /**
233
     * Command line entry point. This method kicks off the building
234
     * of a project object and executes a build using either a given
235
     * target or the default target.
236
     *
237
     * @param array $args command line args
238
     *
239
     * @throws Exception
240
     */
241
    public static function fire($args): void
242
    {
243
        self::start($args);
244
    }
245
246
    /**
247
     * Entry point allowing for more options from other front ends.
248
     *
249
     * This method encapsulates the complete build lifecycle.
250
     *
251
     * @param array $args                     the commandline args passed to phing shell script
252
     * @param array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
253
     *                                        These additional properties will be available using the getDefinedProperty() method and will
254
     *                                        be added to the project's "user" properties
255
     *
256
     * @throws Exception - if there is an error during build
257
     *
258
     * @see    runBuild()
259
     * @see    execute()
260
     */
261
    public static function start($args, array $additionalUserProperties = null)
262
    {
263
        try {
264
            $m = new self();
265
            $m->execute($args);
266
        } catch (Exception $exc) {
267
            self::handleLogfile();
268
            self::printMessage($exc);
269
            self::statusExit(1);
270
271
            return;
272
        }
273
274
        if (null !== $additionalUserProperties) {
275
            foreach ($additionalUserProperties as $key => $value) {
276
                $m::setDefinedProperty($key, $value);
277
            }
278
        }
279
280
        // expect the worst
281
        $exitCode = 1;
282
283
        try {
284
            try {
285
                $m->runBuild();
286
                $exitCode = 0;
287
            } catch (ExitStatusException $ese) {
288
                $exitCode = $ese->getCode();
289
                if (0 !== $exitCode) {
290
                    self::handleLogfile();
291
292
                    throw $ese;
293
                }
294
            }
295
        } catch (BuildException $exc) {
296
            // avoid printing output twice: self::printMessage($exc);
297
        } catch (Throwable $exc) {
298
            self::printMessage($exc);
299
        } finally {
300
            self::handleLogfile();
301
        }
302
        self::statusExit($exitCode);
303
    }
304
305
    /**
306
     * Setup/initialize Phing environment from commandline args.
307
     *
308
     * @param array $args commandline args passed to phing shell
309
     *
310
     * @throws ConfigurationException
311
     */
312
    public function execute($args): void
313
    {
314
        self::$definedProps = new Properties();
315
        $this->searchForThis = null;
316
317
        // 1) First handle any options which should always
318
        // Note: The order in which these are executed is important (if multiple of these options are specified)
319
320
        if (in_array('-help', $args) || in_array('-h', $args)) {
321
            static::printUsage();
322
323
            return;
324
        }
325
326
        if (in_array('-version', $args) || in_array('-v', $args)) {
327
            static::printVersion();
328
329
            return;
330
        }
331
332
        if (in_array('-init', $args) || in_array('-i', $args)) {
333
            $key = array_search('-init', $args) ?: array_search('-i', $args);
334
            $path = $args[$key + 1] ?? null;
335
336
            self::init($path);
337
338
            return;
339
        }
340
341
        if (in_array('-diagnostics', $args)) {
342
            Diagnostics::doReport(new PrintStream(self::$out));
343
344
            return;
345
        }
346
347
        // 2) Next pull out stand-alone args.
348
        // Note: The order in which these are executed is important (if multiple of these options are specified)
349
350
        if (
351
            false !== ($key = array_search('-quiet', $args, true))
352
            || false !== ($key = array_search(
353
                '-q',
354
                $args,
355
                true
356
            ))
357
        ) {
358
            self::$msgOutputLevel = Project::MSG_WARN;
359
            unset($args[$key]);
360
        }
361
362
        if (
363
            false !== ($key = array_search('-emacs', $args, true))
364
            || false !== ($key = array_search('-e', $args, true))
365
        ) {
366
            $this->emacsMode = true;
367
            unset($args[$key]);
368
        }
369
370
        if (false !== ($key = array_search('-verbose', $args, true))) {
371
            self::$msgOutputLevel = Project::MSG_VERBOSE;
372
            unset($args[$key]);
373
        }
374
375
        if (false !== ($key = array_search('-debug', $args, true))) {
376
            self::$msgOutputLevel = Project::MSG_DEBUG;
377
            unset($args[$key]);
378
        }
379
380
        if (
381
            false !== ($key = array_search('-silent', $args, true))
382
            || false !== ($key = array_search('-S', $args, true))
383
        ) {
384
            $this->silent = true;
385
            unset($args[$key]);
386
        }
387
388
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
389
            $this->propertyFileOverride = true;
390
            unset($args[$key]);
391
        }
392
393
        // 3) Finally, cycle through to parse remaining args
394
        //
395
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
396
        $max = $keys ? max($keys) : -1;
397
        for ($i = 0; $i <= $max; ++$i) {
398
            if (!array_key_exists($i, $args)) {
399
                // skip this argument, since it must have been removed above.
400
                continue;
401
            }
402
403
            $arg = $args[$i];
404
405
            if ('-logfile' == $arg) {
406
                try {
407
                    // see: http://phing.info/trac/ticket/65
408
                    if (!isset($args[$i + 1])) {
409
                        $msg = "You must specify a log file when using the -logfile argument\n";
410
411
                        throw new ConfigurationException($msg);
412
                    }
413
414
                    $logFile = new File($args[++$i]);
415
                    $out = new FileOutputStream($logFile); // overwrite
416
                    self::setOutputStream($out);
417
                    self::setErrorStream($out);
418
                    self::$isLogFileUsed = true;
419
                } catch (IOException $ioe) {
420
                    $msg = 'Cannot write on the specified log file. Make sure the path exists and you have write permissions.';
421
422
                    throw new ConfigurationException($msg, $ioe);
423
                }
424
            } elseif ('-buildfile' == $arg || '-file' == $arg || '-f' == $arg) {
425
                if (!isset($args[$i + 1])) {
426
                    $msg = 'You must specify a buildfile when using the -buildfile argument.';
427
428
                    throw new ConfigurationException($msg);
429
                }
430
431
                $this->buildFile = new File($args[++$i]);
432
            } elseif ('-listener' == $arg) {
433
                if (!isset($args[$i + 1])) {
434
                    $msg = 'You must specify a listener class when using the -listener argument';
435
436
                    throw new ConfigurationException($msg);
437
                }
438
439
                $this->listeners[] = $args[++$i];
440
            } elseif (StringHelper::startsWith('-D', $arg)) {
441
                // Evaluating the property information //
442
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
443
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
444
                    $name = $args[++$i];
445
                } else {
446
                    $name = substr($arg, 2);
447
                }
448
449
                $value = null;
450
                $posEq = strpos($name, '=');
451
                if (false !== $posEq) {
452
                    $value = substr($name, $posEq + 1);
453
                    $name = substr($name, 0, $posEq);
454
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith('-D', $args[$i + 1])) {
455
                    $value = $args[++$i];
456
                }
457
                self::$definedProps->setProperty($name, $value);
458
            } elseif ('-logger' == $arg) {
459
                if (!isset($args[$i + 1])) {
460
                    $msg = 'You must specify a classname when using the -logger argument';
461
462
                    throw new ConfigurationException($msg);
463
                }
464
465
                $this->loggerClassname = $args[++$i];
466
            } elseif ('-no-strict' == $arg) {
467
                $this->strictMode = false;
468
            } elseif ('-strict' == $arg) {
469
                $this->strictMode = true;
470
            } elseif ('-inputhandler' == $arg) {
471
                if (null !== $this->inputHandlerClassname) {
472
                    throw new ConfigurationException('Only one input handler class may be specified.');
473
                }
474
                if (!isset($args[$i + 1])) {
475
                    $msg = 'You must specify a classname when using the -inputhandler argument';
476
477
                    throw new ConfigurationException($msg);
478
                }
479
480
                $this->inputHandlerClassname = $args[++$i];
481
            } elseif ('-propertyfile' === $arg) {
482
                $i = $this->handleArgPropertyFile($args, $i);
483
            } elseif ('-keep-going' === $arg || '-k' === $arg) {
484
                $this->keepGoingMode = true;
485
            } elseif ('-longtargets' == $arg) {
486
                self::$definedProps->setProperty('phing.showlongtargets', 1);
487
            } elseif ('-projecthelp' == $arg || '-targets' == $arg || '-list' == $arg || '-l' == $arg || '-p' == $arg) {
488
                // set the flag to display the targets and quit
489
                $this->projectHelp = true;
490
            } elseif ('-find' == $arg) {
491
                // eat up next arg if present, default to build.xml
492
                if ($i < count($args) - 1) {
493
                    $this->searchForThis = $args[++$i];
494
                } else {
495
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
496
                }
497
            } elseif ('-' == substr($arg, 0, 1)) {
498
                // we don't have any more args
499
                self::printUsage();
500
                self::$err->write(PHP_EOL);
501
502
                throw new ConfigurationException('Unknown argument: ' . $arg);
503
            } else {
504
                // if it's no other arg, it may be the target
505
                $this->targets[] = $arg;
506
            }
507
        }
508
509
        // if buildFile was not specified on the command line,
510
        if (null === $this->buildFile) {
511
            // but -find then search for it
512
            if (null !== $this->searchForThis) {
513
                $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

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