Passed
Push — main ( c05734...58efd0 )
by Siad
08:27
created

Phing   F

Complexity

Total Complexity 214

Size/Duplication

Total Lines 1633
Duplicated Lines 0 %

Test Coverage

Coverage 31.83%

Importance

Changes 0
Metric Value
wmc 214
eloc 618
dl 0
loc 1633
ccs 205
cts 644
cp 0.3183
rs 1.982
c 0
b 0
f 0

55 Methods

Rating   Name   Duplication   Size   Complexity  
A printUsage() 0 34 1
A init() 0 4 2
A createLogger() 0 22 4
A stopPhpErrorCapture() 0 3 1
A startPhpErrorCapture() 0 4 1
C handlePhpError() 0 32 12
A addInputHandler() 0 19 5
A clearCapturedPhpErrors() 0 3 1
A printVersion() 0 3 1
A unsetCurrentProject() 0 3 1
A getCapturedPhpErrors() 0 3 1
A log() 0 5 2
A getCurrentProject() 0 3 1
A addBuildListeners() 0 21 4
A setCurrentProject() 0 3 1
A statusExit() 0 4 1
A generateTargetList() 0 34 5
A restoreIni() 0 9 3
A shutdown() 0 6 1
F execute() 0 224 61
A getPhingVersion() 0 22 4
A startup() 0 12 1
A comparePhingVersion() 0 13 3
B getResourcePath() 0 40 9
A findBuildFile() 0 24 4
A setErrorStream() 0 3 1
A import() 0 12 2
B start() 0 39 8
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 39 6
A setIncludePaths() 0 8 3
A getTimer() 0 7 2
A setIni() 0 19 3
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 66 5
A printMessage() 0 9 3
A initPath() 0 25 6
A printTargets() 0 22 3
A currentTimeMillis() 0 5 1
A getProperty() 0 17 2
A initWrite() 0 18 2
A getDefinedProperty() 0 3 1
D runBuild() 0 78 10
A getOutputStream() 0 3 1
A loadPropertyFiles() 0 13 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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing;
21
22
use Exception;
23
use Phing\Exception\BuildException;
24
use Phing\Exception\ConfigurationException;
25
use Phing\Exception\ExitStatusException;
26
use Phing\Input\ConsoleInputHandler;
27
use Phing\Io\File;
28
use Phing\Io\FileOutputStream;
29
use Phing\Io\FileParserFactory;
30
use Phing\Io\FileReader;
31
use Phing\Io\FileSystem;
32
use Phing\Io\FileUtils;
33
use Phing\Io\IOException;
34
use Phing\Io\OutputStream;
35
use Phing\Io\PrintStream;
36
use Phing\Listener\BuildLogger;
37
use Phing\Listener\DefaultLogger;
38
use Phing\Listener\SilentLogger;
39
use Phing\Listener\StreamRequiredBuildLogger;
40
use Phing\Parser\ProjectConfigurator;
41
use Phing\Util\Diagnostics;
42
use Phing\Util\Properties;
43
use Phing\Util\SizeHelper;
44
use Phing\Util\StringHelper;
45
use Phing\Util\Timer;
46
use SebastianBergmann\Version;
47
use Symfony\Component\Console\Output\ConsoleOutput;
48
use Throwable;
49
use function array_filter;
50
use function array_map;
51
use function array_reduce;
52
use function implode;
53
use function sprintf;
54
use function strlen;
55
use function strval;
56
use function trim;
57
use const PHP_EOL;
58
59
/**
60
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
61
 * parsing & handling commandline arguments to assembling the project to shutting down
62
 * and cleaning up in the end.
63
 *
64
 * If you are invoking Phing from an external application, this is still
65
 * the class to use.  Your application can invoke the start() method, passing
66
 * any commandline arguments or additional properties.
67
 *
68
 * @author Andreas Aderhold <[email protected]>
69
 * @author Hans Lellelid <[email protected]>
70
 *
71
 */
72
class Phing
73
{
74
    /**
75
     * Alias for phar file
76
     */
77
    public const PHAR_ALIAS = 'phing.phar';
78
79
    /**
80
     * The default build file name
81
     */
82
    public const DEFAULT_BUILD_FILENAME = "build.xml";
83
    public const PHING_HOME = 'phing.home';
84
    public const PHP_VERSION = 'php.version';
85
    public const PHP_INTERPRETER = 'php.interpreter';
86
87
    /**
88
     * Our current message output status. Follows Project::MSG_XXX
89
     */
90
    private static $msgOutputLevel = Project::MSG_INFO;
91
92
    /**
93
     * PhingFile that we are using for configuration
94
     */
95
    private $buildFile = null;
96
97
    /**
98
     * The build targets
99
     */
100
    private $targets = [];
101
102
    /**
103
     * Set of properties that are passed in from commandline or invoking code.
104
     *
105
     * @var Properties
106
     */
107
    private static $definedProps;
108
109
    /**
110
     * Names of classes to add as listeners to project
111
     */
112
    private $listeners = [];
113
114
    /**
115
     * keep going mode
116
     *
117
     * @var bool
118
     */
119
    private $keepGoingMode = false;
120
121
    private $loggerClassname = null;
122
123
    /**
124
     * The class to handle input (can be only one).
125
     */
126
    private $inputHandlerClassname;
127
128
    /**
129
     * Whether or not log output should be reduced to the minimum.
130
     *
131
     * @var bool
132
     */
133
    private $silent = false;
134
135
    /**
136
     * Indicates whether phing should run in strict mode
137
     */
138
    private $strictMode = false;
139
140
    /**
141
     * Indicates if this phing should be run
142
     */
143
    private $readyToRun = false;
144
145
    /**
146
     * Indicates we should only parse and display the project help information
147
     */
148
    private $projectHelp = false;
149
150
    /**
151
     * Used by utility function getResourcePath()
152
     */
153
    private static $importPaths;
154
155
    /**
156
     * System-wide static properties (moved from System)
157
     */
158
    private static $properties = [];
159
160
    /**
161
     * Static system timer.
162
     */
163
    private static $timer;
164
165
    /**
166
     * The current Project
167
     */
168
    private static $currentProject;
169
170
    /**
171
     * Whether to capture PHP errors to buffer.
172
     */
173
    private static $phpErrorCapture = false;
174
175
    /**
176
     * Whether to values in a property file should override existing values.
177
     */
178
    private $propertyFileOverride = false;
179
180
    /**
181
     * Array of captured PHP errors
182
     */
183
    private static $capturedPhpErrors = [];
184
185
    /**
186
     * @var OUtputStream Stream for standard output.
187
     */
188
    private static $out;
189
190
    /**
191
     * @var OutputStream Stream for error output.
192
     */
193
    private static $err;
194
195
    /**
196
     * @var bool Whether we are using a logfile.
197
     */
198
    private static $isLogFileUsed = false;
199
200
    /**
201
     * Array to hold original ini settings that Phing changes (and needs
202
     * to restore in restoreIni() method).
203
     *
204
     * @var array Struct of array(setting-name => setting-value)
205
     * @see restoreIni()
206
     */
207
    private static $origIniSettings = [];
208
209
    /**
210
     * Whether or not output to the log is to be unadorned.
211
     */
212
    private $emacsMode = false;
213
214
    /**
215
     * @var string
216
     */
217
    private $searchForThis;
218
    private $propertyFiles = [];
219
220
    /**
221
     * Entry point allowing for more options from other front ends.
222
     *
223
     * This method encapsulates the complete build lifecycle.
224
     *
225
     * @param  array $args The commandline args passed to phing shell script.
226
     * @param  array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
227
     *                                         These additional properties will be available using the getDefinedProperty() method and will
228
     *                                         be added to the project's "user" properties
229
     * @see    execute()
230
     * @see    runBuild()
231
     * @throws Exception - if there is an error during build
232
     */
233
    public static function start($args, array $additionalUserProperties = null)
234
    {
235
        try {
236
            $m = new self();
237
            $m->execute($args);
238
        } catch (Exception $exc) {
239
            self::handleLogfile();
240
            self::printMessage($exc);
241
            self::statusExit(1);
242
            return;
243
        }
244
245
        if ($additionalUserProperties !== null) {
246
            foreach ($additionalUserProperties as $key => $value) {
247
                $m::setDefinedProperty($key, $value);
248
            }
249
        }
250
251
        // expect the worst
252
        $exitCode = 1;
253
        try {
254
            try {
255
                $m->runBuild();
256
                $exitCode = 0;
257
            } catch (ExitStatusException $ese) {
258
                $exitCode = $ese->getCode();
259
                if ($exitCode !== 0) {
260
                    self::handleLogfile();
261
                    throw $ese;
262
                }
263
            }
264
        } catch (BuildException $exc) {
265
            // avoid printing output twice: self::printMessage($exc);
266
        } catch (Throwable $exc) {
267
            self::printMessage($exc);
268
        } finally {
269
            self::handleLogfile();
270
        }
271
        self::statusExit($exitCode);
272
    }
273
274
    /**
275
     * This operation is expected to call `exit($int)`, which
276
     * is what the base version does.
277
     * However, it is possible to do something else.
278
     *
279
     * @param int $exitCode code to exit with
280
     */
281
    protected static function statusExit($exitCode)
282
    {
283
        Phing::shutdown();
284
        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...
285
    }
286
287
    /**
288
     * Prints the message of the Exception if it's not null.
289
     *
290
     */
291
    public static function printMessage(Throwable $t)
292
    {
293
        if (self::$err === null) { // Make sure our error output is initialized
294
            self::initializeOutputStreams();
295
        }
296
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
297
            self::$err->write((string) $t . PHP_EOL);
298
        } else {
299
            self::$err->write($t->getMessage() . PHP_EOL);
300
        }
301
    }
302
303
    /**
304
     * Sets the stdout and stderr streams if they are not already set.
305
     */
306 1
    private static function initializeOutputStreams()
307
    {
308 1
        if (self::$out === null) {
309
            if (!defined('STDOUT')) {
310
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
311
            } else {
312
                self::$out = new OutputStream(STDOUT);
313
            }
314
        }
315 1
        if (self::$err === null) {
316
            if (!defined('STDERR')) {
317
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
318
            } else {
319
                self::$err = new OutputStream(STDERR);
320
            }
321
        }
322 1
    }
323
324
    /**
325
     * Sets the stream to use for standard (non-error) output.
326
     *
327
     * @param OutputStream $stream The stream to use for standard output.
328
     */
329 1
    public static function setOutputStream(OutputStream $stream)
330
    {
331 1
        self::$out = $stream;
332 1
    }
333
334
    /**
335
     * Gets the stream to use for standard (non-error) output.
336
     *
337
     * @return OutputStream
338
     */
339
    public static function getOutputStream()
340
    {
341
        return self::$out;
342
    }
343
344
    /**
345
     * Sets the stream to use for error output.
346
     *
347
     * @param OutputStream $stream The stream to use for error output.
348
     */
349 1
    public static function setErrorStream(OutputStream $stream)
350
    {
351 1
        self::$err = $stream;
352 1
    }
353
354
    /**
355
     * Gets the stream to use for error output.
356
     *
357
     * @return OutputStream
358
     */
359
    public static function getErrorStream()
360
    {
361
        return self::$err;
362
    }
363
364
    /**
365
     * Close logfiles, if we have been writing to them.
366
     *
367
     * @since Phing 2.3.0
368
     *
369
     */
370
    private static function handleLogfile()
371
    {
372
        if (self::$isLogFileUsed) {
373
            self::$err->close();
374
            self::$out->close();
375
        }
376
    }
377
378
    /**
379
     * Making output level a static property so that this property
380
     * can be accessed by other parts of the system, enabling
381
     * us to display more information -- e.g. backtraces -- for "debug" level.
382
     *
383
     * @return int
384
     */
385 7
    public static function getMsgOutputLevel()
386
    {
387 7
        return self::$msgOutputLevel;
388
    }
389
390
    /**
391
     * Command line entry point. This method kicks off the building
392
     * of a project object and executes a build using either a given
393
     * target or the default target.
394
     *
395
     * @param array $args Command line args.
396
     *
397
     */
398
    public static function fire($args)
399
    {
400
        self::start($args);
401
    }
402
403
    /**
404
     * Setup/initialize Phing environment from commandline args.
405
     *
406
     * @param array $args commandline args passed to phing shell.
407
     *
408
     * @throws ConfigurationException
409
     *
410
     */
411
    public function execute($args)
412
    {
413
        self::$definedProps = new Properties();
414
        $this->searchForThis = null;
415
416
        // 1) First handle any options which should always
417
        // Note: The order in which these are executed is important (if multiple of these options are specified)
418
419
        if (in_array('-help', $args) || in_array('-h', $args)) {
420
            static::printUsage();
421
422
            return;
423
        }
424
425
        if (in_array('-version', $args) || in_array('-v', $args)) {
426
            static::printVersion();
427
428
            return;
429
        }
430
431
        if (in_array('-init', $args) || in_array('-i', $args)) {
432
            $key = array_search('-init', $args) ?: array_search('-i', $args);
433
            $path = $args[$key + 1] ?? null;
434
435
            self::init($path);
436
437
            return;
438
        }
439
440
        if (in_array('-diagnostics', $args)) {
441
            Diagnostics::doReport(new PrintStream(self::$out));
442
443
            return;
444
        }
445
446
        // 2) Next pull out stand-alone args.
447
        // Note: The order in which these are executed is important (if multiple of these options are specified)
448
449
        if (
450
            false !== ($key = array_search('-quiet', $args, true)) ||
451
            false !== ($key = array_search(
452
                '-q',
453
                $args,
454
                true
455
            ))
456
        ) {
457
            self::$msgOutputLevel = Project::MSG_WARN;
458
            unset($args[$key]);
459
        }
460
461
        if (
462
            false !== ($key = array_search('-emacs', $args, true))
463
            || false !== ($key = array_search('-e', $args, true))
464
        ) {
465
            $this->emacsMode = true;
466
            unset($args[$key]);
467
        }
468
469
        if (false !== ($key = array_search('-verbose', $args, true))) {
470
            self::$msgOutputLevel = Project::MSG_VERBOSE;
471
            unset($args[$key]);
472
        }
473
474
        if (false !== ($key = array_search('-debug', $args, true))) {
475
            self::$msgOutputLevel = Project::MSG_DEBUG;
476
            unset($args[$key]);
477
        }
478
479
        if (
480
            false !== ($key = array_search('-silent', $args, true))
481
            || false !== ($key = array_search('-S', $args, true))
482
        ) {
483
            $this->silent = true;
484
            unset($args[$key]);
485
        }
486
487
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
488
            $this->propertyFileOverride = true;
489
            unset($args[$key]);
490
        }
491
492
        // 3) Finally, cycle through to parse remaining args
493
        //
494
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
495
        $max = $keys ? max($keys) : -1;
496
        for ($i = 0; $i <= $max; $i++) {
497
            if (!array_key_exists($i, $args)) {
498
                // skip this argument, since it must have been removed above.
499
                continue;
500
            }
501
502
            $arg = $args[$i];
503
504
            if ($arg == "-logfile") {
505
                try {
506
                    // see: http://phing.info/trac/ticket/65
507
                    if (!isset($args[$i + 1])) {
508
                        $msg = "You must specify a log file when using the -logfile argument\n";
509
                        throw new ConfigurationException($msg);
510
                    }
511
512
                    $logFile = new File($args[++$i]);
513
                    $out = new FileOutputStream($logFile); // overwrite
514
                    self::setOutputStream($out);
515
                    self::setErrorStream($out);
516
                    self::$isLogFileUsed = true;
517
                } catch (IOException $ioe) {
518
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
519
                    throw new ConfigurationException($msg, $ioe);
520
                }
521
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
522
                if (!isset($args[$i + 1])) {
523
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
524
                    throw new ConfigurationException($msg);
525
                }
526
527
                $this->buildFile = new File($args[++$i]);
528
            } elseif ($arg == "-listener") {
529
                if (!isset($args[$i + 1])) {
530
                    $msg = "You must specify a listener class when using the -listener argument";
531
                    throw new ConfigurationException($msg);
532
                }
533
534
                $this->listeners[] = $args[++$i];
535
            } elseif (StringHelper::startsWith("-D", $arg)) {
536
                // Evaluating the property information //
537
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
538
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
539
                    $name = $args[++$i];
540
                } else {
541
                    $name = substr($arg, 2);
542
                }
543
544
                $value = null;
545
                $posEq = strpos($name, "=");
546
                if ($posEq !== false) {
547
                    $value = substr($name, $posEq + 1);
548
                    $name = substr($name, 0, $posEq);
549
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
550
                    $value = $args[++$i];
551
                }
552
                self::$definedProps->setProperty($name, $value);
553
            } elseif ($arg == "-logger") {
554
                if (!isset($args[$i + 1])) {
555
                    $msg = "You must specify a classname when using the -logger argument";
556
                    throw new ConfigurationException($msg);
557
                }
558
559
                $this->loggerClassname = $args[++$i];
560
            } elseif ($arg == "-no-strict") {
561
                $this->strictMode = false;
562
            } elseif ($arg == "-strict") {
563
                $this->strictMode = true;
564
            } elseif ($arg == "-inputhandler") {
565
                if ($this->inputHandlerClassname !== null) {
566
                    throw new ConfigurationException("Only one input handler class may be specified.");
567
                }
568
                if (!isset($args[$i + 1])) {
569
                    $msg = "You must specify a classname when using the -inputhandler argument";
570
                    throw new ConfigurationException($msg);
571
                }
572
573
                $this->inputHandlerClassname = $args[++$i];
574
            } elseif ($arg === "-propertyfile") {
575
                $i = $this->handleArgPropertyFile($args, $i);
576
            } elseif ($arg === "-keep-going" || $arg === "-k") {
577
                $this->keepGoingMode = true;
578
            } elseif ($arg == "-longtargets") {
579
                self::$definedProps->setProperty('phing.showlongtargets', 1);
580
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
581
                // set the flag to display the targets and quit
582
                $this->projectHelp = true;
583
            } elseif ($arg == "-find") {
584
                // eat up next arg if present, default to build.xml
585
                if ($i < count($args) - 1) {
586
                    $this->searchForThis = $args[++$i];
587
                } else {
588
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
589
                }
590
            } elseif (substr($arg, 0, 1) == "-") {
591
                // we don't have any more args
592
                self::printUsage();
593
                self::$err->write(PHP_EOL);
594
                throw new ConfigurationException("Unknown argument: " . $arg);
595
            } else {
596
                // if it's no other arg, it may be the target
597
                $this->targets[] = $arg;
598
            }
599
        }
600
601
        // if buildFile was not specified on the command line,
602
        if ($this->buildFile === null) {
603
            // but -find then search for it
604
            if ($this->searchForThis !== null) {
605
                $this->buildFile = $this->findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
606
            } else {
607
                $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
608
            }
609
        }
610
611
        try {
612
            // make sure buildfile (or buildfile.dist) exists
613
            if (!$this->buildFile->exists()) {
614
                $distFile = new File($this->buildFile->getAbsolutePath() . ".dist");
615
                if (!$distFile->exists()) {
616
                    throw new ConfigurationException(
617
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
618
                    );
619
                }
620
                $this->buildFile = $distFile;
621
            }
622
623
            // make sure it's not a directory
624
            if ($this->buildFile->isDirectory()) {
625
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
626
            }
627
        } catch (IOException $e) {
628
            // something else happened, buildfile probably not readable
629
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
630
        }
631
632
        $this->loadPropertyFiles();
633
634
        $this->readyToRun = true;
635
    }
636
637
    /**
638
     * Handle the -propertyfile argument.
639
     *
640
     *
641
     *
642
     * @throws ConfigurationException
643
     * @throws IOException
644
     */
645
    private function handleArgPropertyFile(array $args, int $pos): int
646
    {
647
        if (!isset($args[$pos + 1])) {
648
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
649
        }
650
651
        $this->propertyFiles[] = $args[++$pos];
652
653
        return $pos;
654
    }
655
656
    /**
657
     * @throws IOException
658
     */
659
    private function loadPropertyFiles()
660
    {
661
        foreach ($this->propertyFiles as $filename) {
662
            $fileParserFactory = new FileParserFactory();
663
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
664
            $p = new Properties(null, $fileParser);
665
            try {
666
                $p->load(new File($filename));
667
            } catch (IOException $e) {
668
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
669
            }
670
            foreach ($p->getProperties() as $prop => $value) {
671
                self::$definedProps->setProperty($prop, $value);
672
            }
673
        }
674
    }
675
676
    /**
677
     * Search parent directories for the build file.
678
     *
679
     * Takes the given target as a suffix to append to each
680
     * parent directory in search of a build file.  Once the
681
     * root of the file-system has been reached an exception
682
     * is thrown.
683
     *
684
     * @param string $start Start file path.
685
     * @param string $suffix Suffix filename to look for in parents.
686
     *
687
     * @return File A handle to the build file
688
     *@throws ConfigurationException
689
     *
690
     */
691
    private function findBuildFile($start, $suffix)
692
    {
693
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
694
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
695
        }
696
697
        $parent = new File((new File($start))->getAbsolutePath());
698
        $file = new File($parent, $suffix);
699
700
        // check if the target file exists in the current directory
701
        while (!$file->exists()) {
702
            // change to parent directory
703
            $parent = $parent->getParentFile();
704
705
            // if parent is null, then we are at the root of the fs,
706
            // complain that we can't find the build file.
707
            if ($parent === null) {
708
                throw new ConfigurationException("Could not locate a build file!");
709
            }
710
            // refresh our file handle
711
            $file = new File($parent, $suffix);
712
        }
713
714
        return $file;
715
    }
716
717
    /**
718
     * Executes the build.
719
     *
720
     * @throws IOException
721
     * @throws Throwable
722
     */
723
    public function runBuild(): void
724
    {
725
        if (!$this->readyToRun) {
726
            return;
727
        }
728
729
        $project = new Project();
730
731
        self::setCurrentProject($project);
732
        set_error_handler(['Phing\Phing', 'handlePhpError']);
733
734
        $error = null;
735
736
        try {
737
            $this->addBuildListeners($project);
738
            $this->addInputHandler($project);
739
740
            // set this right away, so that it can be used in logging.
741
            $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
742
            $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
743
            $project->setUserProperty("phing.version", static::getPhingVersion());
744
            $project->fireBuildStarted();
745
            $project->init();
746
            $project->setKeepGoingMode($this->keepGoingMode);
747
748
            $e = self::$definedProps->keys();
749
            while (count($e)) {
750
                $arg = (string) array_shift($e);
751
                $value = (string) self::$definedProps->getProperty($arg);
752
                $project->setUserProperty($arg, $value);
753
            }
754
            unset($e);
755
756
            // first use the Configurator to create the project object
757
            // from the given build file.
758
759
            ProjectConfigurator::configureProject($project, $this->buildFile);
760
761
            // Set the project mode
762
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
763
764
            // make sure that minimum required phing version is satisfied
765
            $this->comparePhingVersion($project->getPhingVersion());
766
767
            if ($this->projectHelp) {
768
                $this->printDescription($project);
769
                $this->printTargets($project);
770
                return;
771
            }
772
773
            // make sure that we have a target to execute
774
            if (count($this->targets) === 0) {
775
                $this->targets[] = $project->getDefaultTarget();
776
            }
777
778
            $project->executeTargets($this->targets);
779
        } catch (Throwable $t) {
780
            $error = $t;
781
            throw $t;
782
        } finally {
783
            if (!$this->projectHelp) {
784
                try {
785
                    $project->fireBuildFinished($error);
786
                } catch (Exception $e) {
787
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
788
                    self::$err->write($e->getTraceAsString());
789
                    if ($error !== null) {
790
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
791
                        self::$err->write($error->getTraceAsString());
792
                    }
793
                    throw new BuildException($error);
794
                }
795
            } elseif ($error !== null) {
796
                $project->log($error->getMessage(), Project::MSG_ERR);
797
            }
798
799
            restore_error_handler();
800
            self::unsetCurrentProject();
801
        }
802
    }
803
804
    /**
805
     * @param string $version
806
     *
807
     * @throws BuildException
808
     * @throws ConfigurationException
809
     */
810
    private function comparePhingVersion($version)
811
    {
812
        $current = strtolower(self::getPhingVersion());
813
        $current = trim(str_replace('phing', '', $current));
814
815
        // make sure that version checks are not applied to trunk
816
        if ('dev' === $current) {
817
            return;
818
        }
819
820
        if (-1 == version_compare($current, $version)) {
821
            throw new BuildException(
822
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
823
            );
824
        }
825
    }
826
827
    /**
828
     * Bind any registered build listeners to this project.
829
     *
830
     * This means adding the logger and any build listeners that were specified
831
     * with -listener arg.
832
     *
833
     * @throws BuildException
834
     * @throws ConfigurationException
835
     */
836
    private function addBuildListeners(Project $project)
837
    {
838
        // Add the default listener
839
        $project->addBuildListener($this->createLogger());
840
841
        foreach ($this->listeners as $listenerClassname) {
842
            try {
843
                $clz = Phing::import($listenerClassname);
844
            } catch (Exception $e) {
845
                $msg = "Unable to instantiate specified listener "
846
                    . "class " . $listenerClassname . " : "
847
                    . $e->getMessage();
848
                throw new ConfigurationException($msg);
849
            }
850
851
            $listener = new $clz();
852
853
            if ($listener instanceof StreamRequiredBuildLogger) {
854
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
855
            }
856
            $project->addBuildListener($listener);
857
        }
858
    }
859
860
    /**
861
     * Creates the InputHandler and adds it to the project.
862
     *
863
     * @param Project $project the project instance.
864
     *
865
     * @throws ConfigurationException
866
     */
867
    private function addInputHandler(Project $project)
868
    {
869
        if ($this->inputHandlerClassname === null) {
870
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
871
        } else {
872
            try {
873
                $clz = Phing::import($this->inputHandlerClassname);
874
                $handler = new $clz();
875
                if ($project !== null && method_exists($handler, 'setProject')) {
876
                    $handler->setProject($project);
877
                }
878
            } catch (Exception $e) {
879
                $msg = "Unable to instantiate specified input handler "
880
                    . "class " . $this->inputHandlerClassname . " : "
881
                    . $e->getMessage();
882
                throw new ConfigurationException($msg);
883
            }
884
        }
885
        $project->setInputHandler($handler);
886
    }
887
888
    /**
889
     * Creates the default build logger for sending build events to the log.
890
     *
891
     * @throws BuildException
892
     * @return BuildLogger The created Logger
893
     */
894
    private function createLogger()
895
    {
896
        if ($this->silent) {
897
            $logger = new SilentLogger();
898
            self::$msgOutputLevel = Project::MSG_WARN;
899
        } elseif ($this->loggerClassname !== null) {
900
            self::import($this->loggerClassname);
901
            // get class name part
902
            $classname = self::import($this->loggerClassname);
903
            $logger = new $classname();
904
            if (!($logger instanceof BuildLogger)) {
905
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
906
            }
907
        } else {
908
            $logger = new DefaultLogger();
909
        }
910
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
911
        $logger->setOutputStream(self::$out);
912
        $logger->setErrorStream(self::$err);
913
        $logger->setEmacsMode($this->emacsMode);
914
915
        return $logger;
916
    }
917
918
    /**
919
     * Sets the current Project
920
     *
921
     * @param Project $p
922
     */
923 1
    public static function setCurrentProject($p)
924
    {
925 1
        self::$currentProject = $p;
926 1
    }
927
928
    /**
929
     * Unsets the current Project
930
     */
931 1
    public static function unsetCurrentProject()
932
    {
933 1
        self::$currentProject = null;
934 1
    }
935
936
    /**
937
     * Gets the current Project.
938
     *
939
     * @return Project Current Project or NULL if none is set yet/still.
940
     */
941 5
    public static function getCurrentProject()
942
    {
943 5
        return self::$currentProject;
944
    }
945
946
    /**
947
     * A static convenience method to send a log to the current (last-setup) Project.
948
     * If there is no currently-configured Project, then this will do nothing.
949
     *
950
     * @param string $message
951
     * @param int $priority Project::MSG_INFO, etc.
952
     */
953
    public static function log($message, $priority = Project::MSG_INFO)
954
    {
955
        $p = self::getCurrentProject();
956
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Phing\Project, thus it always evaluated to true.
Loading history...
957
            $p->log($message, $priority);
958
        }
959
    }
960
961
    /**
962
     * Error handler for PHP errors encountered during the build.
963
     * This uses the logging for the currently configured project.
964
     *
965
     * @param $level
966
     * @param string $message
967
     * @param $file
968
     * @param $line
969
     */
970
    public static function handlePhpError($level, $message, $file, $line)
971
    {
972
973
        // don't want to print suppressed errors
974
        if (error_reporting() > 0) {
975
            if (self::$phpErrorCapture) {
976
                self::$capturedPhpErrors[] = [
977
                    'message' => $message,
978
                    'level' => $level,
979
                    'line' => $line,
980
                    'file' => $file,
981
                ];
982
            } else {
983
                $message = '[PHP Error] ' . $message;
984
                $message .= ' [line ' . $line . ' of ' . $file . ']';
985
986
                switch ($level) {
987
                    case E_USER_DEPRECATED:
988
                    case E_DEPRECATED:
989
                    case E_STRICT:
990
                    case E_NOTICE:
991
                    case E_USER_NOTICE:
992
                        self::log($message, Project::MSG_VERBOSE);
993
                        break;
994
                    case E_WARNING:
995
                    case E_USER_WARNING:
996
                        self::log($message, Project::MSG_WARN);
997
                        break;
998
                    case E_ERROR:
999
                    case E_USER_ERROR:
1000
                    default:
1001
                        self::log($message, Project::MSG_ERR);
1002
                } // switch
1003
            } // if phpErrorCapture
1004
        } // if not @
1005
    }
1006
1007
    /**
1008
     * Begins capturing PHP errors to a buffer.
1009
     * While errors are being captured, they are not logged.
1010
     */
1011
    public static function startPhpErrorCapture()
1012
    {
1013
        self::$phpErrorCapture = true;
1014
        self::$capturedPhpErrors = [];
1015
    }
1016
1017
    /**
1018
     * Stops capturing PHP errors to a buffer.
1019
     * The errors will once again be logged after calling this method.
1020
     */
1021
    public static function stopPhpErrorCapture()
1022
    {
1023
        self::$phpErrorCapture = false;
1024
    }
1025
1026
    /**
1027
     * Clears the captured errors without affecting the starting/stopping of the capture.
1028
     */
1029
    public static function clearCapturedPhpErrors()
1030
    {
1031
        self::$capturedPhpErrors = [];
1032
    }
1033
1034
    /**
1035
     * Gets any PHP errors that were captured to buffer.
1036
     *
1037
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1038
     */
1039
    public static function getCapturedPhpErrors()
1040
    {
1041
        return self::$capturedPhpErrors;
1042
    }
1043
1044
    /**
1045
     * Prints the usage of how to use this class
1046
     */
1047 1
    public static function printUsage()
1048
    {
1049 1
        $msg = "";
1050 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1051 1
        $msg .= "Options: " . PHP_EOL;
1052 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1053 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1054 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1055 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1056 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1057 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1058 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1059 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1060 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1061 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1062 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1063 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1064 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1065 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1066 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1067 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1068 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1069 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1070 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1071 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1072 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1073 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1074 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1075 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1076 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1077
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1078 1
        $msg .= PHP_EOL;
1079 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1080 1
        self::$err->write($msg);
1081 1
    }
1082
1083
    /**
1084
     * Prints the current Phing version.
1085
     */
1086
    public static function printVersion()
1087
    {
1088
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1089
    }
1090
1091
    /**
1092
     * Creates generic buildfile
1093
     *
1094
     * @param string $path
1095
     */
1096
    public static function init($path)
1097
    {
1098
        if ($buildfilePath = self::initPath($path)) {
1099
            self::initWrite($buildfilePath);
1100
        }
1101
    }
1102
1103
    /**
1104
     * Returns buildfile's path
1105
     *
1106
     * @param $path
1107
     *
1108
     * @return string
1109
     * @throws ConfigurationException
1110
     */
1111
    protected static function initPath($path)
1112
    {
1113
        // Fallback
1114
        if (empty($path)) {
1115
            $defaultDir = self::getProperty('application.startdir');
1116
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1117
        }
1118
1119
        // Adding filename if necessary
1120
        if (is_dir($path)) {
1121
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1122
        }
1123
1124
        // Check if path is available
1125
        $dirname = dirname($path);
1126
        if (is_dir($dirname) && !is_file($path)) {
1127
            return $path;
1128
        }
1129
1130
        // Path is valid, but buildfile already exists
1131
        if (is_file($path)) {
1132
            throw new ConfigurationException('Buildfile already exists.');
1133
        }
1134
1135
        throw new ConfigurationException('Invalid path for sample buildfile.');
1136
    }
1137
1138
    /**
1139
     * Writes sample buildfile
1140
     *
1141
     * If $buildfilePath does not exist, the buildfile is created.
1142
     *
1143
     * @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...
1144
     *
1145
     * @throws ConfigurationException
1146
     */
1147
    protected static function initWrite($buildfilePath)
1148
    {
1149
        // Overwriting protection
1150
        if (file_exists($buildfilePath)) {
1151
            throw new ConfigurationException('Cannot overwrite existing file.');
1152
        }
1153
1154
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1155
        $content .= '' . PHP_EOL;
1156
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1157
        $content .= '    ' . PHP_EOL;
1158
        $content .= '    <target name="" description="">' . PHP_EOL;
1159
        $content .= '        ' . PHP_EOL;
1160
        $content .= '    </target>' . PHP_EOL;
1161
        $content .= '    ' . PHP_EOL;
1162
        $content .= '</project>' . PHP_EOL;
1163
1164
        file_put_contents($buildfilePath, $content);
1165
    }
1166
1167
    /**
1168
     * Gets the current Phing version based on VERSION.TXT file.
1169
     *
1170
     * @throws ConfigurationException
1171
     *
1172
     * @return string
1173
     */
1174 6
    public static function getPhingVersion()
1175
    {
1176 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1177 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1178 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1179
        }
1180 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1181
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1182
        }
1183
        try { // try to read file
1184 6
            $file = new File($versionPath);
1185 6
            $reader = new FileReader($file);
1186 6
            $phingVersion = trim($reader->read());
1187
        } catch (IOException $iox) {
1188
            throw new ConfigurationException("Can't read version information file");
1189
        }
1190
1191 6
        $basePath = dirname(__DIR__, 2);
1192
1193 6
        $version = new Version($phingVersion, $basePath);
1194
1195 6
        return "Phing " . $version->getVersion();
1196
    }
1197
1198
    /**
1199
     * Print the project description, if any
1200
     *
1201
     *
1202
     * @throws IOException
1203
     */
1204
    public function printDescription(Project $project)
1205
    {
1206
        if ($project->getDescription() !== null) {
1207
            $project->log($project->getDescription());
1208
        }
1209
    }
1210
1211
    /**
1212
     * Print out a list of all targets in the current buildfile
1213
     */
1214 1
    public function printTargets(Project $project)
1215
    {
1216 1
        $visibleTargets = array_filter($project->getTargets(), function (Target $target) {
1217 1
            return !$target->isHidden() && !empty($target->getName());
1218 1
        });
1219 1
        $padding        = array_reduce($visibleTargets, function (int $carry, Target $target) {
1220
            return max(strlen($target->getName()), $carry);
1221 1
        }, 0);
1222
        $categories     = [
1223 1
            'Default target:' => array_filter($visibleTargets, function (Target $target) use ($project) {
1224
                return trim(strval($target)) === $project->getDefaultTarget();
1225 1
            }),
1226 1
            'Main targets:'   => array_filter($visibleTargets, function (Target $target) {
1227
                return !empty($target->getDescription());
1228 1
            }),
1229 1
            'Subtargets:'     => array_filter($visibleTargets, function (Target $target) {
1230
                return empty($target->getDescription());
1231 1
            }),
1232
        ];
1233 1
        foreach ($categories as $title => $targets) {
1234 1
            $targetList = $this->generateTargetList($title, $targets, $padding);
1235 1
            $project->log($targetList, Project::MSG_WARN);
1236
        }
1237 1
    }
1238
1239
    /**
1240
     * Returns a formatted list of target names with an optional description.
1241
     *
1242
     * @param string   $title   Title for this list
1243
     * @param Target[] $targets Targets in this list
1244
     * @param int      $padding Padding for name column
1245
     * @return string
1246
     */
1247 1
    private function generateTargetList(string $title, array $targets, int $padding): string
1248
    {
1249 1
        usort($targets, function (Target $a, Target $b) {
1250
            return $a->getName() <=> $b->getName();
1251 1
        });
1252
1253
        $header = <<<HEADER
1254 1
            $title
1255
            -------------------------------------------------------------------------------
1256
1257
            HEADER;
1258
1259 1
        $getDetails = function (Target $target) use ($padding): string {
1260
            $details = [];
1261
            if (!empty($target->getDescription())) {
1262
                $details[] = $target->getDescription();
1263
            }
1264
            if (!empty($target->getDependencies())) {
1265
                $details[] = ' - depends on: ' . implode(', ', $target->getDependencies());
1266
            }
1267
            if (!empty($target->getIf())) {
1268
                $details[] = ' - if property: ' . $target->getIf();
1269
            }
1270
            if (!empty($target->getUnless())) {
1271
                $details[] = ' - unless property: ' . $target->getUnless();
1272
            }
1273
            $detailsToString = function (?string $name, ?string $detail) use ($padding): string {
1274
                return sprintf(" %-${padding}s  %s", $name, $detail);
1275
            };
1276
1277
            return implode(PHP_EOL, array_map($detailsToString, [$target->getName()], $details));
1278 1
        };
1279
1280 1
        return $header . implode(PHP_EOL, array_map($getDetails, $targets)) . PHP_EOL;
1281
    }
1282
1283
    /**
1284
     * Import a class, supporting the following conventions:
1285
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1286
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1287
     *
1288
     * @param string $classname Name of class
1289
     * @param mixed $classpath String or object supporting __toString()
1290
     *
1291
     * @return string         The unqualified classname (which can be instantiated).
1292
     *
1293
     * @throws BuildException - if cannot find the specified file
1294
     */
1295 857
    public static function import($classname, $classpath = null)
1296
    {
1297
        // first check to see that the class specified hasn't already been included.
1298 857
        if (class_exists($classname)) {
1299 855
            return $classname;
1300
        }
1301
1302 7
        $filename = strtr($classname, ['_' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]) . ".php";
1303
1304 7
        Phing::importFile($filename, $classpath);
1305
1306 5
        return $classname;
1307
    }
1308
1309
    /**
1310
     * Import a PHP file
1311
     *
1312
     * This used to be named __import, however PHP has reserved all method names
1313
     * with a double underscore prefix for future use.
1314
     *
1315
     * @param string $path Path to the PHP file
1316
     * @param mixed $classpath String or object supporting __toString()
1317
     *
1318
     * @throws ConfigurationException
1319
     */
1320 10
    public static function importFile($path, $classpath = null)
1321
    {
1322 10
        if ($classpath) {
1323
            // Apparently casting to (string) no longer invokes __toString() automatically.
1324 2
            if (is_object($classpath)) {
1325
                $classpath = $classpath->__toString();
1326
            }
1327
1328
            // classpaths are currently additive, but we also don't want to just
1329
            // indiscriminantly prepand/append stuff to the include_path.  This means
1330
            // we need to parse current incldue_path, and prepend any
1331
            // specified classpath locations that are not already in the include_path.
1332
            //
1333
            // NOTE:  the reason why we do it this way instead of just changing include_path
1334
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1335
            // include/require class files from within method calls.  This means that not all
1336
            // necessary files will be included in this import() call, and hence we can't
1337
            // change the include_path back without breaking those apps.  While this method could
1338
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1339
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1340
            // really where speed matters more.
1341
1342 2
            $curr_parts = Phing::explodeIncludePath();
1343 2
            $add_parts = Phing::explodeIncludePath($classpath);
1344 2
            $new_parts = array_diff($add_parts, $curr_parts);
1345 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...
1346 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1347
            }
1348
        }
1349
1350 10
        $ret = include_once $path;
1351
1352 8
        if ($ret === false) {
1353
            $msg = "Error importing $path";
1354
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1355
                $x = new Exception("for-path-trace-only");
1356
                $msg .= $x->getTraceAsString();
1357
            }
1358
            throw new ConfigurationException($msg);
1359
        }
1360 8
    }
1361
1362
    /**
1363
     * Looks on include path for specified file.
1364
     *
1365
     * @param string $path
1366
     *
1367
     * @return string File found (null if no file found).
1368
     */
1369 855
    public static function getResourcePath($path)
1370
    {
1371 855
        if (self::$importPaths === null) {
1372
            self::$importPaths = self::explodeIncludePath();
1373
        }
1374
1375 855
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
1376
1377 855
        foreach (self::$importPaths as $prefix) {
1378 855
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1379 855
            if (file_exists($testPath)) {
1380
                return $testPath;
1381
            }
1382
        }
1383
1384
        // Check for the property phing.home
1385 855
        $homeDir = self::getProperty(self::PHING_HOME);
1386 855
        if ($homeDir) {
1387 855
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1388 855
            if (file_exists($testPath)) {
1389 855
                return $testPath;
1390
            }
1391
        }
1392
1393
        // Check for the phing home of phar archive
1394 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1395
            $testPath = self::$importPaths[0] . '/../' . $path;
1396
            if (file_exists($testPath)) {
1397
                return $testPath;
1398
            }
1399
        }
1400
1401
        // Do one additional check based on path of current file (Phing.php)
1402 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR);
1403 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1404 6
        if (file_exists($testPath)) {
1405
            return $testPath;
1406
        }
1407
1408 6
        return null;
1409
    }
1410
1411
    /**
1412
     * Explode an include path into an array
1413
     *
1414
     * If no path provided, uses current include_path. Works around issues that
1415
     * occur when the path includes stream schemas.
1416
     *
1417
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1418
     *
1419
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1420
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1421
     * @param     string|null $path
1422
     * @return    array
1423
     */
1424 16
    public static function explodeIncludePath($path = null)
1425
    {
1426 16
        if (null === $path) {
1427 16
            $path = get_include_path();
1428
        }
1429
1430 16
        if (PATH_SEPARATOR == ':') {
1431
            // On *nix systems, include_paths which include paths with a stream
1432
            // schema cannot be safely explode'd, so we have to be a bit more
1433
            // intelligent in the approach.
1434 16
            $paths = preg_split('#:(?!//)#', $path);
1435
        } else {
1436
            $paths = explode(PATH_SEPARATOR, $path);
1437
        }
1438
1439 16
        return $paths;
1440
    }
1441
1442
    /**
1443
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1444
     *
1445
     */
1446 1
    private static function setSystemConstants()
1447
    {
1448
1449
        /*
1450
         * PHP_OS returns on
1451
         *   WindowsNT4.0sp6  => WINNT
1452
         *   Windows2000      => WINNT
1453
         *   Windows ME       => WIN32
1454
         *   Windows 98SE     => WIN32
1455
         *   FreeBSD 4.5p7    => FreeBSD
1456
         *   Redhat Linux     => Linux
1457
         *   Mac OS X         => Darwin
1458
         */
1459 1
        self::setProperty('host.os', PHP_OS);
1460
1461
        // this is used by some tasks too
1462 1
        self::setProperty('os.name', PHP_OS);
1463
1464
        // it's still possible this won't be defined,
1465
        // e.g. if Phing is being included in another app w/o
1466
        // using the phing.php script.
1467 1
        if (!defined('PHP_CLASSPATH')) {
1468
            define('PHP_CLASSPATH', get_include_path());
1469
        }
1470
1471 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1472
1473
        // try to determine the host filesystem and set system property
1474
        // used by Fileself::getFileSystem to instantiate the correct
1475
        // abstraction layer
1476
1477 1
        if (PHP_OS_FAMILY === 'Windows') {
1478
            self::setProperty('host.fstype', 'WINDOWS');
1479
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1480
        } else {
1481 1
            self::setProperty('host.fstype', 'UNIX');
1482 1
            self::setProperty('user.home', getenv('HOME'));
1483
        }
1484 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1485 1
        self::setProperty('file.separator', FileUtils::getSeparator());
1486 1
        self::setProperty('line.separator', PHP_EOL);
1487 1
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1488 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1489 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1490 1
        self::setProperty('application.startdir', getcwd());
1491 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1492
1493
        // try to detect machine dependent information
1494 1
        $sysInfo = [];
1495 1
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1496 1
            $sysInfo = posix_uname();
1497
        } else {
1498
            $sysInfo['nodename'] = php_uname('n');
1499
            $sysInfo['machine'] = php_uname('m');
1500
            //this is a not so ideal substition, but maybe better than nothing
1501
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1502
            $sysInfo['release'] = php_uname('r');
1503
            $sysInfo['version'] = php_uname('v');
1504
        }
1505
1506 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1507 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1508 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1509 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1510 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1511 1
        unset($sysInfo);
1512 1
    }
1513
1514
    /**
1515
     * This gets a property that was set via command line or otherwise passed into Phing.
1516
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1517
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1518
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1519
     * the pear.log.name property.
1520
     *
1521
     * @param  string $name
1522
     * @return string value of found property (or null, if none found).
1523
     */
1524
    public static function getDefinedProperty($name)
1525
    {
1526
        return self::$definedProps->getProperty($name);
1527
    }
1528
1529
    /**
1530
     * This sets a property that was set via command line or otherwise passed into Phing.
1531
     *
1532
     * @param  string $name
1533
     * @return mixed value of found property (or null, if none found).
1534
     */
1535
    public static function setDefinedProperty($name, $value)
1536
    {
1537
        return self::$definedProps->setProperty($name, $value);
1538
    }
1539
1540
    /**
1541
     * Returns property value for a System property.
1542
     * System properties are "global" properties like application.startdir,
1543
     * and user.dir.  Many of these correspond to similar properties in Java
1544
     * or Ant.
1545
     *
1546
     * @param  string $propName
1547
     * @return string Value of found property (or null, if none found).
1548
     */
1549 873
    public static function getProperty($propName)
1550
    {
1551
1552
        // some properties are detemined on each access
1553
        // some are cached, see below
1554
1555
        // default is the cached value:
1556 873
        $val = self::$properties[$propName] ?? null;
1557
1558
        // special exceptions
1559
        switch ($propName) {
1560 873
            case 'user.dir':
1561 142
                $val = getcwd();
1562 142
                break;
1563
        }
1564
1565 873
        return $val;
1566
    }
1567
1568
    /**
1569
     * Retuns reference to all properties
1570
     */
1571 854
    public static function &getProperties()
1572
    {
1573 854
        return self::$properties;
1574
    }
1575
1576
    /**
1577
     * @param $propName
1578
     * @param $propValue
1579
     * @return string
1580
     */
1581 8
    public static function setProperty($propName, $propValue)
1582
    {
1583 8
        $propName = (string) $propName;
1584 8
        $oldValue = self::getProperty($propName);
1585 8
        self::$properties[$propName] = $propValue;
1586
1587 8
        return $oldValue;
1588
    }
1589
1590
    /**
1591
     * @return float
1592
     */
1593 73
    public static function currentTimeMillis()
1594
    {
1595 73
        [$usec, $sec] = explode(" ", microtime());
1596
1597 73
        return ((float) $usec + (float) $sec);
1598
    }
1599
1600
    /**
1601
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1602
     *
1603
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1604
     */
1605 1
    private static function setIncludePaths()
1606
    {
1607 1
        if (defined('PHP_CLASSPATH')) {
1608 1
            $result = set_include_path(PHP_CLASSPATH);
1609 1
            if ($result === false) {
1610
                throw new ConfigurationException("Could not set PHP include_path.");
1611
            }
1612 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1613
        }
1614 1
    }
1615
1616
    /**
1617
     * Sets PHP INI values that Phing needs.
1618
     */
1619 1
    private static function setIni(): void
1620
    {
1621 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1622
1623
        // We won't bother storing original max_execution_time, since 1) the value in
1624
        // php.ini may be wrong (and there's no way to get the current value) and
1625
        // 2) it would mean something very strange to set it to a value less than time script
1626
        // has already been running, which would be the likely change.
1627
1628 1
        set_time_limit(0);
1629
1630 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1631 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1632
1633 1
        $mem_limit = (int) SizeHelper::fromHumanToBytes(ini_get('memory_limit'));
1634 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1635
            // We do *not* need to save the original value here, since we don't plan to restore
1636
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1637
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1638
        }
1639 1
    }
1640
1641
    /**
1642
     * Restores [most] PHP INI values to their pre-Phing state.
1643
     *
1644
     * Currently the following settings are not restored:
1645
     *  - max_execution_time (because getting current time limit is not possible)
1646
     *  - memory_limit (which may have been increased by Phing)
1647
     */
1648 1
    private static function restoreIni(): void
1649
    {
1650 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1651 1
            switch ($settingName) {
1652 1
                case 'error_reporting':
1653 1
                    error_reporting($settingValue);
1654 1
                    break;
1655
                default:
1656 1
                    ini_set($settingName, $settingValue);
1657
            }
1658
        }
1659 1
    }
1660
1661
    /**
1662
     * Returns reference to Timer object.
1663
     *
1664
     */
1665 2
    public static function getTimer(): Timer
1666
    {
1667 2
        if (self::$timer === null) {
1668
            self::$timer = new Timer();
1669
        }
1670
1671 2
        return self::$timer;
1672
    }
1673
1674
    /**
1675
     * Start up Phing.
1676
     * Sets up the Phing environment but does not initiate the build process.
1677
     *
1678
     * @throws Exception - If the Phing environment cannot be initialized.
1679
     */
1680 1
    public static function startup(): void
1681
    {
1682
1683
        // setup STDOUT and STDERR defaults
1684 1
        self::initializeOutputStreams();
1685
1686
        // some init stuff
1687 1
        self::getTimer()->start();
1688
1689 1
        self::setSystemConstants();
1690 1
        self::setIncludePaths();
1691 1
        self::setIni();
1692 1
    }
1693
1694
    /**
1695
     * Performs any shutdown routines, such as stopping timers.
1696
     *
1697
     * @throws IOException
1698
     */
1699 1
    public static function shutdown(): void
1700
    {
1701 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
1702 1
        self::$msgOutputLevel = Project::MSG_INFO;
1703 1
        self::restoreIni();
1704 1
        self::getTimer()->stop();
1705 1
    }
1706
}
1707