Completed
Push — master ( 9cf089...a49cfd )
by Siad
12:42
created

PhingTask::processFile()   F

Complexity

Conditions 16
Paths 1712

Size

Total Lines 105
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 16.0128

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 56
c 1
b 0
f 0
nc 1712
nop 0
dl 0
loc 105
ccs 52
cts 54
cp 0.963
crap 16.0128
rs 1.4

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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