Passed
Push — master ( 0123ab...cb5cd1 )
by Siad
11:09
created

PhingTask::setOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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
    /**
98
     *  If true, abort the build process if there is a problem with or in the target build file.
99
     *  Defaults to false.
100
     *
101
     * @param boolean $hof new value
102
     */
103 14
    public function setHaltOnFailure($hof)
104
    {
105 14
        $this->haltOnFailure = (bool) $hof;
106 14
    }
107
108
    /**
109
     * Creates a Project instance for the project to call.
110
     *
111
     * @return void
112
     */
113 27
    public function init()
114
    {
115 27
        $this->newProject = new Project();
116 27
        $tdf = $this->project->getTaskDefinitions();
117 27
        $this->newProject->addTaskDefinition("property", $tdf["property"]);
118 27
    }
119
120
    /**
121
     * Called in execute or createProperty if newProject is null.
122
     *
123
     * <p>This can happen if the same instance of this task is run
124
     * twice as newProject is set to null at the end of execute (to
125
     * save memory and help the GC).</p>
126
     *
127
     * <p>Sets all properties that have been defined as nested
128
     * property elements.</p>
129
     */
130 6
    private function reinit()
131
    {
132 6
        $this->init();
133 6
        $count = count($this->properties);
134 6
        for ($i = 0; $i < $count; $i++) {
135
            /**
136
             * @var PropertyTask $p
137
             */
138 6
            $p = $this->properties[$i];
139
            /**
140
             * @var PropertyTask $newP
141
             */
142 6
            $newP = $this->newProject->createTask("property");
143 6
            $newP->setName($p->getName());
144 6
            if ($p->getValue() !== null) {
145 6
                $newP->setValue($p->getValue());
146
            }
147 6
            if ($p->getFile() !== null) {
148
                $newP->setFile($p->getFile());
149
            }
150 6
            if ($p->getPrefix() !== null) {
151
                $newP->setPrefix($p->getPrefix());
152
            }
153 6
            if ($p->getRefid() !== null) {
154
                $newP->setRefid($p->getRefid());
155
            }
156 6
            if ($p->getEnvironment() !== null) {
157
                $newP->setEnvironment($p->getEnvironment());
158
            }
159 6
            if ($p->getUserProperty() !== null) {
160 6
                $newP->setUserProperty($p->getUserProperty());
161
            }
162 6
            $newP->setOverride($p->getOverride());
163 6
            $newP->setLogoutput($p->getLogoutput());
164 6
            $newP->setQuiet($p->getQuiet());
165
166 6
            $this->properties[$i] = $newP;
167
        }
168 6
    }
169
170
    /**
171
     * Main entry point for the task.
172
     *
173
     * @return void
174
     */
175 24
    public function main()
176
    {
177
178
        // Call Phing on the file set with the attribute "phingfile"
179 24
        if ($this->phingFile !== null or $this->dir !== null) {
180 24
            $this->processFile();
181
        }
182
183
        // if no filesets are given stop here; else process filesets
184 21
        if (!empty($this->filesets)) {
185
            // preserve old settings
186
            $savedDir = $this->dir;
187
            $savedPhingFile = $this->phingFile;
188
            $savedTarget = $this->newTarget;
189
190
            // set no specific target for files in filesets
191
            // [HL] I'm commenting this out; I don't know why this should not be supported!
192
            // $this->newTarget = null;
193
194
            foreach ($this->filesets as $fs) {
195
                $ds = $fs->getDirectoryScanner($this->project);
196
197
                $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...
198
                $srcFiles = $ds->getIncludedFiles();
199
200
                foreach ($srcFiles as $fname) {
201
                    $f = new PhingFile($ds->getbasedir(), $fname);
202
                    $f = $f->getAbsoluteFile();
203
                    $this->phingFile = $f->getAbsolutePath();
204
                    $this->dir = $f->getParentFile();
205
                    $this->processFile(); // run Phing!
206
                }
207
            }
208
209
            // side effect free programming ;-)
210
            $this->dir = $savedDir;
211
            $this->phingFile = $savedPhingFile;
212
            $this->newTarget = $savedTarget;
213
214
            // [HL] change back to correct dir
215
            if ($this->dir !== null) {
216
                chdir($this->dir->getAbsolutePath());
217
            }
218
        }
219
220
        // Remove any dangling references to help the GC
221 21
        foreach ($this->properties as $property) {
222 11
            $property->setFallback(null);
223
        }
224 21
    }
225
226
    /**
227
     * Execute phing file.
228
     *
229
     * @throws BuildException
230
     * @return void
231
     */
232 24
    private function processFile()
233
    {
234 24
        $buildFailed = false;
235 24
        $savedDir = $this->dir;
236 24
        $savedPhingFile = $this->phingFile;
237 24
        $savedTarget = $this->newTarget;
238
239 24
        $savedBasedirAbsPath = null; // this is used to save the basedir *if* we change it
240
241
        try {
242 24
            $this->getNewProject();
243
244 24
            $this->initializeProject();
245
246 24
            if ($this->dir !== null) {
247 4
                $dirAbsPath = $this->dir->getAbsolutePath();
248
249
                // BE CAREFUL! -- when the basedir is changed for a project,
250
                // all calls to getAbsolutePath() on a relative-path dir will
251
                // be made relative to the project's basedir!  This means
252
                // that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!
253
254
                // We need to save the current project's basedir first.
255 4
                $savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();
256
257 4
                $this->newProject->setBasedir($this->dir);
258
259
                // Now we must reset $this->dir so that it continues to resolve to the same
260
                // path.
261 4
                $this->dir = new PhingFile($dirAbsPath);
262
263 4
                if ($savedDir !== null) { // has been set explicitly
264 4
                    $this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());
265
                }
266
            } else {
267
                // Since we're not changing the basedir here (for file resolution),
268
                // we don't need to worry about any side-effects in this scanrio.
269 21
                $this->dir = $this->getProject()->getBasedir();
270
            }
271
272 24
            $this->overrideProperties();
273 24
            if ($this->phingFile === null) {
274
                $this->phingFile = "build.xml";
275
            }
276
277 24
            $fu = new FileUtils();
278 24
            $file = $fu->resolveFile($this->dir, $this->phingFile);
279 24
            $this->phingFile = $file->getAbsolutePath();
280
281 24
            $this->log(
282 24
                "Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'",
283 24
                Project::MSG_VERBOSE
284
            );
285
286 24
            $this->newProject->setUserProperty("phing.file", $this->phingFile);
287
288 24
            ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));
289
290 23
            if ($this->newTarget === null) {
291
                $this->newTarget = $this->newProject->getDefaultTarget();
292
            }
293
294
            // Are we trying to call the target in which we are defined?
295
            if (
296 23
                $this->newProject->getBaseDir() == $this->project->getBaseDir()
297 19
                && $this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file")
298 16
                && $this->getOwningTarget() !== null
299 16
                && $this->newTarget == $this->getOwningTarget()->getName()
300
            ) {
301 2
                throw new BuildException("phing task calling its own parent target");
302
            }
303
304 22
            $this->addReferences();
305 22
            $this->newProject->executeTarget($this->newTarget);
306 4
        } catch (Exception $e) {
307 4
            $buildFailed = true;
308 4
            $this->log($e->getMessage(), Project::MSG_ERR);
309 4
            if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {
310 4
                $lines = explode("\n", $e->getTraceAsString());
311 4
                foreach ($lines as $line) {
312 4
                    $this->log($line, Project::MSG_DEBUG);
313
                }
314
            }
315
            // important!!! continue on to perform cleanup tasks.
316
        }
317
318
        // reset environment values to prevent side-effects.
319
320 24
        $this->newProject = null;
321 24
        $pkeys = array_keys($this->properties);
322 24
        foreach ($pkeys as $k) {
323 11
            $this->properties[$k]->setProject(null);
324
        }
325
326 24
        if ($this->output !== null && $this->out !== null) {
327 1
            $this->out->close();
328
        }
329
330 24
        $this->dir = $savedDir;
331 24
        $this->phingFile = $savedPhingFile;
332 24
        $this->newTarget = $savedTarget;
333
334
        // If the basedir for any project was changed, we need to set that back here.
335 24
        if ($savedBasedirAbsPath !== null) {
336 4
            chdir($savedBasedirAbsPath);
337
        }
338
339 24
        if ($this->haltOnFailure && $buildFailed) {
340 3
            throw new BuildException("Execution of the target buildfile failed. Aborting.");
341
        }
342 21
    }
343
344
    /**
345
     * Get the (sub)-Project instance currently in use.
346
     *
347
     * @return Project
348
     */
349 24
    protected function getNewProject(): \Project
350
    {
351 24
        if ($this->newProject === null) {
352 6
            $this->reinit();
353
        }
354 24
        return $this->newProject;
355
    }
356
357
    /**
358
     * Configure the Project, i.e. make intance, attach build listeners
359
     * (copy from father project), add Task and Datatype definitions,
360
     * copy properties and references from old project if these options
361
     * are set via the attributes of the XML tag.
362
     *
363
     * Developer note:
364
     * This function replaces the old methods "init", "_reinit" and
365
     * "_initializeProject".
366
     */
367 24
    private function initializeProject()
368
    {
369 24
        $this->newProject->setInputHandler($this->project->getInputHandler());
370
371 24
        foreach ($this->project->getBuildListeners() as $listener) {
372 24
            $this->newProject->addBuildListener($listener);
373
        }
374
375
        /* Copy things from old project. Datatypes and Tasks are always
376
         * copied, properties and references only if specified so/not
377
         * specified otherwise in the XML definition.
378
         */
379
        // Add Datatype definitions
380 24
        foreach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {
381 24
            $this->newProject->addDataTypeDefinition($typeName, $typeClass);
382
        }
383
384
        // Add Task definitions
385 24
        foreach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {
386 24
            if ($taskClass == "propertytask") {
387
                // we have already added this taskdef in init()
388
                continue;
389
            }
390 24
            $this->newProject->addTaskDefinition($taskName, $taskClass);
391
        }
392
393 24
        if ($this->output !== null) {
394
            try {
395 1
                if ($this->dir !== null) {
396 1
                    $outfile = (new FileUtils())->resolveFile($this->dir, $this->output);
397
                } else {
398 1
                    $outfile = $this->getProject()->resolveFile($this->output);
399
                }
400 1
                $this->out = new FileOutputStream($outfile);
401 1
                $logger = new DefaultLogger();
402 1
                $logger->setMessageOutputLevel(Project::MSG_INFO);
403 1
                $logger->setOutputStream($this->out);
404 1
                $logger->setErrorStream($this->out);
405 1
                $this->newProject->addBuildListener($logger);
406
            } catch (Exception $ex) {
407
                $this->log("Phing: Can't set output to " . $this->output);
408
            }
409
        }
410
411
        // set user-defined properties
412 24
        $this->project->copyUserProperties($this->newProject);
413
414 24
        if (!$this->inheritAll) {
415
            // set System built-in properties separately,
416
            // b/c we won't inherit them.
417 13
            $this->newProject->setSystemProperties();
418
        } else {
419
            // set all properties from calling project
420 12
            $properties = $this->project->getProperties();
421 12
            foreach ($properties as $name => $value) {
422 12
                if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {
423
                    // basedir and phing.file get special treatment in main()
424 12
                    continue;
425
                }
426
                // don't re-set user properties, avoid the warning message
427 12
                if ($this->newProject->getProperty($name) === null) {
428
                    // no user property
429 12
                    $this->newProject->setNewProperty($name, $value);
430
                }
431
            }
432
        }
433 24
    }
434
435
    /**
436
     * Override the properties in the new project with the one
437
     * explicitly defined as nested elements here.
438
     *
439
     * @return void
440
     * @throws BuildException
441
     */
442 24
    private function overrideProperties()
443
    {
444 24
        foreach (array_keys($this->properties) as $i) {
445 11
            $p = $this->properties[$i];
446 11
            $p->setProject($this->newProject);
447 11
            $p->main();
448
        }
449 24
        $this->project->copyInheritedProperties($this->newProject);
450 24
    }
451
452
    /**
453
     * Add the references explicitly defined as nested elements to the
454
     * new project.  Also copy over all references that don't override
455
     * existing references in the new project if inheritrefs has been
456
     * requested.
457
     *
458
     * @return void
459
     * @throws BuildException
460
     */
461 22
    private function addReferences()
462
    {
463
464
        // parent project references
465 22
        $projReferences = $this->project->getReferences();
466
467 22
        $newReferences = $this->newProject->getReferences();
468
469 22
        $subprojRefKeys = [];
470
471 22
        if (count($this->references) > 0) {
472
            for ($i = 0, $count = count($this->references); $i < $count; $i++) {
473
                $ref = $this->references[$i];
474
                $refid = $ref->getRefId();
475
476
                if ($refid === null) {
477
                    throw new BuildException(
478
                        "the refid attribute is required"
479
                        . " for reference elements"
480
                    );
481
                }
482
                if (!isset($projReferences[$refid])) {
483
                    $this->log(
484
                        "Parent project doesn't contain any reference '"
485
                        . $refid . "'",
486
                        Project::MSG_WARN
487
                    );
488
                    continue;
489
                }
490
491
                $subprojRefKeys[] = $refid;
492
                //thisReferences.remove(refid);
493
                $toRefid = $ref->getToRefid();
494
                if ($toRefid === null) {
495
                    $toRefid = $refid;
496
                }
497
                $this->copyReference($refid, $toRefid);
498
            }
499
        }
500
501
        // Now add all references that are not defined in the
502
        // subproject, if inheritRefs is true
503 22
        if ($this->inheritRefs) {
504
            // get the keys that are were not used by the subproject
505 2
            $unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);
506
507 2
            foreach ($unusedRefKeys as $key) {
508 2
                if (isset($newReferences[$key])) {
509 2
                    continue;
510
                }
511 2
                $this->copyReference($key, $key);
512
            }
513
        }
514 22
    }
515
516
    /**
517
     * Try to clone and reconfigure the object referenced by oldkey in
518
     * the parent project and add it to the new project with the key
519
     * newkey.
520
     *
521
     * <p>If we cannot clone it, copy the referenced object itself and
522
     * keep our fingers crossed.</p>
523
     *
524
     * @param  string $oldKey
525
     * @param  string $newKey
526
     * @throws BuildException
527
     * @return void
528
     */
529 2
    private function copyReference($oldKey, $newKey)
530
    {
531 2
        $orig = $this->project->getReference($oldKey);
532 2
        if ($orig === null) {
533
            $this->log(
534
                "No object referenced by " . $oldKey . ". Can't copy to "
535
                . $newKey,
536
                Project::MSG_WARN
537
            );
538
539
            return;
540
        }
541
542 2
        $copy = clone $orig;
543
544 2
        if ($copy instanceof ProjectComponent) {
545 2
            $copy->setProject($this->newProject);
546 2
        } elseif (in_array('setProject', get_class_methods(get_class($copy)))) {
547
            $copy->setProject($this->newProject);
548 2
        } elseif (!($copy instanceof Project)) {
549
            // don't copy the old "Project" itself
550
            $msg = "Error setting new project instance for "
551
                . "reference with id " . $oldKey;
552
            throw new BuildException($msg);
553
        }
554
555 2
        $this->newProject->addReference($newKey, $copy);
556 2
    }
557
558
    /**
559
     * If true, pass all properties to the new phing project.
560
     * Defaults to true.
561
     *
562
     * @param $value
563
     */
564 20
    public function setInheritAll($value)
565
    {
566 20
        $this->inheritAll = (bool) $value;
567 20
    }
568
569
    /**
570
     * If true, pass all references to the new phing project.
571
     * Defaults to false.
572
     *
573
     * @param $value
574
     */
575 12
    public function setInheritRefs($value)
576
    {
577 12
        $this->inheritRefs = (bool) $value;
578 12
    }
579
580
    /**
581
     * The directory to use as a base directory for the new phing project.
582
     * Defaults to the current project's basedir, unless inheritall
583
     * has been set to false, in which case it doesn't have a default
584
     * value. This will override the basedir setting of the called project.
585
     *
586
     * @param $d
587
     */
588 4
    public function setDir($d)
589
    {
590 4
        if (is_string($d)) {
591 4
            $this->dir = new PhingFile($d);
592
        } else {
593
            $this->dir = $d;
594
        }
595 4
    }
596
597
    /**
598
     * The build file to use.
599
     * Defaults to "build.xml". This file is expected to be a filename relative
600
     * to the dir attribute given.
601
     *
602
     * @param $s
603
     */
604 24
    public function setPhingFile($s)
605
    {
606
        // it is a string and not a file to handle relative/absolute
607
        // otherwise a relative file will be resolved based on the current
608
        // basedir.
609 24
        $this->phingFile = $s;
610 24
    }
611
612
    /**
613
     * Alias function for setPhingfile
614
     *
615
     * @param $s
616
     */
617
    public function setBuildfile($s)
618
    {
619
        $this->setPhingFile($s);
620
    }
621
622
    /**
623
     * The target of the new Phing project to execute.
624
     * Defaults to the new project's default target.
625
     *
626
     * @param string $s
627
     */
628 25
    public function setTarget(string $s)
629
    {
630 25
        if ('' === $s) {
631 1
            throw new BuildException("target attribute must not be empty");
632
        }
633
634 24
        $this->newTarget = $s;
635 24
    }
636
637
    /**
638
     * Set the filename to write the output to. This is relative to the value
639
     * of the dir attribute if it has been set or to the base directory of the
640
     * current project otherwise.
641
     * @param string $outputFile the name of the file to which the output should go.
642
     */
643 1
    public function setOutput(string $outputFile): void
644
    {
645 1
        $this->output = $outputFile;
646 1
    }
647
648
    /**
649
     * Property to pass to the new project.
650
     * The property is passed as a 'user property'
651
     */
652 11
    public function createProperty()
653
    {
654 11
        $p = new PropertyTask();
655 11
        $p->setFallback($this->getNewProject());
656 11
        $p->setUserProperty(true);
657 11
        $this->properties[] = $p;
658
659 11
        return $p;
660
    }
661
662
    /**
663
     * Reference element identifying a data type to carry
664
     * over to the new project.
665
     *
666
     * @param PhingReference $ref
667
     */
668 1
    public function addReference(PhingReference $ref)
669
    {
670 1
        $this->references[] = $ref;
671 1
    }
672
}
673