Passed
Push — master ( 844995...62752a )
by Siad
10:51
created

Phing::_findBuildFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 21
ccs 0
cts 10
cp 0
crap 12
rs 9.9666
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
     * Helper to get the parent file for a given file.
650
     *
651
     * @param PhingFile $file
652
     *
653
     * @return PhingFile Parent file or null if none
654
     */
655
    private function _getParentFile(PhingFile $file)
656
    {
657
        $filename = $file->getAbsolutePath();
658
        $file = new PhingFile($filename);
659
        $filename = $file->getParent();
660
661
        return ($filename === null) ? null : new PhingFile($filename);
0 ignored issues
show
introduced by
The condition $filename === null is always false.
Loading history...
662
    }
663
664
    /**
665
     * Search parent directories for the build file.
666
     *
667
     * Takes the given target as a suffix to append to each
668
     * parent directory in search of a build file.  Once the
669
     * root of the file-system has been reached an exception
670
     * is thrown.
671
     *
672
     * @param string $start Start file path.
673
     * @param string $suffix Suffix filename to look for in parents.
674
     *
675
     * @throws ConfigurationException
676
     *
677
     * @return PhingFile A handle to the build file
678
     */
679
    private function _findBuildFile($start, $suffix)
680
    {
681
        $startf = new PhingFile($start);
682
        $parent = new PhingFile($startf->getAbsolutePath());
683
        $file = new PhingFile($parent, $suffix);
684
685
        // check if the target file exists in the current directory
686
        while (!$file->exists()) {
687
            // change to parent directory
688
            $parent = $this->_getParentFile($parent);
689
690
            // if parent is null, then we are at the root of the fs,
691
            // complain that we can't find the build file.
692
            if ($parent === null) {
693
                throw new ConfigurationException("Could not locate a build file!");
694
            }
695
            // refresh our file handle
696
            $file = new PhingFile($parent, $suffix);
697
        }
698
699
        return $file;
700
    }
701
702
    /**
703
     * Executes the build.
704
     *
705
     * @throws ConfigurationException
706
     * @throws Exception
707
     *
708
     * @return void
709
     */
710
    public function runBuild()
711
    {
712
        if (!$this->readyToRun) {
713
            return;
714
        }
715
716
        $project = new Project();
717
718
        self::setCurrentProject($project);
719
        set_error_handler(['Phing', 'handlePhpError']);
720
721
        $error = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $error is dead and can be removed.
Loading history...
722
723
        $this->addBuildListeners($project);
724
        $this->addInputHandler($project);
725
726
        // set this right away, so that it can be used in logging.
727
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
728
        $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
729
730
        try {
731
            $project->fireBuildStarted();
732
            $project->init();
733
        } catch (Exception $exc) {
734
            $project->fireBuildFinished($exc);
735
            throw $exc;
736
        }
737
738
        $project->setKeepGoingMode($this->keepGoingMode);
739
740
        $project->setUserProperty("phing.version", static::getPhingVersion());
741
742
        $e = self::$definedProps->keys();
743
        while (count($e)) {
744
            $arg = (string) array_shift($e);
745
            $value = (string) self::$definedProps->getProperty($arg);
746
            $project->setUserProperty($arg, $value);
747
        }
748
        unset($e);
749
750
        $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
751
752
        // first use the Configurator to create the project object
753
        // from the given build file.
754
755
        try {
756
            ProjectConfigurator::configureProject($project, $this->buildFile);
757
        } catch (Exception $exc) {
758
            $project->fireBuildFinished($exc);
759
            restore_error_handler();
760
            self::unsetCurrentProject();
761
            throw $exc;
762
        }
763
764
        // Set the project mode
765
        $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
766
767
        // make sure that we have a target to execute
768
        if (count($this->targets) === 0) {
769
            $this->targets[] = $project->getDefaultTarget();
770
        }
771
772
        // make sure that minimum required phing version is satisfied
773
        try {
774
            $this->comparePhingVersion($project->getPhingVersion());
775
        } catch (Exception $exc) {
776
            $project->fireBuildFinished($exc);
777
            restore_error_handler();
778
            self::unsetCurrentProject();
779
            throw $exc;
780
        }
781
782
        // execute targets if help param was not given
783
        if (!$this->projectHelp) {
784
            try {
785
                $project->executeTargets($this->targets);
786
            } catch (Exception $exc) {
787
                $project->fireBuildFinished($exc);
788
                restore_error_handler();
789
                self::unsetCurrentProject();
790
                throw $exc;
791
            }
792
        }
793
        // if help is requested print it
794
        if ($this->projectHelp) {
795
            try {
796
                $this->printDescription($project);
797
                $this->printTargets($project);
798
            } catch (Exception $exc) {
799
                $project->fireBuildFinished($exc);
800
                restore_error_handler();
801
                self::unsetCurrentProject();
802
                throw $exc;
803
            }
804
        }
805
806
        // finally {
807
        if (!$this->projectHelp) {
808
            $project->fireBuildFinished(null);
809
        }
810
811
        restore_error_handler();
812
        self::unsetCurrentProject();
813
    }
814
815
    /**
816
     * @param string $version
817
     *
818
     * @return int
819
     *
820
     * @throws BuildException
821
     * @throws ConfigurationException
822
     */
823
    private function comparePhingVersion($version)
824
    {
825
        $current = strtolower(self::getPhingVersion());
826
        $current = trim(str_replace('phing', '', $current));
827
828
        // make sure that version checks are not applied to trunk
829
        if ('dev' === $current) {
830
            return 1;
831
        }
832
833
        if (-1 == version_compare($current, $version)) {
834
            throw new BuildException(
835
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
836
            );
837
        }
838
    }
839
840
    /**
841
     * Bind any registered build listeners to this project.
842
     *
843
     * This means adding the logger and any build listeners that were specified
844
     * with -listener arg.
845
     *
846
     * @param  Project $project
847
     * @throws BuildException
848
     * @throws ConfigurationException
849
     * @return void
850
     */
851
    private function addBuildListeners(Project $project)
852
    {
853
        // Add the default listener
854
        $project->addBuildListener($this->createLogger());
855
856
        foreach ($this->listeners as $listenerClassname) {
857
            try {
858
                $clz = Phing::import($listenerClassname);
859
            } catch (Exception $e) {
860
                $msg = "Unable to instantiate specified listener "
861
                    . "class " . $listenerClassname . " : "
862
                    . $e->getMessage();
863
                throw new ConfigurationException($msg);
864
            }
865
866
            $listener = new $clz();
867
868
            if ($listener instanceof StreamRequiredBuildLogger) {
869
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
870
            }
871
            $project->addBuildListener($listener);
872
        }
873
    }
874
875
    /**
876
     * Creates the InputHandler and adds it to the project.
877
     *
878
     * @param Project $project the project instance.
879
     *
880
     * @throws ConfigurationException
881
     */
882
    private function addInputHandler(Project $project)
883
    {
884
        if ($this->inputHandlerClassname === null) {
885
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
886
        } else {
887
            try {
888
                $clz = Phing::import($this->inputHandlerClassname);
889
                $handler = new $clz();
890
                if ($project !== null && method_exists($handler, 'setProject')) {
891
                    $handler->setProject($project);
892
                }
893
            } catch (Exception $e) {
894
                $msg = "Unable to instantiate specified input handler "
895
                    . "class " . $this->inputHandlerClassname . " : "
896
                    . $e->getMessage();
897
                throw new ConfigurationException($msg);
898
            }
899
        }
900
        $project->setInputHandler($handler);
901
    }
902
903
    /**
904
     * Creates the default build logger for sending build events to the log.
905
     *
906
     * @throws BuildException
907
     * @return BuildLogger The created Logger
908
     */
909
    private function createLogger()
910
    {
911
        if ($this->silent) {
912
            $logger = new SilentLogger();
913
            self::$msgOutputLevel = Project::MSG_WARN;
914
        } elseif ($this->loggerClassname !== null) {
915
            self::import($this->loggerClassname);
916
            // get class name part
917
            $classname = self::import($this->loggerClassname);
918
            $logger = new $classname();
919
            if (!($logger instanceof BuildLogger)) {
920
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
921
            }
922
        } else {
923
            $logger = new DefaultLogger();
924
        }
925
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
926
        $logger->setOutputStream(self::$out);
927
        $logger->setErrorStream(self::$err);
928
        $logger->setEmacsMode($this->emacsMode);
929
930
        return $logger;
931
    }
932
933
    /**
934
     * Sets the current Project
935
     *
936
     * @param Project $p
937
     */
938 1
    public static function setCurrentProject($p)
939
    {
940 1
        self::$currentProject = $p;
941 1
    }
942
943
    /**
944
     * Unsets the current Project
945
     */
946 1
    public static function unsetCurrentProject()
947
    {
948 1
        self::$currentProject = null;
949 1
    }
950
951
    /**
952
     * Gets the current Project.
953
     *
954
     * @return Project Current Project or NULL if none is set yet/still.
955
     */
956 5
    public static function getCurrentProject()
957
    {
958 5
        return self::$currentProject;
959
    }
960
961
    /**
962
     * A static convenience method to send a log to the current (last-setup) Project.
963
     * If there is no currently-configured Project, then this will do nothing.
964
     *
965
     * @param string $message
966
     * @param int $priority Project::MSG_INFO, etc.
967
     */
968
    public static function log($message, $priority = Project::MSG_INFO)
969
    {
970
        $p = self::getCurrentProject();
971
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Project, thus it always evaluated to true.
Loading history...
972
            $p->log($message, $priority);
973
        }
974
    }
975
976
    /**
977
     * Error handler for PHP errors encountered during the build.
978
     * This uses the logging for the currently configured project.
979
     *
980
     * @param $level
981
     * @param string $message
982
     * @param $file
983
     * @param $line
984
     */
985
    public static function handlePhpError($level, $message, $file, $line)
986
    {
987
988
        // don't want to print suppressed errors
989
        if (error_reporting() > 0) {
990
            if (self::$phpErrorCapture) {
991
                self::$capturedPhpErrors[] = [
992
                    'message' => $message,
993
                    'level' => $level,
994
                    'line' => $line,
995
                    'file' => $file
996
                ];
997
            } else {
998
                $message = '[PHP Error] ' . $message;
999
                $message .= ' [line ' . $line . ' of ' . $file . ']';
1000
1001
                switch ($level) {
1002
                    case E_USER_DEPRECATED:
1003
                    case E_DEPRECATED:
1004
                    case E_STRICT:
1005
                    case E_NOTICE:
1006
                    case E_USER_NOTICE:
1007
                        self::log($message, Project::MSG_VERBOSE);
1008
                        break;
1009
                    case E_WARNING:
1010
                    case E_USER_WARNING:
1011
                        self::log($message, Project::MSG_WARN);
1012
                        break;
1013
                    case E_ERROR:
1014
                    case E_USER_ERROR:
1015
                    default:
1016
                        self::log($message, Project::MSG_ERR);
1017
                } // switch
1018
            } // if phpErrorCapture
1019
        } // if not @
1020
    }
1021
1022
    /**
1023
     * Begins capturing PHP errors to a buffer.
1024
     * While errors are being captured, they are not logged.
1025
     */
1026
    public static function startPhpErrorCapture()
1027
    {
1028
        self::$phpErrorCapture = true;
1029
        self::$capturedPhpErrors = [];
1030
    }
1031
1032
    /**
1033
     * Stops capturing PHP errors to a buffer.
1034
     * The errors will once again be logged after calling this method.
1035
     */
1036
    public static function stopPhpErrorCapture()
1037
    {
1038
        self::$phpErrorCapture = false;
1039
    }
1040
1041
    /**
1042
     * Clears the captured errors without affecting the starting/stopping of the capture.
1043
     */
1044
    public static function clearCapturedPhpErrors()
1045
    {
1046
        self::$capturedPhpErrors = [];
1047
    }
1048
1049
    /**
1050
     * Gets any PHP errors that were captured to buffer.
1051
     *
1052
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1053
     */
1054
    public static function getCapturedPhpErrors()
1055
    {
1056
        return self::$capturedPhpErrors;
1057
    }
1058
1059
    /**
1060
     * Prints the usage of how to use this class
1061
     */
1062 1
    public static function printUsage()
1063
    {
1064 1
        $msg = "";
1065 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1066 1
        $msg .= "Options: " . PHP_EOL;
1067 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1068 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1069 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1070 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1071 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1072 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1073 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1074 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1075 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1076 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1077 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1078 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1079 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1080 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1081 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1082 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1083 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1084 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1085 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1086 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1087 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1088 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1089 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1090 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1091 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1092
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1093 1
        $msg .= PHP_EOL;
1094 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1095 1
        self::$err->write($msg);
1096 1
    }
1097
1098
    /**
1099
     * Prints the current Phing version.
1100
     */
1101
    public static function printVersion()
1102
    {
1103
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1104
    }
1105
1106
    /**
1107
     * Creates generic buildfile
1108
     *
1109
     * @param string $path
1110
     */
1111
    public static function init($path)
1112
    {
1113
        if ($buildfilePath = self::initPath($path)) {
1114
            self::initWrite($buildfilePath);
1115
        }
1116
    }
1117
1118
1119
    /**
1120
     * Returns buildfile's path
1121
     *
1122
     * @param $path
1123
     *
1124
     * @return string
1125
     * @throws ConfigurationException
1126
     */
1127
    protected static function initPath($path)
1128
    {
1129
        // Fallback
1130
        if (empty($path)) {
1131
            $defaultDir = self::getProperty('application.startdir');
1132
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1133
        }
1134
1135
        // Adding filename if necessary
1136
        if (is_dir($path)) {
1137
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1138
        }
1139
1140
        // Check if path is available
1141
        $dirname = dirname($path);
1142
        if (is_dir($dirname) && !is_file($path)) {
1143
            return $path;
1144
        }
1145
1146
        // Path is valid, but buildfile already exists
1147
        if (is_file($path)) {
1148
            throw new ConfigurationException('Buildfile already exists.');
1149
        }
1150
1151
        throw new ConfigurationException('Invalid path for sample buildfile.');
1152
    }
1153
1154
1155
    /**
1156
     * Writes sample buildfile
1157
     *
1158
     * If $buildfilePath does not exist, the buildfile is created.
1159
     *
1160
     * @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...
1161
     *
1162
     * @return null
1163
     * @throws ConfigurationException
1164
     */
1165
    protected static function initWrite($buildfilePath)
1166
    {
1167
        // Overwriting protection
1168
        if (file_exists($buildfilePath)) {
1169
            throw new ConfigurationException('Cannot overwrite existing file.');
1170
        }
1171
1172
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1173
        $content .= '' . PHP_EOL;
1174
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1175
        $content .= '    ' . PHP_EOL;
1176
        $content .= '    <target name="" description="">' . PHP_EOL;
1177
        $content .= '        ' . PHP_EOL;
1178
        $content .= '    </target>' . PHP_EOL;
1179
        $content .= '    ' . PHP_EOL;
1180
        $content .= '</project>' . PHP_EOL;
1181
1182
        file_put_contents($buildfilePath, $content);
1183
    }
1184
1185
    /**
1186
     * Gets the current Phing version based on VERSION.TXT file.
1187
     *
1188
     * @throws ConfigurationException
1189
     *
1190
     * @return string
1191
     */
1192 6
    public static function getPhingVersion()
1193
    {
1194 6
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1195 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1196 6
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1197
        }
1198 6
        if ($versionPath === null) {
0 ignored issues
show
introduced by
The condition $versionPath === null is always false.
Loading history...
1199
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1200
        }
1201
        try { // try to read file
1202 6
            $file = new PhingFile($versionPath);
1203 6
            $reader = new FileReader($file);
1204 6
            $phingVersion = trim($reader->read());
1205
        } catch (IOException $iox) {
1206
            throw new ConfigurationException("Can't read version information file");
1207
        }
1208
1209 6
        $basePath = dirname(__DIR__, 2);
1210
1211 6
        $version = new Version($phingVersion, $basePath);
1212
1213 6
        return "Phing " . $version->getVersion();
1214
    }
1215
1216
    /**
1217
     * Print the project description, if any
1218
     *
1219
     * @param Project $project
1220
     *
1221
     * @throws IOException
1222
     */
1223
    public function printDescription(Project $project)
1224
    {
1225
        if ($project->getDescription() !== null) {
1226
            $project->log($project->getDescription());
1227
        }
1228
    }
1229
1230
    /**
1231
     * Print out a list of all targets in the current buildfile
1232
     *
1233
     * @param $project
1234
     */
1235 1
    public function printTargets($project)
1236
    {
1237
        // find the target with the longest name
1238 1
        $maxLength = 0;
1239 1
        $targets = $project->getTargets();
1240 1
        $targetName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetName is dead and can be removed.
Loading history...
1241 1
        $targetDescription = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $targetDescription is dead and can be removed.
Loading history...
1242
        /** @var Target $currentTarget */
1243 1
        $currentTarget = null;
1244
1245
        // split the targets in top-level and sub-targets depending
1246
        // on the presence of a description
1247
1248 1
        $subNames = [];
1249 1
        $subDependencies = [];
1250 1
        $topNameDescMap = [];
1251
1252 1
        foreach ($targets as $currentTarget) {
1253 1
            $targetName = $currentTarget->getName();
1254 1
            $targetDescription = $currentTarget->getDescription();
1255 1
            if ($currentTarget->isHidden()) {
1256
                continue;
1257
            }
1258
1259
            // subtargets are targets w/o descriptions
1260 1
            if ($targetDescription === null) {
1261 1
                $subNames[] = $targetName;
1262 1
                $currentDependencies = $currentTarget->getDependencies();
1263 1
                if (!empty($currentDependencies)) {
1264
                    array_push($subDependencies, ...$currentTarget->getDependencies());
1265
                }
1266
            } else {
1267
                // topNames and topDescriptions are handled later
1268
                // here we store in hash map (for sorting purposes)
1269
                $topNameDescMap[$targetName] = $targetDescription;
1270
                if (strlen($targetName) > $maxLength) {
1271
                    $maxLength = strlen($targetName);
1272
                }
1273
            }
1274
        }
1275
1276
        // Sort the arrays
1277 1
        sort($subNames); // sort array values, resetting keys (which are numeric)
1278 1
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1279
1280 1
        $topNames = array_keys($topNameDescMap);
1281 1
        $topDescriptions = array_values($topNameDescMap);
1282 1
        $topDependencies = $currentTarget->getDependencies();
1283
1284 1
        $defaultTarget = $project->getDefaultTarget();
1285
1286 1
        if ($defaultTarget !== null && $defaultTarget !== "") {
1287
            $defaultName = [];
1288
            $defaultDesc = [];
1289
            $defaultName[] = $defaultTarget;
1290
1291
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1292
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1293
                $defaultDesc = [];
1294
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1295
            }
1296
1297
            $this->_printTargets($project, $defaultName, $defaultDesc, [], "Default target:", $maxLength);
1298
        }
1299 1
        $this->_printTargets($project, $topNames, $topDescriptions, $topDependencies, "Main targets:", $maxLength);
1300 1
        $this->_printTargets($project, $subNames, null, $subDependencies, "Subtargets:", 0);
1301 1
    }
1302
1303
    /**
1304
     * Writes a formatted list of target names with an optional description.
1305
     *
1306
     * @param Project $project
1307
     * @param array $names The names to be printed.
1308
     *                             Must not be <code>null</code>.
1309
     * @param array $descriptions The associated target descriptions.
1310
     *                             May be <code>null</code>, in which case
1311
     *                             no descriptions are displayed.
1312
     *                             If non-<code>null</code>, this should have
1313
     *                             as many elements as <code>names</code>.
1314
     * @param $dependencies
1315
     * @param string $heading The heading to display.
1316
     *                             Should not be <code>null</code>.
1317
     * @param int $maxlen The maximum length of the names of the targets.
1318
     *                             If descriptions are given, they are padded to this
1319
     *                             position so they line up (so long as the names really
1320
     *                             <i>are</i> shorter than this).
1321
     */
1322 1
    private function _printTargets(Project $project, $names, $descriptions, $dependencies, $heading, $maxlen)
1323
    {
1324 1
        $spaces = '  ';
1325 1
        while (strlen($spaces) < $maxlen) {
1326
            $spaces .= $spaces;
1327
        }
1328 1
        $msg = "";
1329 1
        $msg .= $heading . PHP_EOL;
1330 1
        $msg .= str_repeat("-", 79) . PHP_EOL;
1331
1332 1
        $total = count($names);
1333 1
        for ($i = 0; $i < $total; $i++) {
1334 1
            $msg .= " ";
1335 1
            $msg .= $names[$i];
1336 1
            if (!empty($descriptions)) {
1337
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1338
                $msg .= $descriptions[$i];
1339
            }
1340 1
            $msg .= PHP_EOL;
1341 1
            if (!empty($dependencies) && isset($dependencies[$i + 1])) {
1342
                $msg .= '   depends on: ' . implode(', ', $dependencies) . PHP_EOL;
1343
            }
1344
        }
1345
1346 1
        $project->log($msg, Project::MSG_WARN);
1347 1
    }
1348
1349
    /**
1350
     * Import a path, supporting the following conventions:
1351
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1352
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1353
     * - dot-path
1354
     *
1355
     * @param string $dotPath Path
1356
     * @param mixed $classpath String or object supporting __toString()
1357
     *
1358
     * @return string         The unqualified classname (which can be instantiated).
1359
     *
1360
     * @throws BuildException - if cannot find the specified file
1361
     */
1362 794
    public static function import($dotPath, $classpath = null)
1363
    {
1364 794
        if (strpos($dotPath, '.') !== false) {
1365 793
            $classname = StringHelper::unqualify($dotPath);
1366
        } else {
1367 792
            $classname = $dotPath;
1368 792
            $dotPath = '';
1369 792
            $shortClassName = $classname;
1370 792
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1371 792
                $namespace = substr($shortClassName, 0, $lastNsPos);
1372 792
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1373 792
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1374
            }
1375 792
            $dotPath .= str_replace('_', '.', $shortClassName);
1376
        }
1377
1378
        // first check to see that the class specified hasn't already been included.
1379
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1380 794
        if (class_exists($classname)) {
1381 792
            return $classname;
1382
        }
1383
1384 7
        $dotClassname = basename($dotPath);
1385 7
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1386
1387
        // 1- temporarily replace escaped '.' with another illegal char (#)
1388 7
        $tmp = str_replace('\.', '##', $dotClassname);
1389
        // 2- swap out the remaining '.' with DIR_SEP
1390 7
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1391
        // 3- swap back the escaped '.'
1392 7
        $tmp = str_replace('##', '.', $tmp);
1393
1394 7
        $classFile = $tmp . ".php";
1395
1396 7
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1397
1398 7
        Phing::importFile($path, $classpath);
1399
1400 5
        return $classname;
1401
    }
1402
1403
    /**
1404
     * Import a PHP file
1405
     *
1406
     * This used to be named __import, however PHP has reserved all method names
1407
     * with a double underscore prefix for future use.
1408
     *
1409
     * @param string $path Path to the PHP file
1410
     * @param mixed $classpath String or object supporting __toString()
1411
     *
1412
     * @throws ConfigurationException
1413
     */
1414 10
    public static function importFile($path, $classpath = null)
1415
    {
1416 10
        if ($classpath) {
1417
            // Apparently casting to (string) no longer invokes __toString() automatically.
1418 2
            if (is_object($classpath)) {
1419
                $classpath = $classpath->__toString();
1420
            }
1421
1422
            // classpaths are currently additive, but we also don't want to just
1423
            // indiscriminantly prepand/append stuff to the include_path.  This means
1424
            // we need to parse current incldue_path, and prepend any
1425
            // specified classpath locations that are not already in the include_path.
1426
            //
1427
            // NOTE:  the reason why we do it this way instead of just changing include_path
1428
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1429
            // include/require class files from within method calls.  This means that not all
1430
            // necessary files will be included in this import() call, and hence we can't
1431
            // change the include_path back without breaking those apps.  While this method could
1432
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1433
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1434
            // really where speed matters more.
1435
1436 2
            $curr_parts = Phing::explodeIncludePath();
1437 2
            $add_parts = Phing::explodeIncludePath($classpath);
1438 2
            $new_parts = array_diff($add_parts, $curr_parts);
1439 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...
1440 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1441
            }
1442
        }
1443
1444 10
        $ret = include_once $path;
1445
1446 8
        if ($ret === false) {
1447
            $msg = "Error importing $path";
1448
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1449
                $x = new Exception("for-path-trace-only");
1450
                $msg .= $x->getTraceAsString();
1451
            }
1452
            throw new ConfigurationException($msg);
1453
        }
1454 8
    }
1455
1456
    /**
1457
     * Looks on include path for specified file.
1458
     *
1459
     * @param string $path
1460
     *
1461
     * @return string File found (null if no file found).
1462
     */
1463 792
    public static function getResourcePath($path)
1464
    {
1465 792
        if (self::$importPaths === null) {
1466
            self::$importPaths = self::explodeIncludePath();
1467
        }
1468
1469 792
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1470
1471 792
        foreach (self::$importPaths as $prefix) {
1472 792
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1473 792
            if (file_exists($testPath)) {
1474 791
                return $testPath;
1475
            }
1476
        }
1477
1478
        // Check for the property phing.home
1479 792
        $homeDir = self::getProperty(self::PHING_HOME);
1480 792
        if ($homeDir) {
1481 792
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1482 792
            if (file_exists($testPath)) {
1483 792
                return $testPath;
1484
            }
1485
        }
1486
1487
        // Check for the phing home of phar archive
1488 6
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1489
            $testPath = self::$importPaths[0] . '/../' . $path;
1490
            if (file_exists($testPath)) {
1491
                return $testPath;
1492
            }
1493
        }
1494
1495
        // Do one additional check based on path of current file (Phing.php)
1496 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1497 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1498 6
        if (file_exists($testPath)) {
1499
            return $testPath;
1500
        }
1501
1502 6
        return null;
1503
    }
1504
1505
    /**
1506
     * Explode an include path into an array
1507
     *
1508
     * If no path provided, uses current include_path. Works around issues that
1509
     * occur when the path includes stream schemas.
1510
     *
1511
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1512
     *
1513
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1514
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1515
     * @param     string|null $path
1516
     * @return    array
1517
     */
1518 16
    public static function explodeIncludePath($path = null)
1519
    {
1520 16
        if (null === $path) {
1521 16
            $path = get_include_path();
1522
        }
1523
1524 16
        if (PATH_SEPARATOR == ':') {
1525
            // On *nix systems, include_paths which include paths with a stream
1526
            // schema cannot be safely explode'd, so we have to be a bit more
1527
            // intelligent in the approach.
1528 16
            $paths = preg_split('#:(?!//)#', $path);
1529
        } else {
1530
            $paths = explode(PATH_SEPARATOR, $path);
1531
        }
1532
1533 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...
1534
    }
1535
1536
    // -------------------------------------------------------------------------------------------
1537
    // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System)
1538
    // -------------------------------------------------------------------------------------------
1539
1540
    /**
1541
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1542
     *
1543
     * @return void
1544
     */
1545 1
    private static function setSystemConstants()
1546
    {
1547
1548
        /*
1549
         * PHP_OS returns on
1550
         *   WindowsNT4.0sp6  => WINNT
1551
         *   Windows2000      => WINNT
1552
         *   Windows ME       => WIN32
1553
         *   Windows 98SE     => WIN32
1554
         *   FreeBSD 4.5p7    => FreeBSD
1555
         *   Redhat Linux     => Linux
1556
         *   Mac OS X         => Darwin
1557
         */
1558 1
        self::setProperty('host.os', PHP_OS);
1559
1560
        // this is used by some tasks too
1561 1
        self::setProperty('os.name', PHP_OS);
1562
1563
        // it's still possible this won't be defined,
1564
        // e.g. if Phing is being included in another app w/o
1565
        // using the phing.php script.
1566 1
        if (!defined('PHP_CLASSPATH')) {
1567
            define('PHP_CLASSPATH', get_include_path());
1568
        }
1569
1570 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1571
1572
        // try to determine the host filesystem and set system property
1573
        // used by Fileself::getFileSystem to instantiate the correct
1574
        // abstraction layer
1575
1576 1
        if (PHP_OS_FAMILY === 'Windows') {
1577
            self::setProperty('host.fstype', 'WINDOWS');
1578
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1579
        } else {
1580 1
            self::setProperty('host.fstype', 'UNIX');
1581 1
            self::setProperty('user.home', getenv('HOME'));
1582
        }
1583 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1584 1
        self::setProperty('file.separator', FileUtils::$separator);
1585 1
        self::setProperty('line.separator', PHP_EOL);
1586 1
        self::setProperty('path.separator', FileUtils::$pathSeparator);
1587 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1588 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1589 1
        self::setProperty('application.startdir', getcwd());
1590 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1591
1592
        // try to detect machine dependent information
1593 1
        $sysInfo = [];
1594 1
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1595 1
            $sysInfo = posix_uname();
1596
        } else {
1597
            $sysInfo['nodename'] = php_uname('n');
1598
            $sysInfo['machine'] = php_uname('m');
1599
            //this is a not so ideal substition, but maybe better than nothing
1600
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1601
            $sysInfo['release'] = php_uname('r');
1602
            $sysInfo['version'] = php_uname('v');
1603
        }
1604
1605 1
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1606 1
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1607 1
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1608 1
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1609 1
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1610 1
        unset($sysInfo);
1611 1
    }
1612
1613
    /**
1614
     * This gets a property that was set via command line or otherwise passed into Phing.
1615
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1616
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1617
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1618
     * the pear.log.name property.
1619
     *
1620
     * @param  string $name
1621
     * @return string value of found property (or null, if none found).
1622
     */
1623
    public static function getDefinedProperty($name)
1624
    {
1625
        return self::$definedProps->getProperty($name);
1626
    }
1627
1628
    /**
1629
     * This sets a property that was set via command line or otherwise passed into Phing.
1630
     *
1631
     * @param  string $name
1632
     * @param  mixed $value
1633
     * @return mixed value of found property (or null, if none found).
1634
     */
1635
    public static function setDefinedProperty($name, $value)
1636
    {
1637
        return self::$definedProps->setProperty($name, $value);
1638
    }
1639
1640
    /**
1641
     * Returns property value for a System property.
1642
     * System properties are "global" properties like application.startdir,
1643
     * and user.dir.  Many of these correspond to similar properties in Java
1644
     * or Ant.
1645
     *
1646
     * @param  string $propName
1647
     * @return string Value of found property (or null, if none found).
1648
     */
1649 809
    public static function getProperty($propName)
1650
    {
1651
1652
        // some properties are detemined on each access
1653
        // some are cached, see below
1654
1655
        // default is the cached value:
1656 809
        $val = self::$properties[$propName] ?? null;
1657
1658
        // special exceptions
1659
        switch ($propName) {
1660 809
            case 'user.dir':
1661 127
                $val = getcwd();
1662 127
                break;
1663
        }
1664
1665 809
        return $val;
1666
    }
1667
1668
    /**
1669
     * Retuns reference to all properties
1670
     */
1671 791
    public static function &getProperties()
1672
    {
1673 791
        return self::$properties;
1674
    }
1675
1676
    /**
1677
     * @param $propName
1678
     * @param $propValue
1679
     * @return string
1680
     */
1681 7
    public static function setProperty($propName, $propValue)
1682
    {
1683 7
        $propName = (string) $propName;
1684 7
        $oldValue = self::getProperty($propName);
1685 7
        self::$properties[$propName] = $propValue;
1686
1687 7
        return $oldValue;
1688
    }
1689
1690
    /**
1691
     * @return float
1692
     */
1693 64
    public static function currentTimeMillis()
1694
    {
1695 64
        [$usec, $sec] = explode(" ", microtime());
1696
1697 64
        return ((float) $usec + (float) $sec);
1698
    }
1699
1700
    /**
1701
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1702
     *
1703
     * @return void
1704
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1705
     */
1706 1
    private static function setIncludePaths()
1707
    {
1708 1
        if (defined('PHP_CLASSPATH')) {
1709 1
            $result = set_include_path(PHP_CLASSPATH);
1710 1
            if ($result === false) {
1711
                throw new ConfigurationException("Could not set PHP include_path.");
1712
            }
1713 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1714
        }
1715 1
    }
1716
1717
    /**
1718
     * Converts shorthand notation values as returned by ini_get()
1719
     *
1720
     * @see    http://www.php.net/ini_get
1721
     * @param  string|int $val
1722
     * @return int
1723
     */
1724 11
    public static function convertShorthand($val): int
1725
    {
1726 11
        $val = trim($val);
1727 11
        $last = strtolower($val[strlen($val) - 1]);
1728
1729 11
        if (!is_numeric($last)) {
1730 3
            $val = (int) substr($val, 0, -1);
1731
1732 3
            switch ($last) {
1733
                // The 'G' modifier is available since PHP 5.1.0
1734 3
                case 'g':
1735 2
                    $val *= 1024;
1736
                // no break
1737 2
                case 'm':
1738 2
                    $val *= 1024;
1739
                // no break
1740 2
                case 'k':
1741 3
                    $val *= 1024;
1742
            }
1743
        }
1744
1745 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...
1746
    }
1747
1748
    /**
1749
     * Sets PHP INI values that Phing needs.
1750
     */
1751 1
    private static function setIni(): void
1752
    {
1753 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1754
1755
        // We won't bother storing original max_execution_time, since 1) the value in
1756
        // php.ini may be wrong (and there's no way to get the current value) and
1757
        // 2) it would mean something very strange to set it to a value less than time script
1758
        // has already been running, which would be the likely change.
1759
1760 1
        set_time_limit(0);
1761
1762 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1763 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1764 1
        self::$origIniSettings['track_errors'] = ini_set('track_errors', 1);
1765
1766 1
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1767 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1768
            // We do *not* need to save the original value here, since we don't plan to restore
1769
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1770
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1771
        }
1772 1
    }
1773
1774
    /**
1775
     * Restores [most] PHP INI values to their pre-Phing state.
1776
     *
1777
     * Currently the following settings are not restored:
1778
     *  - max_execution_time (because getting current time limit is not possible)
1779
     *  - memory_limit (which may have been increased by Phing)
1780
     */
1781 1
    private static function restoreIni(): void
1782
    {
1783 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1784 1
            switch ($settingName) {
1785 1
                case 'error_reporting':
1786 1
                    error_reporting($settingValue);
1787 1
                    break;
1788
                default:
1789 1
                    ini_set($settingName, $settingValue);
1790
            }
1791
        }
1792 1
    }
1793
1794
    /**
1795
     * Returns reference to Timer object.
1796
     *
1797
     * @return Timer
1798
     */
1799 2
    public static function getTimer(): Timer
1800
    {
1801 2
        if (self::$timer === null) {
1802
            self::$timer = new Timer();
1803
        }
1804
1805 2
        return self::$timer;
1806
    }
1807
1808
    /**
1809
     * Start up Phing.
1810
     * Sets up the Phing environment but does not initiate the build process.
1811
     *
1812
     * @return void
1813
     * @throws Exception - If the Phing environment cannot be initialized.
1814
     */
1815 1
    public static function startup(): void
1816
    {
1817
1818
        // setup STDOUT and STDERR defaults
1819 1
        self::initializeOutputStreams();
1820
1821
        // some init stuff
1822 1
        self::getTimer()->start();
1823
1824 1
        self::setSystemConstants();
1825 1
        self::setIncludePaths();
1826 1
        self::setIni();
1827 1
    }
1828
1829
    /**
1830
     * Performs any shutdown routines, such as stopping timers.
1831
     *
1832
     * @throws IOException
1833
     */
1834 1
    public static function shutdown(): void
1835
    {
1836 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
1837 1
        self::$msgOutputLevel = Project::MSG_INFO;
1838 1
        self::restoreIni();
1839 1
        self::getTimer()->stop();
1840 1
    }
1841
}
1842