Passed
Push — main ( 01fb06...c551fb )
by Siad
06:07
created

Phing::getProperty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 17
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Exception\ConfigurationException;
26
use Phing\Exception\ExitStatusException;
27
use Phing\Input\ConsoleInputHandler;
28
use Phing\Io\File;
29
use Phing\Io\FileOutputStream;
30
use Phing\Io\FileParserFactory;
31
use Phing\Io\FileReader;
32
use Phing\Io\FileSystem;
33
use Phing\Io\FileUtils;
34
use Phing\Io\IOException;
35
use Phing\Io\OutputStream;
36
use Phing\Io\PrintStream;
37
use Phing\Listener\BuildLogger;
38
use Phing\Listener\DefaultLogger;
39
use Phing\Listener\SilentLogger;
40
use Phing\Listener\StreamRequiredBuildLogger;
41
use Phing\Parser\ProjectConfigurator;
42
use Phing\Util\DefaultClock;
43
use Phing\Util\Diagnostics;
44
use Phing\Util\Properties;
45
use Phing\Util\SizeHelper;
46
use Phing\Util\StringHelper;
47
use SebastianBergmann\Version;
48
use Symfony\Component\Console\Output\ConsoleOutput;
49
use Throwable;
50
51
use function array_filter;
52
use function array_map;
53
use function array_reduce;
54
use function implode;
55
use function sprintf;
56
use function strlen;
57
use function strval;
58
use function trim;
59
60
use const PHP_EOL;
61
62
/**
63
 * Entry point into Phing.  This class handles the full lifecycle of a build -- from
64
 * parsing & handling commandline arguments to assembling the project to shutting down
65
 * and cleaning up in the end.
66
 *
67
 * If you are invoking Phing from an external application, this is still
68
 * the class to use.  Your application can invoke the start() method, passing
69
 * any commandline arguments or additional properties.
70
 *
71
 * @author Andreas Aderhold <[email protected]>
72
 * @author Hans Lellelid <[email protected]>
73
 */
74
class Phing
75
{
76
    /**
77
     * Alias for phar file.
78
     */
79
    public const PHAR_ALIAS = 'phing.phar';
80
81
    /**
82
     * The default build file name.
83
     */
84
    public const DEFAULT_BUILD_FILENAME = 'build.xml';
85
    public const DEFAULT_BUILD_CONTENT = <<<'XML'
86
        <?xml version="1.0" encoding="UTF-8" ?>
87
88
        <project name="" description="" default="">
89
            
90
            <target name="" description="">
91
                
92
            </target>
93
            
94
        </project>
95
        XML;
96
    public const PHING_HOME = 'phing.home';
97
    public const PHP_VERSION = 'php.version';
98
    public const PHP_INTERPRETER = 'php.interpreter';
99
100
    /**
101
     * Our current message output status. Follows Project::MSG_XXX.
102
     */
103
    private static $msgOutputLevel = Project::MSG_INFO;
104
    /**
105
     * Set of properties that are passed in from commandline or invoking code.
106
     *
107
     * @var Properties
108
     */
109
    private static $definedProps;
110
    /**
111
     * Used by utility function getResourcePath().
112
     */
113
    private static $importPaths;
114
    /**
115
     * System-wide static properties (moved from System).
116
     */
117
    private static $properties = [];
118
    /**
119
     * Static system timer.
120
     */
121
    private static $timer;
122
    /**
123
     * The current Project.
124
     */
125
    private static $currentProject;
126
    /**
127
     * Whether to capture PHP errors to buffer.
128
     */
129
    private static $phpErrorCapture = false;
130
    /**
131
     * Array of captured PHP errors.
132
     */
133
    private static $capturedPhpErrors = [];
134
    /**
135
     * @var OUtputStream stream for standard output
136
     */
137
    private static $out;
138
    /**
139
     * @var OutputStream stream for error output
140
     */
141
    private static $err;
142
    /**
143
     * @var bool whether we are using a logfile
144
     */
145
    private static $isLogFileUsed = false;
146
    /**
147
     * Array to hold original ini settings that Phing changes (and needs
148
     * to restore in restoreIni() method).
149
     *
150
     * @var array Struct of array(setting-name => setting-value)
151
     *
152
     * @see restoreIni()
153
     */
154
    private static $origIniSettings = [];
155
    /**
156
     * PhingFile that we are using for configuration.
157
     */
158
    private $buildFile;
159
    /**
160
     * The build targets.
161
     */
162
    private $targets = [];
163
    /**
164
     * Names of classes to add as listeners to project.
165
     */
166
    private $listeners = [];
167
    /**
168
     * keep going mode.
169
     *
170
     * @var bool
171
     */
172
    private $keepGoingMode = false;
173
    private $loggerClassname;
174
    /**
175
     * The class to handle input (can be only one).
176
     */
177
    private $inputHandlerClassname;
178
    /**
179
     * Whether or not log output should be reduced to the minimum.
180
     *
181
     * @var bool
182
     */
183
    private $silent = false;
184
    /**
185
     * Indicates whether phing should run in strict mode.
186
     */
187
    private $strictMode = false;
188
    /**
189
     * Indicates if this phing should be run.
190
     */
191
    private $readyToRun = false;
192
    /**
193
     * Indicates we should only parse and display the project help information.
194
     */
195
    private $projectHelp = false;
196
    /**
197
     * Whether to values in a property file should override existing values.
198
     */
199
    private $propertyFileOverride = false;
200
    /**
201
     * Whether or not output to the log is to be unadorned.
202
     */
203
    private $emacsMode = false;
204
205
    /**
206
     * @var string
207
     */
208
    private $searchForThis;
209
    private $propertyFiles = [];
210
211
    /**
212
     * Gets the stream to use for standard (non-error) output.
213
     *
214
     * @return OutputStream
215
     */
216
    public static function getOutputStream()
217
    {
218
        return self::$out;
219
    }
220
221
    /**
222
     * Gets the stream to use for error output.
223
     *
224
     * @return OutputStream
225
     */
226
    public static function getErrorStream()
227
    {
228
        return self::$err;
229
    }
230
231
    /**
232
     * Command line entry point. This method kicks off the building
233
     * of a project object and executes a build using either a given
234
     * target or the default target.
235
     *
236
     * @param array $args command line args
237
     */
238
    public static function fire($args)
239
    {
240
        self::start($args);
241
    }
242
243
    /**
244
     * Entry point allowing for more options from other front ends.
245
     *
246
     * This method encapsulates the complete build lifecycle.
247
     *
248
     * @param array $args the commandline args passed to phing shell script
249
     * @param array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this).
250
     *                                        These additional properties will be available using the getDefinedProperty() method and will
251
     *                                        be added to the project's "user" properties
252
     *
253
     * @throws Exception - if there is an error during build
254
     * @see    runBuild()
255
     *
256
     * @see    execute()
257
     */
258
    public static function start($args, array $additionalUserProperties = null)
259
    {
260
        try {
261
            $m = new self();
262
            $m->execute($args);
263
        } catch (Exception $exc) {
264
            self::handleLogfile();
265
            self::printMessage($exc);
266
            self::statusExit(1);
267
268
            return;
269
        }
270
271
        if (null !== $additionalUserProperties) {
272
            foreach ($additionalUserProperties as $key => $value) {
273
                $m::setDefinedProperty($key, $value);
274
            }
275
        }
276
277
        // expect the worst
278
        $exitCode = 1;
279
280
        try {
281
            try {
282
                $m->runBuild();
283
                $exitCode = 0;
284
            } catch (ExitStatusException $ese) {
285
                $exitCode = $ese->getCode();
286
                if (0 !== $exitCode) {
287
                    self::handleLogfile();
288
289
                    throw $ese;
290
                }
291
            }
292
        } catch (BuildException $exc) {
293
            // avoid printing output twice: self::printMessage($exc);
294
        } catch (Throwable $exc) {
295
            self::printMessage($exc);
296
        } finally {
297
            self::handleLogfile();
298
        }
299
        self::statusExit($exitCode);
300
    }
301
302
    /**
303
     * Setup/initialize Phing environment from commandline args.
304
     *
305
     * @param array $args commandline args passed to phing shell
306
     *
307
     * @throws ConfigurationException
308
     */
309
    public function execute($args)
310
    {
311
        self::$definedProps = new Properties();
312
        $this->searchForThis = null;
313
314
        // 1) First handle any options which should always
315
        // Note: The order in which these are executed is important (if multiple of these options are specified)
316
317
        if (in_array('-help', $args) || in_array('-h', $args)) {
318
            static::printUsage();
319
320
            return;
321
        }
322
323
        if (in_array('-version', $args) || in_array('-v', $args)) {
324
            static::printVersion();
325
326
            return;
327
        }
328
329
        if (in_array('-init', $args) || in_array('-i', $args)) {
330
            $key = array_search('-init', $args) ?: array_search('-i', $args);
331
            $path = $args[$key + 1] ?? null;
332
333
            self::init($path);
334
335
            return;
336
        }
337
338
        if (in_array('-diagnostics', $args)) {
339
            Diagnostics::doReport(new PrintStream(self::$out));
340
341
            return;
342
        }
343
344
        // 2) Next pull out stand-alone args.
345
        // Note: The order in which these are executed is important (if multiple of these options are specified)
346
347
        if (
348
            false !== ($key = array_search('-quiet', $args, true))
349
            || false !== ($key = array_search(
350
                '-q',
351
                $args,
352
                true
353
            ))
354
        ) {
355
            self::$msgOutputLevel = Project::MSG_WARN;
356
            unset($args[$key]);
357
        }
358
359
        if (
360
            false !== ($key = array_search('-emacs', $args, true))
361
            || false !== ($key = array_search('-e', $args, true))
362
        ) {
363
            $this->emacsMode = true;
364
            unset($args[$key]);
365
        }
366
367
        if (false !== ($key = array_search('-verbose', $args, true))) {
368
            self::$msgOutputLevel = Project::MSG_VERBOSE;
369
            unset($args[$key]);
370
        }
371
372
        if (false !== ($key = array_search('-debug', $args, true))) {
373
            self::$msgOutputLevel = Project::MSG_DEBUG;
374
            unset($args[$key]);
375
        }
376
377
        if (
378
            false !== ($key = array_search('-silent', $args, true))
379
            || false !== ($key = array_search('-S', $args, true))
380
        ) {
381
            $this->silent = true;
382
            unset($args[$key]);
383
        }
384
385
        if (false !== ($key = array_search('-propertyfileoverride', $args, true))) {
386
            $this->propertyFileOverride = true;
387
            unset($args[$key]);
388
        }
389
390
        // 3) Finally, cycle through to parse remaining args
391
        //
392
        $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps
393
        $max = $keys ? max($keys) : -1;
394
        for ($i = 0; $i <= $max; ++$i) {
395
            if (!array_key_exists($i, $args)) {
396
                // skip this argument, since it must have been removed above.
397
                continue;
398
            }
399
400
            $arg = $args[$i];
401
402
            if ('-logfile' == $arg) {
403
                try {
404
                    // see: http://phing.info/trac/ticket/65
405
                    if (!isset($args[$i + 1])) {
406
                        $msg = "You must specify a log file when using the -logfile argument\n";
407
408
                        throw new ConfigurationException($msg);
409
                    }
410
411
                    $logFile = new File($args[++$i]);
412
                    $out = new FileOutputStream($logFile); // overwrite
413
                    self::setOutputStream($out);
414
                    self::setErrorStream($out);
415
                    self::$isLogFileUsed = true;
416
                } catch (IOException $ioe) {
417
                    $msg = 'Cannot write on the specified log file. Make sure the path exists and you have write permissions.';
418
419
                    throw new ConfigurationException($msg, $ioe);
420
                }
421
            } elseif ('-buildfile' == $arg || '-file' == $arg || '-f' == $arg) {
422
                if (!isset($args[$i + 1])) {
423
                    $msg = 'You must specify a buildfile when using the -buildfile argument.';
424
425
                    throw new ConfigurationException($msg);
426
                }
427
428
                $this->buildFile = new File($args[++$i]);
429
            } elseif ('-listener' == $arg) {
430
                if (!isset($args[$i + 1])) {
431
                    $msg = 'You must specify a listener class when using the -listener argument';
432
433
                    throw new ConfigurationException($msg);
434
                }
435
436
                $this->listeners[] = $args[++$i];
437
            } elseif (StringHelper::startsWith('-D', $arg)) {
438
                // Evaluating the property information //
439
                // Checking whether arg. is not just a switch, and next arg. does not starts with switch identifier
440
                if (('-D' == $arg) && (!StringHelper::startsWith('-', $args[$i + 1]))) {
441
                    $name = $args[++$i];
442
                } else {
443
                    $name = substr($arg, 2);
444
                }
445
446
                $value = null;
447
                $posEq = strpos($name, '=');
448
                if (false !== $posEq) {
449
                    $value = substr($name, $posEq + 1);
450
                    $name = substr($name, 0, $posEq);
451
                } elseif ($i < count($args) - 1 && !StringHelper::startsWith('-D', $args[$i + 1])) {
452
                    $value = $args[++$i];
453
                }
454
                self::$definedProps->setProperty($name, $value);
455
            } elseif ('-logger' == $arg) {
456
                if (!isset($args[$i + 1])) {
457
                    $msg = 'You must specify a classname when using the -logger argument';
458
459
                    throw new ConfigurationException($msg);
460
                }
461
462
                $this->loggerClassname = $args[++$i];
463
            } elseif ('-no-strict' == $arg) {
464
                $this->strictMode = false;
465
            } elseif ('-strict' == $arg) {
466
                $this->strictMode = true;
467
            } elseif ('-inputhandler' == $arg) {
468
                if (null !== $this->inputHandlerClassname) {
469
                    throw new ConfigurationException('Only one input handler class may be specified.');
470
                }
471
                if (!isset($args[$i + 1])) {
472
                    $msg = 'You must specify a classname when using the -inputhandler argument';
473
474
                    throw new ConfigurationException($msg);
475
                }
476
477
                $this->inputHandlerClassname = $args[++$i];
478
            } elseif ('-propertyfile' === $arg) {
479
                $i = $this->handleArgPropertyFile($args, $i);
480
            } elseif ('-keep-going' === $arg || '-k' === $arg) {
481
                $this->keepGoingMode = true;
482
            } elseif ('-longtargets' == $arg) {
483
                self::$definedProps->setProperty('phing.showlongtargets', 1);
484
            } elseif ('-projecthelp' == $arg || '-targets' == $arg || '-list' == $arg || '-l' == $arg || '-p' == $arg) {
485
                // set the flag to display the targets and quit
486
                $this->projectHelp = true;
487
            } elseif ('-find' == $arg) {
488
                // eat up next arg if present, default to build.xml
489
                if ($i < count($args) - 1) {
490
                    $this->searchForThis = $args[++$i];
491
                } else {
492
                    $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
493
                }
494
            } elseif ('-' == substr($arg, 0, 1)) {
495
                // we don't have any more args
496
                self::printUsage();
497
                self::$err->write(PHP_EOL);
498
499
                throw new ConfigurationException('Unknown argument: ' . $arg);
500
            } else {
501
                // if it's no other arg, it may be the target
502
                $this->targets[] = $arg;
503
            }
504
        }
505
506
        // if buildFile was not specified on the command line,
507
        if (null === $this->buildFile) {
508
            // but -find then search for it
509
            if (null !== $this->searchForThis) {
510
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), $this->searchForThis);
0 ignored issues
show
Bug introduced by
$this->searchForThis of type void is incompatible with the type string expected by parameter $suffix of Phing\Phing::findBuildFile(). ( Ignorable by Annotation )

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

510
                $this->buildFile = $this->findBuildFile(self::getProperty('user.dir'), /** @scrutinizer ignore-type */ $this->searchForThis);
Loading history...
511
            } else {
512
                $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
513
            }
514
        }
515
516
        try {
517
            // make sure buildfile (or buildfile.dist) exists
518
            if (!$this->buildFile->exists()) {
519
                $distFile = new File($this->buildFile->getAbsolutePath() . '.dist');
520
                if (!$distFile->exists()) {
521
                    throw new ConfigurationException(
522
                        'Buildfile: ' . $this->buildFile->__toString() . ' does not exist!'
523
                    );
524
                }
525
                $this->buildFile = $distFile;
526
            }
527
528
            // make sure it's not a directory
529
            if ($this->buildFile->isDirectory()) {
530
                throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is a dir!');
531
            }
532
        } catch (IOException $e) {
533
            // something else happened, buildfile probably not readable
534
            throw new ConfigurationException('Buildfile: ' . $this->buildFile->__toString() . ' is not readable!');
535
        }
536
537
        $this->loadPropertyFiles();
538
539
        $this->readyToRun = true;
540
    }
541
542
    /**
543
     * Prints the usage of how to use this class.
544
     */
545 1
    public static function printUsage()
546
    {
547 1
        $msg = '';
548 1
        $msg .= 'phing [options] [target [target2 [target3] ...]]' . PHP_EOL;
549 1
        $msg .= 'Options: ' . PHP_EOL;
550 1
        $msg .= '  -h -help               print this message' . PHP_EOL;
551 1
        $msg .= '  -l -list               list available targets in this project' . PHP_EOL;
552 1
        $msg .= '  -i -init [file]        generates an initial buildfile' . PHP_EOL;
553 1
        $msg .= '  -v -version            print the version information and exit' . PHP_EOL;
554 1
        $msg .= '  -q -quiet              be extra quiet' . PHP_EOL;
555 1
        $msg .= '  -S -silent             print nothing but task outputs and build failures' . PHP_EOL;
556 1
        $msg .= '  -verbose               be extra verbose' . PHP_EOL;
557 1
        $msg .= '  -debug                 print debugging information' . PHP_EOL;
558 1
        $msg .= '  -emacs, -e             produce logging information without adornments' . PHP_EOL;
559 1
        $msg .= '  -diagnostics           print diagnostics information' . PHP_EOL;
560 1
        $msg .= '  -strict                runs build in strict mode, considering a warning as error' . PHP_EOL;
561 1
        $msg .= '  -no-strict             runs build normally (overrides buildfile attribute)' . PHP_EOL;
562 1
        $msg .= '  -longtargets           show target descriptions during build' . PHP_EOL;
563 1
        $msg .= '  -logfile <file>        use given file for log' . PHP_EOL;
564 1
        $msg .= '  -logger <classname>    the class which is to perform logging' . PHP_EOL;
565 1
        $msg .= '  -listener <classname>  add an instance of class as a project listener' . PHP_EOL;
566 1
        $msg .= '  -f -buildfile <file>   use given buildfile' . PHP_EOL;
567 1
        $msg .= '  -D<property>=<value>   use value for given property' . PHP_EOL;
568 1
        $msg .= '  -keep-going, -k        execute all targets that do not depend' . PHP_EOL;
569 1
        $msg .= '                         on failed target(s)' . PHP_EOL;
570 1
        $msg .= '  -propertyfile <file>   load all properties from file' . PHP_EOL;
571 1
        $msg .= '  -propertyfileoverride  values in property file override existing values' . PHP_EOL;
572 1
        $msg .= '  -find <file>           search for buildfile towards the root of the' . PHP_EOL;
573 1
        $msg .= '                         filesystem and use it' . PHP_EOL;
574 1
        $msg .= '  -inputhandler <file>   the class to use to handle user input' . PHP_EOL;
575
        //$msg .= "  -recursive <file>      search for buildfile downwards and use it" . PHP_EOL;
576 1
        $msg .= PHP_EOL;
577 1
        $msg .= 'Report bugs to https://github.com/phingofficial/phing/issues' . PHP_EOL;
578 1
        self::$err->write($msg);
579 1
    }
580
581
    /**
582
     * Prints the current Phing version.
583
     */
584
    public static function printVersion()
585
    {
586
        self::$out->write(self::getPhingVersion() . PHP_EOL);
587
    }
588
589
    /**
590
     * Gets the current Phing version based on VERSION.TXT file.
591
     *
592
     * @return string
593
     * @throws ConfigurationException
594
     *
595
     */
596 6
    public static function getPhingVersion()
597
    {
598 6
        $versionPath = self::getResourcePath('phing/etc/VERSION.TXT');
599 6
        if (null === $versionPath) {
0 ignored issues
show
introduced by
The condition null === $versionPath is always false.
Loading history...
600 6
            $versionPath = self::getResourcePath('etc/VERSION.TXT');
601
        }
602 6
        if (null === $versionPath) {
0 ignored issues
show
introduced by
The condition null === $versionPath is always false.
Loading history...
603
            throw new ConfigurationException('No VERSION.TXT file found; try setting phing.home environment variable.');
604
        }
605
606
        try { // try to read file
607 6
            $file = new File($versionPath);
608 6
            $reader = new FileReader($file);
609 6
            $phingVersion = trim($reader->read());
610
        } catch (IOException $iox) {
611
            throw new ConfigurationException("Can't read version information file");
612
        }
613
614 6
        $basePath = dirname(__DIR__, 2);
615
616 6
        $version = new Version($phingVersion, $basePath);
617
618 6
        return 'Phing ' . $version->getVersion();
619
    }
620
621
    /**
622
     * Looks on include path for specified file.
623
     *
624
     * @param string $path
625
     *
626
     * @return string file found (null if no file found)
627
     */
628 828
    public static function getResourcePath($path)
629
    {
630 828
        if (null === self::$importPaths) {
631
            self::$importPaths = self::explodeIncludePath();
632
        }
633
634 828
        $path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);
635
636 828
        foreach (self::$importPaths as $prefix) {
637 828
            $testPath = $prefix . DIRECTORY_SEPARATOR . $path;
638 828
            if (file_exists($testPath)) {
639
                return $testPath;
640
            }
641
        }
642
643
        // Check for the property phing.home
644 828
        $homeDir = self::getProperty(self::PHING_HOME);
645 828
        if ($homeDir) {
646 828
            $testPath = $homeDir . DIRECTORY_SEPARATOR . $path;
647 828
            if (file_exists($testPath)) {
648 828
                return $testPath;
649
            }
650
        }
651
652
        // Check for the phing home of phar archive
653 6
        if (0 === strpos(self::$importPaths[0], 'phar://')) {
654
            $testPath = self::$importPaths[0] . '/../' . $path;
655
            if (file_exists($testPath)) {
656
                return $testPath;
657
            }
658
        }
659
660
        // Do one additional check based on path of current file (Phing.php)
661 6
        $maybeHomeDir = realpath(__DIR__ . DIRECTORY_SEPARATOR);
662 6
        $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path;
663 6
        if (file_exists($testPath)) {
664
            return $testPath;
665
        }
666
667 6
        return null;
668
    }
669
670
    /**
671
     * Explode an include path into an array.
672
     *
673
     * If no path provided, uses current include_path. Works around issues that
674
     * occur when the path includes stream schemas.
675
     *
676
     * Pulled from Zend_Loader::explodeIncludePath() in ZF1.
677
     *
678
     * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
679
     * @license   http://framework.zend.com/license/new-bsd New BSD License
680
     *
681
     * @param null|string $path
682
     *
683
     * @return array
684
     */
685 16
    public static function explodeIncludePath($path = null)
686
    {
687 16
        if (null === $path) {
688 16
            $path = get_include_path();
689
        }
690
691 16
        if (PATH_SEPARATOR == ':') {
692
            // On *nix systems, include_paths which include paths with a stream
693
            // schema cannot be safely explode'd, so we have to be a bit more
694
            // intelligent in the approach.
695 16
            $paths = preg_split('#:(?!//)#', $path);
696
        } else {
697
            $paths = explode(PATH_SEPARATOR, $path);
698
        }
699
700 16
        return $paths;
701
    }
702
703
    /**
704
     * Returns property value for a System property.
705
     * System properties are "global" properties like application.startdir,
706
     * and user.dir.  Many of these correspond to similar properties in Java
707
     * or Ant.
708
     *
709
     * @param string $propName
710
     *
711
     * @return string value of found property (or null, if none found)
712
     */
713 846
    public static function getProperty($propName)
714
    {
715
        // some properties are detemined on each access
716
        // some are cached, see below
717
718
        // default is the cached value:
719 846
        $val = self::$properties[$propName] ?? null;
720
721
        // special exceptions
722
        switch ($propName) {
723 846
            case 'user.dir':
724 140
                $val = getcwd();
725
726 140
                break;
727
        }
728
729 846
        return $val;
730
    }
731
732
    /**
733
     * Creates generic buildfile.
734
     *
735
     * @param string $path
736
     */
737
    public static function init($path)
738
    {
739
        if ($buildfilePath = self::initPath($path)) {
740
            self::initWrite($buildfilePath);
741
        }
742
    }
743
744
    /**
745
     * Returns buildfile's path.
746
     *
747
     * @param $path
748
     *
749
     * @return string
750
     * @throws ConfigurationException
751
     *
752
     */
753
    protected static function initPath($path)
754
    {
755
        // Fallback
756
        if (empty($path)) {
757
            $defaultDir = self::getProperty('application.startdir');
758
            $path = $defaultDir . DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
759
        }
760
761
        // Adding filename if necessary
762
        if (is_dir($path)) {
763
            $path .= DIRECTORY_SEPARATOR . self::DEFAULT_BUILD_FILENAME;
764
        }
765
766
        // Check if path is available
767
        $dirname = dirname($path);
768
        if (is_dir($dirname) && !is_file($path)) {
769
            return $path;
770
        }
771
772
        // Path is valid, but buildfile already exists
773
        if (is_file($path)) {
774
            throw new ConfigurationException('Buildfile already exists.');
775
        }
776
777
        throw new ConfigurationException('Invalid path for sample buildfile.');
778
    }
779
780
    /**
781
     * Writes sample buildfile.
782
     *
783
     * If $buildfilePath does not exist, the buildfile is created.
784
     *
785
     * @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...
786
     *
787
     * @throws ConfigurationException
788
     */
789
    protected static function initWrite($buildfilePath)
790
    {
791
        // Overwriting protection
792
        if (file_exists($buildfilePath)) {
793
            throw new ConfigurationException('Cannot overwrite existing file.');
794
        }
795
796
        file_put_contents($buildfilePath, self::DEFAULT_BUILD_CONTENT);
797
    }
798
799
    /**
800
     * Sets the stream to use for standard (non-error) output.
801
     *
802
     * @param OutputStream $stream the stream to use for standard output
803
     */
804 1
    public static function setOutputStream(OutputStream $stream)
805
    {
806 1
        self::$out = $stream;
807 1
    }
808
809
    /**
810
     * Sets the stream to use for error output.
811
     *
812
     * @param OutputStream $stream the stream to use for error output
813
     */
814 1
    public static function setErrorStream(OutputStream $stream)
815
    {
816 1
        self::$err = $stream;
817 1
    }
818
819
    /**
820
     * Handle the -propertyfile argument.
821
     *
822
     * @throws ConfigurationException
823
     * @throws IOException
824
     */
825
    private function handleArgPropertyFile(array $args, int $pos): int
826
    {
827
        if (!isset($args[$pos + 1])) {
828
            throw new ConfigurationException('You must specify a filename when using the -propertyfile argument');
829
        }
830
831
        $this->propertyFiles[] = $args[++$pos];
832
833
        return $pos;
834
    }
835
836
    /**
837
     * Search parent directories for the build file.
838
     *
839
     * Takes the given target as a suffix to append to each
840
     * parent directory in search of a build file.  Once the
841
     * root of the file-system has been reached an exception
842
     * is thrown.
843
     *
844
     * @param string $start start file path
845
     * @param string $suffix suffix filename to look for in parents
846
     *
847
     * @return File A handle to the build file
848
     * @throws ConfigurationException
849
     *
850
     */
851
    private function findBuildFile($start, $suffix)
852
    {
853
        if (self::getMsgOutputLevel() >= Project::MSG_INFO) {
854
            self::$out->write('Searching for ' . $suffix . ' ...' . PHP_EOL);
855
        }
856
857
        $parent = new File((new File($start))->getAbsolutePath());
858
        $file = new File($parent, $suffix);
859
860
        // check if the target file exists in the current directory
861
        while (!$file->exists()) {
862
            // change to parent directory
863
            $parent = $parent->getParentFile();
864
865
            // if parent is null, then we are at the root of the fs,
866
            // complain that we can't find the build file.
867
            if (null === $parent) {
868
                throw new ConfigurationException('Could not locate a build file!');
869
            }
870
            // refresh our file handle
871
            $file = new File($parent, $suffix);
872
        }
873
874
        return $file;
875
    }
876
877
    /**
878
     * Making output level a static property so that this property
879
     * can be accessed by other parts of the system, enabling
880
     * us to display more information -- e.g. backtraces -- for "debug" level.
881
     *
882
     * @return int
883
     */
884 7
    public static function getMsgOutputLevel()
885
    {
886 7
        return self::$msgOutputLevel;
887
    }
888
889
    /**
890
     * @throws IOException
891
     */
892
    private function loadPropertyFiles()
893
    {
894
        foreach ($this->propertyFiles as $filename) {
895
            $fileParserFactory = new FileParserFactory();
896
            $fileParser = $fileParserFactory->createParser(pathinfo($filename, PATHINFO_EXTENSION));
897
            $p = new Properties(null, $fileParser);
898
899
            try {
900
                $p->load(new File($filename));
901
            } catch (IOException $e) {
902
                self::$out->write('Could not load property file ' . $filename . ': ' . $e->getMessage());
903
            }
904
            foreach ($p->getProperties() as $prop => $value) {
905
                self::$definedProps->setProperty($prop, $value);
906
            }
907
        }
908
    }
909
910
    /**
911
     * Close logfiles, if we have been writing to them.
912
     *
913
     * @since Phing 2.3.0
914
     */
915
    private static function handleLogfile()
916
    {
917
        if (self::$isLogFileUsed) {
918
            self::$err->close();
919
            self::$out->close();
920
        }
921
    }
922
923
    /**
924
     * Prints the message of the Exception if it's not null.
925
     */
926
    public static function printMessage(Throwable $t)
927
    {
928
        if (null === self::$err) { // Make sure our error output is initialized
929
            self::initializeOutputStreams();
930
        }
931
        if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) {
932
            self::$err->write((string) $t . PHP_EOL);
933
        } else {
934
            self::$err->write($t->getMessage() . PHP_EOL);
935
        }
936
    }
937
938
    /**
939
     * Sets the stdout and stderr streams if they are not already set.
940
     */
941 1
    private static function initializeOutputStreams()
942
    {
943 1
        if (null === self::$out) {
944
            if (!defined('STDOUT')) {
945
                self::$out = new OutputStream(fopen('php://stdout', 'w'));
946
            } else {
947
                self::$out = new OutputStream(STDOUT);
948
            }
949
        }
950 1
        if (null === self::$err) {
951
            if (!defined('STDERR')) {
952
                self::$err = new OutputStream(fopen('php://stderr', 'w'));
953
            } else {
954
                self::$err = new OutputStream(STDERR);
955
            }
956
        }
957 1
    }
958
959
    /**
960
     * This operation is expected to call `exit($int)`, which
961
     * is what the base version does.
962
     * However, it is possible to do something else.
963
     *
964
     * @param int $exitCode code to exit with
965
     */
966
    protected static function statusExit($exitCode)
967
    {
968
        Phing::shutdown();
969
970
        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...
971
    }
972
973
    /**
974
     * Performs any shutdown routines, such as stopping timers.
975
     *
976
     * @throws IOException
977
     */
978 1
    public static function shutdown(): void
979
    {
980 1
        FileSystem::getFileSystem()::deleteFilesOnExit();
981 1
        self::$msgOutputLevel = Project::MSG_INFO;
982 1
        self::restoreIni();
983 1
        self::getTimer()->stop();
984 1
    }
985
986
    /**
987
     * Restores [most] PHP INI values to their pre-Phing state.
988
     *
989
     * Currently the following settings are not restored:
990
     *  - max_execution_time (because getting current time limit is not possible)
991
     *  - memory_limit (which may have been increased by Phing)
992
     */
993 1
    private static function restoreIni(): void
994
    {
995 1
        foreach (self::$origIniSettings as $settingName => $settingValue) {
996 1
            switch ($settingName) {
997 1
                case 'error_reporting':
998 1
                    error_reporting($settingValue);
999
1000 1
                    break;
1001
1002
                default:
1003 1
                    ini_set($settingName, $settingValue);
1004
            }
1005
        }
1006 1
    }
1007
1008
    /**
1009
     * Returns reference to DefaultClock object.
1010
     */
1011 2
    public static function getTimer(): DefaultClock
1012
    {
1013 2
        if (null === self::$timer) {
1014
            self::$timer = new DefaultClock();
1015
        }
1016
1017 2
        return self::$timer;
1018
    }
1019
1020
    /**
1021
     * This sets a property that was set via command line or otherwise passed into Phing.
1022
     *
1023
     * @param string $name
1024
     * @param mixed $value
1025
     *
1026
     * @return mixed value of found property (or null, if none found)
1027
     */
1028
    public static function setDefinedProperty($name, $value)
1029
    {
1030
        return self::$definedProps->setProperty($name, $value);
1031
    }
1032
1033
    /**
1034
     * Executes the build.
1035
     *
1036
     * @throws IOException
1037
     * @throws Throwable
1038
     */
1039
    public function runBuild(): void
1040
    {
1041
        if (!$this->readyToRun) {
1042
            return;
1043
        }
1044
1045
        $project = new Project();
1046
1047
        self::setCurrentProject($project);
1048
        set_error_handler(['Phing\Phing', 'handlePhpError']);
1049
1050
        $error = null;
1051
1052
        try {
1053
            $this->addBuildListeners($project);
1054
            $this->addInputHandler($project);
1055
1056
            // set this right away, so that it can be used in logging.
1057
            $project->setUserProperty('phing.file', $this->buildFile->getAbsolutePath());
1058
            $project->setUserProperty('phing.dir', dirname($this->buildFile->getAbsolutePath()));
1059
            $project->setUserProperty('phing.version', static::getPhingVersion());
1060
            $project->fireBuildStarted();
1061
            $project->init();
1062
            $project->setKeepGoingMode($this->keepGoingMode);
1063
1064
            $e = self::$definedProps->keys();
1065
            while (count($e)) {
1066
                $arg = (string) array_shift($e);
1067
                $value = (string) self::$definedProps->getProperty($arg);
1068
                $project->setUserProperty($arg, $value);
1069
            }
1070
            unset($e);
1071
1072
            // first use the Configurator to create the project object
1073
            // from the given build file.
1074
1075
            ProjectConfigurator::configureProject($project, $this->buildFile);
1076
1077
            // Set the project mode
1078
            $project->setStrictMode(StringHelper::booleanValue($this->strictMode));
1079
1080
            // make sure that minimum required phing version is satisfied
1081
            $this->comparePhingVersion($project->getPhingVersion());
1082
1083
            if ($this->projectHelp) {
1084
                $this->printDescription($project);
1085
                $this->printTargets($project);
1086
1087
                return;
1088
            }
1089
1090
            // make sure that we have a target to execute
1091
            if (0 === count($this->targets)) {
1092
                $this->targets[] = $project->getDefaultTarget();
1093
            }
1094
1095
            $project->executeTargets($this->targets);
1096
        } catch (Throwable $t) {
1097
            $error = $t;
1098
1099
            throw $t;
1100
        } finally {
1101
            if (!$this->projectHelp) {
1102
                try {
1103
                    $project->fireBuildFinished($error);
1104
                } catch (Exception $e) {
1105
                    self::$err->write('Caught an exception while logging the end of the build.  Exception was:' . PHP_EOL);
1106
                    self::$err->write($e->getTraceAsString());
1107
                    if (null !== $error) {
1108
                        self::$err->write('There has been an error prior to that:' . PHP_EOL);
1109
                        self::$err->write($error->getTraceAsString());
1110
                    }
1111
1112
                    throw new BuildException($error);
1113
                }
1114
            } elseif (null !== $error) {
1115
                $project->log($error->getMessage(), Project::MSG_ERR);
1116
            }
1117
1118
            restore_error_handler();
1119
            self::unsetCurrentProject();
1120
        }
1121
    }
1122
1123
    /**
1124
     * Bind any registered build listeners to this project.
1125
     *
1126
     * This means adding the logger and any build listeners that were specified
1127
     * with -listener arg.
1128
     *
1129
     * @throws BuildException
1130
     * @throws ConfigurationException
1131
     */
1132
    private function addBuildListeners(Project $project)
1133
    {
1134
        // Add the default listener
1135
        $project->addBuildListener($this->createLogger());
1136
1137
        foreach ($this->listeners as $listenerClassname) {
1138
            try {
1139
                $clz = Phing::import($listenerClassname);
1140
            } catch (Exception $e) {
1141
                $msg = 'Unable to instantiate specified listener '
1142
                    . 'class ' . $listenerClassname . ' : '
1143
                    . $e->getMessage();
1144
1145
                throw new ConfigurationException($msg);
1146
            }
1147
1148
            $listener = new $clz();
1149
1150
            if ($listener instanceof StreamRequiredBuildLogger) {
1151
                throw new ConfigurationException('Unable to add ' . $listenerClassname . ' as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)');
1152
            }
1153
            $project->addBuildListener($listener);
1154
        }
1155
    }
1156
1157
    /**
1158
     * Creates the default build logger for sending build events to the log.
1159
     *
1160
     * @return BuildLogger The created Logger
1161
     * @throws BuildException
1162
     *
1163
     */
1164
    private function createLogger()
1165
    {
1166
        if ($this->silent) {
1167
            $logger = new SilentLogger();
1168
            self::$msgOutputLevel = Project::MSG_WARN;
1169
        } elseif (null !== $this->loggerClassname) {
1170
            self::import($this->loggerClassname);
1171
            // get class name part
1172
            $classname = self::import($this->loggerClassname);
1173
            $logger = new $classname();
1174
            if (!($logger instanceof BuildLogger)) {
1175
                throw new BuildException($classname . ' does not implement the BuildLogger interface.');
1176
            }
1177
        } else {
1178
            $logger = new DefaultLogger();
1179
        }
1180
        $logger->setMessageOutputLevel(self::$msgOutputLevel);
1181
        $logger->setOutputStream(self::$out);
1182
        $logger->setErrorStream(self::$err);
1183
        $logger->setEmacsMode($this->emacsMode);
1184
1185
        return $logger;
1186
    }
1187
1188
    /**
1189
     * Import a class, supporting the following conventions:
1190
     * - PEAR style (@see http://pear.php.net/manual/en/standards.naming.php)
1191
     * - PSR-0 (@see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md).
1192
     *
1193
     * @param string $classname Name of class
1194
     * @param mixed $classpath String or object supporting __toString()
1195
     *
1196
     * @throws BuildException - if cannot find the specified file
1197
     *
1198
     * @return string the unqualified classname (which can be instantiated)
1199
     */
1200 830
    public static function import($classname, $classpath = null)
1201
    {
1202
        // first check to see that the class specified hasn't already been included.
1203 830
        if (class_exists($classname)) {
1204 828
            return $classname;
1205
        }
1206
1207 6
        $filename = strtr($classname, ['_' => DIRECTORY_SEPARATOR, '\\' => DIRECTORY_SEPARATOR]) . '.php';
1208
1209 6
        Phing::importFile($filename, $classpath);
1210
1211 5
        return $classname;
1212
    }
1213
1214
    /**
1215
     * Import a PHP file.
1216
     *
1217
     * This used to be named __import, however PHP has reserved all method names
1218
     * with a double underscore prefix for future use.
1219
     *
1220
     * @param string $path Path to the PHP file
1221
     * @param mixed $classpath String or object supporting __toString()
1222
     *
1223
     * @throws ConfigurationException
1224
     */
1225 9
    public static function importFile($path, $classpath = null)
1226
    {
1227 9
        if ($classpath) {
1228
            // Apparently casting to (string) no longer invokes __toString() automatically.
1229 2
            if (is_object($classpath)) {
1230
                $classpath = $classpath->__toString();
1231
            }
1232
1233
            // classpaths are currently additive, but we also don't want to just
1234
            // indiscriminantly prepand/append stuff to the include_path.  This means
1235
            // we need to parse current incldue_path, and prepend any
1236
            // specified classpath locations that are not already in the include_path.
1237
            //
1238
            // NOTE:  the reason why we do it this way instead of just changing include_path
1239
            // and then changing it back, is that in many cases applications (e.g. Propel) will
1240
            // include/require class files from within method calls.  This means that not all
1241
            // necessary files will be included in this import() call, and hence we can't
1242
            // change the include_path back without breaking those apps.  While this method could
1243
            // be more expensive than switching & switching back (not sure, but maybe), it makes it
1244
            // possible to write far less expensive run-time applications (e.g. using Propel), which is
1245
            // really where speed matters more.
1246
1247 2
            $curr_parts = Phing::explodeIncludePath();
1248 2
            $add_parts = Phing::explodeIncludePath($classpath);
1249 2
            $new_parts = array_diff($add_parts, $curr_parts);
1250 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...
1251 1
                set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts)));
1252
            }
1253
        }
1254
1255 9
        $ret = include_once $path;
1256
1257 8
        if (false === $ret) {
1258
            $msg = "Error importing {$path}";
1259
            if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) {
1260
                $x = new Exception('for-path-trace-only');
1261
                $msg .= $x->getTraceAsString();
1262
            }
1263
1264
            throw new ConfigurationException($msg);
1265
        }
1266 8
    }
1267
1268
    /**
1269
     * Creates the InputHandler and adds it to the project.
1270
     *
1271
     * @param Project $project the project instance
1272
     *
1273
     * @throws ConfigurationException
1274
     */
1275
    private function addInputHandler(Project $project)
1276
    {
1277
        if (null === $this->inputHandlerClassname) {
1278
            $handler = new ConsoleInputHandler(STDIN, new ConsoleOutput());
1279
        } else {
1280
            try {
1281
                $clz = Phing::import($this->inputHandlerClassname);
1282
                $handler = new $clz();
1283
                if (null !== $project && method_exists($handler, 'setProject')) {
1284
                    $handler->setProject($project);
1285
                }
1286
            } catch (Exception $e) {
1287
                $msg = 'Unable to instantiate specified input handler '
1288
                    . 'class ' . $this->inputHandlerClassname . ' : '
1289
                    . $e->getMessage();
1290
1291
                throw new ConfigurationException($msg);
1292
            }
1293
        }
1294
        $project->setInputHandler($handler);
1295
    }
1296
1297
    /**
1298
     * @param string $version
1299
     *
1300
     * @throws BuildException
1301
     * @throws ConfigurationException
1302
     */
1303
    private function comparePhingVersion($version)
1304
    {
1305
        $current = strtolower(self::getPhingVersion());
1306
        $current = trim(str_replace('phing', '', $current));
1307
1308
        // make sure that version checks are not applied to trunk
1309
        if ('dev' === $current) {
1310
            return;
1311
        }
1312
1313
        if (-1 == version_compare($current, $version)) {
1314
            throw new BuildException(
1315
                sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)
1316
            );
1317
        }
1318
    }
1319
1320
    /**
1321
     * Print the project description, if any.
1322
     *
1323
     * @throws IOException
1324
     */
1325
    public function printDescription(Project $project)
1326
    {
1327
        if (null !== $project->getDescription()) {
1328
            $project->log($project->getDescription());
1329
        }
1330
    }
1331
1332
    /**
1333
     * Print out a list of all targets in the current buildfile.
1334
     */
1335 1
    public function printTargets(Project $project)
1336
    {
1337 1
        $visibleTargets = array_filter($project->getTargets(), function (Target $target) {
1338 1
            return !$target->isHidden() && !empty($target->getName());
1339 1
        });
1340 1
        $padding = array_reduce($visibleTargets, function (int $carry, Target $target) {
1341
            return max(strlen($target->getName()), $carry);
1342 1
        }, 0);
1343
        $categories = [
1344 1
            'Default target:' => array_filter($visibleTargets, function (Target $target) use ($project) {
1345
                return trim(strval($target)) === $project->getDefaultTarget();
1346 1
            }),
1347 1
            'Main targets:' => array_filter($visibleTargets, function (Target $target) {
1348
                return !empty($target->getDescription());
1349 1
            }),
1350 1
            'Subtargets:' => array_filter($visibleTargets, function (Target $target) {
1351
                return empty($target->getDescription());
1352 1
            }),
1353
        ];
1354 1
        foreach ($categories as $title => $targets) {
1355 1
            $targetList = $this->generateTargetList($title, $targets, $padding);
1356 1
            $project->log($targetList, Project::MSG_WARN);
1357
        }
1358 1
    }
1359
1360
    /**
1361
     * Returns a formatted list of target names with an optional description.
1362
     *
1363
     * @param string $title Title for this list
1364
     * @param Target[] $targets Targets in this list
1365
     * @param int $padding Padding for name column
1366
     */
1367 1
    private function generateTargetList(string $title, array $targets, int $padding): string
1368
    {
1369 1
        usort($targets, function (Target $a, Target $b) {
1370
            return $a->getName() <=> $b->getName();
1371 1
        });
1372
1373
        $header = <<<HEADER
1374 1
            {$title}
1375
            -------------------------------------------------------------------------------
1376
1377
            HEADER;
1378
1379 1
        $getDetails = function (Target $target) use ($padding): string {
1380
            $details = [];
1381
            if (!empty($target->getDescription())) {
1382
                $details[] = $target->getDescription();
1383
            }
1384
            if (!empty($target->getDependencies())) {
1385
                $details[] = ' - depends on: ' . implode(', ', $target->getDependencies());
1386
            }
1387
            if (!empty($target->getIf())) {
1388
                $details[] = ' - if property: ' . $target->getIf();
1389
            }
1390
            if (!empty($target->getUnless())) {
1391
                $details[] = ' - unless property: ' . $target->getUnless();
1392
            }
1393
            $detailsToString = function (?string $name, ?string $detail) use ($padding): string {
1394
                return sprintf(" %-{$padding}s  %s", $name, $detail);
1395
            };
1396
1397
            return implode(PHP_EOL, array_map($detailsToString, [$target->getName()], $details));
1398 1
        };
1399
1400 1
        return $header . implode(PHP_EOL, array_map($getDetails, $targets)) . PHP_EOL;
1401
    }
1402
1403
    /**
1404
     * Unsets the current Project.
1405
     */
1406 1
    public static function unsetCurrentProject()
1407
    {
1408 1
        self::$currentProject = null;
1409 1
    }
1410
1411
    /**
1412
     * Error handler for PHP errors encountered during the build.
1413
     * This uses the logging for the currently configured project.
1414
     *
1415
     * @param $level
1416
     * @param string $message
1417
     * @param $file
1418
     * @param $line
1419
     */
1420
    public static function handlePhpError($level, $message, $file, $line)
1421
    {
1422
        // don't want to print suppressed errors
1423
        if (error_reporting() > 0) {
1424
            if (self::$phpErrorCapture) {
1425
                self::$capturedPhpErrors[] = [
1426
                    'message' => $message,
1427
                    'level' => $level,
1428
                    'line' => $line,
1429
                    'file' => $file,
1430
                ];
1431
            } else {
1432
                $message = '[PHP Error] ' . $message;
1433
                $message .= ' [line ' . $line . ' of ' . $file . ']';
1434
1435
                switch ($level) {
1436
                    case E_USER_DEPRECATED:
1437
                    case E_DEPRECATED:
1438
                    case E_STRICT:
1439
                    case E_NOTICE:
1440
                    case E_USER_NOTICE:
1441
                        self::log($message, Project::MSG_VERBOSE);
1442
1443
                        break;
1444
1445
                    case E_WARNING:
1446
                    case E_USER_WARNING:
1447
                        self::log($message, Project::MSG_WARN);
1448
1449
                        break;
1450
1451
                    case E_ERROR:
1452
                    case E_USER_ERROR:
1453
                    default:
1454
                        self::log($message, Project::MSG_ERR);
1455
                } // switch
1456
            } // if phpErrorCapture
1457
        } // if not @
1458
    }
1459
1460
    /**
1461
     * A static convenience method to send a log to the current (last-setup) Project.
1462
     * If there is no currently-configured Project, then this will do nothing.
1463
     *
1464
     * @param string $message
1465
     * @param int $priority project::MSG_INFO, etc
1466
     */
1467
    public static function log($message, $priority = Project::MSG_INFO)
1468
    {
1469
        $p = self::getCurrentProject();
1470
        if ($p) {
0 ignored issues
show
introduced by
$p is of type Phing\Project, thus it always evaluated to true.
Loading history...
1471
            $p->log($message, $priority);
1472
        }
1473
    }
1474
1475
    /**
1476
     * Gets the current Project.
1477
     *
1478
     * @return Project current Project or NULL if none is set yet/still
1479
     */
1480 3
    public static function getCurrentProject()
1481
    {
1482 3
        return self::$currentProject;
1483
    }
1484
1485
    /**
1486
     * Sets the current Project.
1487
     *
1488
     * @param Project $p
1489
     */
1490 1
    public static function setCurrentProject($p)
1491
    {
1492 1
        self::$currentProject = $p;
1493 1
    }
1494
1495
    /**
1496
     * Begins capturing PHP errors to a buffer.
1497
     * While errors are being captured, they are not logged.
1498
     */
1499
    public static function startPhpErrorCapture()
1500
    {
1501
        self::$phpErrorCapture = true;
1502
        self::$capturedPhpErrors = [];
1503
    }
1504
1505
    /**
1506
     * Stops capturing PHP errors to a buffer.
1507
     * The errors will once again be logged after calling this method.
1508
     */
1509
    public static function stopPhpErrorCapture()
1510
    {
1511
        self::$phpErrorCapture = false;
1512
    }
1513
1514
    /**
1515
     * Clears the captured errors without affecting the starting/stopping of the capture.
1516
     */
1517
    public static function clearCapturedPhpErrors()
1518
    {
1519
        self::$capturedPhpErrors = [];
1520
    }
1521
1522
    /**
1523
     * Gets any PHP errors that were captured to buffer.
1524
     *
1525
     * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level)
1526
     */
1527
    public static function getCapturedPhpErrors()
1528
    {
1529
        return self::$capturedPhpErrors;
1530
    }
1531
1532
    /**
1533
     * This gets a property that was set via command line or otherwise passed into Phing.
1534
     * "Defined" in this case means "externally defined".  The reason this method exists is to
1535
     * provide a public means of accessing commandline properties for (e.g.) logger or listener
1536
     * scripts.  E.g. to specify which logfile to use, PearLogger needs to be able to access
1537
     * the pear.log.name property.
1538
     *
1539
     * @param string $name
1540
     *
1541
     * @return string value of found property (or null, if none found)
1542
     */
1543
    public static function getDefinedProperty($name)
1544
    {
1545
        return self::$definedProps->getProperty($name);
1546
    }
1547
1548
    /**
1549
     * Retuns reference to all properties.
1550
     */
1551 827
    public static function &getProperties()
1552
    {
1553 827
        return self::$properties;
1554
    }
1555
1556
    /**
1557
     * Start up Phing.
1558
     * Sets up the Phing environment but does not initiate the build process.
1559
     *
1560
     * @throws exception - If the Phing environment cannot be initialized
1561
     */
1562 1
    public static function startup(): void
1563
    {
1564
        // setup STDOUT and STDERR defaults
1565 1
        self::initializeOutputStreams();
1566
1567
        // some init stuff
1568 1
        self::getTimer()->start();
1569
1570 1
        self::setSystemConstants();
1571 1
        self::setIncludePaths();
1572 1
        self::setIni();
1573 1
    }
1574
1575
    /**
1576
     * Set System constants which can be retrieved by calling Phing::getProperty($propName).
1577
     */
1578 1
    private static function setSystemConstants()
1579
    {
1580
        /*
1581
         * PHP_OS returns on
1582
         *   WindowsNT4.0sp6  => WINNT
1583
         *   Windows2000      => WINNT
1584
         *   Windows ME       => WIN32
1585
         *   Windows 98SE     => WIN32
1586
         *   FreeBSD 4.5p7    => FreeBSD
1587
         *   Redhat Linux     => Linux
1588
         *   Mac OS X         => Darwin
1589
         */
1590 1
        self::setProperty('host.os', PHP_OS);
1591
1592
        // this is used by some tasks too
1593 1
        self::setProperty('os.name', PHP_OS);
1594
1595
        // it's still possible this won't be defined,
1596
        // e.g. if Phing is being included in another app w/o
1597
        // using the phing.php script.
1598 1
        if (!defined('PHP_CLASSPATH')) {
1599
            define('PHP_CLASSPATH', get_include_path());
1600
        }
1601
1602 1
        self::setProperty('php.classpath', PHP_CLASSPATH);
1603
1604
        // try to determine the host filesystem and set system property
1605
        // used by Fileself::getFileSystem to instantiate the correct
1606
        // abstraction layer
1607
1608 1
        if (PHP_OS_FAMILY === 'Windows') {
1609
            self::setProperty('host.fstype', 'WINDOWS');
1610
            self::setProperty('user.home', getenv('HOMEDRIVE') . getenv('HOMEPATH'));
1611
        } else {
1612 1
            self::setProperty('host.fstype', 'UNIX');
1613 1
            self::setProperty('user.home', getenv('HOME'));
1614
        }
1615 1
        self::setProperty(self::PHP_INTERPRETER, PHP_BINARY);
1616 1
        self::setProperty('file.separator', FileUtils::getSeparator());
1617 1
        self::setProperty('line.separator', PHP_EOL);
1618 1
        self::setProperty('path.separator', FileUtils::getPathSeparator());
1619 1
        self::setProperty(self::PHP_VERSION, PHP_VERSION);
1620 1
        self::setProperty('php.tmpdir', sys_get_temp_dir());
1621 1
        self::setProperty('application.startdir', getcwd());
1622 1
        self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT');
1623
1624
        // try to detect machine dependent information
1625 1
        $sysInfo = [];
1626 1
        if (function_exists('posix_uname') && 0 !== stripos(PHP_OS, 'WIN')) {
1627 1
            $sysInfo = posix_uname();
1628
        } else {
1629
            $sysInfo['nodename'] = php_uname('n');
1630
            $sysInfo['machine'] = php_uname('m');
1631
            //this is a not so ideal substition, but maybe better than nothing
1632
            $sysInfo['domain'] = $_SERVER['SERVER_NAME'] ?? 'unknown';
1633
            $sysInfo['release'] = php_uname('r');
1634
            $sysInfo['version'] = php_uname('v');
1635
        }
1636
1637 1
        self::setProperty('host.name', $sysInfo['nodename'] ?? 'unknown');
1638 1
        self::setProperty('host.arch', $sysInfo['machine'] ?? 'unknown');
1639 1
        self::setProperty('host.domain', $sysInfo['domain'] ?? 'unknown');
1640 1
        self::setProperty('host.os.release', $sysInfo['release'] ?? 'unknown');
1641 1
        self::setProperty('host.os.version', $sysInfo['version'] ?? 'unknown');
1642 1
        unset($sysInfo);
1643 1
    }
1644
1645
    /**
1646
     * @param $propName
1647
     * @param $propValue
1648
     *
1649
     * @return string
1650
     */
1651 8
    public static function setProperty($propName, $propValue)
1652
    {
1653 8
        $propName = (string) $propName;
1654 8
        $oldValue = self::getProperty($propName);
1655 8
        self::$properties[$propName] = $propValue;
1656
1657 8
        return $oldValue;
1658
    }
1659
1660
    /**
1661
     * Sets the include path to PHP_CLASSPATH constant (if this has been defined).
1662
     *
1663
     * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason)
1664
     */
1665 1
    private static function setIncludePaths()
1666
    {
1667 1
        if (defined('PHP_CLASSPATH')) {
1668 1
            $result = set_include_path(PHP_CLASSPATH);
1669 1
            if (false === $result) {
1670
                throw new ConfigurationException('Could not set PHP include_path.');
1671
            }
1672 1
            self::$origIniSettings['include_path'] = $result; // save original value for setting back later
1673
        }
1674 1
    }
1675
1676
    /**
1677
     * Sets PHP INI values that Phing needs.
1678
     */
1679 1
    private static function setIni(): void
1680
    {
1681 1
        self::$origIniSettings['error_reporting'] = error_reporting(E_ALL);
1682
1683
        // We won't bother storing original max_execution_time, since 1) the value in
1684
        // php.ini may be wrong (and there's no way to get the current value) and
1685
        // 2) it would mean something very strange to set it to a value less than time script
1686
        // has already been running, which would be the likely change.
1687
1688 1
        set_time_limit(0);
1689
1690 1
        self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off');
1691 1
        self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1');
1692
1693 1
        $mem_limit = (int) SizeHelper::fromHumanToBytes(ini_get('memory_limit'));
1694 1
        if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) {
1695
            // We do *not* need to save the original value here, since we don't plan to restore
1696
            // this after shutdown (we don't trust the effectiveness of PHP's garbage collection).
1697
            ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects
1698
        }
1699 1
    }
1700
}
1701