Passed
Push — master ( cb5cd1...da0f4b )
by Siad
10:24
created

PhingTask::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 10
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
/**
21
 * Task that invokes phing on another build file.
22
 *
23
 * Use this task, for example, if you have nested buildfiles in your project. Unlike
24
 * AntTask, PhingTask can even support filesets:
25
 *
26
 * <pre>
27
 *   <phing>
28
 *    <fileset dir="${srcdir}">
29
 *      <include name="** /build.xml" /> <!-- space added after ** is there because of PHP comment syntax -->
30
 *      <exclude name="build.xml" />
31
 *    </fileset>
32
 *   </phing>
33
 * </pre>
34
 *
35
 * @author  Hans Lellelid <[email protected]>
36
 * @package phing.tasks.system
37
 */
38
class PhingTask extends Task
39
{
40
    use FileSetAware;
41
42
    /**
43
     * the basedir where is executed the build file
44
     */
45
    private $dir;
46
47
    /**
48
     * build.xml (can be absolute) in this case dir will be ignored
49
     */
50
    private $phingFile;
51
52
    /**
53
     * the target to call if any
54
     */
55
    protected $newTarget;
56
57
    /**
58
     * should we inherit properties from the parent ?
59
     */
60
    private $inheritAll = true;
61
62
    /**
63
     * should we inherit references from the parent ?
64
     */
65
    private $inheritRefs = false;
66
67
    /**
68
     * the properties to pass to the new project
69
     */
70
    private $properties = [];
71
72
    /**
73
     * the references to pass to the new project
74
     */
75
    private $references = [];
76
77
    /**
78
     * The temporary project created to run the build file
79
     *
80
     * @var Project
81
     */
82
    private $newProject;
83
84
    /**
85
     * Fail the build process when the called build fails?
86
     */
87
    private $haltOnFailure = false;
88
89
    /**
90
     * @var OutputStream
91
     */
92
    private $out;
93
94
    /** @var string */
95
    private $output;
96
97 27
    public function __construct(Task $owner = null)
98
    {
99 27
        if ($owner !== null) {
100
            $this->bindToOwner($owner);
101
        }
102 27
        parent::__construct();
103 27
    }
104
105
    /**
106
     *  If true, abort the build process if there is a problem with or in the target build file.
107
     *  Defaults to false.
108
     *
109
     * @param bool $hof new value
110
     */
111 14
    public function setHaltOnFailure($hof)
112
    {
113 14
        $this->haltOnFailure = (bool) $hof;
114 14
    }
115
116
    /**
117
     * Creates a Project instance for the project to call.
118
     *
119
     * @return void
120
     */
121 27
    public function init()
122
    {
123 27
        $this->newProject = new Project();
124 27
        $tdf = $this->project->getTaskDefinitions();
125 27
        $this->newProject->addTaskDefinition("property", $tdf["property"]);
126 27
    }
127
128
    /**
129
     * Called in execute or createProperty if newProject is null.
130
     *
131
     * <p>This can happen if the same instance of this task is run
132
     * twice as newProject is set to null at the end of execute (to
133
     * save memory and help the GC).</p>
134
     *
135
     * <p>Sets all properties that have been defined as nested
136
     * property elements.</p>
137
     */
138 6
    private function reinit()
139
    {
140 6
        $this->init();
141
142 6
        $count = count($this->properties);
143 6
        for ($i = 0; $i < $count; $i++) {
144
            /**
145
             * @var PropertyTask $p
146
             */
147 6
            $p = $this->properties[$i];
148
            /**
149
             * @var PropertyTask $newP
150
             */
151 6
            $newP = $this->newProject->createTask("property");
152 6
            $newP->setName($p->getName());
153 6
            if ($p->getValue() !== null) {
154 6
                $newP->setValue($p->getValue());
155
            }
156 6
            if ($p->getFile() !== null) {
157
                $newP->setFile($p->getFile());
158
            }
159 6
            if ($p->getPrefix() !== null) {
160
                $newP->setPrefix($p->getPrefix());
161
            }
162 6
            if ($p->getRefid() !== null) {
163
                $newP->setRefid($p->getRefid());
164
            }
165 6
            if ($p->getEnvironment() !== null) {
166
                $newP->setEnvironment($p->getEnvironment());
167
            }
168 6
            if ($p->getUserProperty() !== null) {
169 6
                $newP->setUserProperty($p->getUserProperty());
170
            }
171 6
            $newP->setOverride($p->getOverride());
172 6
            $newP->setLogoutput($p->getLogoutput());
173 6
            $newP->setQuiet($p->getQuiet());
174
175 6
            $this->properties[$i] = $newP;
176
        }
177 6
    }
178
179
    /**
180
     * Main entry point for the task.
181
     *
182
     * @return void
183
     */
184 24
    public function main()
185
    {
186
        // Call Phing on the file set with the attribute "phingfile"
187 24
        if ($this->phingFile !== null || $this->dir !== null) {
188 24
            $this->processFile();
189
        }
190
191
        // if no filesets are given stop here; else process filesets
192 21
        if (!empty($this->filesets)) {
193
            // preserve old settings
194
            $savedDir = $this->dir;
195
            $savedPhingFile = $this->phingFile;
196
            $savedTarget = $this->newTarget;
197
198
            // set no specific target for files in filesets
199
            // [HL] I'm commenting this out; I don't know why this should not be supported!
200
            // $this->newTarget = null;
201
202
            foreach ($this->filesets as $fs) {
203
                $ds = $fs->getDirectoryScanner($this->project);
204
205
                $fromDir = $fs->getDir($this->project);
0 ignored issues
show
Unused Code introduced by
The assignment to $fromDir is dead and can be removed.
Loading history...
206
                $srcFiles = $ds->getIncludedFiles();
207
208
                foreach ($srcFiles as $fname) {
209
                    $f = new PhingFile($ds->getbasedir(), $fname);
210
                    $f = $f->getAbsoluteFile();
211
                    $this->phingFile = $f->getAbsolutePath();
212
                    $this->dir = $f->getParentFile();
213
                    $this->processFile(); // run Phing!
214
                }
215
            }
216
217
            // side effect free programming ;-)
218
            $this->dir = $savedDir;
219
            $this->phingFile = $savedPhingFile;
220
            $this->newTarget = $savedTarget;
221
222
            // [HL] change back to correct dir
223
            if ($this->dir !== null) {
224
                chdir($this->dir->getAbsolutePath());
225
            }
226
        }
227
228
        // Remove any dangling references to help the GC
229 21
        foreach ($this->properties as $property) {
230 11
            $property->setFallback(null);
231
        }
232 21
    }
233
234
    /**
235
     * Execute phing file.
236
     *
237
     * @throws BuildException
238
     */
239 24
    private function processFile(): void
240
    {
241 24
        $buildFailed = false;
242 24
        $savedDir = $this->dir;
243 24
        $savedPhingFile = $this->phingFile;
244 24
        $savedTarget = $this->newTarget;
245
246 24
        $savedBasedirAbsPath = null; // this is used to save the basedir *if* we change it
247
248
        try {
249 24
            $this->getNewProject();
250
251 24
            $this->initializeProject();
252
253 24
            if ($this->dir !== null) {
254 4
                $dirAbsPath = $this->dir->getAbsolutePath();
255
256
                // BE CAREFUL! -- when the basedir is changed for a project,
257
                // all calls to getAbsolutePath() on a relative-path dir will
258
                // be made relative to the project's basedir!  This means
259
                // that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!
260
261
                // We need to save the current project's basedir first.
262 4
                $savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();
263
264 4
                $this->newProject->setBasedir($this->dir);
265
266
                // Now we must reset $this->dir so that it continues to resolve to the same
267
                // path.
268 4
                $this->dir = new PhingFile($dirAbsPath);
269
270 4
                if ($savedDir !== null) { // has been set explicitly
271 4
                    $this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());
272
                }
273
            } else {
274
                // Since we're not changing the basedir here (for file resolution),
275
                // we don't need to worry about any side-effects in this scanrio.
276 21
                $this->dir = $this->getProject()->getBasedir();
277
            }
278
279 24
            $this->overrideProperties();
280 24
            $this->phingFile = $this->phingFile ?? 'build.xml';
281
282 24
            $fu = new FileUtils();
283 24
            $file = $fu->resolveFile($this->dir, $this->phingFile);
284 24
            $this->phingFile = $file->getAbsolutePath();
285
286 24
            $this->log(
287 24
                "Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'",
288 24
                Project::MSG_VERBOSE
289
            );
290
291 24
            $this->newProject->setUserProperty("phing.file", $this->phingFile);
292
293 24
            ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));
294
295 23
            if ($this->newTarget === null) {
296
                $this->newTarget = $this->newProject->getDefaultTarget();
297
            }
298
299
            // Are we trying to call the target in which we are defined?
300
            if (
301 23
                $this->newProject->getBaseDir() == $this->project->getBaseDir()
302 19
                && $this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file")
303 16
                && $this->getOwningTarget() !== null
304 16
                && $this->newTarget == $this->getOwningTarget()->getName()
305
            ) {
306 2
                throw new BuildException("phing task calling its own parent target");
307
            }
308
309 21
            $this->addReferences();
310 21
            $this->newProject->executeTarget($this->newTarget);
311 4
        } catch (Exception $e) {
312 4
            $buildFailed = true;
313 4
            $this->log($e->getMessage(), Project::MSG_ERR);
314 4
            if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {
315 4
                $lines = explode("\n", $e->getTraceAsString());
316 4
                foreach ($lines as $line) {
317 4
                    $this->log($line, Project::MSG_DEBUG);
318
                }
319
            }
320
            // important!!! continue on to perform cleanup tasks.
321 21
        } finally {
322
            // reset environment values to prevent side-effects.
323
324 24
            $this->newProject = null;
325 24
            $pkeys = array_keys($this->properties);
326 24
            foreach ($pkeys as $k) {
327 11
                $this->properties[$k]->setProject(null);
328
            }
329
330 24
            if ($this->output !== null && $this->out !== null) {
331 1
                $this->out->close();
332
            }
333
334 24
            $this->dir = $savedDir;
335 24
            $this->phingFile = $savedPhingFile;
336 24
            $this->newTarget = $savedTarget;
337
338
            // If the basedir for any project was changed, we need to set that back here.
339 24
            if ($savedBasedirAbsPath !== null) {
340 4
                chdir($savedBasedirAbsPath);
341
            }
342
343 24
            if ($this->haltOnFailure && $buildFailed) {
344 24
                throw new BuildException("Execution of the target buildfile failed. Aborting.");
345
            }
346
        }
347 21
    }
348
349
    /**
350
     * Get the (sub)-Project instance currently in use.
351
     *
352
     * @return Project
353
     */
354 24
    protected function getNewProject(): \Project
355
    {
356 24
        if ($this->newProject === null) {
357 6
            $this->reinit();
358
        }
359 24
        return $this->newProject;
360
    }
361
362
    /**
363
     * Configure the Project, i.e. make intance, attach build listeners
364
     * (copy from father project), add Task and Datatype definitions,
365
     * copy properties and references from old project if these options
366
     * are set via the attributes of the XML tag.
367
     *
368
     * Developer note:
369
     * This function replaces the old methods "init", "_reinit" and
370
     * "_initializeProject".
371
     */
372 24
    private function initializeProject()
373
    {
374 24
        $this->newProject->setInputHandler($this->project->getInputHandler());
375
376 24
        foreach ($this->project->getBuildListeners() as $listener) {
377 24
            $this->newProject->addBuildListener($listener);
378
        }
379
380
        /* Copy things from old project. Datatypes and Tasks are always
381
         * copied, properties and references only if specified so/not
382
         * specified otherwise in the XML definition.
383
         */
384
        // Add Datatype definitions
385 24
        foreach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {
386 24
            $this->newProject->addDataTypeDefinition($typeName, $typeClass);
387
        }
388
389
        // Add Task definitions
390 24
        foreach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {
391 24
            if ($taskClass == "propertytask") {
392
                // we have already added this taskdef in init()
393
                continue;
394
            }
395 24
            $this->newProject->addTaskDefinition($taskName, $taskClass);
396
        }
397
398 24
        if ($this->output !== null) {
399
            try {
400 1
                if ($this->dir !== null) {
401 1
                    $outfile = (new FileUtils())->resolveFile($this->dir, $this->output);
402
                } else {
403 1
                    $outfile = $this->getProject()->resolveFile($this->output);
404
                }
405 1
                $this->out = new FileOutputStream($outfile);
406 1
                $logger = new DefaultLogger();
407 1
                $logger->setMessageOutputLevel(Project::MSG_INFO);
408 1
                $logger->setOutputStream($this->out);
409 1
                $logger->setErrorStream($this->out);
410 1
                $this->newProject->addBuildListener($logger);
411
            } catch (Exception $ex) {
412
                $this->log("Phing: Can't set output to " . $this->output);
413
            }
414
        }
415
416
        // set user-defined properties
417 24
        $this->project->copyUserProperties($this->newProject);
418
419 24
        if (!$this->inheritAll) {
420
            // set System built-in properties separately,
421
            // b/c we won't inherit them.
422 13
            $this->newProject->setSystemProperties();
423
        } else {
424
            // set all properties from calling project
425 12
            $properties = $this->project->getProperties();
426 12
            foreach ($properties as $name => $value) {
427 12
                if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {
428
                    // basedir and phing.file get special treatment in main()
429 12
                    continue;
430
                }
431
                // don't re-set user properties, avoid the warning message
432 12
                if ($this->newProject->getProperty($name) === null) {
433
                    // no user property
434 12
                    $this->newProject->setNewProperty($name, $value);
435
                }
436
            }
437
        }
438 24
    }
439
440
    /**
441
     * Override the properties in the new project with the one
442
     * explicitly defined as nested elements here.
443
     *
444
     * @return void
445
     * @throws BuildException
446
     */
447 24
    private function overrideProperties()
448
    {
449 24
        foreach (array_keys($this->properties) as $i) {
450 11
            $p = $this->properties[$i];
451 11
            $p->setProject($this->newProject);
452 11
            $p->main();
453
        }
454 24
        $this->project->copyInheritedProperties($this->newProject);
455 24
    }
456
457
    /**
458
     * Add the references explicitly defined as nested elements to the
459
     * new project.  Also copy over all references that don't override
460
     * existing references in the new project if inheritrefs has been
461
     * requested.
462
     *
463
     * @return void
464
     * @throws BuildException
465
     */
466 21
    private function addReferences()
467
    {
468
469
        // parent project references
470 21
        $projReferences = $this->project->getReferences();
471
472 21
        $newReferences = $this->newProject->getReferences();
473
474 21
        $subprojRefKeys = [];
475
476 21
        if (count($this->references) > 0) {
477
            for ($i = 0, $count = count($this->references); $i < $count; $i++) {
478
                /** @var Reference $ref */
479
                $ref = $this->references[$i];
480
                $refid = $ref->getRefId();
481
482
                if ($refid === null) {
483
                    throw new BuildException('the refid attribute is required for reference elements');
484
                }
485
                if (!isset($projReferences[$refid])) {
486
                    $this->log("Parent project doesn't contain any reference '" . $refid . "'", Project::MSG_WARN);
487
                    continue;
488
                }
489
490
                $subprojRefKeys[] = $refid;
491
                //thisReferences.remove(refid);
492
                $toRefid = $ref->getToRefid();
0 ignored issues
show
Bug introduced by
The method getToRefid() does not exist on Reference. Did you maybe mean getRefId()? ( Ignorable by Annotation )

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

492
                /** @scrutinizer ignore-call */ 
493
                $toRefid = $ref->getToRefid();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
493
                if ($toRefid === null) {
494
                    $toRefid = $refid;
495
                }
496
                $this->copyReference($refid, $toRefid);
497
            }
498
        }
499
500
        // Now add all references that are not defined in the
501
        // subproject, if inheritRefs is true
502 21
        if ($this->inheritRefs) {
503
            // get the keys that are were not used by the subproject
504 2
            $unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);
505
506 2
            foreach ($unusedRefKeys as $key) {
507 2
                if (isset($newReferences[$key])) {
508 2
                    continue;
509
                }
510 2
                $this->copyReference($key, $key);
511
            }
512
        }
513 21
    }
514
515
    /**
516
     * Try to clone and reconfigure the object referenced by oldkey in
517
     * the parent project and add it to the new project with the key
518
     * newkey.
519
     *
520
     * <p>If we cannot clone it, copy the referenced object itself and
521
     * keep our fingers crossed.</p>
522
     *
523
     * @param  string $oldKey
524
     * @param  string $newKey
525
     * @throws BuildException
526
     * @return void
527
     */
528 2
    private function copyReference($oldKey, $newKey)
529
    {
530 2
        $orig = $this->project->getReference($oldKey);
531 2
        if ($orig === null) {
532
            $this->log(
533
                "No object referenced by " . $oldKey . ". Can't copy to "
534
                . $newKey,
535
                Project::MSG_WARN
536
            );
537
538
            return;
539
        }
540
541 2
        $copy = clone $orig;
542
543 2
        if ($copy instanceof ProjectComponent) {
544 2
            $copy->setProject($this->newProject);
545 2
        } elseif (in_array('setProject', get_class_methods(get_class($copy)))) {
546
            $copy->setProject($this->newProject);
547 2
        } elseif (!($copy instanceof Project)) {
548
            // don't copy the old "Project" itself
549
            $msg = "Error setting new project instance for "
550
                . "reference with id " . $oldKey;
551
            throw new BuildException($msg);
552
        }
553
554 2
        $this->newProject->addReference($newKey, $copy);
555 2
    }
556
557
    /**
558
     * If true, pass all properties to the new phing project.
559
     * Defaults to true.
560
     *
561
     * @param $value
562
     */
563 20
    public function setInheritAll($value)
564
    {
565 20
        $this->inheritAll = (bool) $value;
566 20
    }
567
568
    /**
569
     * If true, pass all references to the new phing project.
570
     * Defaults to false.
571
     *
572
     * @param $value
573
     */
574 12
    public function setInheritRefs($value)
575
    {
576 12
        $this->inheritRefs = (bool) $value;
577 12
    }
578
579
    /**
580
     * The directory to use as a base directory for the new phing project.
581
     * Defaults to the current project's basedir, unless inheritall
582
     * has been set to false, in which case it doesn't have a default
583
     * value. This will override the basedir setting of the called project.
584
     *
585
     * @param PhingFile $d
586
     */
587 4
    public function setDir(PhingFile $d): void
588
    {
589 4
        $this->dir = $d;
590 4
    }
591
592
    /**
593
     * The build file to use.
594
     * Defaults to "build.xml". This file is expected to be a filename relative
595
     * to the dir attribute given.
596
     *
597
     * @param $s
598
     */
599 24
    public function setPhingFile($s)
600
    {
601
        // it is a string and not a file to handle relative/absolute
602
        // otherwise a relative file will be resolved based on the current
603
        // basedir.
604 24
        $this->phingFile = $s;
605 24
    }
606
607
    /**
608
     * Alias function for setPhingfile
609
     *
610
     * @param $s
611
     */
612
    public function setBuildfile($s)
613
    {
614
        $this->setPhingFile($s);
615
    }
616
617
    /**
618
     * The target of the new Phing project to execute.
619
     * Defaults to the new project's default target.
620
     *
621
     * @param string $s
622
     */
623 25
    public function setTarget(string $s)
624
    {
625 25
        if ('' === $s) {
626 1
            throw new BuildException("target attribute must not be empty");
627
        }
628
629 24
        $this->newTarget = $s;
630 24
    }
631
632
    /**
633
     * Set the filename to write the output to. This is relative to the value
634
     * of the dir attribute if it has been set or to the base directory of the
635
     * current project otherwise.
636
     * @param string $outputFile the name of the file to which the output should go.
637
     */
638 1
    public function setOutput(string $outputFile): void
639
    {
640 1
        $this->output = $outputFile;
641 1
    }
642
643
    /**
644
     * Property to pass to the new project.
645
     * The property is passed as a 'user property'
646
     */
647 11
    public function createProperty()
648
    {
649 11
        $p = new PropertyTask();
650 11
        $p->setFallback($this->getNewProject());
651 11
        $p->setUserProperty(true);
652 11
        $this->properties[] = $p;
653
654 11
        return $p;
655
    }
656
657
    /**
658
     * Reference element identifying a data type to carry
659
     * over to the new project.
660
     *
661
     * @param PhingReference $ref
662
     */
663 1
    public function addReference(PhingReference $ref)
664
    {
665 1
        $this->references[] = $ref;
666 1
    }
667
}
668