Passed
Push — main ( 4889b2...808ce0 )
by Michiel
07:43
created

Phing::_printTargets()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.3949

Importance

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