Viewing file: Project.php (33.58 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php /* * $Id: Project.php 1097 2011-05-17 17:10:32Z mrook $ * * 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/system/io/PhingFile.php'; include_once 'phing/util/FileUtils.php'; include_once 'phing/TaskAdapter.php'; include_once 'phing/util/StringHelper.php'; include_once 'phing/BuildEvent.php'; include_once 'phing/input/DefaultInputHandler.php';
/** * The Phing project class. Represents a completely configured Phing project. * The class defines the project and all tasks/targets. It also contains * methods to start a build as well as some properties and FileSystem * abstraction. * * @author Andreas Aderhold <andi@binarycloud.com> * @author Hans Lellelid <hans@xmpl.org> * @version $Revision: 1097 $ * @package phing */ class Project {
// Logging level constants. const MSG_DEBUG = 4; const MSG_VERBOSE = 3; const MSG_INFO = 2; const MSG_WARN = 1; const MSG_ERR = 0; /** contains the targets */ private $targets = array(); /** global filterset (future use) */ private $globalFilterSet = array(); /** all globals filters (future use) */ private $globalFilters = array(); /** Project properties map (usually String to String). */ private $properties = array(); /** * Map of "user" properties (as created in the Ant task, for example). * Note that these key/value pairs are also always put into the * project properties, so only the project properties need to be queried. * Mapping is String to String. */ private $userProperties = array(); /** * Map of inherited "user" properties - that are those "user" * properties that have been created by tasks and not been set * from the command line or a GUI tool. * Mapping is String to String. */ private $inheritedProperties = array(); /** task definitions for this project*/ private $taskdefs = array(); /** type definitions for this project */ private $typedefs = array(); /** holds ref names and a reference to the referred object*/ private $references = array(); /** The InputHandler being used by this project. */ private $inputHandler; /* -- properties that come in via xml attributes -- */ /** basedir (PhingFile object) */ private $basedir; /** the default target name */ private $defaultTarget = 'all'; /** project name (required) */ private $name; /** project description */ private $description;
/** require phing version */ private $phingVersion;
/** a FileUtils object */ private $fileUtils; /** Build listeneers */ private $listeners = array();
/** * Constructor, sets any default vars. */ function __construct() { $this->fileUtils = new FileUtils(); $this->inputHandler = new DefaultInputHandler(); }
/** * Sets the input handler */ public function setInputHandler(InputHandler $handler) { $this->inputHandler = $handler; }
/** * Retrieves the current input handler. */ public function getInputHandler() { return $this->inputHandler; }
/** inits the project, called from main app */ function init() { // set builtin properties $this->setSystemProperties(); // load default tasks $taskdefs = Phing::getResourcePath("phing/tasks/defaults.properties"); try { // try to load taskdefs $props = new Properties(); $in = new PhingFile((string)$taskdefs);
if ($in === null) { throw new BuildException("Can't load default task list"); } $props->load($in);
$enum = $props->propertyNames(); foreach($enum as $key) { $value = $props->getProperty($key); $this->addTaskDefinition($key, $value); } } catch (IOException $ioe) { throw new BuildException("Can't load default task list"); }
// load default tasks $typedefs = Phing::getResourcePath("phing/types/defaults.properties");
try { // try to load typedefs $props = new Properties(); $in = new PhingFile((string)$typedefs); if ($in === null) { throw new BuildException("Can't load default datatype list"); } $props->load($in);
$enum = $props->propertyNames(); foreach($enum as $key) { $value = $props->getProperty($key); $this->addDataTypeDefinition($key, $value); } } catch(IOException $ioe) { throw new BuildException("Can't load default datatype list"); } }
/** returns the global filterset (future use) */ function getGlobalFilterSet() { return $this->globalFilterSet; }
// --------------------------------------------------------- // Property methods // --------------------------------------------------------- /** * Sets a property. Any existing property of the same name * is overwritten, unless it is a user property. * @param string $name The name of property to set. * Must not be <code>null</code>. * @param string $value The new value of the property. * Must not be <code>null</code>. * @return void */ public function setProperty($name, $value) { // command line properties take precedence if (isset($this->userProperties[$name])) { $this->log("Override ignored for user property " . $name, Project::MSG_VERBOSE); return; }
if (isset($this->properties[$name])) { $this->log("Overriding previous definition of property " . $name, Project::MSG_VERBOSE); }
$this->log("Setting project property: " . $name . " -> " . $value, Project::MSG_DEBUG); $this->properties[$name] = $value; }
/** * Sets a property if no value currently exists. If the property * exists already, a message is logged and the method returns with * no other effect. * * @param string $name The name of property to set. * Must not be <code>null</code>. * @param string $value The new value of the property. * Must not be <code>null</code>. * @since 2.0 */ public function setNewProperty($name, $value) { if (isset($this->properties[$name])) { $this->log("Override ignored for property " . $name, Project::MSG_DEBUG); return; } $this->log("Setting project property: " . $name . " -> " . $value, Project::MSG_DEBUG); $this->properties[$name] = $value; }
/** * Sets a user property, which cannot be overwritten by * set/unset property calls. Any previous value is overwritten. * @param string $name The name of property to set. * Must not be <code>null</code>. * @param string $value The new value of the property. * Must not be <code>null</code>. * @see #setProperty() */ public function setUserProperty($name, $value) { $this->log("Setting ro project property: " . $name . " -> " . $value, Project::MSG_DEBUG); $this->userProperties[$name] = $value; $this->properties[$name] = $value; }
/** * Sets a user property, which cannot be overwritten by set/unset * property calls. Any previous value is overwritten. Also marks * these properties as properties that have not come from the * command line. * * @param string $name The name of property to set. * Must not be <code>null</code>. * @param string $value The new value of the property. * Must not be <code>null</code>. * @see #setProperty() */ public function setInheritedProperty($name, $value) { $this->inheritedProperties[$name] = $value; $this->setUserProperty($name, $value); }
/** * Sets a property unless it is already defined as a user property * (in which case the method returns silently). * * @param name The name of the property. * Must not be <code>null</code>. * @param value The property value. Must not be <code>null</code>. */ private function setPropertyInternal($name, $value) { if (isset($this->userProperties[$name])) { $this->log("Override ignored for user property " . $name, Project::MSG_VERBOSE); return; } $this->properties[$name] = $value; }
/** * Returns the value of a property, if it is set. * * @param string $name The name of the property. * May be <code>null</code>, in which case * the return value is also <code>null</code>. * @return string The property value, or <code>null</code> for no match * or if a <code>null</code> name is provided. */ public function getProperty($name) { if (!isset($this->properties[$name])) { return null; } $found = $this->properties[$name]; // check to see if there are unresolved property references if (false !== strpos($found, '${')) { // attempt to resolve properties $found = $this->replaceProperties($found); // save resolved value $this->properties[$name] = $found; } return $found; }
/** * Replaces ${} style constructions in the given value with the * string value of the corresponding data types. * * @param value The string to be scanned for property references. * May be <code>null</code>. * * @return the given string with embedded property names replaced * by values, or <code>null</code> if the given string is * <code>null</code>. * * @exception BuildException if the given value has an unclosed * property name, e.g. <code>${xxx</code> */ public function replaceProperties($value) { return ProjectConfigurator::replaceProperties($this, $value, $this->properties); }
/** * Returns the value of a user property, if it is set. * * @param string $name The name of the property. * May be <code>null</code>, in which case * the return value is also <code>null</code>. * @return string The property value, or <code>null</code> for no match * or if a <code>null</code> name is provided. */ public function getUserProperty($name) { if (!isset($this->userProperties[$name])) { return null; } return $this->userProperties[$name]; }
/** * Returns a copy of the properties table. * @return array A hashtable containing all properties * (including user properties). */ public function getProperties() { return $this->properties; }
/** * Returns a copy of the user property hashtable * @return a hashtable containing just the user properties */ public function getUserProperties() { return $this->userProperties; }
/** * Copies all user properties that have been set on the command * line or a GUI tool from this instance to the Project instance * given as the argument. * * <p>To copy all "user" properties, you will also have to call * {@link #copyInheritedProperties copyInheritedProperties}.</p> * * @param Project $other the project to copy the properties to. Must not be null. * @return void * @since phing 2.0 */ public function copyUserProperties(Project $other) { foreach($this->userProperties as $arg => $value) { if (isset($this->inheritedProperties[$arg])) { continue; } $other->setUserProperty($arg, $value); } }
/** * Copies all user properties that have not been set on the * command line or a GUI tool from this instance to the Project * instance given as the argument. * * <p>To copy all "user" properties, you will also have to call * {@link #copyUserProperties copyUserProperties}.</p> * * @param other the project to copy the properties to. Must not be null. * * @since phing 2.0 */ public function copyInheritedProperties(Project $other) { foreach($this->userProperties as $arg => $value) { if ($other->getUserProperty($arg) !== null) { continue; } $other->setInheritedProperty($arg, $value); } } // --------------------------------------------------------- // END Properties methods // ---------------------------------------------------------
function setDefaultTarget($targetName) { $this->defaultTarget = (string) trim($targetName); }
function getDefaultTarget() { return (string) $this->defaultTarget; }
/** * Sets the name of the current project * * @param string name of project * @return void * @access public * @author Andreas Aderhold, andi@binarycloud.com */
function setName($name) { $this->name = (string) trim($name); $this->setProperty("phing.project.name", $this->name); }
/** * Returns the name of this project * * @return string projectname * @access public * @author Andreas Aderhold, andi@binarycloud.com */ function getName() { return (string) $this->name; }
/** Set the projects description */ function setDescription($description) { $this->description = (string) trim($description); }
/** return the description, null otherwise */ function getDescription() { return $this->description; }
/** Set the minimum required phing version **/ function setPhingVersion($version) { $version = str_replace('phing', '', strtolower($version)); $this->phingVersion = (string)trim($version); }
/** Get the minimum required phing version **/ function getPhingVersion() { if($this->phingVersion === null) { $this->setPhingVersion(Phing::getPhingVersion()); } return $this->phingVersion; }
/** Set basedir object from xml*/ function setBasedir($dir) { if ($dir instanceof PhingFile) { $dir = $dir->getAbsolutePath(); }
$dir = $this->fileUtils->normalize($dir);
$dir = new PhingFile((string) $dir); if (!$dir->exists()) { throw new BuildException("Basedir ".$dir->getAbsolutePath()." does not exist"); } if (!$dir->isDirectory()) { throw new BuildException("Basedir ".$dir->getAbsolutePath()." is not a directory"); } $this->basedir = $dir; $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath()); $this->log("Project base dir set to: " . $this->basedir->getPath(), Project::MSG_VERBOSE); // [HL] added this so that ./ files resolve correctly. This may be a mistake ... or may be in wrong place. chdir($dir->getAbsolutePath()); }
/** * Returns the basedir of this project * * @return PhingFile Basedir PhingFile object * @access public * @throws BuildException * @author Andreas Aderhold, andi@binarycloud.com */ function getBasedir() { if ($this->basedir === null) { try { // try to set it $this->setBasedir("."); } catch (BuildException $exc) { throw new BuildException("Can not set default basedir. ".$exc->getMessage()); } } return $this->basedir; }
/** * Sets system properties and the environment variables for this project. * * @return void */ function setSystemProperties() { // first get system properties $systemP = array_merge( self::getProperties(), Phing::getProperties() ); foreach($systemP as $name => $value) { $this->setPropertyInternal($name, $value); } // and now the env vars foreach($_SERVER as $name => $value) { // skip arrays if (is_array($value)) { continue; } $this->setPropertyInternal('env.' . $name, $value); } return true; }
/** * Adds a task definition. * @param string $name Name of tag. * @param string $class The class path to use. * @param string $classpath The classpat to use. */ function addTaskDefinition($name, $class, $classpath = null) { $name = $name; $class = $class; if ($class === "") { $this->log("Task $name has no class defined.", Project::MSG_ERR); } elseif (!isset($this->taskdefs[$name])) { Phing::import($class, $classpath); $this->taskdefs[$name] = $class; $this->log(" +Task definiton: $name ($class)", Project::MSG_DEBUG); } else { $this->log("Task $name ($class) already registerd, skipping", Project::MSG_VERBOSE); } }
function getTaskDefinitions() { return $this->taskdefs; }
/** * Adds a data type definition. * @param string $name Name of tag. * @param string $class The class path to use. * @param string $classpath The classpat to use. */ function addDataTypeDefinition($typeName, $typeClass, $classpath = null) { if (!isset($this->typedefs[$typeName])) { Phing::import($typeClass, $classpath); $this->typedefs[$typeName] = $typeClass; $this->log(" +User datatype: $typeName ($typeClass)", Project::MSG_DEBUG); } else { $this->log("Type $typeName ($typeClass) already registerd, skipping", Project::MSG_VERBOSE); } }
function getDataTypeDefinitions() { return $this->typedefs; }
/** add a new target to the project */ function addTarget($targetName, &$target) { if (isset($this->targets[$targetName])) { throw new BuildException("Duplicate target: $targetName"); } $this->addOrReplaceTarget($targetName, $target); }
function addOrReplaceTarget($targetName, &$target) { $this->log(" +Target: $targetName", Project::MSG_DEBUG); $target->setProject($this); $this->targets[$targetName] = $target;
$ctx = $this->getReference("phing.parsing.context"); $current = $ctx->getConfigurator()->getCurrentTargets(); $current[$targetName] = $target; }
function getTargets() { return $this->targets; }
/** * Create a new task instance and return reference to it. This method is * sorta factory like. A _local_ instance is created and a reference returned to * that instance. Usually PHP destroys local variables when the function call * ends. But not if you return a reference to that variable. * This is kinda error prone, because if no reference exists to the variable * it is destroyed just like leaving the local scope with primitive vars. There's no * central place where the instance is stored as in other OOP like languages. * * [HL] Well, ZE2 is here now, and this is still working. We'll leave this alone * unless there's any good reason not to. * * @param string $taskType Task name * @return Task A task object * @throws BuildException * Exception */ function createTask($taskType) { try { $classname = ""; $tasklwr = strtolower($taskType); foreach ($this->taskdefs as $name => $class) { if (strtolower($name) === $tasklwr) { $classname = $class; break; } } if ($classname === "") { return null; } $cls = Phing::import($classname); if (!class_exists($cls)) { throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)"); } $o = new $cls(); if ($o instanceof Task) { $task = $o; } else { $this->log (" (Using TaskAdapter for: $taskType)", Project::MSG_DEBUG); // not a real task, try adapter $taskA = new TaskAdapter(); $taskA->setProxy($o); $task = $taskA; } $task->setProject($this); $task->setTaskType($taskType); // set default value, can be changed by the user $task->setTaskName($taskType); $this->log (" +Task: " . $taskType, Project::MSG_DEBUG); } catch (Exception $t) { throw new BuildException("Could not create task of type: " . $taskType, $t); } // everything fine return reference return $task; }
/** * Create a datatype instance and return reference to it * See createTask() for explanation how this works * * @param string Type name * @return object A datatype object * @throws BuildException * Exception */ function createDataType($typeName) { try { $cls = ""; $typelwr = strtolower($typeName); foreach ($this->typedefs as $name => $class) { if (strtolower($name) === $typelwr) { $cls = StringHelper::unqualify($class); break; } } if ($cls === "") { return null; } if (!class_exists($cls)) { throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)"); } $type = new $cls(); $this->log(" +Type: $typeName", Project::MSG_DEBUG); if (!($type instanceof DataType)) { throw new Exception("$class is not an instance of phing.types.DataType"); } if ($type instanceof ProjectComponent) { $type->setProject($this); } } catch (Exception $t) { throw new BuildException("Could not create type: $typeName", $t); } // everything fine return reference return $type; }
/** * Executes a list of targets * * @param array List of target names to execute * @return void * @throws BuildException */ function executeTargets($targetNames) { foreach($targetNames as $tname) { $this->executeTarget($tname); } }
/** * Executes a target * * @param string Name of Target to execute * @return void * @throws BuildException */ function executeTarget($targetName) {
// complain about executing void if ($targetName === null) { throw new BuildException("No target specified"); }
// invoke topological sort of the target tree and run all targets // until targetName occurs. $sortedTargets = $this->_topoSort($targetName, $this->targets);
$curIndex = (int) 0; $curTarget = null; do { try { $curTarget = $sortedTargets[$curIndex++]; $curTarget->performTasks(); } catch (BuildException $exc) { $this->log("Execution of target \"".$curTarget->getName()."\" failed for the following reason: ".$exc->getMessage(), Project::MSG_ERR); throw $exc; } } while ($curTarget->getName() !== $targetName); }
function resolveFile($fileName, $rootDir = null) { if ($rootDir === null) { return $this->fileUtils->resolveFile($this->basedir, $fileName); } else { return $this->fileUtils->resolveFile($rootDir, $fileName); } }
/** * Topologically sort a set of Targets. * @param $root is the (String) name of the root Target. The sort is * created in such a way that the sequence of Targets until the root * target is the minimum possible such sequence. * @param $targets is a array representing a "name to Target" mapping * @return An array of Strings with the names of the targets in * sorted order. */ function _topoSort($root, &$targets) {
$root = (string) $root; $ret = array(); $state = array(); $visiting = array();
// We first run a DFS based sort using the root as the starting node. // This creates the minimum sequence of Targets to the root node. // We then do a sort on any remaining unVISITED targets. // This is unnecessary for doing our build, but it catches // circular dependencies or missing Targets on the entire // dependency tree, not just on the Targets that depend on the // build Target.
$this->_tsort($root, $targets, $state, $visiting, $ret);
$retHuman = ""; for ($i=0, $_i=count($ret); $i < $_i; $i++) { $retHuman .= $ret[$i]->toString()." "; } $this->log("Build sequence for target '$root' is: $retHuman", Project::MSG_VERBOSE);
$keys = array_keys($targets); while($keys) { $curTargetName = (string) array_shift($keys); if (!isset($state[$curTargetName])) { $st = null; } else { $st = (string) $state[$curTargetName]; }
if ($st === null) { $this->_tsort($curTargetName, $targets, $state, $visiting, $ret); } elseif ($st === "VISITING") { throw new Exception("Unexpected node in visiting state: $curTargetName"); } }
$retHuman = ""; for ($i=0,$_i=count($ret); $i < $_i; $i++) { $retHuman .= $ret[$i]->toString()." "; } $this->log("Complete build sequence is: $retHuman", Project::MSG_VERBOSE);
return $ret; }
// one step in a recursive DFS traversal of the target dependency tree. // - The array "state" contains the state (VISITED or VISITING or null) // of all the target names. // - The stack "visiting" contains a stack of target names that are // currently on the DFS stack. (NB: the target names in "visiting" are // exactly the target names in "state" that are in the VISITING state.) // 1. Set the current target to the VISITING state, and push it onto // the "visiting" stack. // 2. Throw a BuildException if any child of the current node is // in the VISITING state (implies there is a cycle.) It uses the // "visiting" Stack to construct the cycle. // 3. If any children have not been VISITED, tsort() the child. // 4. Add the current target to the Vector "ret" after the children // have been visited. Move the current target to the VISITED state. // "ret" now contains the sorted sequence of Targets upto the current // Target.
function _tsort($root, &$targets, &$state, &$visiting, &$ret) { $state[$root] = "VISITING"; $visiting[] = $root;
if (!isset($targets[$root]) || !($targets[$root] instanceof Target)) { $target = null; } else { $target = $targets[$root]; }
// make sure we exist if ($target === null) { $sb = "Target '$root' does not exist in this project."; array_pop($visiting); if (!empty($visiting)) { $parent = (string) $visiting[count($visiting)-1]; $sb .= "It is used from target '$parent'."; } throw new BuildException($sb); }
$deps = $target->getDependencies();
while($deps) { $cur = (string) array_shift($deps); if (!isset($state[$cur])) { $m = null; } else { $m = (string) $state[$cur]; } if ($m === null) { // not been visited $this->_tsort($cur, $targets, $state, $visiting, $ret); } elseif ($m == "VISITING") { // currently visiting this node, so have a cycle throw $this->_makeCircularException($cur, $visiting); } }
$p = (string) array_pop($visiting); if ($root !== $p) { throw new Exception("Unexpected internal error: expected to pop $root but got $p"); }
$state[$root] = "VISITED"; $ret[] = $target; }
function _makeCircularException($end, $stk) { $sb = "Circular dependency: $end"; do { $c = (string) array_pop($stk); $sb .= " <- ".$c; } while($c != $end); return new BuildException($sb); }
/** * Adds a reference to an object. This method is called when the parser * detects a id="foo" attribute. It passes the id as $name and a reference * to the object assigned to this id as $value */ function addReference($name, $object) { if (isset($this->references[$name])) { $this->log("Overriding previous definition of reference to $name", Project::MSG_WARN); } $this->log("Adding reference: $name -> ".get_class($object), Project::MSG_DEBUG); $this->references[$name] = $object; }
/** * Returns the references array. * @return array */ function getReferences() { return $this->references; } /** * Returns a specific reference. * @param string $key The reference id/key. * @return Reference or null if not defined */ function getReference($key) { if (isset($this->references[$key])) { return $this->references[$key]; } return null; // just to be explicit }
/** * Abstracting and simplifyling Logger calls for project messages */ function log($msg, $level = Project::MSG_INFO) { $this->logObject($this, $msg, $level); }
function logObject($obj, $msg, $level) { $this->fireMessageLogged($obj, $msg, $level); }
function addBuildListener(BuildListener $listener) { $this->listeners[] = $listener; }
function removeBuildListener(BuildListener $listener) { $newarray = array(); for ($i=0, $size=count($this->listeners); $i < $size; $i++) { if ($this->listeners[$i] !== $listener) { $newarray[] = $this->listeners[$i]; } } $this->listeners = $newarray; }
function getBuildListeners() { return $this->listeners; }
function fireBuildStarted() { $event = new BuildEvent($this); foreach($this->listeners as $listener) { $listener->buildStarted($event); } }
function fireBuildFinished($exception) { $event = new BuildEvent($this); $event->setException($exception); foreach($this->listeners as $listener) { $listener->buildFinished($event); } }
function fireTargetStarted($target) { $event = new BuildEvent($target); foreach($this->listeners as $listener) { $listener->targetStarted($event); } }
function fireTargetFinished($target, $exception) { $event = new BuildEvent($target); $event->setException($exception); foreach($this->listeners as $listener) { $listener->targetFinished($event); } }
function fireTaskStarted($task) { $event = new BuildEvent($task); foreach($this->listeners as $listener) { $listener->taskStarted($event); } }
function fireTaskFinished($task, $exception) { $event = new BuildEvent($task); $event->setException($exception); foreach($this->listeners as $listener) { $listener->taskFinished($event); } }
function fireMessageLoggedEvent($event, $message, $priority) { $event->setMessage($message, $priority); foreach($this->listeners as $listener) { $listener->messageLogged($event); } }
function fireMessageLogged($object, $message, $priority) { $this->fireMessageLoggedEvent(new BuildEvent($object), $message, $priority); } }
|