Passed
Push — master ( e1f86a...4e1a3a )
by Siad
05:23
created

Phing::execute()   F

Complexity

Conditions 61
Paths > 20000

Size

Total Lines 224
Code Lines 138

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 3782

Importance

Changes 0
Metric Value
cc 61
eloc 138
nc 86020
nop 1
dl 0
loc 224
ccs 0
cts 129
cp 0
crap 3782
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the LGPL. For more information please see
17
 * <http://phing.info>.
18
 */
19
20
namespace Phing;
21
22
use Phing\Exception\BuildException;
23
use Phing\Listener\BuildLogger;
24
use Phing\Exception\ConfigurationException;
25
use Phing\Input\ConsoleInputHandler;
26
use Phing\Listener\DefaultLogger;
27
use Phing\Util\Diagnostics;
28
use Exception;
29
use Phing\Exception\ExitStatusException;
30
use Phing\Io\FileOutputStream;
31
use Phing\Io\FileParserFactory;
32
use Phing\Io\FileReader;
33
use Phing\Io\FileSystem;
34
use Phing\Io\FileUtils;
35
use Phing\Io\IOException;
36
use Phing\Io\OutputStream;
37
use Phing\Io\File;
38
use Phing\Io\PrintStream;
39
use Phing\Project;
40
use Phing\Parser\ProjectConfigurator;
41
use Phing\Util\Properties;
42
use SebastianBergmann\Version;
43
use Phing\Listener\SilentLogger;
44
use Phing\Util\SizeHelper;
45
use Phing\Listener\StreamRequiredBuildLogger;
46
use Phing\Util\StringHelper;
47
use Symfony\Component\Console\Output\ConsoleOutput;
48
use Phing\Target;
49
use Throwable;
50
use Phing\Util\Timer;
51
52
/**
53
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
54
 * parsing & handling commandline arguments to assembling the project to shutting down
55
 * and cleaning up in the end.
56
 *
57
 * If you are invoking Phing from an external application, this is still
58
 * the class to use.  Your application can invoke the start() method, passing
59
 * any commandline arguments or additional properties.
60
 *
61
 * @author Andreas Aderhold <[email protected]>
62
 * @author Hans Lellelid <[email protected]>
63
 *
64
 * @package phing
65
 */
66
class Phing
67
{
68
    /**
69
     * Alias for phar file
70
     */
71
    public const PHAR_ALIAS = 'phing.phar';
72
73
    /**
74
     * The default build file name
75
     */
76
    public const DEFAULT_BUILD_FILENAME = "build.xml";
77
    public const PHING_HOME = 'phing.home';
78
    public const PHP_VERSION = 'php.version';
79
    public const PHP_INTERPRETER = 'php.interpreter';
80
81
    /**
82
     * Our current message output status. Follows Project::MSG_XXX
83
     */
84
    private static $msgOutputLevel = Project::MSG_INFO;
85
86
    /**
87
     * PhingFile that we are using for configuration
88
     */
89
    private $buildFile = null;
90
91
    /**
92
     * The build targets
93
     */
94
    private $targets = [];
95
96
    /**
97
     * Set of properties that are passed in from commandline or invoking code.
98
     *
99
     * @var Properties
100
     */
101
    private static $definedProps;
102
103
    /**
104
     * Names of classes to add as listeners to project
105
     */
106
    private $listeners = [];
107
108
    /**
109
     * keep going mode
110
     *
111
     * @var bool $keepGoingMode
112
     */
113
    private $keepGoingMode = false;
114
115
    private $loggerClassname = null;
116
117
    /**
118
     * The class to handle input (can be only one).
119
     */
120
    private $inputHandlerClassname;
121
122
    /**
123
     * Whether or not log output should be reduced to the minimum.
124
     *
125
     * @var bool $silent
126
     */
127
    private $silent = false;
128
129
    /**
130
     * Indicates whether phing should run in strict mode
131
     */
132
    private $strictMode = false;
133
134
    /**
135
     * Indicates if this phing should be run
136
     */
137
    private $readyToRun = false;
138
139
    /**
140
     * Indicates we should only parse and display the project help information
141
     */
142
    private $projectHelp = false;
143
144
    /**
145
     * Used by utility function getResourcePath()
146
     */
147
    private static $importPaths;
148
149
    /**
150
     * System-wide static properties (moved from System)
151
     */
152
    private static $properties = [];
153
154
    /**
155
     * Static system timer.
156
     */
157
    private static $timer;
158
159
    /**
160
     * The current Project
161
     */
162
    private static $currentProject;
163
164
    /**
165
     * Whether to capture PHP errors to buffer.
166
     */
167
    private static $phpErrorCapture = false;
168
169
    /**
170
     * Whether to values in a property file should override existing values.
171
     */
172
    private $propertyFileOverride = false;
173
174
    /**
175
     * Array of captured PHP errors
176
     */
177
    private static $capturedPhpErrors = [];
178
179
    /**
180
     * @var OUtputStream Stream for standard output.
181
     */
182
    private static $out;
183
184
    /**
185
     * @var OutputStream Stream for error output.
186
     */
187
    private static $err;
188
189
    /**
190
     * @var boolean Whether we are using a logfile.
191
     */
192
    private static $isLogFileUsed = false;
193
194
    /**
195
     * Array to hold original ini settings that Phing changes (and needs
196
     * to restore in restoreIni() method).
197
     *
198
     * @var array Struct of array(setting-name => setting-value)
199
     * @see restoreIni()
200
     */
201
    private static $origIniSettings = [];
202
203
    /**
204
     * Whether or not output to the log is to be unadorned.
205
     */
206
    private $emacsMode = false;
207
208
    /**
209
     * @var string
210
     */
211
    private $searchForThis;
212
    private $propertyFiles = [];
213
214
    /**
215
     * Entry point allowing for more options from other front ends.
216
     *
217
     * This method encapsulates the complete build lifecycle.
218
     *
219
     * @param  array $args The commandline args passed to phing shell script.
220
     * @param  array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
221
     *                                         These additional properties will be available using the getDefinedProperty() method and will
222
     *                                         be added to the project's "user" properties
223
     * @see    execute()
224
     * @see    runBuild()
225
     * @throws Exception - if there is an error during build
226
     */
227
    public static function start($args, array $additionalUserProperties = null)
228
    {
229
        try {
230
            $m = new self();
231
            $m->execute($args);
232
        } catch (Exception $exc) {
233
            self::handleLogfile();
234
            self::printMessage($exc);
235
            self::statusExit(1);
236
            return;
237
        }
238
239
        if ($additionalUserProperties !== null) {
240
            foreach ($additionalUserProperties as $key => $value) {
241
                $m::setDefinedProperty($key, $value);
242
            }
243
        }
244
245
        // expect the worst
246
        $exitCode = 1;
247
        try {
248
            try {
249
                $m->runBuild();
250
                $exitCode = 0;
251
            } catch (ExitStatusException $ese) {
252
                $exitCode = $ese->getCode();
253
                if ($exitCode !== 0) {
254
                    self::handleLogfile();
255
                    throw $ese;
256
                }
257
            }
258
        } catch (BuildException $exc) {
259
            // avoid printing output twice: self::printMessage($exc);
260
        } catch (Throwable $exc) {
261
            self::printMessage($exc);
262
        } finally {
263
            self::handleLogfile();
264
        }
265
        self::statusExit($exitCode);
266
    }
267
268
    /**
269
     * This operation is expected to call `exit($int)`, which
270
     * is what the base version does.
271
     * However, it is possible to do something else.
272
     *
273
     * @param int $exitCode code to exit with
274
     */
275
    protected static function statusExit($exitCode)
276
    {
277
        Phing::shutdown();
278
        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...
279
    }
280
281
    /**
282
     * Prints the message of the Exception if it's not null.
283
     *
284
     * @param Throwable $t
285
     */
286
    public static function printMessage(Throwable $t)
287
    {
288
        if (self::$err === null) { // Make sure our error output is initialized
289
            self::initializeOutputStreams();
290
        }
291
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
292
            self::$err->write((string) $t . PHP_EOL);
293
        } else {
294
            self::$err->write($t->getMessage() . PHP_EOL);
295
        }
296
    }
297
298
    /**
299
     * Sets the stdout and stderr streams if they are not already set.
300
     */
301 1
    private static function initializeOutputStreams()
302
    {
303 1
        if (self::$out === null) {
304
            if (!defined('STDOUT')) {
305
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
306
            } else {
307
                self::$out = new OutputStream(STDOUT);
308
            }
309
        }
310 1
        if (self::$err === null) {
311
            if (!defined('STDERR')) {
312
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
313
            } else {
314
                self::$err = new OutputStream(STDERR);
315
            }
316
        }
317 1
    }
318
319
    /**
320
     * Sets the stream to use for standard (non-error) output.
321
     *
322
     * @param OutputStream $stream The stream to use for standard output.
323
     */
324 1
    public static function setOutputStream(OutputStream $stream)
325
    {
326 1
        self::$out = $stream;
327 1
    }
328
329
    /**
330
     * Gets the stream to use for standard (non-error) output.
331
     *
332
     * @return OutputStream
333
     */
334
    public static function getOutputStream()
335
    {
336
        return self::$out;
337
    }
338
339
    /**
340
     * Sets the stream to use for error output.
341
     *
342
     * @param OutputStream $stream The stream to use for error output.
343
     */
344 1
    public static function setErrorStream(OutputStream $stream)
345
    {
346 1
        self::$err = $stream;
347 1
    }
348
349
    /**
350
     * Gets the stream to use for error output.
351
     *
352
     * @return OutputStream
353
     */
354
    public static function getErrorStream()
355
    {
356
        return self::$err;
357
    }
358
359
    /**
360
     * Close logfiles, if we have been writing to them.
361
     *
362
     * @since Phing 2.3.0
363
     *
364
     * @return void
365
     */
366
    private static function handleLogfile()
367
    {
368
        if (self::$isLogFileUsed) {
369
            self::$err->close();
370
            self::$out->close();
371
        }
372
    }
373
374
    /**
375
     * Making output level a static property so that this property
376
     * can be accessed by other parts of the system, enabling
377
     * us to display more information -- e.g. backtraces -- for "debug" level.
378
     *
379
     * @return int
380
     */
381 7
    public static function getMsgOutputLevel()
382
    {
383 7
        return self::$msgOutputLevel;
384
    }
385
386
    /**
387
     * Command line entry point. This method kicks off the building
388
     * of a project object and executes a build using either a given
389
     * target or the default target.
390
     *
391
     * @param array $args Command line args.
392
     *
393
     * @return void
394
     */
395
    public static function fire($args)
396
    {
397
        self::start($args, null);
398
    }
399
400
    /**
401
     * Setup/initialize Phing environment from commandline args.
402
     *
403
     * @param array $args commandline args passed to phing shell.
404
     *
405
     * @throws ConfigurationException
406
     *
407
     * @return void
408
     */
409
    public function execute($args)
410
    {
411
        self::$definedProps = new Properties();
412
        $this->searchForThis = null;
413
414
        // 1) First handle any options which should always
415
        // Note: The order in which these are executed is important (if multiple of these options are specified)
416
417
        if (in_array('-help', $args) || in_array('-h', $args)) {
418
            static::printUsage();
419
420
            return;
421
        }
422
423
        if (in_array('-version', $args) || in_array('-v', $args)) {
424
            static::printVersion();
425
426
            return;
427
        }
428
429
        if (in_array('-init', $args) || in_array('-i', $args)) {
430
            $key = array_search('-init', $args) ?: array_search('-i', $args);
431
            $path = $args[$key + 1] ?? null;
432
433
            self::init($path);
434
435
            return;
436
        }
437
438
        if (in_array('-diagnostics', $args)) {
439
            Diagnostics::doReport(new PrintStream(self::$out));
440
441
            return;
442
        }
443
444
        // 2) Next pull out stand-alone args.
445
        // Note: The order in which these are executed is important (if multiple of these options are specified)
446
447
        if (
448
            false !== ($key = array_search('-quiet', $args, true)) ||
449
            false !== ($key = array_search(
450
                '-q',
451
                $args,
452
                true
453
            ))
454
        ) {
455
            self::$msgOutputLevel = Project::MSG_WARN;
456
            unset($args[$key]);
457
        }
458
459
        if (
460
            false !== ($key = array_search('-emacs', $args, true))
461
            || false !== ($key = array_search('-e', $args, true))
462
        ) {
463
            $this->emacsMode = true;
464
            unset($args[$key]);
465
        }
466
467
        if (false !== ($key = array_search('-verbose', $args, true))) {
468
            self::$msgOutputLevel = Project::MSG_VERBOSE;
469
            unset($args[$key]);
470
        }
471
472
        if (false !== ($key = array_search('-debug', $args, true))) {
473
            self::$msgOutputLevel = Project::MSG_DEBUG;
474
            unset($args[$key]);
475
        }
476
477
        if (
478
            false !== ($key = array_search('-silent', $args, true))
479
            || false !== ($key = array_search('-S', $args, true))
480
        ) {
481
            $this->silent = true;
482
            unset($args[$key]);
483
        }
484
485
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
486
            $this->propertyFileOverride = true;
487
            unset($args[$key]);
488
        }
489
490
        // 3) Finally, cycle through to parse remaining args
491
        //
492
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
493
        $max = $keys ? max($keys) : -1;
494
        for ($i = 0; $i <= $max; $i++) {
495
            if (!array_key_exists($i, $args)) {
496
                // skip this argument, since it must have been removed above.
497
                continue;
498
            }
499
500
            $arg = $args[$i];
501
502
            if ($arg == "-logfile") {
503
                try {
504
                    // see: http://phing.info/trac/ticket/65
505
                    if (!isset($args[$i + 1])) {
506
                        $msg = "You must specify a log file when using the -logfile argument\n";
507
                        throw new ConfigurationException($msg);
508
                    }
509
510
                    $logFile = new File($args[++$i]);
511
                    $out = new FileOutputStream($logFile); // overwrite
512
                    self::setOutputStream($out);
513
                    self::setErrorStream($out);
514
                    self::$isLogFileUsed = true;
515
                } catch (IOException $ioe) {
516
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
517
                    throw new ConfigurationException($msg, $ioe);
518
                }
519
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
520
                if (!isset($args[$i + 1])) {
521
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
522
                    throw new ConfigurationException($msg);
523
                }
524
525
                $this->buildFile = new File($args[++$i]);
526
            } elseif ($arg == "-listener") {
527
                if (!isset($args[$i + 1])) {
528
                    $msg = "You must specify a listener class when using the -listener argument";
529
                    throw new ConfigurationException($msg);
530
                }
531
532
                $this->listeners[] = $args[++$i];
533
            } elseif (StringHelper::startsWith("-D", $arg)) {
534
                // Evaluating the property information //
535
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
536
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
537
                    $name = $args[++$i];
538
                } else {
539
                    $name = substr($arg, 2);
540
                }
541
542
                $value = null;
543
                $posEq = strpos($name, "=");
544
                if ($posEq !== false) {
545
                    $value = substr($name, $posEq + 1);
546
                    $name = substr($name, 0, $posEq);
547
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
548
                    $value = $args[++$i];
549
                }
550
                self::$definedProps->setProperty($name, $value);
551
            } elseif ($arg == "-logger") {
552
                if (!isset($args[$i + 1])) {
553
                    $msg = "You must specify a classname when using the -logger argument";
554
                    throw new ConfigurationException($msg);
555
                }
556
557
                $this->loggerClassname = $args[++$i];
558
            } elseif ($arg == "-no-strict") {
559
                $this->strictMode = false;
560
            } elseif ($arg == "-strict") {
561
                $this->strictMode = true;
562
            } elseif ($arg == "-inputhandler") {
563
                if ($this->inputHandlerClassname !== null) {
564
                    throw new ConfigurationException("Only one input handler class may be specified.");
565
                }
566
                if (!isset($args[$i + 1])) {
567
                    $msg = "You must specify a classname when using the -inputhandler argument";
568
                    throw new ConfigurationException($msg);
569
                }
570
571
                $this->inputHandlerClassname = $args[++$i];
572
            } elseif ($arg === "-propertyfile") {
573
                $i = $this->handleArgPropertyFile($args, $i);
574
            } elseif ($arg === "-keep-going" || $arg === "-k") {
575
                $this->keepGoingMode = true;
576
            } elseif ($arg == "-longtargets") {
577
                self::$definedProps->setProperty('phing.showlongtargets', 1);
578
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
579
                // set the flag to display the targets and quit
580
                $this->projectHelp = true;
581
            } elseif ($arg == "-find") {
582
                // eat up next arg if present, default to build.xml
583
                if ($i < count($args) - 1) {
584
                    $this->searchForThis = $args[++$i];
585
                } else {
586
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
587
                }
588
            } elseif (substr($arg, 0, 1) == "-") {
589
                // we don't have any more args
590
                self::printUsage();
591
                self::$err->write(PHP_EOL);
592
                throw new ConfigurationException("Unknown argument: " . $arg);
593
            } else {
594
                // if it's no other arg, it may be the target
595
                $this->targets[] = $arg;
596
            }
597
        }
598
599
        // if buildFile was not specified on the command line,
600
        if ($this->buildFile === null) {
601
            // but -find then search for it
602
            if ($this->searchForThis !== null) {
603
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
604
            } else {
605
                $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
606
            }
607
        }
608
609
        try {
610
            // make sure buildfile (or buildfile.dist) exists
611
            if (!$this->buildFile->exists()) {
612
                $distFile = new File($this->buildFile->getAbsolutePath() . ".dist");
613
                if (!$distFile->exists()) {
614
                    throw new ConfigurationException(
615
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
616
                    );
617
                }
618
                $this->buildFile = $distFile;
619
            }
620
621
            // make sure it's not a directory
622
            if ($this->buildFile->isDirectory()) {
623
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
624
            }
625
        } catch (IOException $e) {
626
            // something else happened, buildfile probably not readable
627
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
628
        }
629
630
        $this->loadPropertyFiles();
631
632
        $this->readyToRun = true;
633
    }
634
635
    /**
636
     * Handle the -propertyfile argument.
637
     *
638
     * @param array $args
639
     * @param int $pos
640
     *
641
     * @return int
642
     *
643
     * @throws ConfigurationException
644
     * @throws IOException
645
     */
646
    private function handleArgPropertyFile(array $args, int $pos): int
647
    {
648
        if (!isset($args[$pos + 1])) {
649
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
650
        }
651
652
        $this->propertyFiles[] = $args[++$pos];
653
654
        return $pos;
655
    }
656
657
    /**
658
     * @throws IOException
659
     */
660
    private function loadPropertyFiles()
661
    {
662
        foreach ($this->propertyFiles as $filename) {
663
            $fileParserFactory = new FileParserFactory();
664
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
665
            $p = new Properties(null, $fileParser);
666
            try {
667
                $p->load(new File($filename));
668
            } catch (IOException $e) {
669
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
670
            }
671
            foreach ($p->getProperties() as $prop => $value) {
672
                self::$definedProps->setProperty($prop, $value);
673
            }
674
        }
675
    }
676
677
    /**
678
     * Search parent directories for the build file.
679
     *
680
     * Takes the given target as a suffix to append to each
681
     * parent directory in search of a build file.  Once the
682
     * root of the file-system has been reached an exception
683
     * is thrown.
684
     *
685
     * @param string $start Start file path.
686
     * @param string $suffix Suffix filename to look for in parents.
687
     *
688
     * @return File A handle to the build file
689
     *@throws ConfigurationException
690
     *
691
     */
692
    private function _findBuildFile($start, $suffix)
693
    {
694
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
695
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
696
        }
697
698
        $parent = new File((new File($start))->getAbsolutePath());
699
        $file = new File($parent, $suffix);
700
701
        // check if the target file exists in the current directory
702
        while (!$file->exists()) {
703
            // change to parent directory
704
            $parent = $parent->getParentFile();
705
706
            // if parent is null, then we are at the root of the fs,
707
            // complain that we can't find the build file.
708
            if ($parent === null) {
709
                throw new ConfigurationException("Could not locate a build file!");
710
            }
711
            // refresh our file handle
712
            $file = new File($parent, $suffix);
713
        }
714
715
        return $file;
716
    }
717
718
    /**
719
     * Executes the build.
720
     *
721
     * @throws IOException
722
     * @throws Throwable
723
     */
724
    public function runBuild(): void
725
    {
726
        if (!$this->readyToRun) {
727
            return;
728
        }
729
730
        $project = new Project();
731
732
        self::setCurrentProject($project);
733
        set_error_handler(['Phing\Phing', 'handlePhpError']);
734
735
        $error = null;
736
737
        try {
738
            $this->addBuildListeners($project);
739
            $this->addInputHandler($project);
740
741
            // set this right away, so that it can be used in logging.
742
            $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
743
            $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
744
            $project->setUserProperty("phing.version", static::getPhingVersion());
745
            $project->fireBuildStarted();
746
            $project->init();
747
            $project->setKeepGoingMode($this->keepGoingMode);
748
749
            $e = self::$definedProps->keys();
750
            while (count($e)) {
751
                $arg = (string) array_shift($e);
752
                $value = (string) self::$definedProps->getProperty($arg);
753
                $project->setUserProperty($arg, $value);
754
            }
755
            unset($e);
756
757
            // first use the Configurator to create the project object
758
            // from the given build file.
759
760
            ProjectConfigurator::configureProject($project, $this->buildFile);
761
762
            // Set the project mode
763
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
764
765
            // make sure that minimum required phing version is satisfied
766
            $this->comparePhingVersion($project->getPhingVersion());
767
768
            if ($this->projectHelp) {
769
                $this->printDescription($project);
770
                $this->printTargets($project);
771
                return;
772
            }
773
774
            // make sure that we have a target to execute
775
            if (count($this->targets) === 0) {
776
                $this->targets[] = $project->getDefaultTarget();
777
            }
778
779
            $project->executeTargets($this->targets);
780
        } catch (Throwable $t) {
781
            $error = $t;
782
            throw $t;
783
        } finally {
784
            if (!$this->projectHelp) {
785
                try {
786
                    $project->fireBuildFinished($error);
787
                } catch (Exception $e) {
788
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
789
                    self::$err->write($e->getTraceAsString());
790
                    if ($error !== null) {
791
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
792
                        self::$err->write($error->getTraceAsString());
793
                    }
794
                    throw new BuildException($t);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $t does not seem to be defined for all execution paths leading up to this point.
Loading history...
795
                }
796
            } elseif ($error !== null) {
797
                $project->log($error->getMessage(), Project::MSG_ERR);
798
            }
799
800
            restore_error_handler();
801
            self::unsetCurrentProject();
802
        }
803
    }
804
805
    /**
806
     * @param string $version
807
     *
808
     * @throws BuildException
809
     * @throws ConfigurationException
810
     */
811
    private function comparePhingVersion($version)
812
    {
813
        $current = strtolower(self::getPhingVersion());
814
        $current = trim(str_replace('phing', '', $current));
815
816
        // make sure that version checks are not applied to trunk
817
        if ('dev' === $current) {
818
            return;
819
        }
820
821
        if (-1 == version_compare($current, $version)) {
822
            throw new BuildException(
823
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
824
            );
825
        }
826
    }
827
828
    /**
829
     * Bind any registered build listeners to this project.
830
     *
831
     * This means adding the logger and any build listeners that were specified
832
     * with -listener arg.
833
     *
834
     * @param  Project $project
835
     * @throws BuildException
836
     * @throws ConfigurationException
837
     * @return void
838
     */
839
    private function addBuildListeners(Project $project)
840
    {
841
        // Add the default listener
842
        $project->addBuildListener($this->createLogger());
843
844
        foreach ($this->listeners as $listenerClassname) {
845
            try {
846
                $clz = Phing::import($listenerClassname);
847
            } catch (Exception $e) {
848
                $msg = "Unable to instantiate specified listener "
849
                    . "class " . $listenerClassname . " : "
850
                    . $e->getMessage();
851
                throw new ConfigurationException($msg);
852
            }
853
854
            $listener = new $clz();
855
856
            if ($listener instanceof StreamRequiredBuildLogger) {
857
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
858
            }
859
            $project->addBuildListener($listener);
860
        }
861
    }
862
863
    /**
864
     * Creates the InputHandler and adds it to the project.
865
     *
866
     * @param Project $project the project instance.
867
     *
868
     * @throws ConfigurationException
869
     */
870
    private function addInputHandler(Project $project)
871
    {
872
        if ($this->inputHandlerClassname === null) {
873
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
874
        } else {
875
            try {
876
                $clz = Phing::import($this->inputHandlerClassname);
877
                $handler = new $clz();
878
                if ($project !== null && method_exists($handler, 'setProject')) {
879
                    $handler->setProject($project);
880
                }
881
            } catch (Exception $e) {
882
                $msg = "Unable to instantiate specified input handler "
883
                    . "class " . $this->inputHandlerClassname . " : "
884
                    . $e->getMessage();
885
                throw new ConfigurationException($msg);
886
            }
887
        }
888
        $project->setInputHandler($handler);
889
    }
890
891
    /**
892
     * Creates the default build logger for sending build events to the log.
893
     *
894
     * @throws BuildException
895
     * @return BuildLogger The created Logger
896
     */
897
    private function createLogger()
898
    {
899
        if ($this->silent) {
900
            $logger = new SilentLogger();
901
            self::$msgOutputLevel = Project::MSG_WARN;
902
        } elseif ($this->loggerClassname !== null) {
903
            self::import($this->loggerClassname);
904
            // get class name part
905
            $classname = self::import($this->loggerClassname);
906
            $logger = new $classname();
907
            if (!($logger instanceof BuildLogger)) {
908
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
909
            }
910
        } else {
911
            $logger = new DefaultLogger();
912
        }
913
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
914
        $logger->setOutputStream(self::$out);
915
        $logger->setErrorStream(self::$err);
916
        $logger->setEmacsMode($this->emacsMode);
917
918
        return $logger;
919
    }
920
921
    /**
922
     * Sets the current Project
923
     *
924
     * @param Project $p
925
     */
926 1
    public static function setCurrentProject($p)
927
    {
928 1
        self::$currentProject = $p;
929 1
    }
930
931
    /**
932
     * Unsets the current Project
933
     */
934 1
    public static function unsetCurrentProject()
935
    {
936 1
        self::$currentProject = null;
937 1
    }
938
939
    /**
940
     * Gets the current Project.
941
     *
942
     * @return Project Current Project or NULL if none is set yet/still.
943
     */
944 5
    public static function getCurrentProject()
945
    {
946 5
        return self::$currentProject;
947
    }
948
949
    /**
950
     * A static convenience method to send a log to the current (last-setup) Project.
951
     * If there is no currently-configured Project, then this will do nothing.
952
     *
953
     * @param string $message
954
     * @param int $priority Project::MSG_INFO, etc.
955
     */
956
    public static function log($message, $priority = Project::MSG_INFO)
957
    {
958
        $p = self::getCurrentProject();
959
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Phing\Project, thus it always evaluated to true.
Loading history...
960
            $p->log($message, $priority);
961
        }
962
    }
963
964
    /**
965
     * Error handler for PHP errors encountered during the build.
966
     * This uses the logging for the currently configured project.
967
     *
968
     * @param $level
969
     * @param string $message
970
     * @param $file
971
     * @param $line
972
     */
973
    public static function handlePhpError($level, $message, $file, $line)
974
    {
975
976
        // don't want to print suppressed errors
977
        if (error_reporting() > 0) {
978
            if (self::$phpErrorCapture) {
979
                self::$capturedPhpErrors[] = [
980
                    'message' => $message,
981
                    'level' => $level,
982
                    'line' => $line,
983
                    'file' => $file
984
                ];
985
            } else {
986
                $message = '[PHP Error] ' . $message;
987
                $message .= ' [line ' . $line . ' of ' . $file . ']';
988
989
                switch ($level) {
990
                    case E_USER_DEPRECATED:
991
                    case E_DEPRECATED:
992
                    case E_STRICT:
993
                    case E_NOTICE:
994
                    case E_USER_NOTICE:
995
                        self::log($message, Project::MSG_VERBOSE);
996
                        break;
997
                    case E_WARNING:
998
                    case E_USER_WARNING:
999
                        self::log($message, Project::MSG_WARN);
1000
                        break;
1001
                    case E_ERROR:
1002
                    case E_USER_ERROR:
1003
                    default:
1004
                        self::log($message, Project::MSG_ERR);
1005
                } // switch
1006
            } // if phpErrorCapture
1007
        } // if not @
1008
    }
1009
1010
    /**
1011
     * Begins capturing PHP errors to a buffer.
1012
     * While errors are being captured, they are not logged.
1013
     */
1014
    public static function startPhpErrorCapture()
1015
    {
1016
        self::$phpErrorCapture = true;
1017
        self::$capturedPhpErrors = [];
1018
    }
1019
1020
    /**
1021
     * Stops capturing PHP errors to a buffer.
1022
     * The errors will once again be logged after calling this method.
1023
     */
1024
    public static function stopPhpErrorCapture()
1025
    {
1026
        self::$phpErrorCapture = false;
1027
    }
1028
1029
    /**
1030
     * Clears the captured errors without affecting the starting/stopping of the capture.
1031
     */
1032
    public static function clearCapturedPhpErrors()
1033
    {
1034
        self::$capturedPhpErrors = [];
1035
    }
1036
1037
    /**
1038
     * Gets any PHP errors that were captured to buffer.
1039
     *
1040
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1041
     */
1042
    public static function getCapturedPhpErrors()
1043
    {
1044
        return self::$capturedPhpErrors;
1045
    }
1046
1047
    /**
1048
     * Prints the usage of how to use this class
1049
     */
1050 1
    public static function printUsage()
1051
    {
1052 1
        $msg = "";
1053 1
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1054 1
        $msg .= "Options: " . PHP_EOL;
1055 1
        $msg .= "  -h -help               print this message" . PHP_EOL;
1056 1
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1057 1
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1058 1
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1059 1
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1060 1
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1061 1
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1062 1
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1063 1
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1064 1
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1065 1
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1066 1
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1067 1
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1068 1
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1069 1
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1070 1
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1071 1
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1072 1
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1073 1
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1074 1
        $msg .= "                         on failed target(s)" . PHP_EOL;
1075 1
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1076 1
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1077 1
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1078 1
        $msg .= "                         filesystem and use it" . PHP_EOL;
1079 1
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1080
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1081 1
        $msg .= PHP_EOL;
1082 1
        $msg .= "Report bugs to <[email protected]>" . PHP_EOL;
1083 1
        self::$err->write($msg);
1084 1
    }
1085
1086
    /**
1087
     * Prints the current Phing version.
1088
     */
1089
    public static function printVersion()
1090
    {
1091
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1092
    }
1093
1094
    /**
1095
     * Creates generic buildfile
1096
     *
1097
     * @param string $path
1098
     */
1099
    public static function init($path)
1100
    {
1101
        if ($buildfilePath = self::initPath($path)) {
1102
            self::initWrite($buildfilePath);
1103
        }
1104
    }
1105
1106
1107
    /**
1108
     * Returns buildfile's path
1109
     *
1110
     * @param $path
1111
     *
1112
     * @return string
1113
     * @throws ConfigurationException
1114
     */
1115
    protected static function initPath($path)
1116
    {
1117
        // Fallback
1118
        if (empty($path)) {
1119
            $defaultDir = self::getProperty('application.startdir');
1120
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1121
        }
1122
1123
        // Adding filename if necessary
1124
        if (is_dir($path)) {
1125
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1126
        }
1127
1128
        // Check if path is available
1129
        $dirname = dirname($path);
1130
        if (is_dir($dirname) && !is_file($path)) {
1131
            return $path;
1132
        }
1133
1134
        // Path is valid, but buildfile already exists
1135
        if (is_file($path)) {
1136
            throw new ConfigurationException('Buildfile already exists.');
1137
        }
1138
1139
        throw new ConfigurationException('Invalid path for sample buildfile.');
1140
    }
1141
1142
1143
    /**
1144
     * Writes sample buildfile
1145
     *
1146
     * If $buildfilePath does not exist, the buildfile is created.
1147
     *
1148
     * @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...
1149
     *
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 File($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 1
                    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 840
    public static function import($dotPath, $classpath = null)
1350
    {
1351 840
        if (strpos($dotPath, '.') !== false) {
1352 838
            $classname = StringHelper::unqualify($dotPath);
1353
        } else {
1354 839
            $classname = $dotPath;
1355 839
            $dotPath = '';
1356 839
            $shortClassName = $classname;
1357 839
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1358 839
                $namespace = substr($shortClassName, 0, $lastNsPos);
1359 839
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1360 839
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1361
            }
1362 839
            $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 840
        if (class_exists($classname)) {
1368 838
            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);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type array; however, parameter $path of Phing\Phing::importFile() does only seem to accept string, 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

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