Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/** $Id: PhingTask.php 303 2007-11-08 20:39:33Z hans $** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.** This software consists of voluntary contributions made by many individuals* and is licensed under the LGPL. For more information please see* <http://phing.info>.*/include_once 'phing/Task.php';include_once 'phing/util/FileUtils.php';include_once 'phing/types/Reference.php';include_once 'phing/tasks/system/PropertyTask.php';/*** Task that invokes phing on another build file.** Use this task, for example, if you have nested buildfiles in your project. Unlike* AntTask, PhingTask can even support filesets:** <pre>* <phing>* <fileset dir="${srcdir}">* <include name="** /build.xml" /> <!-- space added after ** is there because of PHP comment syntax -->* <exclude name="build.xml" />* </fileset>* </phing>* </pre>** @author Hans Lellelid <hans@xmpl.org>* @version $Revision: 1.20 $* @package phing.tasks.system*/class PhingTask extends Task {/** the basedir where is executed the build file */private $dir;/** build.xml (can be absolute) in this case dir will be ignored */private $phingFile;/** the target to call if any */protected $newTarget;/** should we inherit properties from the parent ? */private $inheritAll = true;/** should we inherit references from the parent ? */private $inheritRefs = false;/** the properties to pass to the new project */private $properties = array();/** the references to pass to the new project */private $references = array();/** The filesets that contain the files PhingTask is to be run on. */private $filesets = array();/** the temporary project created to run the build file */private $newProject;/** Fail the build process when the called build fails? */private $haltOnFailure = false;/*** If true, abort the build process if there is a problem with or in the target build file.* Defaults to false.** @param boolean new value*/public function setHaltOnFailure($hof) {$this->haltOnFailure = (boolean) $hof;}/*** Creates a Project instance for the project to call.* @return void*/public function init() {$this->newProject = new Project();$tdf = $this->project->getTaskDefinitions();$this->newProject->addTaskDefinition("property", $tdf["property"]);}/*** Called in execute or createProperty if newProject is null.** <p>This can happen if the same instance of this task is run* twice as newProject is set to null at the end of execute (to* save memory and help the GC).</p>** <p>Sets all properties that have been defined as nested* property elements.</p>*/private function reinit() {$this->init();$count = count($this->properties);for ($i = 0; $i < $count; $i++) {$p = $this->properties[$i];$newP = $this->newProject->createTask("property");$newP->setName($p->getName());if ($p->getValue() !== null) {$newP->setValue($p->getValue());}if ($p->getFile() !== null) {$newP->setFile($p->getFile());}if ($p->getPrefix() !== null) {$newP->setPrefix($p->getPrefix());}if ($p->getRefid() !== null) {$newP->setRefid($p->getRefid());}if ($p->getEnvironment() !== null) {$newP->setEnvironment($p->getEnvironment());}if ($p->getUserProperty() !== null) {$newP->setUserProperty($p->getUserProperty());}if ($p->getOverride() !== null) {$newP->setOverride($p->getOverride());}$this->properties[$i] = $newP;}}/*** Main entry point for the task.** @return void*/public function main() {// Call Phing on the file set with the attribute "phingfile"if ($this->phingFile !== null or $this->dir !== null) {$this->processFile();}// if no filesets are given stop here; else process filesetsif (empty($this->filesets)) {return;}// preserve old settings$savedDir = $this->dir;$savedPhingFile = $this->phingFile;$savedTarget = $this->newTarget;// set no specific target for files in filesets// [HL] I'm commenting this out; I don't know why this should not be supported!// $this->newTarget = null;foreach($this->filesets as $fs) {$ds = $fs->getDirectoryScanner($this->project);$fromDir = $fs->getDir($this->project);$srcFiles = $ds->getIncludedFiles();foreach($srcFiles as $fname) {$f = new PhingFile($ds->getbasedir(), $fname);$f = $f->getAbsoluteFile();$this->phingFile = $f->getAbsolutePath();$this->dir = $f->getParentFile();$this->processFile(); // run Phing!}}// side effect free programming ;-)$this->dir = $savedDir;$this->phingFile = $savedPhingFile;$this->newTarget = $savedTarget;// [HL] change back to correct dirif ($this->dir !== null) {chdir($this->dir->getAbsolutePath());}}/*** Execute phing file.** @return void*/private function processFile() {$buildFailed = false;$savedDir = $this->dir;$savedPhingFile = $this->phingFile;$savedTarget = $this->newTarget;$savedBasedirAbsPath = null; // this is used to save the basedir *if* we change ittry {if ($this->newProject === null) {$this->reinit();}$this->initializeProject();if ($this->dir !== null) {$dirAbsPath = $this->dir->getAbsolutePath();// BE CAREFUL! -- when the basedir is changed for a project,// all calls to getAbsolutePath() on a relative-path dir will// be made relative to the project's basedir! This means// that subsequent calls to $this->dir->getAbsolutePath() will be WRONG!// We need to save the current project's basedir first.$savedBasedirAbsPath = $this->getProject()->getBasedir()->getAbsolutePath();$this->newProject->setBasedir($this->dir);// Now we must reset $this->dir so that it continues to resolve to the same// path.$this->dir = new PhingFile($dirAbsPath);if ($savedDir !== null) { // has been set explicitly$this->newProject->setInheritedProperty("project.basedir", $this->dir->getAbsolutePath());}} else {// Since we're not changing the basedir here (for file resolution),// we don't need to worry about any side-effects in this scanrio.$this->dir = $this->getProject()->getBasedir();}$this->overrideProperties();if ($this->phingFile === null) {$this->phingFile = "build.xml";}$fu = new FileUtils();$file = $fu->resolveFile($this->dir, $this->phingFile);$this->phingFile = $file->getAbsolutePath();$this->log("Calling Buildfile '" . $this->phingFile . "' with target '" . $this->newTarget . "'");$this->newProject->setUserProperty("phing.file", $this->phingFile);ProjectConfigurator::configureProject($this->newProject, new PhingFile($this->phingFile));if ($this->newTarget === null) {$this->newTarget = $this->newProject->getDefaultTarget();}// Are we trying to call the target in which we are defined?if ($this->newProject->getBaseDir() == $this->project->getBaseDir() &&$this->newProject->getProperty("phing.file") == $this->project->getProperty("phing.file") &&$this->getOwningTarget() !== null &&$this->newTarget == $this->getOwningTarget()->getName()) {throw new BuildException("phing task calling its own parent target");}$this->addReferences();$this->newProject->executeTarget($this->newTarget);} catch (Exception $e) {$buildFailed = true;$this->log($e->getMessage(), Project::MSG_ERR);if (Phing::getMsgOutputLevel() <= Project::MSG_DEBUG) {$lines = explode("\n", $e->getTraceAsString());foreach($lines as $line) {$this->log($line, Project::MSG_DEBUG);}}// important!!! continue on to perform cleanup tasks.}// reset environment values to prevent side-effects.$this->newProject = null;$pkeys = array_keys($this->properties);foreach($pkeys as $k) {$this->properties[$k]->setProject(null);}$this->dir = $savedDir;$this->phingFile = $savedPhingFile;$this->newTarget = $savedTarget;// If the basedir for any project was changed, we need to set that back here.if ($savedBasedirAbsPath !== null) {chdir($savedBasedirAbsPath);}if ($this->haltOnFailure && $buildFailed) {throw new BuildException("Execution of the target buildfile failed. Aborting.");}}/*** Configure the Project, i.e. make intance, attach build listeners* (copy from father project), add Task and Datatype definitions,* copy properties and references from old project if these options* are set via the attributes of the XML tag.** Developer note:* This function replaces the old methods "init", "_reinit" and* "_initializeProject".** @access protected*/private function initializeProject() {$this->newProject->setInputHandler($this->project->getInputHandler());foreach($this->project->getBuildListeners() as $listener) {$this->newProject->addBuildListener($listener);}/* Copy things from old project. Datatypes and Tasks are always* copied, properties and references only if specified so/not* specified otherwise in the XML definition.*/// Add Datatype definitionsforeach ($this->project->getDataTypeDefinitions() as $typeName => $typeClass) {$this->newProject->addDataTypeDefinition($typeName, $typeClass);}// Add Task definitionsforeach ($this->project->getTaskDefinitions() as $taskName => $taskClass) {if ($taskClass == "propertytask") {// we have already added this taskdef in init()continue;}$this->newProject->addTaskDefinition($taskName, $taskClass);}// set user-defined properties$this->project->copyUserProperties($this->newProject);if (!$this->inheritAll) {// set System built-in properties separately,// b/c we won't inherit them.$this->newProject->setSystemProperties();} else {// set all properties from calling project$properties = $this->project->getProperties();foreach ($properties as $name => $value) {if ($name == "basedir" || $name == "phing.file" || $name == "phing.version") {// basedir and phing.file get special treatment in main()continue;}// don't re-set user properties, avoid the warning messageif ($this->newProject->getProperty($name) === null){// no user property$this->newProject->setNewProperty($name, $value);}}}}/*** Override the properties in the new project with the one* explicitly defined as nested elements here.* @return void* @throws BuildException*/private function overrideProperties() {foreach(array_keys($this->properties) as $i) {$p = $this->properties[$i];$p->setProject($this->newProject);$p->main();}$this->project->copyInheritedProperties($this->newProject);}/*** Add the references explicitly defined as nested elements to the* new project. Also copy over all references that don't override* existing references in the new project if inheritrefs has been* requested.** @return void* @throws BuildException*/private function addReferences() {// parent project references$projReferences = $this->project->getReferences();$newReferences = $this->newProject->getReferences();$subprojRefKeys = array();if (count($this->references) > 0) {for ($i=0, $count=count($this->references); $i < $count; $i++) {$ref = $this->references[$i];$refid = $ref->getRefId();if ($refid === null) {throw new BuildException("the refid attribute is required". " for reference elements");}if (!isset($projReferences[$refid])) {$this->log("Parent project doesn't contain any reference '". $refid . "'",Project::MSG_WARN);continue;}$subprojRefKeys[] = $refid;//thisReferences.remove(refid);$toRefid = $ref->getToRefid();if ($toRefid === null) {$toRefid = $refid;}$this->copyReference($refid, $toRefid);}}// Now add all references that are not defined in the// subproject, if inheritRefs is trueif ($this->inheritRefs) {// get the keys that are were not used by the subproject$unusedRefKeys = array_diff(array_keys($projReferences), $subprojRefKeys);foreach($unusedRefKeys as $key) {if (isset($newReferences[$key])) {continue;}$this->copyReference($key, $key);}}}/*** Try to clone and reconfigure the object referenced by oldkey in* the parent project and add it to the new project with the key* newkey.** <p>If we cannot clone it, copy the referenced object itself and* keep our fingers crossed.</p>** @param string $oldKey* @param string $newKey* @return void*/private function copyReference($oldKey, $newKey) {$orig = $this->project->getReference($oldKey);if ($orig === null) {$this->log("No object referenced by " . $oldKey . ". Can't copy to ".$newKey,PROJECT_SG_WARN);return;}$copy = clone $orig;if ($copy instanceof ProjectComponent) {$copy->setProject($this->newProject);} elseif (in_array('setProject', get_class_methods(get_class($copy)))) {$copy->setProject($this->newProject);} elseif ($copy instanceof Project) {// don't copy the old "Project" itself} else {$msg = "Error setting new project instance for ". "reference with id " . $oldKey;throw new BuildException($msg);}$this->newProject->addReference($newKey, $copy);}/*** If true, pass all properties to the new phing project.* Defaults to true.** @access public*/function setInheritAll($value) {$this->inheritAll = (boolean) $value;}/*** If true, pass all references to the new phing project.* Defaults to false.** @access public*/function setInheritRefs($value) {$this->inheritRefs = (boolean)$value;}/*** The directory to use as a base directory for the new phing project.* Defaults to the current project's basedir, unless inheritall* has been set to false, in which case it doesn't have a default* value. This will override the basedir setting of the called project.** @access public*/function setDir($d) {if ( is_string($d) )$this->dir = new PhingFile($d);else$this->dir = $d;}/*** The build file to use.* Defaults to "build.xml". This file is expected to be a filename relative* to the dir attribute given.** @access public*/function setPhingfile($s) {// it is a string and not a file to handle relative/absolute// otherwise a relative file will be resolved based on the current// basedir.$this->phingFile = $s;}/*** Alias function for setPhingfile** @access public*/function setBuildfile($s) {$this->setPhingFile($s);}/*** The target of the new Phing project to execute.* Defaults to the new project's default target.** @access public*/function setTarget($s) {$this->newTarget = $s;}/*** Support for filesets; This method returns a reference to an instance* of a FileSet object.** @return FileSet*/function createFileSet() {$num = array_push($this->filesets, new FileSet());return $this->filesets[$num-1];}/*** Property to pass to the new project.* The property is passed as a 'user property'** @access public*/function createProperty() {$p = new PropertyTask();$p->setFallback($this->newProject);$p->setUserProperty(true);$this->properties[] = $p;return $p;}/*** Reference element identifying a data type to carry* over to the new project.** @access public*/function createReference() {$num = array_push($this->references, new PhingReference());return $this->references[$num-1];}}/*** Helper class that implements the nested <reference>* element of <phing> and <phingcall>.*/class PhingReference extends Reference {private $targetid = null;/*** Set the id that this reference to be stored under in the* new project.** @param targetid the id under which this reference will be passed to* the new project */public function setToRefid($targetid) {$this->targetid = $targetid;}/*** Get the id under which this reference will be stored in the new* project** @return the id of the reference in the new project.*/public function getToRefid() {return $this->targetid;}}