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 <andi@binarycloud.com>
33
 * @author Hans Lellelid <hans@xmpl.org>
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 0
    public static function start($args, array $additionalUserProperties = null)
199
    {
200
        try {
201 0
            $m = new self();
202 0
            $m->execute($args);
203 0
        } catch (Exception $exc) {
204 0
            self::handleLogfile();
205 0
            self::printMessage($exc);
206 0
            self::statusExit(1);
207 0
            return;
208
        }
209

210 0
        if ($additionalUserProperties !== null) {
211 0
            foreach ($additionalUserProperties as $key => $value) {
212 0
                $m::setDefinedProperty($key, $value);
213
            }
214
        }
215

216
        // expect the worst
217 0
        $exitCode = 1;
218
        try {
219
            try {
220 0
                $m->runBuild();
221 0
                $exitCode = 0;
222 0
            } catch (ExitStatusException $ese) {
223 0
                $exitCode = $ese->getCode();
224 0
                if ($exitCode !== 0) {
225 0
                    self::handleLogfile();
226 0
                    throw $ese;
227
                }
228
            }
229 0
        } catch (BuildException $exc) {
230
            // avoid printing output twice: self::printMessage($exc);
231 0
        } catch (Throwable $exc) {
232 0
            self::printMessage($exc);
233 0
        } finally {
234 0
            self::handleLogfile();
235
        }
236 0
        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 0
    protected static function statusExit($exitCode)
247
    {
248 0
        Phing::shutdown();
249 0
        exit($exitCode);
250
    }
251

252
    /**
253
     * Prints the message of the Exception if it's not null.
254
     *
255
     * @param Throwable $t
256
     */
257 0
    public static function printMessage(Throwable $t)
258
    {
259 0
        if (self::$err === null) { // Make sure our error output is initialized
260 0
            self::initializeOutputStreams();
261
        }
262 0
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
263 0
            self::$err->write((string) $t . PHP_EOL);
264
        } else {
265 0
            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 2
    private static function initializeOutputStreams()
273
    {
274 2
        if (self::$out === null) {
275 0
            if (!defined('STDOUT')) {
276 0
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
277
            } else {
278 0
                self::$out = new OutputStream(STDOUT);
279
            }
280
        }
281 2
        if (self::$err === null) {
282 0
            if (!defined('STDERR')) {
283 0
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
284
            } else {
285 0
                self::$err = new OutputStream(STDERR);
286
            }
287
        }
288
    }
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 2
    public static function setOutputStream(OutputStream $stream)
296
    {
297 2
        self::$out = $stream;
298
    }
299

300
    /**
301
     * Gets the stream to use for standard (non-error) output.
302
     *
303
     * @return OutputStream
304
     */
305 0
    public static function getOutputStream()
306
    {
307 0
        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 2
    public static function setErrorStream(OutputStream $stream)
316
    {
317 2
        self::$err = $stream;
318
    }
319

320
    /**
321
     * Gets the stream to use for error output.
322
     *
323
     * @return OutputStream
324
     */
325 0
    public static function getErrorStream()
326
    {
327 0
        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 0
    private static function handleLogfile()
338
    {
339 0
        if (self::$isLogFileUsed) {
340 0
            self::$err->close();
341 0
            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 2
    public static function getMsgOutputLevel()
353
    {
354 2
        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 0
    public static function fire($args)
367
    {
368 0
        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 0
    public function execute($args)
381
    {
382 0
        self::$definedProps = new Properties();
383 0
        $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 0
        if (in_array('-help', $args) || in_array('-h', $args)) {
389 0
            static::printUsage();
390

391 0
            return;
392
        }
393

394 0
        if (in_array('-version', $args) || in_array('-v', $args)) {
395 0
            static::printVersion();
396

397 0
            return;
398
        }
399

400 0
        if (in_array('-init', $args) || in_array('-i', $args)) {
401 0
            $key = array_search('-init', $args) ?: array_search('-i', $args);
402 0
            $path = $args[$key + 1] ?? null;
403

404 0
            self::init($path);
405

406 0
            return;
407
        }
408

409 0
        if (in_array('-diagnostics', $args)) {
410 0
            Diagnostics::doReport(new PrintStream(self::$out));
411

412 0
            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 0
            false !== ($key = array_search('-quiet', $args, true)) ||
420 0
            false !== ($key = array_search(
421 0
                '-q',
422 0
                $args,
423 0
                true
424
            ))
425
        ) {
426 0
            self::$msgOutputLevel = Project::MSG_WARN;
427 0
            unset($args[$key]);
428
        }
429

430
        if (
431 0
            false !== ($key = array_search('-emacs', $args, true))
432 0
            || false !== ($key = array_search('-e', $args, true))
433
        ) {
434 0
            $this->emacsMode = true;
435 0
            unset($args[$key]);
436
        }
437

438 0
        if (false !== ($key = array_search('-verbose', $args, true))) {
439 0
            self::$msgOutputLevel = Project::MSG_VERBOSE;
440 0
            unset($args[$key]);
441
        }
442

443 0
        if (false !== ($key = array_search('-debug', $args, true))) {
444 0
            self::$msgOutputLevel = Project::MSG_DEBUG;
445 0
            unset($args[$key]);
446
        }
447

448
        if (
449 0
            false !== ($key = array_search('-silent', $args, true))
450 0
            || false !== ($key = array_search('-S', $args, true))
451
        ) {
452 0
            $this->silent = true;
453 0
            unset($args[$key]);
454
        }
455

456 0
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
457 0
            $this->propertyFileOverride = true;
458 0
            unset($args[$key]);
459
        }
460

461
        // 3) Finally, cycle through to parse remaining args
462
        //
463 0
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
464 0
        $max = $keys ? max($keys) : -1;
465 0
        for ($i = 0; $i <= $max; $i++) {
466 0
            if (!array_key_exists($i, $args)) {
467
                // skip this argument, since it must have been removed above.
468 0
                continue;
469
            }
470

471 0
            $arg = $args[$i];
472

473 0
            if ($arg == "-logfile") {
474
                try {
475
                    // see: http://phing.info/trac/ticket/65
476 0
                    if (!isset($args[$i + 1])) {
477 0
                        $msg = "You must specify a log file when using the -logfile argument\n";
478 0
                        throw new ConfigurationException($msg);
479
                    }
480

481 0
                    $logFile = new PhingFile($args[++$i]);
482 0
                    $out = new FileOutputStream($logFile); // overwrite
483 0
                    self::setOutputStream($out);
484 0
                    self::setErrorStream($out);
485 0
                    self::$isLogFileUsed = true;
486 0
                } catch (IOException $ioe) {
487 0
                    $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
488 0
                    throw new ConfigurationException($msg, $ioe);
489
                }
490 0
            } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
491 0
                if (!isset($args[$i + 1])) {
492 0
                    $msg = "You must specify a buildfile when using the -buildfile argument.";
493 0
                    throw new ConfigurationException($msg);
494
                }
495

496 0
                $this->buildFile = new PhingFile($args[++$i]);
497 0
            } elseif ($arg == "-listener") {
498 0
                if (!isset($args[$i + 1])) {
499 0
                    $msg = "You must specify a listener class when using the -listener argument";
500 0
                    throw new ConfigurationException($msg);
501
                }
502

503 0
                $this->listeners[] = $args[++$i];
504 0
            } 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 0
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
508 0
                    $name = $args[++$i];
509
                } else {
510 0
                    $name = substr($arg, 2);
511
                }
512

513 0
                $value = null;
514 0
                $posEq = strpos($name, "=");
515 0
                if ($posEq !== false) {
516 0
                    $value = substr($name, $posEq + 1);
517 0
                    $name = substr($name, 0, $posEq);
518 0
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith("-D", $args[$i + 1])) {
519 0
                    $value = $args[++$i];
520
                }
521 0
                self::$definedProps->setProperty($name, $value);
522 0
            } elseif ($arg == "-logger") {
523 0
                if (!isset($args[$i + 1])) {
524 0
                    $msg = "You must specify a classname when using the -logger argument";
525 0
                    throw new ConfigurationException($msg);
526
                }
527

528 0
                $this->loggerClassname = $args[++$i];
529 0
            } elseif ($arg == "-no-strict") {
530 0
                $this->strictMode = false;
531 0
            } elseif ($arg == "-strict") {
532 0
                $this->strictMode = true;
533 0
            } elseif ($arg == "-inputhandler") {
534 0
                if ($this->inputHandlerClassname !== null) {
535 0
                    throw new ConfigurationException("Only one input handler class may be specified.");
536
                }
537 0
                if (!isset($args[$i + 1])) {
538 0
                    $msg = "You must specify a classname when using the -inputhandler argument";
539 0
                    throw new ConfigurationException($msg);
540
                }
541

542 0
                $this->inputHandlerClassname = $args[++$i];
543 0
            } elseif ($arg === "-propertyfile") {
544 0
                $i = $this->handleArgPropertyFile($args, $i);
545 0
            } elseif ($arg === "-keep-going" || $arg === "-k") {
546 0
                $this->keepGoingMode = true;
547 0
            } elseif ($arg == "-longtargets") {
548 0
                self::$definedProps->setProperty('phing.showlongtargets', 1);
549 0
            } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
550
                // set the flag to display the targets and quit
551 0
                $this->projectHelp = true;
552 0
            } elseif ($arg == "-find") {
553
                // eat up next arg if present, default to build.xml
554 0
                if ($i < count($args) - 1) {
555 0
                    $this->searchForThis = $args[++$i];
556
                } else {
557 0
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
558
                }
559 0
            } elseif (substr($arg, 0, 1) == "-") {
560
                // we don't have any more args
561 0
                self::printUsage();
562 0
                self::$err->write(PHP_EOL);
563 0
                throw new ConfigurationException("Unknown argument: " . $arg);
564
            } else {
565
                // if it's no other arg, it may be the target
566 0
                $this->targets[] = $arg;
567
            }
568
        }
569

570
        // if buildFile was not specified on the command line,
571 0
        if ($this->buildFile === null) {
572
            // but -find then search for it
573 0
            if ($this->searchForThis !== null) {
574 0
                $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
575
            } else {
576 0
                $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
577
            }
578
        }
579

580
        try {
581
            // make sure buildfile (or buildfile.dist) exists
582 0
            if (!$this->buildFile->exists()) {
583 0
                $distFile = new PhingFile($this->buildFile->getAbsolutePath() . ".dist");
584 0
                if (!$distFile->exists()) {
585 0
                    throw new ConfigurationException(
586 0
                        "Buildfile: " . $this->buildFile->__toString() . " does not exist!"
587
                    );
588
                }
589 0
                $this->buildFile = $distFile;
590
            }
591

592
            // make sure it's not a directory
593 0
            if ($this->buildFile->isDirectory()) {
594 0
                throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
595
            }
596 0
        } catch (IOException $e) {
597
            // something else happened, buildfile probably not readable
598 0
            throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is not readable!");
599
        }
600

601 0
        $this->loadPropertyFiles();
602

603 0
        $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 0
    private function handleArgPropertyFile(array $args, int $pos): int
618
    {
619 0
        if (!isset($args[$pos + 1])) {
620 0
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
621
        }
622

623 0
        $this->propertyFiles[] = $args[++$pos];
624

625 0
        return $pos;
626
    }
627

628
    /**
629
     * @throws IOException
630
     */
631 0
    private function loadPropertyFiles()
632
    {
633 0
        foreach ($this->propertyFiles as $filename) {
634 0
            $fileParserFactory = new FileParserFactory();
635 0
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
636 0
            $p = new Properties(null, $fileParser);
637
            try {
638 0
                $p->load(new PhingFile($filename));
639 0
            } catch (IOException $e) {
640 0
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
641
            }
642 0
            foreach ($p->getProperties() as $prop => $value) {
643 0
                self::$definedProps->setProperty($prop, $value);
644
            }
645
        }
646
    }
647

648
    /**
649
     * Search parent directories for the build file.
650
     *
651
     * Takes the given target as a suffix to append to each
652
     * parent directory in search of a build file.  Once the
653
     * root of the file-system has been reached an exception
654
     * is thrown.
655
     *
656
     * @param string $start Start file path.
657
     * @param string $suffix Suffix filename to look for in parents.
658
     *
659
     * @throws ConfigurationException
660
     *
661
     * @return PhingFile A handle to the build file
662
     */
663 0
    private function _findBuildFile($start, $suffix)
664
    {
665 0
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
666 0
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
667
        }
668

669 0
        $parent = new PhingFile((new PhingFile($start))->getAbsolutePath());
670 0
        $file = new PhingFile($parent, $suffix);
671

672
        // check if the target file exists in the current directory
673 0
        while (!$file->exists()) {
674
            // change to parent directory
675 0
            $parent = $parent->getParentFile();
676

677
            // if parent is null, then we are at the root of the fs,
678
            // complain that we can't find the build file.
679 0
            if ($parent === null) {
680 0
                throw new ConfigurationException("Could not locate a build file!");
681
            }
682
            // refresh our file handle
683 0
            $file = new PhingFile($parent, $suffix);
684
        }
685

686 0
        return $file;
687
    }
688

689
    /**
690
     * Executes the build.
691
     *
692
     * @throws IOException
693
     * @throws Throwable
694
     */
695 0
    public function runBuild(): void
696
    {
697 0
        if (!$this->readyToRun) {
698 0
            return;
699
        }
700

701 0
        $project = new Project();
702

703 0
        self::setCurrentProject($project);
704 0
        set_error_handler(['Phing', 'handlePhpError']);
705

706 0
        $error = null;
707

708
        try {
709 0
            $this->addBuildListeners($project);
710 0
            $this->addInputHandler($project);
711

712
            // set this right away, so that it can be used in logging.
713 0
            $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
714 0
            $project->setUserProperty("phing.dir", dirname($this->buildFile->getAbsolutePath()));
715 0
            $project->setUserProperty("phing.version", static::getPhingVersion());
716 0
            $project->fireBuildStarted();
717 0
            $project->init();
718 0
            $project->setKeepGoingMode($this->keepGoingMode);
719

720 0
            $e = self::$definedProps->keys();
721 0
            while (count($e)) {
722 0
                $arg = (string) array_shift($e);
723 0
                $value = (string) self::$definedProps->getProperty($arg);
724 0
                $project->setUserProperty($arg, $value);
725
            }
726 0
            unset($e);
727

728
            // first use the Configurator to create the project object
729
            // from the given build file.
730

731 0
            ProjectConfigurator::configureProject($project, $this->buildFile);
732

733
            // Set the project mode
734 0
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
735

736
            // make sure that minimum required phing version is satisfied
737 0
            $this->comparePhingVersion($project->getPhingVersion());
738

739 0
            if ($this->projectHelp) {
740 0
                $this->printDescription($project);
741 0
                $this->printTargets($project);
742 0
                return;
743
            }
744

745
            // make sure that we have a target to execute
746 0
            if (count($this->targets) === 0) {
747 0
                $this->targets[] = $project->getDefaultTarget();
748
            }
749

750 0
            $project->executeTargets($this->targets);
751 0
        } catch (Throwable $t) {
752 0
            $error = $t;
753 0
            throw $t;
754 0
        } finally {
755 0
            if (!$this->projectHelp) {
756
                try {
757 0
                    $project->fireBuildFinished($error);
758 0
                } catch (Exception $e) {
759 0
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
760 0
                    self::$err->write($e->getTraceAsString());
761 0
                    if ($error !== null) {
762 0
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
763 0
                        self::$err->write($error->getTraceAsString());
764
                    }
765 0
                    throw new BuildException($t);
766
                }
767 0
            } elseif ($error !== null) {
768 0
                $project->log($error->getMessage(), Project::MSG_ERR);
769
            }
770

771 0
            restore_error_handler();
772 0
            self::unsetCurrentProject();
773
        }
774
    }
775

776
    /**
777
     * @param string $version
778
     *
779
     * @return int
780
     *
781
     * @throws BuildException
782
     * @throws ConfigurationException
783
     */
784 0
    private function comparePhingVersion($version)
785
    {
786 0
        $current = strtolower(self::getPhingVersion());
787 0
        $current = trim(str_replace('phing', '', $current));
788

789
        // make sure that version checks are not applied to trunk
790 0
        if ('dev' === $current) {
791 0
            return 1;
792
        }
793

794 0
        if (-1 == version_compare($current, $version)) {
795 0
            throw new BuildException(
796 0
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
797
            );
798
        }
799
    }
800

801
    /**
802
     * Bind any registered build listeners to this project.
803
     *
804
     * This means adding the logger and any build listeners that were specified
805
     * with -listener arg.
806
     *
807
     * @param  Project $project
808
     * @throws BuildException
809
     * @throws ConfigurationException
810
     * @return void
811
     */
812 0
    private function addBuildListeners(Project $project)
813
    {
814
        // Add the default listener
815 0
        $project->addBuildListener($this->createLogger());
816

817 0
        foreach ($this->listeners as $listenerClassname) {
818
            try {
819 0
                $clz = Phing::import($listenerClassname);
820 0
            } catch (Exception $e) {
821
                $msg = "Unable to instantiate specified listener "
822 0
                    . "class " . $listenerClassname . " : "
823 0
                    . $e->getMessage();
824 0
                throw new ConfigurationException($msg);
825
            }
826

827 0
            $listener = new $clz();
828

829 0
            if ($listener instanceof StreamRequiredBuildLogger) {
830 0
                throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)");
831
            }
832 0
            $project->addBuildListener($listener);
833
        }
834
    }
835

836
    /**
837
     * Creates the InputHandler and adds it to the project.
838
     *
839
     * @param Project $project the project instance.
840
     *
841
     * @throws ConfigurationException
842
     */
843 0
    private function addInputHandler(Project $project)
844
    {
845 0
        if ($this->inputHandlerClassname === null) {
846 0
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
847
        } else {
848
            try {
849 0
                $clz = Phing::import($this->inputHandlerClassname);
850 0
                $handler = new $clz();
851 0
                if ($project !== null && method_exists($handler, 'setProject')) {
852 0
                    $handler->setProject($project);
853
                }
854 0
            } catch (Exception $e) {
855
                $msg = "Unable to instantiate specified input handler "
856 0
                    . "class " . $this->inputHandlerClassname . " : "
857 0
                    . $e->getMessage();
858 0
                throw new ConfigurationException($msg);
859
            }
860
        }
861 0
        $project->setInputHandler($handler);
862
    }
863

864
    /**
865
     * Creates the default build logger for sending build events to the log.
866
     *
867
     * @throws BuildException
868
     * @return BuildLogger The created Logger
869
     */
870 0
    private function createLogger()
871
    {
872 0
        if ($this->silent) {
873 0
            $logger = new SilentLogger();
874 0
            self::$msgOutputLevel = Project::MSG_WARN;
875 0
        } elseif ($this->loggerClassname !== null) {
876 0
            self::import($this->loggerClassname);
877
            // get class name part
878 0
            $classname = self::import($this->loggerClassname);
879 0
            $logger = new $classname();
880 0
            if (!($logger instanceof BuildLogger)) {
881 0
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
882
            }
883
        } else {
884 0
            $logger = new DefaultLogger();
885
        }
886 0
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
887 0
        $logger->setOutputStream(self::$out);
888 0
        $logger->setErrorStream(self::$err);
889 0
        $logger->setEmacsMode($this->emacsMode);
890

891 0
        return $logger;
892
    }
893

894
    /**
895
     * Sets the current Project
896
     *
897
     * @param Project $p
898
     */
899 2
    public static function setCurrentProject($p)
900
    {
901 2
        self::$currentProject = $p;
902
    }
903

904
    /**
905
     * Unsets the current Project
906
     */
907 2
    public static function unsetCurrentProject()
908
    {
909 2
        self::$currentProject = null;
910
    }
911

912
    /**
913
     * Gets the current Project.
914
     *
915
     * @return Project Current Project or NULL if none is set yet/still.
916
     */
917 2
    public static function getCurrentProject()
918
    {
919 2
        return self::$currentProject;
920
    }
921

922
    /**
923
     * A static convenience method to send a log to the current (last-setup) Project.
924
     * If there is no currently-configured Project, then this will do nothing.
925
     *
926
     * @param string $message
927
     * @param int $priority Project::MSG_INFO, etc.
928
     */
929 0
    public static function log($message, $priority = Project::MSG_INFO)
930
    {
931 0
        $p = self::getCurrentProject();
932 0
        if ($p) {
933 0
            $p->log($message, $priority);
934
        }
935
    }
936

937
    /**
938
     * Error handler for PHP errors encountered during the build.
939
     * This uses the logging for the currently configured project.
940
     *
941
     * @param $level
942
     * @param string $message
943
     * @param $file
944
     * @param $line
945
     */
946 0
    public static function handlePhpError($level, $message, $file, $line)
947
    {
948

949
        // don't want to print suppressed errors
950 0
        if (error_reporting() > 0) {
951 0
            if (self::$phpErrorCapture) {
952 0
                self::$capturedPhpErrors[] = [
953 0
                    'message' => $message,
954 0
                    'level' => $level,
955 0
                    'line' => $line,
956 0
                    'file' => $file
957
                ];
958
            } else {
959 0
                $message = '[PHP Error] ' . $message;
960 0
                $message .= ' [line ' . $line . ' of ' . $file . ']';
961

962 0
                switch ($level) {
963
                    case E_USER_DEPRECATED:
964
                    case E_DEPRECATED:
965
                    case E_STRICT:
966
                    case E_NOTICE:
967
                    case E_USER_NOTICE:
968 0
                        self::log($message, Project::MSG_VERBOSE);
969 0
                        break;
970
                    case E_WARNING:
971
                    case E_USER_WARNING:
972 0
                        self::log($message, Project::MSG_WARN);
973 0
                        break;
974
                    case E_ERROR:
975
                    case E_USER_ERROR:
976
                    default:
977 0
                        self::log($message, Project::MSG_ERR);
978
                } // switch
979
            } // if phpErrorCapture
980
        } // if not @
981
    }
982

983
    /**
984
     * Begins capturing PHP errors to a buffer.
985
     * While errors are being captured, they are not logged.
986
     */
987 0
    public static function startPhpErrorCapture()
988
    {
989 0
        self::$phpErrorCapture = true;
990 0
        self::$capturedPhpErrors = [];
991
    }
992

993
    /**
994
     * Stops capturing PHP errors to a buffer.
995
     * The errors will once again be logged after calling this method.
996
     */
997 0
    public static function stopPhpErrorCapture()
998
    {
999 0
        self::$phpErrorCapture = false;
1000
    }
1001

1002
    /**
1003
     * Clears the captured errors without affecting the starting/stopping of the capture.
1004
     */
1005 0
    public static function clearCapturedPhpErrors()
1006
    {
1007 0
        self::$capturedPhpErrors = [];
1008
    }
1009

1010
    /**
1011
     * Gets any PHP errors that were captured to buffer.
1012
     *
1013
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1014
     */
1015 0
    public static function getCapturedPhpErrors()
1016
    {
1017 0
        return self::$capturedPhpErrors;
1018
    }
1019

1020
    /**
1021
     * Prints the usage of how to use this class
1022
     */
1023 2
    public static function printUsage()
1024
    {
1025 2
        $msg = "";
1026 2
        $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL;
1027 2
        $msg .= "Options: " . PHP_EOL;
1028 2
        $msg .= "  -h -help               print this message" . PHP_EOL;
1029 2
        $msg .= "  -l -list               list available targets in this project" . PHP_EOL;
1030 2
        $msg .= "  -i -init [file]        generates an initial buildfile" . PHP_EOL;
1031 2
        $msg .= "  -v -version            print the version information and exit" . PHP_EOL;
1032 2
        $msg .= "  -q -quiet              be extra quiet" . PHP_EOL;
1033 2
        $msg .= "  -S -silent             print nothing but task outputs and build failures" . PHP_EOL;
1034 2
        $msg .= "  -verbose               be extra verbose" . PHP_EOL;
1035 2
        $msg .= "  -debug                 print debugging information" . PHP_EOL;
1036 2
        $msg .= "  -emacs, -e             produce logging information without adornments" . PHP_EOL;
1037 2
        $msg .= "  -diagnostics           print diagnostics information" . PHP_EOL;
1038 2
        $msg .= "  -strict                runs build in strict mode, considering a warning as error" . PHP_EOL;
1039 2
        $msg .= "  -no-strict             runs build normally (overrides buildfile attribute)" . PHP_EOL;
1040 2
        $msg .= "  -longtargets           show target descriptions during build" . PHP_EOL;
1041 2
        $msg .= "  -logfile <file>        use given file for log" . PHP_EOL;
1042 2
        $msg .= "  -logger <classname>    the class which is to perform logging" . PHP_EOL;
1043 2
        $msg .= "  -listener <classname>  add an instance of class as a project listener" . PHP_EOL;
1044 2
        $msg .= "  -f -buildfile <file>   use given buildfile" . PHP_EOL;
1045 2
        $msg .= "  -D<property>=<value>   use value for given property" . PHP_EOL;
1046 2
        $msg .= "  -keep-going, -k        execute all targets that do not depend" . PHP_EOL;
1047 2
        $msg .= "                         on failed target(s)" . PHP_EOL;
1048 2
        $msg .= "  -propertyfile <file>   load all properties from file" . PHP_EOL;
1049 2
        $msg .= "  -propertyfileoverride  values in property file override existing values" . PHP_EOL;
1050 2
        $msg .= "  -find <file>           search for buildfile towards the root of the" . PHP_EOL;
1051 2
        $msg .= "                         filesystem and use it" . PHP_EOL;
1052 2
        $msg .= "  -inputhandler <file>   the class to use to handle user input" . PHP_EOL;
1053
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
1054 2
        $msg .= PHP_EOL;
1055 2
        $msg .= "Report bugs to <dev@phing.tigris.org>" . PHP_EOL;
1056 2
        self::$err->write($msg);
1057
    }
1058

1059
    /**
1060
     * Prints the current Phing version.
1061
     */
1062 0
    public static function printVersion()
1063
    {
1064 0
        self::$out->write(self::getPhingVersion() . PHP_EOL);
1065
    }
1066

1067
    /**
1068
     * Creates generic buildfile
1069
     *
1070
     * @param string $path
1071
     */
1072 0
    public static function init($path)
1073
    {
1074 0
        if ($buildfilePath = self::initPath($path)) {
1075 0
            self::initWrite($buildfilePath);
1076
        }
1077
    }
1078

1079

1080
    /**
1081
     * Returns buildfile's path
1082
     *
1083
     * @param $path
1084
     *
1085
     * @return string
1086
     * @throws ConfigurationException
1087
     */
1088 0
    protected static function initPath($path)
1089
    {
1090
        // Fallback
1091 0
        if (empty($path)) {
1092 0
            $defaultDir = self::getProperty('application.startdir');
1093 0
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1094
        }
1095

1096
        // Adding filename if necessary
1097 0
        if (is_dir($path)) {
1098 0
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
1099
        }
1100

1101
        // Check if path is available
1102 0
        $dirname = dirname($path);
1103 0
        if (is_dir($dirname) && !is_file($path)) {
1104 0
            return $path;
1105
        }
1106

1107
        // Path is valid, but buildfile already exists
1108 0
        if (is_file($path)) {
1109 0
            throw new ConfigurationException('Buildfile already exists.');
1110
        }
1111

1112 0
        throw new ConfigurationException('Invalid path for sample buildfile.');
1113
    }
1114

1115

1116
    /**
1117
     * Writes sample buildfile
1118
     *
1119
     * If $buildfilePath does not exist, the buildfile is created.
1120
     *
1121
     * @param $buildfilePath buildfile's location
1122
     *
1123
     * @return null
1124
     * @throws ConfigurationException
1125
     */
1126 0
    protected static function initWrite($buildfilePath)
1127
    {
1128
        // Overwriting protection
1129 0
        if (file_exists($buildfilePath)) {
1130 0
            throw new ConfigurationException('Cannot overwrite existing file.');
1131
        }
1132

1133 0
        $content = '<?xml version="1.0" encoding="UTF-8" ?>' . PHP_EOL;
1134 0
        $content .= '' . PHP_EOL;
1135 0
        $content .= '<project name="" description="" default="">' . PHP_EOL;
1136 0
        $content .= '    ' . PHP_EOL;
1137 0
        $content .= '    <target name="" description="">' . PHP_EOL;
1138 0
        $content .= '        ' . PHP_EOL;
1139 0
        $content .= '    </target>' . PHP_EOL;
1140 0
        $content .= '    ' . PHP_EOL;
1141 0
        $content .= '</project>' . PHP_EOL;
1142

1143 0
        file_put_contents($buildfilePath, $content);
1144
    }
1145

1146
    /**
1147
     * Gets the current Phing version based on VERSION.TXT file.
1148
     *
1149
     * @throws ConfigurationException
1150
     *
1151
     * @return string
1152
     */
1153 2
    public static function getPhingVersion()
1154
    {
1155 2
        $versionPath = self::getResourcePath("phing/etc/VERSION.TXT");
1156 2
        if ($versionPath === null) {
1157 2
            $versionPath = self::getResourcePath("etc/VERSION.TXT");
1158
        }
1159 2
        if ($versionPath === null) {
1160 0
            throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable.");
1161
        }
1162
        try { // try to read file
1163 2
            $file = new PhingFile($versionPath);
1164 2
            $reader = new FileReader($file);
1165 2
            $phingVersion = trim($reader->read());
1166 0
        } catch (IOException $iox) {
1167 0
            throw new ConfigurationException("Can't read version information file");
1168
        }
1169

1170 2
        $basePath = dirname(__DIR__, 2);
1171

1172 2
        $version = new Version($phingVersion, $basePath);
1173

1174 2
        return "Phing " . $version->getVersion();
1175
    }
1176

1177
    /**
1178
     * Print the project description, if any
1179
     *
1180
     * @param Project $project
1181
     *
1182
     * @throws IOException
1183
     */
1184 0
    public function printDescription(Project $project)
1185
    {
1186 0
        if ($project->getDescription() !== null) {
1187 0
            $project->log($project->getDescription());
1188
        }
1189
    }
1190

1191
    /**
1192
     * Print out a list of all targets in the current buildfile
1193
     *
1194
     * @param $project
1195
     */
1196 2
    public function printTargets($project)
1197
    {
1198
        // find the target with the longest name
1199 2
        $maxLength = 0;
1200 2
        $targets = $project->getTargets();
1201 2
        $targetName = null;
1202 2
        $targetDescription = null;
1203
        /** @var Target $currentTarget */
1204 2
        $currentTarget = null;
1205

1206
        // split the targets in top-level and sub-targets depending
1207
        // on the presence of a description
1208

1209 2
        $subNames = [];
1210 2
        $subDependencies = [];
1211 2
        $topNameDescMap = [];
1212

1213 2
        foreach ($targets as $currentTarget) {
1214 2
            $targetName = $currentTarget->getName();
1215 2
            $targetDescription = $currentTarget->getDescription();
1216 2
            if ($currentTarget->isHidden()) {
1217 0
                continue;
1218
            }
1219

1220
            // subtargets are targets w/o descriptions
1221 2
            if ($targetDescription === null) {
1222 2
                $subNames[] = $targetName;
1223 2
                $currentDependencies = $currentTarget->getDependencies();
1224 2
                if (!empty($currentDependencies)) {
1225 0
                    array_push($subDependencies, ...$currentTarget->getDependencies());
1226
                }
1227
            } else {
1228
                // topNames and topDescriptions are handled later
1229
                // here we store in hash map (for sorting purposes)
1230 0
                $topNameDescMap[$targetName] = $targetDescription;
1231 0
                if (strlen($targetName) > $maxLength) {
1232 0
                    $maxLength = strlen($targetName);
1233
                }
1234
            }
1235
        }
1236

1237
        // Sort the arrays
1238 2
        sort($subNames); // sort array values, resetting keys (which are numeric)
1239 2
        ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations
1240

1241 2
        $topNames = array_keys($topNameDescMap);
1242 2
        $topDescriptions = array_values($topNameDescMap);
1243 2
        $topDependencies = $currentTarget->getDependencies();
1244

1245 2
        $defaultTarget = $project->getDefaultTarget();
1246

1247 2
        if ($defaultTarget !== null && $defaultTarget !== "") {
1248 0
            $defaultName = [];
1249 0
            $defaultDesc = [];
1250 0
            $defaultName[] = $defaultTarget;
1251

1252 0
            $indexOfDefDesc = array_search($defaultTarget, $topNames, true);
1253 0
            if ($indexOfDefDesc !== false && $indexOfDefDesc >= 0) {
1254 0
                $defaultDesc = [];
1255 0
                $defaultDesc[] = $topDescriptions[$indexOfDefDesc];
1256
            }
1257

1258 0
            $this->_printTargets($project, $defaultName, $defaultDesc, [], "Default target:", $maxLength);
1259
        }
1260 2
        $this->_printTargets($project, $topNames, $topDescriptions, $topDependencies, "Main targets:", $maxLength);
1261 2
        $this->_printTargets($project, $subNames, null, $subDependencies, "Subtargets:", 0);
1262
    }
1263

1264
    /**
1265
     * Writes a formatted list of target names with an optional description.
1266
     *
1267
     * @param Project $project
1268
     * @param array $names The names to be printed.
1269
     *                             Must not be <code>null</code>.
1270
     * @param array $descriptions The associated target descriptions.
1271
     *                             May be <code>null</code>, in which case
1272
     *                             no descriptions are displayed.
1273
     *                             If non-<code>null</code>, this should have
1274
     *                             as many elements as <code>names</code>.
1275
     * @param $dependencies
1276
     * @param string $heading The heading to display.
1277
     *                             Should not be <code>null</code>.
1278
     * @param int $maxlen The maximum length of the names of the targets.
1279
     *                             If descriptions are given, they are padded to this
1280
     *                             position so they line up (so long as the names really
1281
     *                             <i>are</i> shorter than this).
1282
     */
1283 2
    private function _printTargets(Project $project, $names, $descriptions, $dependencies, $heading, $maxlen)
1284
    {
1285 2
        $spaces = '  ';
1286 2
        while (strlen($spaces) < $maxlen) {
1287 0
            $spaces .= $spaces;
1288
        }
1289 2
        $msg = "";
1290 2
        $msg .= $heading . PHP_EOL;
1291 2
        $msg .= str_repeat("-", 79) . PHP_EOL;
1292

1293 2
        $total = count($names);
1294 2
        for ($i = 0; $i < $total; $i++) {
1295 2
            $msg .= " ";
1296 2
            $msg .= $names[$i];
1297 2
            if (!empty($descriptions)) {
1298 0
                $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2);
1299 0
                $msg .= $descriptions[$i];
1300
            }
1301 2
            $msg .= PHP_EOL;
1302 2
            if (!empty($dependencies) && isset($dependencies[$i + 1])) {
1303 0
                $msg .= '   depends on: ' . implode(', ', $dependencies) . PHP_EOL;
1304
            }
1305
        }
1306

1307 2
        $project->log($msg, Project::MSG_WARN);
1308
    }
1309

1310
    /**
1311
     * Import a path, supporting the following conventions:
1312
     * - PEAR style (@link http://pear.php.net/manual/en/standards.naming.php)
1313
     * - PSR-0 (@link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
1314
     * - dot-path
1315
     *
1316
     * @param string $dotPath Path
1317
     * @param mixed $classpath String or object supporting __toString()
1318
     *
1319
     * @return string         The unqualified classname (which can be instantiated).
1320
     *
1321
     * @throws BuildException - if cannot find the specified file
1322
     */
1323 2
    public static function import($dotPath, $classpath = null)
1324
    {
1325 2
        if (strpos($dotPath, '.') !== false) {
1326 2
            $classname = StringHelper::unqualify($dotPath);
1327
        } else {
1328 2
            $classname = $dotPath;
1329 2
            $dotPath = '';
1330 2
            $shortClassName = $classname;
1331 2
            if (($lastNsPos = strrpos($shortClassName, '\\'))) {
1332 2
                $namespace = substr($shortClassName, 0, $lastNsPos);
1333 2
                $shortClassName = substr($shortClassName, $lastNsPos + 1);
1334 2
                $dotPath = str_replace('\\', '.', $namespace) . '.';
1335
            }
1336 2
            $dotPath .= str_replace('_', '.', $shortClassName);
1337
        }
1338

1339
        // first check to see that the class specified hasn't already been included.
1340
        // (this also handles case where this method is called w/ a classname rather than dotpath)
1341 2
        if (class_exists($classname)) {
1342 2
            return $classname;
1343
        }
1344

1345 2
        $dotClassname = basename($dotPath);
1346 2
        $dotClassnamePos = strlen($dotPath) - strlen($dotClassname);
1347

1348
        // 1- temporarily replace escaped '.' with another illegal char (#)
1349 2
        $tmp = str_replace('\.', '##', $dotClassname);
1350
        // 2- swap out the remaining '.' with DIR_SEP
1351 2
        $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR);
1352
        // 3- swap back the escaped '.'
1353 2
        $tmp = str_replace('##', '.', $tmp);
1354

1355 2
        $classFile = $tmp . ".php";
1356

1357 2
        $path = substr_replace($dotPath, $classFile, $dotClassnamePos);
1358

1359 2
        Phing::importFile($path, $classpath);
1360

1361 2
        return $classname;
1362
    }
1363

1364
    /**
1365
     * Import a PHP file
1366
     *
1367
     * This used to be named __import, however PHP has reserved all method names
1368
     * with a double underscore prefix for future use.
1369
     *
1370
     * @param string $path Path to the PHP file
1371
     * @param mixed $classpath String or object supporting __toString()
1372
     *
1373
     * @throws ConfigurationException
1374
     */
1375 2
    public static function importFile($path, $classpath = null)
1376
    {
1377 2
        if ($classpath) {
1378
            // Apparently casting to (string) no longer invokes __toString() automatically.
1379 2
            if (is_object($classpath)) {
1380 0
                $classpath = $classpath->__toString();
1381
            }
1382

1383
            // classpaths are currently additive, but we also don't want to just
1384
            // indiscriminantly prepand/append stuff to the include_path.  This means
1385
            // we need to parse current incldue_path, and prepend any
1386
            // specified classpath locations that are not already in the include_path.
1387
            //
1388
            // NOTE:  the reason why we do it this way instead of just changing include_path
1389
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1390
            // include/require class files from within method calls.  This means that not all
1391
            // necessary files will be included in this import() call, and hence we can't
1392
            // change the include_path back without breaking those apps.  While this method could
1393
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1394
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1395
            // really where speed matters more.
1396

1397 2
            $curr_parts = Phing::explodeIncludePath();
1398 2
            $add_parts = Phing::explodeIncludePath($classpath);
1399 2
            $new_parts = array_diff($add_parts, $curr_parts);
1400 2
            if ($new_parts) {
1401 2
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1402
            }
1403
        }
1404

1405 2
        $ret = include_once $path;
1406

1407 2
        if ($ret === false) {
1408 0
            $msg = "Error importing $path";
1409 0
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1410 0
                $x = new Exception("for-path-trace-only");
1411 0
                $msg .= $x->getTraceAsString();
1412
            }
1413 0
            throw new ConfigurationException($msg);
1414
        }
1415
    }
1416

1417
    /**
1418
     * Looks on include path for specified file.
1419
     *
1420
     * @param string $path
1421
     *
1422
     * @return string File found (null if no file found).
1423
     */
1424 2
    public static function getResourcePath($path)
1425
    {
1426 2
        if (self::$importPaths === null) {
1427 0
            self::$importPaths = self::explodeIncludePath();
1428
        }
1429

1430 2
        $path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
1431

1432 2
        foreach (self::$importPaths as $prefix) {
1433 2
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
1434 2
            if (file_exists($testPath)) {
1435 2
                return $testPath;
1436
            }
1437
        }
1438

1439
        // Check for the property phing.home
1440 2
        $homeDir = self::getProperty(self::PHING_HOME);
1441 2
        if ($homeDir) {
1442 2
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
1443 2
            if (file_exists($testPath)) {
1444 2
                return $testPath;
1445
            }
1446
        }
1447

1448
        // Check for the phing home of phar archive
1449 2
        if (strpos(self::$importPaths[0], 'phar://') === 0) {
1450 0
            $testPath = self::$importPaths[0] . '/../' . $path;
1451 0
            if (file_exists($testPath)) {
1452 0
                return $testPath;
1453
            }
1454
        }
1455

1456
        // Do one additional check based on path of current file (Phing.php)
1457 2
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..');
1458 2
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
1459 2
        if (file_exists($testPath)) {
1460 0
            return $testPath;
1461
        }
1462

1463 2
        return null;
1464
    }
1465

1466
    /**
1467
     * Explode an include path into an array
1468
     *
1469
     * If no path provided, uses current include_path. Works around issues that
1470
     * occur when the path includes stream schemas.
1471
     *
1472
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
1473
     *
1474
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
1475
     * @license   http://framework.zend.com/license/new-bsd New BSD License
1476
     * @param     string|null $path
1477
     * @return    array
1478
     */
1479 2
    public static function explodeIncludePath($path = null)
1480
    {
1481 2
        if (null === $path) {
1482 2
            $path = get_include_path();
1483
        }
1484

1485 2
        if (PATH_SEPARATOR == ':') {
1486
            // On *nix systems, include_paths which include paths with a stream
1487
            // schema cannot be safely explode'd, so we have to be a bit more
1488
            // intelligent in the approach.
1489 2
            $paths = preg_split('#:(?!//)#', $path);
1490
        } else {
1491
            $paths = explode(PATH_SEPARATOR, $path);
1492
        }
1493

1494 2
        return $paths;
1495
    }
1496

1497
    /**
1498
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1499
     *
1500
     * @return void
1501
     */
1502 2
    private static function setSystemConstants()
1503
    {
1504

1505
        /*
1506
         * PHP_OS returns on
1507
         *   WindowsNT4.0sp6  => WINNT
1508
         *   Windows2000      => WINNT
1509
         *   Windows ME       => WIN32
1510
         *   Windows 98SE     => WIN32
1511
         *   FreeBSD 4.5p7    => FreeBSD
1512
         *   Redhat Linux     => Linux
1513
         *   Mac OS X         => Darwin
1514
         */
1515 2
        self::setProperty('host.os', PHP_OS);
1516

1517
        // this is used by some tasks too
1518 2
        self::setProperty('os.name', PHP_OS);
1519

1520
        // it's still possible this won't be defined,
1521
        // e.g. if Phing is being included in another app w/o
1522
        // using the phing.php script.
1523 2
        if (!defined('PHP_CLASSPATH')) {
1524 0
            define('PHP_CLASSPATH', get_include_path());
1525
        }
1526

1527 2
        self::setProperty('php.classpath', PHP_CLASSPATH);
1528

1529
        // try to determine the host filesystem and set system property
1530
        // used by Fileself::getFileSystem to instantiate the correct
1531
        // abstraction layer
1532

1533 2
        if (PHP_OS_FAMILY === 'Windows') {
1534
            self::setProperty('host.fstype', 'WINDOWS');
1535
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1536
        } else {
1537 2
            self::setProperty('host.fstype', 'UNIX');
1538 2
            self::setProperty('user.home', getenv('HOME'));
1539
        }
1540 2
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1541 2
        self::setProperty('file.separator', FileUtils::getSeparator());
1542 2
        self::setProperty('line.separator', PHP_EOL);
1543 2
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1544 2
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1545 2
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1546 2
        self::setProperty('application.startdir', getcwd());
1547 2
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1548

1549
        // try to detect machine dependent information
1550 2
        $sysInfo = [];
1551 2
        if (function_exists("posix_uname") && stripos(PHP_OS, 'WIN') !== 0) {
1552 2
            $sysInfo = posix_uname();
1553
        } else {
1554 0
            $sysInfo['nodename'] = php_uname('n');
1555 0
            $sysInfo['machine'] = php_uname('m');
1556
            //this is a not so ideal substition, but maybe better than nothing
1557 0
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? "unknown";
1558 0
            $sysInfo['release'] = php_uname('r');
1559 0
            $sysInfo['version'] = php_uname('v');
1560
        }
1561

1562 2
        self::setProperty("host.name", $sysInfo['nodename'] ?? "unknown");
1563 2
        self::setProperty("host.arch", $sysInfo['machine'] ?? "unknown");
1564 2
        self::setProperty("host.domain", $sysInfo['domain'] ?? "unknown");
1565 2
        self::setProperty("host.os.release", $sysInfo['release'] ?? "unknown");
1566 2
        self::setProperty("host.os.version", $sysInfo['version'] ?? "unknown");
1567 2
        unset($sysInfo);
1568
    }
1569

1570
    /**
1571
     * This gets a property that was set via command line or otherwise passed into Phing.
1572
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1573
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1574
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1575
     * the pear.log.name property.
1576
     *
1577
     * @param  string $name
1578
     * @return string value of found property (or null, if none found).
1579
     */
1580 0
    public static function getDefinedProperty($name)
1581
    {
1582 0
        return self::$definedProps->getProperty($name);
1583
    }
1584

1585
    /**
1586
     * This sets a property that was set via command line or otherwise passed into Phing.
1587
     *
1588
     * @param  string $name
1589
     * @param  mixed $value
1590
     * @return mixed value of found property (or null, if none found).
1591
     */
1592 0
    public static function setDefinedProperty($name, $value)
1593
    {
1594 0
        return self::$definedProps->setProperty($name, $value);
1595
    }
1596

1597
    /**
1598
     * Returns property value for a System property.
1599
     * System properties are "global" properties like application.startdir,
1600
     * and user.dir.  Many of these correspond to similar properties in Java
1601
     * or Ant.
1602
     *
1603
     * @param  string $propName
1604
     * @return string Value of found property (or null, if none found).
1605
     */
1606 2
    public static function getProperty($propName)
1607
    {
1608

1609
        // some properties are detemined on each access
1610
        // some are cached, see below
1611

1612
        // default is the cached value:
1613 2
        $val = self::$properties[$propName] ?? null;
1614

1615
        // special exceptions
1616
        switch ($propName) {
1617 2
            case 'user.dir':
1618 2
                $val = getcwd();
1619 2
                break;
1620
        }
1621

1622 2
        return $val;
1623
    }
1624

1625
    /**
1626
     * Retuns reference to all properties
1627
     */
1628 2
    public static function &getProperties()
1629
    {
1630 2
        return self::$properties;
1631
    }
1632

1633
    /**
1634
     * @param $propName
1635
     * @param $propValue
1636
     * @return string
1637
     */
1638 2
    public static function setProperty($propName, $propValue)
1639
    {
1640 2
        $propName = (string) $propName;
1641 2
        $oldValue = self::getProperty($propName);
1642 2
        self::$properties[$propName] = $propValue;
1643

1644 2
        return $oldValue;
1645
    }
1646

1647
    /**
1648
     * @return float
1649
     */
1650 2
    public static function currentTimeMillis()
1651
    {
1652 2
        [$usec, $sec] = explode(" ", microtime());
1653

1654 2
        return ((float) $usec + (float) $sec);
1655
    }
1656

1657
    /**
1658
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1659
     *
1660
     * @return void
1661
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1662
     */
1663 2
    private static function setIncludePaths()
1664
    {
1665 2
        if (defined('PHP_CLASSPATH')) {
1666 2
            $result = set_include_path(PHP_CLASSPATH);
1667 2
            if ($result === false) {
1668 0
                throw new ConfigurationException("Could not set PHP include_path.");
1669
            }
1670 2
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1671
        }
1672
    }
1673

1674
    /**
1675
     * Converts shorthand notation values as returned by ini_get()
1676
     *
1677
     * @see    http://www.php.net/ini_get
1678
     * @param  string|int $val
1679
     * @return int
1680
     */
1681 2
    public static function convertShorthand($val): int
1682
    {
1683 2
        $val = trim($val);
1684 2
        $last = strtolower($val[strlen($val) - 1]);
1685

1686 2
        if (!is_numeric($last)) {
1687 2
            $val = (int) substr($val, 0, -1);
1688

1689 2
            switch ($last) {
1690
                // The 'G' modifier is available since PHP 5.1.0
1691 2
                case 'g':
1692 2
                    $val *= 1024;
1693
                // no break
1694 2
                case 'm':
1695 2
                    $val *= 1024;
1696
                // no break
1697 2
                case 'k':
1698 2
                    $val *= 1024;
1699
            }
1700
        }
1701

1702 2
        return $val;
1703
    }
1704

1705
    /**
1706
     * Sets PHP INI values that Phing needs.
1707
     */
1708 2
    private static function setIni(): void
1709
    {
1710 2
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1711

1712
        // We won't bother storing original max_execution_time, since 1) the value in
1713
        // php.ini may be wrong (and there's no way to get the current value) and
1714
        // 2) it would mean something very strange to set it to a value less than time script
1715
        // has already been running, which would be the likely change.
1716

1717 2
        set_time_limit(0);
1718

1719 2
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1720 2
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1721

1722 2
        $mem_limit = (int) self::convertShorthand(ini_get('memory_limit'));
1723 2
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1724
            // We do *not* need to save the original value here, since we don't plan to restore
1725
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1726 0
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1727
        }
1728
    }
1729

1730
    /**
1731
     * Restores [most] PHP INI values to their pre-Phing state.
1732
     *
1733
     * Currently the following settings are not restored:
1734
     *  - max_execution_time (because getting current time limit is not possible)
1735
     *  - memory_limit (which may have been increased by Phing)
1736
     */
1737 2
    private static function restoreIni(): void
1738
    {
1739 2
        foreach (self::$origIniSettings as $settingName => $settingValue) {
1740 2
            switch ($settingName) {
1741 2
                case 'error_reporting':
1742 2
                    error_reporting($settingValue);
1743 2
                    break;
1744
                default:
1745 2
                    ini_set($settingName, $settingValue);
1746
            }
1747
        }
1748
    }
1749

1750
    /**
1751
     * Returns reference to Timer object.
1752
     *
1753
     * @return Timer
1754
     */
1755 2
    public static function getTimer(): Timer
1756
    {
1757 2
        if (self::$timer === null) {
1758 0
            self::$timer = new Timer();
1759
        }
1760

1761 2
        return self::$timer;
1762
    }
1763

1764
    /**
1765
     * Start up Phing.
1766
     * Sets up the Phing environment but does not initiate the build process.
1767
     *
1768
     * @return void
1769
     * @throws Exception - If the Phing environment cannot be initialized.
1770
     */
1771 2
    public static function startup(): void
1772
    {
1773

1774
        // setup STDOUT and STDERR defaults
1775 2
        self::initializeOutputStreams();
1776

1777
        // some init stuff
1778 2
        self::getTimer()->start();
1779

1780 2
        self::setSystemConstants();
1781 2
        self::setIncludePaths();
1782 2
        self::setIni();
1783
    }
1784

1785
    /**
1786
     * Performs any shutdown routines, such as stopping timers.
1787
     *
1788
     * @throws IOException
1789
     */
1790 2
    public static function shutdown(): void
1791
    {
1792 2
        FileSystem::getFileSystem()::deleteFilesOnExit();
1793 2
        self::$msgOutputLevel = Project::MSG_INFO;
1794 2
        self::restoreIni();
1795 2
        self::getTimer()->stop();
1796
    }
1797
}

Read our documentation on viewing source code .

Loading