Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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