Passed
Push — master ( b8e482...12f115 )
by Siad
11:08
created

Phing::comparePhingVersion()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 13
ccs 0
cts 8
cp 0
crap 12
rs 10
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
use SebastianBergmann\Version;
21
use Symfony\Component\Console\Output\ConsoleOutput;
22
23
/**
24
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
25
 * parsing & handling commandline arguments to assembling the project to shutting down
26
 * and cleaning up in the end.
27
 *
28
 * If you are invoking Phing from an external application, this is still
29
 * the class to use.  Your application can invoke the start() method, passing
30
 * any commandline arguments or additional properties.
31
 *
32
 * @author Andreas Aderhold <[email protected]>
33
 * @author Hans Lellelid <[email protected]>
34
 *
35
 * @package phing
36
 */
37
class Phing
38
{
39
    /**
40
     * Alias for phar file
41
     */
42
    public const PHAR_ALIAS = 'phing.phar';
43
44
    /**
45
     * The default build file name
46
     */
47
    public const DEFAULT_BUILD_FILENAME = "build.xml";
48
    public const PHING_HOME = 'phing.home';
49
    public const PHP_VERSION = 'php.version';
50
    public const PHP_INTERPRETER = 'php.interpreter';
51
52
    /**
53
     * Our current message output status. Follows Project::MSG_XXX
54
     */
55
    private static $msgOutputLevel = Project::MSG_INFO;
56
57
    /**
58
     * PhingFile that we are using for configuration
59
     */
60
    private $buildFile = null;
61
62
    /**
63
     * The build targets
64
     */
65
    private $targets = [];
66
67
    /**
68
     * Set of properties that are passed in from commandline or invoking code.
69
     *
70
     * @var Properties
71
     */
72
    private static $definedProps;
73
74
    /**
75
     * Names of classes to add as listeners to project
76
     */
77
    private $listeners = [];
78
79
    /**
80
     * keep going mode
81
     *
82
     * @var bool $keepGoingMode
83
     */
84
    private $keepGoingMode = false;
85
86
    private $loggerClassname = null;
87
88
    /**
89
     * The class to handle input (can be only one).
90
     */
91
    private $inputHandlerClassname;
92
93
    /**
94
     * Whether or not log output should be reduced to the minimum.
95
     *
96
     * @var bool $silent
97
     */
98
    private $silent = false;
99
100
    /**
101
     * Indicates whether phing should run in strict mode
102
     */
103
    private $strictMode = false;
104
105
    /**
106
     * Indicates if this phing should be run
107
     */
108
    private $readyToRun = false;
109
110
    /**
111
     * Indicates we should only parse and display the project help information
112
     */
113
    private $projectHelp = false;
114
115
    /**
116
     * Used by utility function getResourcePath()
117
     */
118
    private static $importPaths;
119
120
    /**
121
     * System-wide static properties (moved from System)
122
     */
123
    private static $properties = [];
124
125
    /**
126
     * Static system timer.
127
     */
128
    private static $timer;
129
130
    /**
131
     * The current Project
132
     */
133
    private static $currentProject;
134
135
    /**
136
     * Whether to capture PHP errors to buffer.
137
     */
138
    private static $phpErrorCapture = false;
139
140
    /**
141
     * Whether to values in a property file should override existing values.
142
     */
143
    private $propertyFileOverride = false;
144
145
    /**
146
     * Array of captured PHP errors
147
     */
148
    private static $capturedPhpErrors = [];
149
150
    /**
151
     * @var OUtputStream Stream for standard output.
152
     */
153
    private static $out;
154
155
    /**
156
     * @var OutputStream Stream for error output.
157
     */
158
    private static $err;
159
160
    /**
161
     * @var boolean Whether we are using a logfile.
162
     */
163
    private static $isLogFileUsed = false;
164
165
    /**
166
     * Array to hold original ini settings that Phing changes (and needs
167
     * to restore in restoreIni() method).
168
     *
169
     * @var array Struct of array(setting-name => setting-value)
170
     * @see restoreIni()
171
     */
172
    private static $origIniSettings = [];
173
174
    /**
175
     * Whether or not output to the log is to be unadorned.
176
     */
177
    private $emacsMode = false;
178
179
    /**
180
     * @var string
181
     */
182
    private $searchForThis;
183
    private $propertyFiles = [];
184
185
    /**
186
     * Entry point allowing for more options from other front ends.
187
     *
188
     * This method encapsulates the complete build lifecycle.
189
     *
190
     * @param  array $args The commandline args passed to phing shell script.
191
     * @param  array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
192
     *                                         These additional properties will be available using the getDefinedProperty() method and will
193
     *                                         be added to the project's "user" properties
194
     * @see    execute()
195
     * @see    runBuild()
196
     * @throws Exception - if there is an error during build
197
     */
198
    public static function start($args, array $additionalUserProperties = null)
199
    {
200
        try {
201
            $m = new self();
202
            $m->execute($args);
203
        } catch (Exception $exc) {
204
            self::handleLogfile();
205
            self::printMessage($exc);
206
            self::statusExit(1);
207
            return;
208
        }
209
210
        if ($additionalUserProperties !== null) {
211
            foreach ($additionalUserProperties as $key => $value) {
212
                $m::setDefinedProperty($key, $value);
213
            }
214
        }
215
216
        // expect the worst
217
        $exitCode = 1;
218
        try {
219
            try {
220
                $m->runBuild();
221
                $exitCode = 0;
222
            } catch (ExitStatusException $ese) {
223
                $exitCode = $ese->getCode();
224
                if ($exitCode !== 0) {
225
                    self::handleLogfile();
226
                    throw $ese;
227
                }
228
            }
229
        } catch (BuildException $exc) {
230
            // avoid printing output twice: self::printMessage($exc);
231
        } catch (Throwable $exc) {
232
            self::printMessage($exc);
233
        } finally {
234
            self::handleLogfile();
235
        }
236
        self::statusExit($exitCode);
237
    }
238
239
    /**
240
     * This operation is expected to call `exit($int)`, which
241
     * is what the base version does.
242
     * However, it is possible to do something else.
243
     *
244
     * @param int $exitCode code to exit with
245
     */
246
    protected static function statusExit($exitCode)
247
    {
248
        Phing::shutdown();
249
        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...
250
    }
251
252
    /**
253
     * Prints the message of the Exception if it's not null.
254
     *
255
     * @param Throwable $t
256
     */
257
    public static function printMessage(Throwable $t)
258
    {
259
        if (self::$err === null) { // Make sure our error output is initialized
260
            self::initializeOutputStreams();
261
        }
262
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
263
            self::$err->write((string) $t . PHP_EOL);
264
        } else {
265
            self::$err->write($t->getMessage() . PHP_EOL);
266
        }
267
    }
268
269
    /**
270
     * Sets the stdout and stderr streams if they are not already set.
271
     */
272 1
    private static function initializeOutputStreams()
273
    {
274 1
        if (self::$out === null) {
275
            if (!defined('STDOUT')) {
276
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://stdout', 'w') can also be of type false; however, parameter $stream of OutputStream::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

276
                self::$out = new OutputStream(/** @scrutinizer ignore-type */ fopen('php://stdout', 'w'));
Loading history...
277
            } else {
278
                self::$out = new OutputStream(STDOUT);
279
            }
280
        }
281 1
        if (self::$err === null) {
282
            if (!defined('STDERR')) {
283
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
284
            } else {
285
                self::$err = new OutputStream(STDERR);
286
            }
287
        }
288 1
    }
289
290
    /**
291
     * Sets the stream to use for standard (non-error) output.
292
     *
293
     * @param OutputStream $stream The stream to use for standard output.
294
     */
295 1
    public static function setOutputStream(OutputStream $stream)
296
    {
297 1
        self::$out = $stream;
298 1
    }
299
300
    /**
301
     * Gets the stream to use for standard (non-error) output.
302
     *
303
     * @return OutputStream
304
     */
305
    public static function getOutputStream()
306
    {
307
        return self::$out;
308
    }
309
310
    /**
311
     * Sets the stream to use for error output.
312
     *
313
     * @param OutputStream $stream The stream to use for error output.
314
     */
315 1
    public static function setErrorStream(OutputStream $stream)
316
    {
317 1
        self::$err = $stream;
318 1
    }
319
320
    /**
321
     * Gets the stream to use for error output.
322
     *
323
     * @return OutputStream
324
     */
325
    public static function getErrorStream()
326
    {
327
        return self::$err;
328
    }
329
330
    /**
331
     * Close logfiles, if we have been writing to them.
332
     *
333
     * @since Phing 2.3.0
334
     *
335
     * @return void
336
     */
337
    private static function handleLogfile()
338
    {
339
        if (self::$isLogFileUsed) {
340
            self::$err->close();
341
            self::$out->close();
342
        }
343
    }
344
345
    /**
346
     * Making output level a static property so that this property
347
     * can be accessed by other parts of the system, enabling
348
     * us to display more information -- e.g. backtraces -- for "debug" level.
349
     *
350
     * @return int
351
     */
352 4
    public static function getMsgOutputLevel()
353
    {
354 4
        return self::$msgOutputLevel;
355
    }
356
357
    /**
358
     * Command line entry point. This method kicks off the building
359
     * of a project object and executes a build using either a given
360
     * target or the default target.
361
     *
362
     * @param array $args Command line args.
363
     *
364
     * @return void
365
     */
366
    public static function fire($args)
367
    {
368
        self::start($args, null);
369
    }
370
371
    /**
372
     * Setup/initialize Phing environment from commandline args.
373
     *
374
     * @param array $args commandline args passed to phing shell.
375
     *
376
     * @throws ConfigurationException
377
     *
378
     * @return void
379
     */
380
    public function execute($args)
381
    {
382
        self::$definedProps = new Properties();
383
        $this->searchForThis = null;
384
385
        // 1) First handle any options which should always
386
        // Note: The order in which these are executed is important (if multiple of these options are specified)
387
388
        if (in_array('-help', $args) || in_array('-h', $args)) {
389
            static::printUsage();
390
391
            return;
392
        }
393
394
        if (in_array('-version', $args) || in_array('-v', $args)) {
395
            static::printVersion();
396
397
            return;
398
        }
399
400
        if (in_array('-init', $args) || in_array('-i', $args)) {
401
            $key = array_search('-init', $args) ?: array_search('-i', $args);
402
            $path = $args[$key + 1] ?? null;
403
404
            self::init($path);
405
406
            return;
407
        }
408
409
        if (in_array('-diagnostics', $args)) {
410
            Diagnostics::doReport(new PrintStream(self::$out));
411
412
            return;
413
        }
414
415
        // 2) Next pull out stand-alone args.
416
        // Note: The order in which these are executed is important (if multiple of these options are specified)
417
418
        if (
419
            false !== ($key = array_search('-quiet', $args, true)) ||
420
            false !== ($key = array_search(
421
                '-q',
422
                $args,
423
                true
424
            ))
425
        ) {
426
            self::$msgOutputLevel = Project::MSG_WARN;
427
            unset($args[$key]);
428
        }
429
430
        if (
431
            false !== ($key = array_search('-emacs', $args, true))
432
            || false !== ($key = array_search('-e', $args, true))
433
        ) {
434
            $this->emacsMode = true;
435
            unset($args[$key]);
436
        }
437
438
        if (false !== ($key = array_search('-verbose', $args, true))) {
439
            self::$msgOutputLevel = Project::MSG_VERBOSE;
440
            unset($args[$key]);
441
        }
442
443
        if (false !== ($key = array_search('-debug', $args, true))) {
444
            self::$msgOutputLevel = Project::MSG_DEBUG;
445
            unset($args[$key]);
446
        }
447
448
        if (
449
            false !== ($key = array_search('-silent', $args, true))
450
            || false !== ($key = array_search('-S', $args, true))
451
        ) {
452
            $this->silent = true;
453
            unset($args[$key]);
454
        }
455
456
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
457
            $this->propertyFileOverride = true;
458
            unset($args[$key]);
459
        }
460
461
        // 3) Finally, cycle through to parse remaining args
462
        //
463
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
464
        $max = $keys ? max($keys) : -1;
465
        for ($i = 0; $i <= $max; $i++) {
466
            if (!array_key_exists($i, $args)) {
467
                // skip this argument, since it must have been removed above.
468
                continue;
469
            }
470
471
            $arg = $args[$i];
472
473
            if ($arg == "-logfile") {
474
                try {
475
                    // see: http://phing.info/trac/ticket/65
476
                    if (!isset($args[$i + 1])) {
477
                        $msg = "You must specify a log file when using the -logfile argument\n";
478
                        throw new ConfigurationException($msg);
479
                    }
480
481
                    $logFile = new PhingFile($args[++$i]);
482
                    $out = new FileOutputStream($logFile); // overwrite
483
                    self::setOutputStream($out);
484
                    self::setErrorStream($out);
485
                    self::$isLogFileUsed = true;
486
                } catch (IOException $ioe) {
487
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
488
                    throw new ConfigurationException($msg, $ioe);
489
                }
490
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
491
                if (!isset($args[$i + 1])) {
492
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
493
                    throw new ConfigurationException($msg);
494
                }
495
496
                $this->buildFile = new PhingFile($args[++$i]);
497
            } elseif ($arg == "-listener") {
498
                if (!isset($args[$i + 1])) {
499
                    $msg = "You must specify a listener class when using the -listener argument";
500
                    throw new ConfigurationException($msg);
501
                }
502
503
                $this->listeners[] = $args[++$i];
504
            } elseif (StringHelper::startsWith("-D", $arg)) {
505
                // Evaluating the property information //
506
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
507
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
508
                    $name = $args[++$i];
509
                } else {
510
                    $name = substr($arg, 2);
511
                }
512
513
                $value = null;
514
                $posEq = strpos($name, "=");
515
                if ($posEq !== false) {
516
                    $value = substr($name, $posEq + 1);
517
                    $name = substr($name, 0, $posEq);
518
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
519
                    $value = $args[++$i];
520
                }
521
                self::$definedProps->setProperty($name, $value);
522
            } elseif ($arg == "-logger") {
523
                if (!isset($args[$i + 1])) {
524
                    $msg = "You must specify a classname when using the -logger argument";
525
                    throw new ConfigurationException($msg);
526
                }
527
528
                $this->loggerClassname = $args[++$i];
529
            } elseif ($arg == "-no-strict") {
530
                $this->strictMode = false;
531
            } elseif ($arg == "-strict") {
532
                $this->strictMode = true;
533
            } elseif ($arg == "-inputhandler") {
534
                if ($this->inputHandlerClassname !== null) {
535
                    throw new ConfigurationException("Only one input handler class may be specified.");
536
                }
537
                if (!isset($args[$i + 1])) {
538
                    $msg = "You must specify a classname when using the -inputhandler argument";
539
                    throw new ConfigurationException($msg);
540
                }
541
542
                $this->inputHandlerClassname = $args[++$i];
543
            } elseif ($arg === "-propertyfile") {
544
                $i = $this->handleArgPropertyFile($args, $i);
545
            } elseif ($arg === "-keep-going" || $arg === "-k") {
546
                $this->keepGoingMode = true;
547
            } elseif ($arg == "-longtargets") {
548
                self::$definedProps->setProperty('phing.showlongtargets', 1);
549
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
550
                // set the flag to display the targets and quit
551
                $this->projectHelp = true;
552
            } elseif ($arg == "-find") {
553
                // eat up next arg if present, default to build.xml
554
                if ($i < count($args) - 1) {
555
                    $this->searchForThis = $args[++$i];
556
                } else {
557
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
558
                }
559
            } elseif (substr($arg, 0, 1) == "-") {
560
                // we don't have any more args
561
                self::printUsage();
562
                self::$err->write(PHP_EOL);
563
                throw new ConfigurationException("Unknown argument: " . $arg);
564
            } else {
565
                // if it's no other arg, it may be the target
566
                $this->targets[] = $arg;
567
            }
568
        }
569
570
        // if buildFile was not specified on the command line,
571
        if ($this->buildFile === null) {
572
            // but -find then search for it
573
            if ($this->searchForThis !== null) {
574
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
575
            } else {
576
                $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
577
            }
578
        }
579
580
        try {
581
            // make sure buildfile (or buildfile.dist) exists
582
            if (!$this->buildFile->exists()) {
583
                $distFile = new PhingFile($this->buildFile->getAbsolutePath() . ".dist");
584
                if (!$distFile->exists()) {
585
                    throw new ConfigurationException(
586
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
587
                    );
588
                }
589
                $this->buildFile = $distFile;
590
            }
591
592
            // make sure it's not a directory
593
            if ($this->buildFile->isDirectory()) {
594
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
595
            }
596
        } catch (IOException $e) {
597
            // something else happened, buildfile probably not readable
598
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
599
        }
600
601
        $this->loadPropertyFiles();
602
603
        $this->readyToRun = true;
604
    }
605
606
    /**
607
     * Handle the -propertyfile argument.
608
     *
609
     * @param array $args
610
     * @param int $pos
611
     *
612
     * @return int
613
     *
614
     * @throws ConfigurationException
615
     * @throws IOException
616
     */
617
    private function handleArgPropertyFile(array $args, int $pos): int
618
    {
619
        if (!isset($args[$pos + 1])) {
620
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
621
        }
622
623
        $this->propertyFiles[] = $args[++$pos];
624
625
        return $pos;
626
    }
627
628
    /**
629
     * @throws IOException
630
     */
631
    private function loadPropertyFiles()
632
    {
633
        foreach ($this->propertyFiles as $filename) {
634
            $fileParserFactory = new FileParserFactory();
635
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
636
            $p = new Properties(null, $fileParser);
637
            try {
638
                $p->load(new PhingFile($filename));
639
            } catch (IOException $e) {
640
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
641
            }
642
            foreach ($p->getProperties() as $prop => $value) {
643
                self::$definedProps->setProperty($prop, $value);
644
            }
645
        }
646
    }
647
648
    /**
649
     * Search parent directories for the build file.
650
     *
651
     * Takes the given target as a suffix to append to each
652
     * parent directory in search of a build file.  Once the
653
     * root of the file-system has been reached an exception
654
     * is thrown.
655
     *
656
     * @param string $start Start file path.
657
     * @param string $suffix Suffix filename to look for in parents.
658
     *
659
     * @throws ConfigurationException
660
     *
661
     * @return PhingFile A handle to the build file
662
     */
663
    private function _findBuildFile($start, $suffix)
664
    {
665
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
666
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
667
        }
668
669
        $parent = new PhingFile((new PhingFile($start))->getAbsolutePath());
670
        $file = new PhingFile($parent, $suffix);
671
672
        // check if the target file exists in the current directory
673
        while (!$file->exists()) {
674
            // change to parent directory
675
            $parent = $parent->getParentFile();
676
677
            // if parent is null, then we are at the root of the fs,
678
            // complain that we can't find the build file.
679
            if ($parent === null) {
680
                throw new ConfigurationException("Could not locate a build file!");
681
            }
682
            // refresh our file handle
683
            $file = new PhingFile($parent, $suffix);
684
        }
685
686
        return $file;
687
    }
688
689
    /**
690
     * Executes the build.
691
     *
692
     * @throws ConfigurationException
693
     * @throws Exception
694
     *
695
     * @return void
696
     */
697
    public function runBuild()
698
    {
699
        if (!$this->readyToRun) {
700
            return;
701
        }
702
703
        $project = new Project();
704
705
        self::setCurrentProject($project);
706
        set_error_handler(['Phing', 'handlePhpError']);
707
708
        $error = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $error is dead and can be removed.
Loading history...
709
710
        $this->addBuildListeners($project);
711
        $this->addInputHandler($project);
712
713
        // set this right away, so that it can be used in logging.
714
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
715
        $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
716
717
        try {
718
            $project->fireBuildStarted();
719
            $project->init();
720
        } catch (Exception $exc) {
721
            $project->fireBuildFinished($exc);
722
            throw $exc;
723
        }
724
725
        $project->setKeepGoingMode($this->keepGoingMode);
726
727
        $project->setUserProperty("phing.version", static::getPhingVersion());
728
729
        $e = self::$definedProps->keys();
730
        while (count($e)) {
731
            $arg = (string) array_shift($e);
732
            $value = (string) self::$definedProps->getProperty($arg);
733
            $project->setUserProperty($arg, $value);
734
        }
735
        unset($e);
736
737
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
738
739
        // first use the Configurator to create the project object
740
        // from the given build file.
741
742
        try {
743
            ProjectConfigurator::configureProject($project, $this->buildFile);
744
        } catch (Exception $exc) {
745
            $project->fireBuildFinished($exc);
746
            restore_error_handler();
747
            self::unsetCurrentProject();
748
            throw $exc;
749
        }
750
751
        // Set the project mode
752
        $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
753
754
        // make sure that we have a target to execute
755
        if (count($this->targets) === 0) {
756
            $this->targets[] = $project->getDefaultTarget();
757
        }
758
759
        // make sure that minimum required phing version is satisfied
760
        try {
761
            $this->comparePhingVersion($project->getPhingVersion());
762
        } catch (Exception $exc) {
763
            $project->fireBuildFinished($exc);
764
            restore_error_handler();
765
            self::unsetCurrentProject();
766
            throw $exc;
767
        }
768
769
        // execute targets if help param was not given
770
        if (!$this->projectHelp) {
771
            try {
772
                $project->executeTargets($this->targets);
773
            } catch (Exception $exc) {
774
                $project->fireBuildFinished($exc);
775
                restore_error_handler();
776
                self::unsetCurrentProject();
777
                throw $exc;
778
            }
779
        }
780
        // if help is requested print it
781
        if ($this->projectHelp) {
782
            try {
783
                $this->printDescription($project);
784
                $this->printTargets($project);
785
            } catch (Exception $exc) {
786
                $project->fireBuildFinished($exc);
787
                restore_error_handler();
788
                self::unsetCurrentProject();
789
                throw $exc;
790
            }
791
        }
792
793
        // finally {
794
        if (!$this->projectHelp) {
795
            $project->fireBuildFinished(null);
796
        }
797
798
        restore_error_handler();
799
        self::unsetCurrentProject();
800
    }
801
802
    /**
803
     * @param string $version
804
     *
805
     * @return int
806
     *
807
     * @throws BuildException
808
     * @throws ConfigurationException
809
     */
810
    private function comparePhingVersion($version)
811
    {
812
        $current = strtolower(self::getPhingVersion());
813
        $current = trim(str_replace('phing', '', $current));
814
815
        // make sure that version checks are not applied to trunk
816
        if ('dev' === $current) {
817
            return 1;
818
        }
819
820
        if (-1 == version_compare($current, $version)) {
821
            throw new BuildException(
822
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
823
            );
824
        }
825
    }
826
827
    /**
828
     * Bind any registered build listeners to this project.
829
     *
830
     * This means adding the logger and any build listeners that were specified
831
     * with -listener arg.
832
     *
833
     * @param  Project $project
834
     * @throws BuildException
835
     * @throws ConfigurationException
836
     * @return void
837
     */
838
    private function addBuildListeners(Project $project)
839
    {
840
        // Add the default listener
841
        $project->addBuildListener($this->createLogger());
842
843
        foreach ($this->listeners as $listenerClassname) {
844
            try {
845
                $clz = Phing::import($listenerClassname);
846
            } catch (Exception $e) {
847
                $msg = "Unable to instantiate specified listener "
848
                    . "class " . $listenerClassname . " : "
849
                    . $e->getMessage();
850
                throw new ConfigurationException($msg);
851
            }
852
853
            $listener = new $clz();
854
855
            if ($listener instanceof StreamRequiredBuildLogger) {
856
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
857
            }
858
            $project->addBuildListener($listener);
859
        }
860
    }
861
862
    /**
863
     * Creates the InputHandler and adds it to the project.
864
     *
865
     * @param Project $project the project instance.
866
     *
867
     * @throws ConfigurationException
868
     */
869
    private function addInputHandler(Project $project)
870
    {
871
        if ($this->inputHandlerClassname === null) {
872
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
873
        } else {
874
            try {
875
                $clz = Phing::import($this->inputHandlerClassname);
876
                $handler = new $clz();
877
                if ($project !== null && method_exists($handler, 'setProject')) {
878
                    $handler->setProject($project);
879
                }
880
            } catch (Exception $e) {
881
                $msg = "Unable to instantiate specified input handler "
882
                    . "class " . $this->inputHandlerClassname . " : "
883
                    . $e->getMessage();
884
                throw new ConfigurationException($msg);
885
            }
886
        }
887
        $project->setInputHandler($handler);
888
    }
889
890
    /**
891
     * Creates the default build logger for sending build events to the log.
892
     *
893
     * @throws BuildException
894
     * @return BuildLogger The created Logger
895
     */
896
    private function createLogger()
897
    {
898
        if ($this->silent) {
899
            $logger = new SilentLogger();
900
            self::$msgOutputLevel = Project::MSG_WARN;
901
        } elseif ($this->loggerClassname !== null) {
902
            self::import($this->loggerClassname);
903
            // get class name part
904
            $classname = self::import($this->loggerClassname);
905
            $logger = new $classname();
906
            if (!($logger instanceof BuildLogger)) {
907
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
908
            }
909
        } else {
910
            $logger = new DefaultLogger();
911
        }
912
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
913
        $logger->setOutputStream(self::$out);
914
        $logger->setErrorStream(self::$err);
915
        $logger->setEmacsMode($this->emacsMode);
916
917
        return $logger;
918
    }
919
920
    /**
921
     * Sets the current Project
922
     *
923
     * @param Project $p
924
     */
925 1
    public static function setCurrentProject($p)
926
    {
927 1
        self::$currentProject = $p;
928 1
    }
929
930
    /**
931
     * Unsets the current Project
932
     */
933 1
    public static function unsetCurrentProject()
934
    {
935 1
        self::$currentProject = null;
936 1
    }
937
938
    /**
939
     * Gets the current Project.
940
     *
941
     * @return Project Current Project or NULL if none is set yet/still.
942
     */
943 5
    public static function getCurrentProject()
944
    {
945 5
        return self::$currentProject;
946
    }
947
948
    /**
949
     * A static convenience method to send a log to the current (last-setup) Project.
950
     * If there is no currently-configured Project, then this will do nothing.
951
     *
952
     * @param string $message
953
     * @param int $priority Project::MSG_INFO, etc.
954
     */
955
    public static function log($message, $priority = Project::MSG_INFO)
956
    {
957
        $p = self::getCurrentProject();
958
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Project, thus it always evaluated to true.
Loading history...
959
            $p->log($message, $priority);
960
        }
961
    }
962
963
    /**
964
     * Error handler for PHP errors encountered during the build.
965
     * This uses the logging for the currently configured project.
966
     *
967
     * @param $level
968
     * @param string $message
969
     * @param $file
970
     * @param $line
971
     */
972
    public static function handlePhpError($level, $message, $file, $line)
973
    {
974
975
        // don't want to print suppressed errors
976
        if (error_reporting() > 0) {
977
            if (self::$phpErrorCapture) {
978
                self::$capturedPhpErrors[] = [
979
                    'message' => $message,
980
                    'level' => $level,
981
                    'line' => $line,
982
                    'file' => $file
983
                ];
984
            } else {
985
                $message = '[PHP Error] ' . $message;
986
                $message .= ' [line ' . $line . ' of ' . $file . ']';
987
988
                switch ($level) {
989
                    case E_USER_DEPRECATED:
990
                    case E_DEPRECATED:
991
                    case E_STRICT:
992
                    case E_NOTICE:
993
                    case E_USER_NOTICE:
994
                        self::log($message, Project::MSG_VERBOSE);
995
                        break;
996
                    case E_WARNING:
997
                    case E_USER_WARNING:
998
                        self::log($message, Project::MSG_WARN);
999
                        break;
1000
                    case E_ERROR:
1001
                    case E_USER_ERROR:
1002
                    default:
1003
                        self::log($message, Project::MSG_ERR);
1004
                } // switch
1005
            } // if phpErrorCapture
1006
        } // if not @
1007
    }
1008
1009
    /**
1010
     * Begins capturing PHP errors to a buffer.
1011
     * While errors are being captured, they are not logged.
1012
     */
1013
    public static function startPhpErrorCapture()
1014
    {
1015
        self::$phpErrorCapture = true;
1016
        self::$capturedPhpErrors = [];
1017
    }
1018
1019
    /**
1020
     * Stops capturing PHP errors to a buffer.
1021
     * The errors will once again be logged after calling this method.
1022
     */
1023
    public static function stopPhpErrorCapture()
1024
    {
1025
        self::$phpErrorCapture = false;
1026
    }
1027
1028
    /**
1029
     * Clears the captured errors without affecting the starting/stopping of the capture.
1030
     */
1031
    public static function clearCapturedPhpErrors()
1032
    {
1033
        self::$capturedPhpErrors = [];
1034
    }
1035
1036
    /**
1037
     * Gets any PHP errors that were captured to buffer.
1038
     *
1039
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1040
     */
1041
    public static function getCapturedPhpErrors()
1042
    {
1043
        return self::$capturedPhpErrors;
1044
    }
1045
1046
    /**
1047
     * Prints the usage of how to use this class
1048
     */
1049 1
    public static function printUsage()
1050
    {
1051 1
        $msg = "";
1052 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1053 1
        $msg .= "Options: " . PHP_EOL;
1054 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1055 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1056 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1057 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1058 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1059 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1060 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1061 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1062 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1063 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1064 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1065 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1066 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1067 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1068 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1069 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1070 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1071 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1072 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1073 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1074 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1075 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1076 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1077 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1078 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1079
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1080 1
        $msg .= PHP_EOL;
1081 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1082 1
        self::$err->write($msg);
1083 1
    }
1084
1085
    /**
1086
     * Prints the current Phing version.
1087
     */
1088
    public static function printVersion()
1089
    {
1090
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1091
    }
1092
1093
    /**
1094
     * Creates generic buildfile
1095
     *
1096
     * @param string $path
1097
     */
1098
    public static function init($path)
1099
    {
1100
        if ($buildfilePath = self::initPath($path)) {
1101
            self::initWrite($buildfilePath);
1102
        }
1103
    }
1104
1105
1106
    /**
1107
     * Returns buildfile's path
1108
     *
1109
     * @param $path
1110
     *
1111
     * @return string
1112
     * @throws ConfigurationException
1113
     */
1114
    protected static function initPath($path)
1115
    {
1116
        // Fallback
1117
        if (empty($path)) {
1118
            $defaultDir = self::getProperty('application.startdir');
1119
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1120
        }
1121
1122
        // Adding filename if necessary
1123
        if (is_dir($path)) {
1124
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1125
        }
1126
1127
        // Check if path is available
1128
        $dirname = dirname($path);
1129
        if (is_dir($dirname) && !is_file($path)) {
1130
            return $path;
1131
        }
1132
1133
        // Path is valid, but buildfile already exists
1134
        if (is_file($path)) {
1135
            throw new ConfigurationException('Buildfile already exists.');
1136
        }
1137
1138
        throw new ConfigurationException('Invalid path for sample buildfile.');
1139
    }
1140
1141
1142
    /**
1143
     * Writes sample buildfile
1144
     *
1145
     * If $buildfilePath does not exist, the buildfile is created.
1146
     *
1147
     * @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...
1148
     *
1149
     * @return null
1150
     * @throws ConfigurationException
1151
     */
1152
    protected static function initWrite($buildfilePath)
1153
    {
1154
        // Overwriting protection
1155
        if (file_exists($buildfilePath)) {
1156
            throw new ConfigurationException('Cannot overwrite existing file.');
1157
        }
1158
1159
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1160
        $content .= '' . PHP_EOL;
1161
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1162
        $content .= '    ' . PHP_EOL;
1163
        $content .= '    <target name="" description="">' . PHP_EOL;
1164
        $content .= '        ' . PHP_EOL;
1165
        $content .= '    </target>' . PHP_EOL;
1166
        $content .= '    ' . PHP_EOL;
1167
        $content .= '</project>' . PHP_EOL;
1168
1169
        file_put_contents($buildfilePath, $content);
1170
    }
1171
1172
    /**
1173
     * Gets the current Phing version based on VERSION.TXT file.
1174
     *
1175
     * @throws ConfigurationException
1176
     *
1177
     * @return string
1178
     */
1179 6
    public static function getPhingVersion()
1180
    {
1181 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1182 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1183 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1184
        }
1185 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1186
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1187
        }
1188
        try { // try to read file
1189 6
            $file = new PhingFile($versionPath);
1190 6
            $reader = new FileReader($file);
1191 6
            $phingVersion = trim($reader->read());
1192
        } catch (IOException $iox) {
1193
            throw new ConfigurationException("Can't read version information file");
1194
        }
1195
1196 6
        $basePath = dirname(__DIR__, 2);
1197
1198 6
        $version = new Version($phingVersion, $basePath);
1199
1200 6
        return "Phing " . $version->getVersion();
1201
    }
1202
1203
    /**
1204
     * Print the project description, if any
1205
     *
1206
     * @param Project $project
1207
     *
1208
     * @throws IOException
1209
     */
1210
    public function printDescription(Project $project)
1211
    {
1212
        if ($project->getDescription() !== null) {
1213
            $project->log($project->getDescription());
1214
        }
1215
    }
1216
1217
    /**
1218
     * Print out a list of all targets in the current buildfile
1219
     *
1220
     * @param $project
1221
     */
1222 1
    public function printTargets($project)
1223
    {
1224
        // find the target with the longest name
1225 1
        $maxLength = 0;
1226 1
        $targets = $project->getTargets();
1227 1
        $targetName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetName is dead and can be removed.
Loading history...
1228 1
        $targetDescription = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetDescription is dead and can be removed.
Loading history...
1229
        /** @var Target $currentTarget */
1230 1
        $currentTarget = null;
1231
1232
        // split the targets in top-level and sub-targets depending
1233
        // on the presence of a description
1234
1235 1
        $subNames = [];
1236 1
        $subDependencies = [];
1237 1
        $topNameDescMap = [];
1238
1239 1
        foreach ($targets as $currentTarget) {
1240 1
            $targetName = $currentTarget->getName();
1241 1
            $targetDescription = $currentTarget->getDescription();
1242 1
            if ($currentTarget->isHidden()) {
1243
                continue;
1244
            }
1245
1246
            // subtargets are targets w/o descriptions
1247 1
            if ($targetDescription === null) {
1248 1
                $subNames[] = $targetName;
1249 1
                $currentDependencies = $currentTarget->getDependencies();
1250 1
                if (!empty($currentDependencies)) {
1251
                    array_push($subDependencies, ...$currentTarget->getDependencies());
1252
                }
1253
            } else {
1254
                // topNames and topDescriptions are handled later
1255
                // here we store in hash map (for sorting purposes)
1256
                $topNameDescMap[$targetName] = $targetDescription;
1257
                if (strlen($targetName) > $maxLength) {
1258
                    $maxLength = strlen($targetName);
1259
                }
1260
            }
1261
        }
1262
1263
        // Sort the arrays
1264 1
        sort($subNames); // sort array values, resetting keys (which are numeric)
1265 1
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1266
1267 1
        $topNames = array_keys($topNameDescMap);
1268 1
        $topDescriptions = array_values($topNameDescMap);
1269 1
        $topDependencies = $currentTarget->getDependencies();
1270
1271 1
        $defaultTarget = $project->getDefaultTarget();
1272
1273 1
        if ($defaultTarget !== null && $defaultTarget !== "") {
1274
            $defaultName = [];
1275
            $defaultDesc = [];
1276
            $defaultName[] = $defaultTarget;
1277
1278
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1279
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1280
                $defaultDesc = [];
1281
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1282
            }
1283
1284
            $this->_printTargets($project, $defaultName, $defaultDesc, [], "Default target:", $maxLength);
1285
        }
1286 1
        $this->_printTargets($project, $topNames, $topDescriptions, $topDependencies, "Main targets:", $maxLength);
1287 1
        $this->_printTargets($project, $subNames, null, $subDependencies, "Subtargets:", 0);
1288 1
    }
1289
1290
    /**
1291
     * Writes a formatted list of target names with an optional description.
1292
     *
1293
     * @param Project $project
1294
     * @param array $names The names to be printed.
1295
     *                             Must not be <code>null</code>.
1296
     * @param array $descriptions The associated target descriptions.
1297
     *                             May be <code>null</code>, in which case
1298
     *                             no descriptions are displayed.
1299
     *                             If non-<code>null</code>, this should have
1300
     *                             as many elements as <code>names</code>.
1301
     * @param $dependencies
1302
     * @param string $heading The heading to display.
1303
     *                             Should not be <code>null</code>.
1304
     * @param int $maxlen The maximum length of the names of the targets.
1305
     *                             If descriptions are given, they are padded to this
1306
     *                             position so they line up (so long as the names really
1307
     *                             <i>are</i> shorter than this).
1308
     */
1309 1
    private function _printTargets(Project $project, $names, $descriptions, $dependencies, $heading, $maxlen)
1310
    {
1311 1
        $spaces = '  ';
1312 1
        while (strlen($spaces) < $maxlen) {
1313
            $spaces .= $spaces;
1314
        }
1315 1
        $msg = "";
1316 1
        $msg .= $heading . PHP_EOL;
1317 1
        $msg .= str_repeat("-", 79) . PHP_EOL;
1318
1319 1
        $total = count($names);
1320 1
        for ($i = 0; $i < $total; $i++) {
1321 1
            $msg .= " ";
1322 1
            $msg .= $names[$i];
1323 1
            if (!empty($descriptions)) {
1324
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1325
                $msg .= $descriptions[$i];
1326
            }
1327 1
            $msg .= PHP_EOL;
1328 1
            if (!empty($dependencies) && isset($dependencies[$i + 1])) {
1329
                $msg .= '   depends on: ' . implode(', ', $dependencies) . PHP_EOL;
1330
            }
1331
        }
1332
1333 1
        $project->log($msg, Project::MSG_WARN);
1334 1
    }
1335
1336
    /**
1337
     * Import a path, supporting the following conventions:
1338
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1339
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1340
     * - dot-path
1341
     *
1342
     * @param string $dotPath Path
1343
     * @param mixed $classpath String or object supporting __toString()
1344
     *
1345
     * @return string         The unqualified classname (which can be instantiated).
1346
     *
1347
     * @throws BuildException - if cannot find the specified file
1348
     */
1349 794
    public static function import($dotPath, $classpath = null)
1350
    {
1351 794
        if (strpos($dotPath, '.') !== false) {
1352 793
            $classname = StringHelper::unqualify($dotPath);
1353
        } else {
1354 792
            $classname = $dotPath;
1355 792
            $dotPath = '';
1356 792
            $shortClassName = $classname;
1357 792
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1358 792
                $namespace = substr($shortClassName, 0, $lastNsPos);
1359 792
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1360 792
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1361
            }
1362 792
            $dotPath .= str_replace('_', '.', $shortClassName);
1363
        }
1364
1365
        // first check to see that the class specified hasn't already been included.
1366
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1367 794
        if (class_exists($classname)) {
1368 792
            return $classname;
1369
        }
1370
1371 7
        $dotClassname = basename($dotPath);
1372 7
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1373
1374
        // 1- temporarily replace escaped '.' with another illegal char (#)
1375 7
        $tmp = str_replace('\.', '##', $dotClassname);
1376
        // 2- swap out the remaining '.' with DIR_SEP
1377 7
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1378
        // 3- swap back the escaped '.'
1379 7
        $tmp = str_replace('##', '.', $tmp);
1380
1381 7
        $classFile = $tmp . ".php";
1382
1383 7
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1384
1385 7
        Phing::importFile($path, $classpath);
1386
1387 5
        return $classname;
1388
    }
1389
1390
    /**
1391
     * Import a PHP file
1392
     *
1393
     * This used to be named __import, however PHP has reserved all method names
1394
     * with a double underscore prefix for future use.
1395
     *
1396
     * @param string $path Path to the PHP file
1397
     * @param mixed $classpath String or object supporting __toString()
1398
     *
1399
     * @throws ConfigurationException
1400
     */
1401 10
    public static function importFile($path, $classpath = null)
1402
    {
1403 10
        if ($classpath) {
1404
            // Apparently casting to (string) no longer invokes __toString() automatically.
1405 2
            if (is_object($classpath)) {
1406
                $classpath = $classpath->__toString();
1407
            }
1408
1409
            // classpaths are currently additive, but we also don't want to just
1410
            // indiscriminantly prepand/append stuff to the include_path.  This means
1411
            // we need to parse current incldue_path, and prepend any
1412
            // specified classpath locations that are not already in the include_path.
1413
            //
1414
            // NOTE:  the reason why we do it this way instead of just changing include_path
1415
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1416
            // include/require class files from within method calls.  This means that not all
1417
            // necessary files will be included in this import() call, and hence we can't
1418
            // change the include_path back without breaking those apps.  While this method could
1419
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1420
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1421
            // really where speed matters more.
1422
1423 2
            $curr_parts = Phing::explodeIncludePath();
1424 2
            $add_parts = Phing::explodeIncludePath($classpath);
1425 2
            $new_parts = array_diff($add_parts, $curr_parts);
1426 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...
1427 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1428
            }
1429
        }
1430
1431 10
        $ret = include_once $path;
1432
1433 8
        if ($ret === false) {
1434
            $msg = "Error importing $path";
1435
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1436
                $x = new Exception("for-path-trace-only");
1437
                $msg .= $x->getTraceAsString();
1438
            }
1439
            throw new ConfigurationException($msg);
1440
        }
1441 8
    }
1442
1443
    /**
1444
     * Looks on include path for specified file.
1445
     *
1446
     * @param string $path
1447
     *
1448
     * @return string File found (null if no file found).
1449
     */
1450 792
    public static function getResourcePath($path)
1451
    {
1452 792
        if (self::$importPaths === null) {
1453
            self::$importPaths = self::explodeIncludePath();
1454
        }
1455
1456 792
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1457
1458 792
        foreach (self::$importPaths as $prefix) {
1459 792
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1460 792
            if (file_exists($testPath)) {
1461 791
                return $testPath;
1462
            }
1463
        }
1464
1465
        // Check for the property phing.home
1466 792
        $homeDir = self::getProperty(self::PHING_HOME);
1467 792
        if ($homeDir) {
1468 792
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1469 792
            if (file_exists($testPath)) {
1470 792
                return $testPath;
1471
            }
1472
        }
1473
1474
        // Check for the phing home of phar archive
1475 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1476
            $testPath = self::$importPaths[0] . '/../' . $path;
1477
            if (file_exists($testPath)) {
1478
                return $testPath;
1479
            }
1480
        }
1481
1482
        // Do one additional check based on path of current file (Phing.php)
1483 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1484 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1485 6
        if (file_exists($testPath)) {
1486
            return $testPath;
1487
        }
1488
1489 6
        return null;
1490
    }
1491
1492
    /**
1493
     * Explode an include path into an array
1494
     *
1495
     * If no path provided, uses current include_path. Works around issues that
1496
     * occur when the path includes stream schemas.
1497
     *
1498
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1499
     *
1500
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1501
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1502
     * @param     string|null $path
1503
     * @return    array
1504
     */
1505 16
    public static function explodeIncludePath($path = null)
1506
    {
1507 16
        if (null === $path) {
1508 16
            $path = get_include_path();
1509
        }
1510
1511 16
        if (PATH_SEPARATOR == ':') {
1512
            // On *nix systems, include_paths which include paths with a stream
1513
            // schema cannot be safely explode'd, so we have to be a bit more
1514
            // intelligent in the approach.
1515 16
            $paths = preg_split('#:(?!//)#', $path);
1516
        } else {
1517
            $paths = explode(PATH_SEPARATOR, $path);
1518
        }
1519
1520 16
        return $paths;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $paths could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1521
    }
1522
1523
    // -------------------------------------------------------------------------------------------
1524
    // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System)
1525
    // -------------------------------------------------------------------------------------------
1526
1527
    /**
1528
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1529
     *
1530
     * @return void
1531
     */
1532 1
    private static function setSystemConstants()
1533
    {
1534
1535
        /*
1536
         * PHP_OS returns on
1537
         *   WindowsNT4.0sp6  => WINNT
1538
         *   Windows2000      => WINNT
1539
         *   Windows ME       => WIN32
1540
         *   Windows 98SE     => WIN32
1541
         *   FreeBSD 4.5p7    => FreeBSD
1542
         *   Redhat Linux     => Linux
1543
         *   Mac OS X         => Darwin
1544
         */
1545 1
        self::setProperty('host.os', PHP_OS);
1546
1547
        // this is used by some tasks too
1548 1
        self::setProperty('os.name', PHP_OS);
1549
1550
        // it's still possible this won't be defined,
1551
        // e.g. if Phing is being included in another app w/o
1552
        // using the phing.php script.
1553 1
        if (!defined('PHP_CLASSPATH')) {
1554
            define('PHP_CLASSPATH', get_include_path());
1555
        }
1556
1557 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1558
1559
        // try to determine the host filesystem and set system property
1560
        // used by Fileself::getFileSystem to instantiate the correct
1561
        // abstraction layer
1562
1563 1
        if (PHP_OS_FAMILY === 'Windows') {
1564
            self::setProperty('host.fstype', 'WINDOWS');
1565
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1566
        } else {
1567 1
            self::setProperty('host.fstype', 'UNIX');
1568 1
            self::setProperty('user.home', getenv('HOME'));
1569
        }
1570 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1571 1
        self::setProperty('file.separator', FileUtils::$separator);
1572 1
        self::setProperty('line.separator', PHP_EOL);
1573 1
        self::setProperty('path.separator', FileUtils::$pathSeparator);
1574 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1575 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1576 1
        self::setProperty('application.startdir', getcwd());
1577 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1578
1579
        // try to detect machine dependent information
1580 1
        $sysInfo = [];
1581 1
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1582 1
            $sysInfo = posix_uname();
1583
        } else {
1584
            $sysInfo['nodename'] = php_uname('n');
1585
            $sysInfo['machine'] = php_uname('m');
1586
            //this is a not so ideal substition, but maybe better than nothing
1587
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1588
            $sysInfo['release'] = php_uname('r');
1589
            $sysInfo['version'] = php_uname('v');
1590
        }
1591
1592 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1593 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1594 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1595 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1596 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1597 1
        unset($sysInfo);
1598 1
    }
1599
1600
    /**
1601
     * This gets a property that was set via command line or otherwise passed into Phing.
1602
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1603
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1604
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1605
     * the pear.log.name property.
1606
     *
1607
     * @param  string $name
1608
     * @return string value of found property (or null, if none found).
1609
     */
1610
    public static function getDefinedProperty($name)
1611
    {
1612
        return self::$definedProps->getProperty($name);
1613
    }
1614
1615
    /**
1616
     * This sets a property that was set via command line or otherwise passed into Phing.
1617
     *
1618
     * @param  string $name
1619
     * @param  mixed $value
1620
     * @return mixed value of found property (or null, if none found).
1621
     */
1622
    public static function setDefinedProperty($name, $value)
1623
    {
1624
        return self::$definedProps->setProperty($name, $value);
1625
    }
1626
1627
    /**
1628
     * Returns property value for a System property.
1629
     * System properties are "global" properties like application.startdir,
1630
     * and user.dir.  Many of these correspond to similar properties in Java
1631
     * or Ant.
1632
     *
1633
     * @param  string $propName
1634
     * @return string Value of found property (or null, if none found).
1635
     */
1636 809
    public static function getProperty($propName)
1637
    {
1638
1639
        // some properties are detemined on each access
1640
        // some are cached, see below
1641
1642
        // default is the cached value:
1643 809
        $val = self::$properties[$propName] ?? null;
1644
1645
        // special exceptions
1646
        switch ($propName) {
1647 809
            case 'user.dir':
1648 127
                $val = getcwd();
1649 127
                break;
1650
        }
1651
1652 809
        return $val;
1653
    }
1654
1655
    /**
1656
     * Retuns reference to all properties
1657
     */
1658 791
    public static function &getProperties()
1659
    {
1660 791
        return self::$properties;
1661
    }
1662
1663
    /**
1664
     * @param $propName
1665
     * @param $propValue
1666
     * @return string
1667
     */
1668 7
    public static function setProperty($propName, $propValue)
1669
    {
1670 7
        $propName = (string) $propName;
1671 7
        $oldValue = self::getProperty($propName);
1672 7
        self::$properties[$propName] = $propValue;
1673
1674 7
        return $oldValue;
1675
    }
1676
1677
    /**
1678
     * @return float
1679
     */
1680 64
    public static function currentTimeMillis()
1681
    {
1682 64
        [$usec, $sec] = explode(" ", microtime());
1683
1684 64
        return ((float) $usec + (float) $sec);
1685
    }
1686
1687
    /**
1688
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1689
     *
1690
     * @return void
1691
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1692
     */
1693 1
    private static function setIncludePaths()
1694
    {
1695 1
        if (defined('PHP_CLASSPATH')) {
1696 1
            $result = set_include_path(PHP_CLASSPATH);
1697 1
            if ($result === false) {
1698
                throw new ConfigurationException("Could not set PHP include_path.");
1699
            }
1700 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1701
        }
1702 1
    }
1703
1704
    /**
1705
     * Converts shorthand notation values as returned by ini_get()
1706
     *
1707
     * @see    http://www.php.net/ini_get
1708
     * @param  string|int $val
1709
     * @return int
1710
     */
1711 11
    public static function convertShorthand($val): int
1712
    {
1713 11
        $val = trim($val);
1714 11
        $last = strtolower($val[strlen($val) - 1]);
1715
1716 11
        if (!is_numeric($last)) {
1717 3
            $val = (int) substr($val, 0, -1);
1718
1719 3
            switch ($last) {
1720
                // The 'G' modifier is available since PHP 5.1.0
1721 3
                case 'g':
1722 2
                    $val *= 1024;
1723
                // no break
1724 2
                case 'm':
1725 2
                    $val *= 1024;
1726
                // no break
1727 2
                case 'k':
1728 3
                    $val *= 1024;
1729
            }
1730
        }
1731
1732 11
        return $val;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $val could return the type string which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
1733
    }
1734
1735
    /**
1736
     * Sets PHP INI values that Phing needs.
1737
     */
1738 1
    private static function setIni(): void
1739
    {
1740 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1741
1742
        // We won't bother storing original max_execution_time, since 1) the value in
1743
        // php.ini may be wrong (and there's no way to get the current value) and
1744
        // 2) it would mean something very strange to set it to a value less than time script
1745
        // has already been running, which would be the likely change.
1746
1747 1
        set_time_limit(0);
1748
1749 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1750 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1751
1752 1
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1753 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1754
            // We do *not* need to save the original value here, since we don't plan to restore
1755
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1756
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1757
        }
1758 1
    }
1759
1760
    /**
1761
     * Restores [most] PHP INI values to their pre-Phing state.
1762
     *
1763
     * Currently the following settings are not restored:
1764
     *  - max_execution_time (because getting current time limit is not possible)
1765
     *  - memory_limit (which may have been increased by Phing)
1766
     */
1767 1
    private static function restoreIni(): void
1768
    {
1769 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1770 1
            switch ($settingName) {
1771 1
                case 'error_reporting':
1772 1
                    error_reporting($settingValue);
1773 1
                    break;
1774
                default:
1775 1
                    ini_set($settingName, $settingValue);
1776
            }
1777
        }
1778 1
    }
1779
1780
    /**
1781
     * Returns reference to Timer object.
1782
     *
1783
     * @return Timer
1784
     */
1785 2
    public static function getTimer(): Timer
1786
    {
1787 2
        if (self::$timer === null) {
1788
            self::$timer = new Timer();
1789
        }
1790
1791 2
        return self::$timer;
1792
    }
1793
1794
    /**
1795
     * Start up Phing.
1796
     * Sets up the Phing environment but does not initiate the build process.
1797
     *
1798
     * @return void
1799
     * @throws Exception - If the Phing environment cannot be initialized.
1800
     */
1801 1
    public static function startup(): void
1802
    {
1803
1804
        // setup STDOUT and STDERR defaults
1805 1
        self::initializeOutputStreams();
1806
1807
        // some init stuff
1808 1
        self::getTimer()->start();
1809
1810 1
        self::setSystemConstants();
1811 1
        self::setIncludePaths();
1812 1
        self::setIni();
1813 1
    }
1814
1815
    /**
1816
     * Performs any shutdown routines, such as stopping timers.
1817
     *
1818
     * @throws IOException
1819
     */
1820 1
    public static function shutdown(): void
1821
    {
1822 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
1823 1
        self::$msgOutputLevel = Project::MSG_INFO;
1824 1
        self::restoreIni();
1825 1
        self::getTimer()->stop();
1826 1
    }
1827
}
1828