Passed
Push — main ( 78e7ac...e705ca )
by Siad
23:32
created

Phing::printTargets()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0987

Importance

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