Phing   F
last analyzed

Complexity

Total Complexity 216

Size/Duplication

Total Lines 1656
Duplicated Lines 0 %

Test Coverage

Coverage 26.59%

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 216
eloc 625
c 5
b 0
f 1
dl 0
loc 1656
ccs 155
cts 583
cp 0.2659
rs 1.975

55 Methods

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

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