diff options
Diffstat (limited to 'buildscripts/phing/classes/phing/Phing.php')
-rwxr-xr-x[-rw-r--r--] | buildscripts/phing/classes/phing/Phing.php | 1155 |
1 files changed, 704 insertions, 451 deletions
diff --git a/buildscripts/phing/classes/phing/Phing.php b/buildscripts/phing/classes/phing/Phing.php index bb8d67f6..d663ee42 100644..100755 --- a/buildscripts/phing/classes/phing/Phing.php +++ b/buildscripts/phing/classes/phing/Phing.php @@ -1,6 +1,6 @@ <?php /* - * $Id: Phing.php,v 1.51 2006/01/06 15:12:33 hlellelid Exp $ + * $Id: 1ad418f51ac07c9afaadf1c1f4befd5535f5b390 $ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -18,13 +18,14 @@ * and is licensed under the LGPL. For more information please see * <http://phing.info>. */ - + require_once 'phing/Project.php'; require_once 'phing/ProjectComponent.php'; require_once 'phing/Target.php'; require_once 'phing/Task.php'; include_once 'phing/BuildException.php'; +include_once 'phing/ConfigurationException.php'; include_once 'phing/BuildEvent.php'; include_once 'phing/parser/Location.php'; @@ -41,11 +42,13 @@ include_once 'phing/parser/NestedElementHandler.php'; include_once 'phing/system/util/Properties.php'; include_once 'phing/util/StringHelper.php'; include_once 'phing/system/io/PhingFile.php'; +include_once 'phing/system/io/OutputStream.php'; +include_once 'phing/system/io/FileOutputStream.php'; include_once 'phing/system/io/FileReader.php'; include_once 'phing/system/util/Register.php'; /** - * Entry point into Phing. This class handles the full lifecycle of a build -- from + * Entry point into Phing. This class handles the full lifecycle of a build -- from * parsing & handling commandline arguments to assembling the project to shutting down * and cleaning up in the end. * @@ -55,7 +58,7 @@ include_once 'phing/system/util/Register.php'; * * @author Andreas Aderhold <andi@binarycloud.com> * @author Hans Lellelid <hans@xmpl.org> - * @version $Revision: 1.51 $ + * @version $Id: 1ad418f51ac07c9afaadf1c1f4befd5535f5b390 $ * @package phing */ class Phing { @@ -63,8 +66,8 @@ class Phing { /** The default build file name */ const DEFAULT_BUILD_FILENAME = "build.xml"; - /** Our current message output status. Follows PROJECT_MSG_XXX */ - private static $msgOutputLevel = PROJECT_MSG_INFO; + /** Our current message output status. Follows Project::MSG_XXX */ + private static $msgOutputLevel = Project::MSG_INFO; /** PhingFile that we are using for configuration */ private $buildFile = null; @@ -85,90 +88,166 @@ class Phing { /** The class to handle input (can be only one). */ private $inputHandlerClassname; - + /** Indicates if this phing should be run */ private $readyToRun = false; /** Indicates we should only parse and display the project help information */ private $projectHelp = false; - + /** Used by utility function getResourcePath() */ private static $importPaths; - + /** System-wide static properties (moved from System) */ private static $properties = array(); - + /** Static system timer. */ private static $timer; - - /** The current Project */ - private static $currentProject; - - /** Whether to capture PHP errors to buffer. */ - private static $phpErrorCapture = false; - - /** Array of captured PHP errors */ - private static $capturedPhpErrors = array(); - + + /** The current Project */ + private static $currentProject; + + /** Whether to capture PHP errors to buffer. */ + private static $phpErrorCapture = false; + + /** Array of captured PHP errors */ + private static $capturedPhpErrors = array(); + /** - * Prints the message of the Exception if it's not null. + * @var OUtputStream Stream for standard output. */ - function printMessage(Exception $t) { - print($t->getMessage() . "\n"); - if (self::getMsgOutputLevel() === PROJECT_MSG_DEBUG) { - print($t->getTraceAsString()."\n"); - if ($t instanceof Exception) { - $c = $t->getCause(); - if ($c !== null) { - print("Wrapped exception trace:\n"); - print($c->getTraceAsString() . "\n"); - } - } - } // if output level is DEBUG - } + private static $out; - /** + /** + * @var OutputStream Stream for error output. + */ + private static $err; + + /** + * @var boolean Whether we are using a logfile. + */ + private static $isLogFileUsed = false; + + /** + * Array to hold original ini settings that Phing changes (and needs + * to restore in restoreIni() method). + * + * @var array Struct of array(setting-name => setting-value) + * @see restoreIni() + */ + private static $origIniSettings = array(); + + /** * Entry point allowing for more options from other front ends. - * + * * This method encapsulates the complete build lifecycle. - * - * @param array &$args The commandline args passed to phing shell script. + * + * @param array $args The commandline args passed to phing shell script. * @param array $additionalUserProperties Any additional properties to be passed to Phing (alternative front-end might implement this). * These additional properties will be available using the getDefinedProperty() method and will - * be added to the project's "user" properties. - * @return void + * be added to the project's "user" properties * @see execute() * @see runBuild() + * @throws Exception - if there is an error during build */ - public static function start(&$args, $additionalUserProperties = null) { + public static function start($args, array $additionalUserProperties = null) { try { $m = new Phing(); $m->execute($args); } catch (Exception $exc) { - $m->printMessage($exc); - self::halt(-1); // Parameter error + self::handleLogfile(); + throw $exc; } if ($additionalUserProperties !== null) { - $keys = $m->additionalUserProperties->keys(); - while(count($keys)) { - $key = array_shift($keys); - $property = $m->additionalUserProperties->getProperty($key); - $m->setDefinedProperty($key, $property); + foreach($additionalUserProperties as $key => $value) { + $m->setDefinedProperty($key, $value); } } try { $m->runBuild(); } catch(Exception $exc) { - self::halt(1); // Errors occured + self::handleLogfile(); + throw $exc; } - + // everything fine, shutdown - self::halt(0); // no errors, everything is cake + self::handleLogfile(); } - + + /** + * Prints the message of the Exception if it's not null. + * @param Exception $t + */ + public static function printMessage(Exception $t) { + if (self::$err === null) { // Make sure our error output is initialized + self::initializeOutputStreams(); + } + if (self::getMsgOutputLevel() >= Project::MSG_VERBOSE) { + self::$err->write($t->__toString() . PHP_EOL); + } else { + self::$err->write($t->getMessage() . PHP_EOL); + } + } + + /** + * Sets the stdout and stderr streams if they are not already set. + */ + private static function initializeOutputStreams() { + if (self::$out === null) { + self::$out = new OutputStream(fopen("php://stdout", "w")); + } + if (self::$err === null) { + self::$err = new OutputStream(fopen("php://stderr", "w")); + } + } + + /** + * Sets the stream to use for standard (non-error) output. + * @param OutputStream $stream The stream to use for standard output. + */ + public static function setOutputStream(OutputStream $stream) { + self::$out = $stream; + } + + /** + * Gets the stream to use for standard (non-error) output. + * @return OutputStream + */ + public static function getOutputStream() { + return self::$out; + } + + /** + * Sets the stream to use for error output. + * @param OutputStream $stream The stream to use for error output. + */ + public static function setErrorStream(OutputStream $stream) { + self::$err = $stream; + } + + /** + * Gets the stream to use for error output. + * @return OutputStream + */ + public static function getErrorStream() { + return self::$err; + } + + /** + * Close logfiles, if we have been writing to them. + * + * @since Phing 2.3.0 + */ + private static function handleLogfile() { + if (self::$isLogFileUsed) { + self::$err->close(); + self::$out->close(); + } + } + /** * Making output level a static property so that this property * can be accessed by other parts of the system, enabling @@ -177,8 +256,8 @@ class Phing { */ public static function getMsgOutputLevel() { return self::$msgOutputLevel; - } - + } + /** * Command line entry point. This method kicks off the building * of a project object and executes a build using either a given @@ -190,65 +269,92 @@ class Phing { public static function fire($args) { self::start($args, null); } - + /** * Setup/initialize Phing environment from commandline args. * @param array $args commandline args passed to phing shell. * @return void */ public function execute($args) { - + self::$definedProps = new Properties(); $this->searchForThis = null; - // cycle through given args - for ($i = 0, $argcount = count($args); $i < $argcount; ++$i) { - // ++$i intentional here, as first param is script name + // 1) First handle any options which should always + // Note: The order in which these are executed is important (if multiple of these options are specified) + + if (in_array('-help', $args) || in_array('-h', $args)) { + $this->printUsage(); + return; + } + + if (in_array('-version', $args) || in_array('-v', $args)) { + $this->printVersion(); + return; + } + + // 2) Next pull out stand-alone args. + // Note: The order in which these are executed is important (if multiple of these options are specified) + + if (false !== ($key = array_search('-quiet', $args, true)) || false !== ($key = array_search('-q', $args, true))) { + self::$msgOutputLevel = Project::MSG_WARN; + unset($args[$key]); + } + + if (false !== ($key = array_search('-verbose', $args, true))) { + self::$msgOutputLevel = Project::MSG_VERBOSE; + unset($args[$key]); + } + + if (false !== ($key = array_search('-debug', $args, true))) { + self::$msgOutputLevel = Project::MSG_DEBUG; + unset($args[$key]); + } + + // 3) Finally, cycle through to parse remaining args + // + $keys = array_keys($args); // Use keys and iterate to max(keys) since there may be some gaps + $max = $keys ? max($keys) : -1; + for($i=0; $i <= $max; $i++) { + + if (!array_key_exists($i, $args)) { + // skip this argument, since it must have been removed above. + continue; + } + $arg = $args[$i]; - if ($arg == "-help" || $arg == "-h") { - $this->printUsage(); - return; - } elseif ($arg == "-version" || $arg == "-v") { - $this->printVersion(); - return; - } elseif ($arg == "-quiet" || $arg == "-q") { - self::$msgOutputLevel = PROJECT_MSG_WARN; - } elseif ($arg == "-verbose") { - $this->printVersion(); - self::$msgOutputLevel = PROJECT_MSG_VERBOSE; - } elseif ($arg == "-debug") { - $this->printVersion(); - self::$msgOutputLevel = PROJECT_MSG_DEBUG; - } elseif ($arg == "-logfile") { - try { // try to set logfile + if ($arg == "-logfile") { + try { + // see: http://phing.info/trac/ticket/65 if (!isset($args[$i+1])) { - print("You must specify a log file when using the -logfile argument\n"); - return; + $msg = "You must specify a log file when using the -logfile argument\n"; + throw new ConfigurationException($msg); } else { $logFile = new PhingFile($args[++$i]); - $this->loggerClassname = 'phing.listener.PearLogger'; - $this->setDefinedProperty('pear.log.name', $logFile->getAbsolutePath()); + $out = new FileOutputStream($logFile); // overwrite + self::setOutputStream($out); + self::setErrorStream($out); + self::$isLogFileUsed = true; } } catch (IOException $ioe) { - print("Cannot write on the specified log file. Make sure the path exists and you have write permissions.\n"); - throw $ioe; + $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions."; + throw new ConfigurationException($msg, $ioe); } } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") { if (!isset($args[$i+1])) { - print("You must specify a buildfile when using the -buildfile argument\n"); - return; + $msg = "You must specify a buildfile when using the -buildfile argument."; + throw new ConfigurationException($msg); } else { $this->buildFile = new PhingFile($args[++$i]); } } elseif ($arg == "-listener") { if (!isset($args[$i+1])) { - print("You must specify a listener class when using the -listener argument\n"); - return; + $msg = "You must specify a listener class when using the -listener argument"; + throw new ConfigurationException($msg); } else { $this->listeners[] = $args[++$i]; } - } elseif (StringHelper::startsWith("-D", $arg)) { $name = substr($arg, 2); $value = null; @@ -256,28 +362,41 @@ class Phing { if ($posEq !== false) { $value = substr($name, $posEq+1); $name = substr($name, 0, $posEq); - } elseif ($i < count($args)-1) { + } elseif ($i < count($args)-1 && !StringHelper::startsWith("-D", $args[$i + 1])) { $value = $args[++$i]; } self::$definedProps->setProperty($name, $value); } elseif ($arg == "-logger") { if (!isset($args[$i+1])) { - print("You must specify a classname when using the -logger argument\n"); - return; + $msg = "You must specify a classname when using the -logger argument"; + throw new ConfigurationException($msg); } else { $this->loggerClassname = $args[++$i]; } } elseif ($arg == "-inputhandler") { if ($this->inputHandlerClassname !== null) { - throw new BuildException("Only one input handler class may be specified."); + throw new ConfigurationException("Only one input handler class may be specified."); } if (!isset($args[$i+1])) { - print("You must specify a classname when using the -inputhandler argument\n"); - return; + $msg = "You must specify a classname when using the -inputhandler argument"; + throw new ConfigurationException($msg); } else { $this->inputHandlerClassname = $args[++$i]; } - } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l") { + } elseif ($arg == "-propertyfile") { + if (!isset($args[$i+1])) { + $msg = "You must specify a filename when using the -propertyfile argument"; + throw new ConfigurationException($msg); + } else { + $p = new Properties(); + $p->load(new PhingFile($args[++$i])); + foreach ($p->getProperties() as $prop => $value) { + $this->setProperty($prop, $value); + } + } + } elseif ($arg == "-longtargets") { + self::$definedProps->setProperty('phing.showlongtargets', 1); + } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") { // set the flag to display the targets and quit $this->projectHelp = true; } elseif ($arg == "-find") { @@ -289,8 +408,8 @@ class Phing { } } elseif (substr($arg,0,1) == "-") { // we don't have any more args - print("Unknown argument: $arg\n"); - $this->printUsage(); + self::$err->write("Unknown argument: $arg" . PHP_EOL); + self::printUsage(); return; } else { // if it's no other arg, it may be the target @@ -309,12 +428,12 @@ class Phing { } // make sure buildfile exists if (!$this->buildFile->exists()) { - throw new BuildException("Buildfile: " . $this->buildFile->__toString() . " does not exist!"); + throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " does not exist!"); } // make sure it's not a directory - if ($this->buildFile->isDirectory()) { - throw new BuildException("Buildfile: " . $this->buildFile->__toString() . " is a dir!"); + if ($this->buildFile->isDirectory()) { + throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!"); } $this->readyToRun = true; @@ -326,15 +445,10 @@ class Phing { * @param PhingFile $file * @return PhingFile Parent file or null if none */ - function _getParentFile(PhingFile $file) { + private function _getParentFile(PhingFile $file) { $filename = $file->getAbsolutePath(); $file = new PhingFile($filename); $filename = $file->getParent(); - - if ($filename !== null && self::$msgOutputLevel >= PROJECT_MSG_VERBOSE) { - print("Searching in $filename\n"); - } - return ($filename === null) ? null : new PhingFile($filename); } @@ -345,17 +459,14 @@ class Phing { * parent directory in search of a build file. Once the * root of the file-system has been reached an exception * is thrown. - * + * * @param string $start Start file path. * @param string $suffix Suffix filename to look for in parents. * @return PhingFile A handle to the build file * * @throws BuildException Failed to locate a build file */ - function _findBuildFile($start, $suffix) { - if (self::$msgOutputLevel >= PROJECT_MSG_INFO) { - print("Searching for $suffix ...\n"); - } + private function _findBuildFile($start, $suffix) { $startf = new PhingFile($start); $parent = new PhingFile($startf->getAbsolutePath()); $file = new PhingFile($parent, $suffix); @@ -368,7 +479,7 @@ class Phing { // if parent is null, then we are at the root of the fs, // complain that we can't find the build file. if ($parent === null) { - throw new BuildException("Could not locate a build file!"); + throw new ConfigurationException("Could not locate a build file!"); } // refresh our file handle $file = new PhingFile($parent, $suffix); @@ -385,17 +496,17 @@ class Phing { if (!$this->readyToRun) { return; } - + $project = new Project(); - - self::setCurrentProject($project); - set_error_handler(array('Phing', 'handlePhpError')); - + + self::setCurrentProject($project); + set_error_handler(array('Phing', 'handlePhpError')); + $error = null; $this->addBuildListeners($project); $this->addInputHandler($project); - + // set this right away, so that it can be used in logging. $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath()); @@ -404,7 +515,7 @@ class Phing { $project->init(); } catch (Exception $exc) { $project->fireBuildFinished($exc); - throw $exc; + throw $exc; } $project->setUserProperty("phing.version", $this->getPhingVersion()); @@ -421,30 +532,40 @@ class Phing { // first use the Configurator to create the project object // from the given build file. - + try { ProjectConfigurator::configureProject($project, $this->buildFile); } catch (Exception $exc) { $project->fireBuildFinished($exc); - restore_error_handler(); - self::unsetCurrentProject(); + restore_error_handler(); + self::unsetCurrentProject(); throw $exc; - } + } // make sure that we have a target to execute if (count($this->targets) === 0) { $this->targets[] = $project->getDefaultTarget(); } + // make sure that minimum required phing version is satisfied + try { + $this->comparePhingVersion($project->getPhingVersion()); + } catch(Exception $exc) { + $project->fireBuildFinished($exc); + restore_error_handler(); + self::unsetCurrentProject(); + throw $exc; + } + // execute targets if help param was not given if (!$this->projectHelp) { - - try { + + try { $project->executeTargets($this->targets); } catch (Exception $exc) { $project->fireBuildFinished($exc); - restore_error_handler(); - self::unsetCurrentProject(); + restore_error_handler(); + self::unsetCurrentProject(); throw $exc; } } @@ -455,32 +576,68 @@ class Phing { $this->printTargets($project); } catch (Exception $exc) { $project->fireBuildFinished($exc); - restore_error_handler(); - self::unsetCurrentProject(); + restore_error_handler(); + self::unsetCurrentProject(); throw $exc; } } - + // finally { if (!$this->projectHelp) { $project->fireBuildFinished(null); } - - restore_error_handler(); - self::unsetCurrentProject(); + + restore_error_handler(); + self::unsetCurrentProject(); } - + + private function comparePhingVersion($version) { + $current = strtolower(self::getPhingVersion()); + $current = trim(str_replace('phing', '', $current)); + + // make sure that version checks are not applied to trunk + if('dev' === $current) { + return 1; + } + + if(-1 == version_compare($current, $version)) { + throw new BuildException( + sprintf('Incompatible Phing version (%s). Version "%s" required.', $current, $version)); + } + } + /** - * Bind any default build listeners to this project. - * Currently this means adding the logger. + * Bind any registered build listeners to this project. + * + * This means adding the logger and any build listeners that were specified + * with -listener arg. + * * @param Project $project * @return void */ private function addBuildListeners(Project $project) { // Add the default listener $project->addBuildListener($this->createLogger()); + + foreach($this->listeners as $listenerClassname) { + try { + $clz = Phing::import($listenerClassname); + } catch (Exception $x) { + $msg = "Unable to instantiate specified listener " + . "class " . $listenerClassname . " : " + . $e->getMessage(); + throw new ConfigurationException($msg); + } + + $listener = new $clz(); + + if ($listener instanceof StreamRequiredBuildLogger) { + throw new ConfigurationException("Unable to add " . $listenerClassname . " as a listener, since it requires explicit error/output streams. (You can specify it as a -logger.)"); + } + $project->addBuildListener($listener); + } } - + /** * Creates the InputHandler and adds it to the project. * @@ -498,12 +655,12 @@ class Phing { $handler = new $clz(); if ($project !== null && method_exists($handler, 'setProject')) { $handler->setProject($project); - } + } } catch (Exception $e) { $msg = "Unable to instantiate specified input handler " - . "class " . $this->inputHandlerClassname . " : " - . $e->getMessage(); - throw new BuildException($msg); + . "class " . $this->inputHandlerClassname . " : " + . $e->getMessage(); + throw new ConfigurationException($msg); } } $project->setInputHandler($handler); @@ -511,165 +668,185 @@ class Phing { /** * Creates the default build logger for sending build events to the log. - * @return BuildListener The created Logger + * @return BuildLogger The created Logger */ private function createLogger() { if ($this->loggerClassname !== null) { self::import($this->loggerClassname); - // get class name part + // get class name part $classname = self::import($this->loggerClassname); $logger = new $classname; + if (!($logger instanceof BuildLogger)) { + throw new BuildException($classname . ' does not implement the BuildLogger interface.'); + } } else { require_once 'phing/listener/DefaultLogger.php'; $logger = new DefaultLogger(); } $logger->setMessageOutputLevel(self::$msgOutputLevel); + $logger->setOutputStream(self::$out); + $logger->setErrorStream(self::$err); return $logger; } - - /** - * Sets the current Project - * @param Project $p - */ - public static function setCurrentProject($p) { - self::$currentProject = $p; - } - - /** - * Unsets the current Project - */ - public static function unsetCurrentProject() { - self::$currentProject = null; - } - - /** - * Gets the current Project. - * @return Project Current Project or NULL if none is set yet/still. - */ - public static function getCurrentProject() { - return self::$currentProject; - } - - /** - * A static convenience method to send a log to the current (last-setup) Project. - * If there is no currently-configured Project, then this will do nothing. - * @param string $message - * @param int $priority PROJECT_MSG_INFO, etc. - */ - public static function log($message, $priority = PROJECT_MSG_INFO) { - $p = self::getCurrentProject(); - if ($p) { - $p->log($message, $priority); - } - } - - /** - * Error handler for PHP errors encountered during the build. - * This uses the logging for the currently configured project. - */ - public static function handlePhpError($level, $message, $file, $line) { - + + /** + * Sets the current Project + * @param Project $p + */ + public static function setCurrentProject($p) { + self::$currentProject = $p; + } + + /** + * Unsets the current Project + */ + public static function unsetCurrentProject() { + self::$currentProject = null; + } + + /** + * Gets the current Project. + * @return Project Current Project or NULL if none is set yet/still. + */ + public static function getCurrentProject() { + return self::$currentProject; + } + + /** + * A static convenience method to send a log to the current (last-setup) Project. + * If there is no currently-configured Project, then this will do nothing. + * @param string $message + * @param int $priority Project::MSG_INFO, etc. + */ + public static function log($message, $priority = Project::MSG_INFO) { + $p = self::getCurrentProject(); + if ($p) { + $p->log($message, $priority); + } + } + + /** + * Error handler for PHP errors encountered during the build. + * This uses the logging for the currently configured project. + */ + public static function handlePhpError($level, $message, $file, $line) { + // don't want to print supressed errors if (error_reporting() > 0) { - - if (self::$phpErrorCapture) { - - self::$capturedPhpErrors[] = array('message' => $message, 'level' => $level, 'line' => $line, 'file' => $file); - - } else { - - $message = '[PHP Error] ' . $message; - $message .= ' [line ' . $line . ' of ' . $file . ']'; - - switch ($level) { - - case E_STRICT: - case E_NOTICE: - case E_USER_NOTICE: - self::log($message, PROJECT_MSG_VERBOSE); - break; - case E_WARNING: - case E_USER_WARNING: - self::log($message, PROJECT_MSG_WARN); - break; - case E_ERROR: - case E_USER_ERROR: - default: - self::log($message, PROJECT_MSG_ERR); - - } // switch - - } // if phpErrorCapture - + + if (self::$phpErrorCapture) { + + self::$capturedPhpErrors[] = array('message' => $message, 'level' => $level, 'line' => $line, 'file' => $file); + + } else { + + $message = '[PHP Error] ' . $message; + $message .= ' [line ' . $line . ' of ' . $file . ']'; + + switch ($level) { + case 16384: // E_USER_DEPRECATED + case 8192: // E_DEPRECATED + case E_STRICT: + case E_NOTICE: + case E_USER_NOTICE: + self::log($message, Project::MSG_VERBOSE); + break; + case E_WARNING: + case E_USER_WARNING: + self::log($message, Project::MSG_WARN); + break; + case E_ERROR: + case E_USER_ERROR: + default: + self::log($message, Project::MSG_ERR); + + } // switch + + } // if phpErrorCapture + } // if not @ - - } - - /** - * Begins capturing PHP errors to a buffer. - * While errors are being captured, they are not logged. - */ - public static function startPhpErrorCapture() { - self::$phpErrorCapture = true; - self::$capturedPhpErrors = array(); - } - - /** - * Stops capturing PHP errors to a buffer. - * The errors will once again be logged after calling this method. - */ - public static function stopPhpErrorCapture() { - self::$phpErrorCapture = false; - } - - /** - * Clears the captured errors without affecting the starting/stopping of the capture. - */ - public static function clearCapturedPhpErrors() { - self::$capturedPhpErrors = array(); - } - - /** - * Gets any PHP errors that were captured to buffer. - * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level) - */ - public static function getCapturedPhpErrors() { - return self::$capturedPhpErrors; - } - + + } + + /** + * Begins capturing PHP errors to a buffer. + * While errors are being captured, they are not logged. + */ + public static function startPhpErrorCapture() { + self::$phpErrorCapture = true; + self::$capturedPhpErrors = array(); + } + + /** + * Stops capturing PHP errors to a buffer. + * The errors will once again be logged after calling this method. + */ + public static function stopPhpErrorCapture() { + self::$phpErrorCapture = false; + } + + /** + * Clears the captured errors without affecting the starting/stopping of the capture. + */ + public static function clearCapturedPhpErrors() { + self::$capturedPhpErrors = array(); + } + + /** + * Gets any PHP errors that were captured to buffer. + * @return array array('message' => message, 'line' => line number, 'file' => file name, 'level' => error level) + */ + public static function getCapturedPhpErrors() { + return self::$capturedPhpErrors; + } + /** Prints the usage of how to use this class */ - function printUsage() { - $lSep = self::getProperty("line.separator"); + public static function printUsage() { + $msg = ""; - $msg .= "phing [options] [target [target2 [target3] ...]]" . $lSep; - $msg .= "Options: " . $lSep; - $msg .= " -h -help print this message" . $lSep; - $msg .= " -l -list list available targets in this project" . $lSep; - $msg .= " -v -version print the version information and exit" . $lSep; - $msg .= " -q -quiet be extra quiet" . $lSep; - $msg .= " -verbose be extra verbose" . $lSep; - $msg .= " -debug print debugging information" . $lSep; - $msg .= " -logfile <file> use given file for log" . $lSep; - $msg .= " -logger <classname> the class which is to perform logging" . $lSep; - $msg .= " -f -buildfile <file> use given buildfile" . $lSep; - $msg .= " -D<property>=<value> use value for given property" . $lSep; - $msg .= " -find <file> search for buildfile towards the root of the" . $lSep; - $msg .= " filesystem and use it" . $lSep; - //$msg .= " -recursive <file> search for buildfile downwards and use it" . $lSep; - $msg .= $lSep; - $msg .= "Report bugs to <dev@phing.tigris.org>".$lSep; - print($msg); + $msg .= "phing [options] [target [target2 [target3] ...]]" . PHP_EOL; + $msg .= "Options: " . PHP_EOL; + $msg .= " -h -help print this message" . PHP_EOL; + $msg .= " -l -list list available targets in this project" . PHP_EOL; + $msg .= " -v -version print the version information and exit" . PHP_EOL; + $msg .= " -q -quiet be extra quiet" . PHP_EOL; + $msg .= " -verbose be extra verbose" . PHP_EOL; + $msg .= " -debug print debugging information" . PHP_EOL; + $msg .= " -longtargets show target descriptions during build" . PHP_EOL; + $msg .= " -logfile <file> use given file for log" . PHP_EOL; + $msg .= " -logger <classname> the class which is to perform logging" . PHP_EOL; + $msg .= " -f -buildfile <file> use given buildfile" . PHP_EOL; + $msg .= " -D<property>=<value> use value for given property" . PHP_EOL; + $msg .= " -propertyfile <file> load all properties from file" . PHP_EOL; + $msg .= " -find <file> search for buildfile towards the root of the" . PHP_EOL; + $msg .= " filesystem and use it" . PHP_EOL; + $msg .= " -inputhandler <file> the class to use to handle user input" . PHP_EOL; + //$msg .= " -recursive <file> search for buildfile downwards and use it" . PHP_EOL; + $msg .= PHP_EOL; + $msg .= "Report bugs to <dev@phing.tigris.org>".PHP_EOL; + self::$err->write($msg); } - function printVersion() { - print(self::getPhingVersion()."\n"); + /** + * Prints the current Phing version. + */ + public static function printVersion() { + self::$out->write(self::getPhingVersion().PHP_EOL); } - function getPhingVersion() { + /** + * Gets the current Phing version based on VERSION.TXT file. + * @return string + * @throws BuildException - if unable to find version file. + */ + public static function getPhingVersion() { $versionPath = self::getResourcePath("phing/etc/VERSION.TXT"); - if ($versionPath === null) { - $versionPath = self::getResourcePath("etc/VERSION.TXT"); - } + if ($versionPath === null) { + $versionPath = self::getResourcePath("etc/VERSION.TXT"); + } + if ($versionPath === null) { + throw new ConfigurationException("No VERSION.TXT file found; try setting phing.home environment variable."); + } try { // try to read file $buffer = null; $file = new PhingFile($versionPath); @@ -679,16 +856,17 @@ class Phing { //$buffer = "PHING version 1.0, Released 2002-??-??"; $phingVersion = $buffer; } catch (IOException $iox) { - print("Can't read version information file\n"); - throw new BuildException("Build failed"); - } + throw new ConfigurationException("Can't read version information file"); + } return $phingVersion; } - /** Print the project description, if any */ - function printDescription(Project $project) { + /** + * Print the project description, if any + */ + public static function printDescription(Project $project) { if ($project->getDescription() !== null) { - print($project->getDescription()."\n"); + self::$out->write($project->getDescription() . PHP_EOL); } } @@ -704,31 +882,34 @@ class Phing { // split the targets in top-level and sub-targets depending // on the presence of a description - + $subNames = array(); $topNameDescMap = array(); - - foreach($targets as $currentTarget) { + + foreach($targets as $currentTarget) { $targetName = $currentTarget->getName(); - $targetDescription = $currentTarget->getDescription(); - + $targetDescription = $currentTarget->getDescription(); + if ($currentTarget->isHidden()) { + continue; + } + // subtargets are targets w/o descriptions if ($targetDescription === null) { $subNames[] = $targetName; } else { // topNames and topDescriptions are handled later // here we store in hash map (for sorting purposes) - $topNameDescMap[$targetName] = $targetDescription; + $topNameDescMap[$targetName] = $targetDescription; if (strlen($targetName) > $maxLength) { $maxLength = strlen($targetName); } } } - + // Sort the arrays - sort($subNames); // sort array values, resetting keys (which are numeric) + sort($subNames); // sort array values, resetting keys (which are numeric) ksort($topNameDescMap); // sort the keys (targetName) keeping key=>val associations - + $topNames = array_keys($topNameDescMap); $topDescriptions = array_values($topNameDescMap); @@ -750,7 +931,7 @@ class Phing { } $this->_printTargets($topNames, $topDescriptions, "Main targets:", $maxLength); $this->_printTargets($subNames, null, "Subtargets:", 0); - } + } /** * Writes a formatted list of target names with an optional description. @@ -770,14 +951,14 @@ class Phing { * <i>are</i> shorter than this). */ private function _printTargets($names, $descriptions, $heading, $maxlen) { - $lSep = self::getProperty("line.separator"); + $spaces = ' '; while (strlen($spaces) < $maxlen) { $spaces .= $spaces; } $msg = ""; - $msg .= $heading . $lSep; - $msg .= str_repeat("-",79) . $lSep; + $msg .= $heading . PHP_EOL; + $msg .= str_repeat("-",79) . PHP_EOL; $total = count($names); for($i=0; $i < $total; $i++) { @@ -787,58 +968,74 @@ class Phing { $msg .= substr($spaces, 0, $maxlen - strlen($names[$i]) + 2); $msg .= $descriptions[$i]; } - $msg .= $lSep; + $msg .= PHP_EOL; } if ($total > 0) { - print $msg . $lSep; - } - } - - /** - * Import a dot-path notation class path. - * @param string $dotPath - * @param mixed $classpath String or object supporting __toString() - * @return string The unqualified classname (which can be instantiated). - * @throws BuildException - if cannot find the specified file - */ - public static function import($dotPath, $classpath = null) { + self::$out->write($msg . PHP_EOL); + } + } + + /** + * Import a dot-path notation class path. + * @param string $dotPath + * @param mixed $classpath String or object supporting __toString() + * @return string The unqualified classname (which can be instantiated). + * @throws BuildException - if cannot find the specified file + */ + public static function import($dotPath, $classpath = null) { + + /// check if this is a PEAR-style path (@link http://pear.php.net/manual/en/standards.naming.php) + if (strpos($dotPath, '.') === false && strpos($dotPath, '_') !== false) { + $classname = $dotPath; + $dotPath = str_replace('_', '.', $dotPath); + } else { + $classname = StringHelper::unqualify($dotPath); + } // first check to see that the class specified hasn't already been included. // (this also handles case where this method is called w/ a classname rather than dotpath) - $classname = StringHelper::unqualify($dotPath); - if (class_exists($classname, false)) { + if (class_exists($classname)) { return $classname; } - + $dotClassname = basename($dotPath); $dotClassnamePos = strlen($dotPath) - strlen($dotClassname); - $classFile = strtr($dotClassname, '.', DIRECTORY_SEPARATOR) . ".php"; + + // 1- temporarily replace escaped '.' with another illegal char (#) + $tmp = str_replace('\.', '##', $dotClassname); + // 2- swap out the remaining '.' with DIR_SEP + $tmp = strtr($tmp, '.', DIRECTORY_SEPARATOR); + // 3- swap back the escaped '.' + $tmp = str_replace('##', '.', $tmp); + + $classFile = $tmp . ".php"; + $path = substr_replace($dotPath, $classFile, $dotClassnamePos); - + Phing::__import($path, $classpath); - + return $classname; - } - - /** - * Import a PHP file - * @param string $path Path to the PHP file - * @param mixed $classpath String or object supporting __toString() - * @throws BuildException - if cannot find the specified file - */ - public static function __import($path, $classpath = null) { - + } + + /** + * Import a PHP file + * @param string $path Path to the PHP file + * @param mixed $classpath String or object supporting __toString() + * @throws BuildException - if cannot find the specified file + */ + public static function __import($path, $classpath = null) { + if ($classpath) { - + // Apparently casting to (string) no longer invokes __toString() automatically. if (is_object($classpath)) { $classpath = $classpath->__toString(); } - + // classpaths are currently additive, but we also don't want to just // indiscriminantly prepand/append stuff to the include_path. This means // we need to parse current incldue_path, and prepend any - // specified classpath locations that are not already in the include_path. + // specified classpath locations that are not already in the include_path. // // NOTE: the reason why we do it this way instead of just changing include_path // and then changing it back, is that in many cases applications (e.g. Propel) will @@ -848,88 +1045,89 @@ class Phing { // be more expensive than switching & switching back (not sure, but maybe), it makes it // possible to write far less expensive run-time applications (e.g. using Propel), which is // really where speed matters more. - - $curr_parts = explode(PATH_SEPARATOR, ini_get('include_path')); + + $curr_parts = explode(PATH_SEPARATOR, get_include_path()); $add_parts = explode(PATH_SEPARATOR, $classpath); $new_parts = array_diff($add_parts, $curr_parts); if ($new_parts) { - if (self::getMsgOutputLevel() === PROJECT_MSG_DEBUG) { - print("Phing::import() prepending new include_path components: " . implode(PATH_SEPARATOR, $new_parts) . "\n"); - } - ini_set('include_path', implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts))); + set_include_path(implode(PATH_SEPARATOR, array_merge($new_parts, $curr_parts))); } } - $ret = include_once($path); - + $ret = include_once($path); + if ($ret === false) { - $e = new BuildException("Error importing $path"); - if (self::getMsgOutputLevel() === PROJECT_MSG_DEBUG) { - // We can't log this because listeners belong - // to projects. We'll just print it -- of course - // that isn't very compatible w/ other frontends (but - // there aren't any right now, so I'm not stressing) - print("Error importing $path\n"); - print($e->getTraceAsString()."\n"); - } - throw $e; + $msg = "Error importing $path"; + if (self::getMsgOutputLevel() >= Project::MSG_DEBUG) { + $x = new Exception("for-path-trace-only"); + $msg .= $x->getTraceAsString(); + } + throw new ConfigurationException($msg); } - - return; - } - - /** - * Looks on include path for specified file. - * @return string File found (null if no file found). - */ - public static function getResourcePath($path) { - + } + + /** + * Looks on include path for specified file. + * @return string File found (null if no file found). + */ + public static function getResourcePath($path) { + if (self::$importPaths === null) { - $paths = ini_get("include_path"); + $paths = get_include_path(); self::$importPaths = explode(PATH_SEPARATOR, ini_get("include_path")); } - + $path = str_replace('\\', DIRECTORY_SEPARATOR, $path); $path = str_replace('/', DIRECTORY_SEPARATOR, $path); foreach (self::$importPaths as $prefix) { - $foo_path = $prefix . DIRECTORY_SEPARATOR . $path; - if (file_exists($foo_path)) { - return $foo_path; + $testPath = $prefix . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; } } - + // Check for the property phing.home - $home_dir = self::getProperty('phing.home'); - - if ($home_dir) - { - $home_path = $home_dir . DIRECTORY_SEPARATOR . $path; - - if (file_exists($home_path)) - { - return $home_path; - } - } - + $homeDir = self::getProperty('phing.home'); + if ($homeDir) { + $testPath = $homeDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } + // If we are using this via PEAR then check for the file in the data dir // This is a bit of a hack, but works better than previous solution of assuming // data_dir is on the include_path. - $data_dir = '@DATA-DIR@'; - if ($data_dir{0} != '@') { // if we're using PEAR then the @ DATA-DIR @ token will have been substituted. - $data_path = $data_dir . DIRECTORY_SEPARATOR . $path; - if (file_exists($data_path)) { - return $data_path; - } + $dataDir = '@DATA-DIR@'; + if ($dataDir{0} != '@') { // if we're using PEAR then the @ DATA-DIR @ token will have been substituted. + if (!file_exists($dataDir)) { + self::log("The PEAR data_dir setting is incorrect: {$dataDir}.", Project::MSG_ERR); + self::log("Please edit using 'pear config-set data_dir ...' and re-install Phing.", Project::MSG_ERR); + return null; + } + + $testPath = $dataDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } + } else { + // We're not using PEAR, so do one additional check based on path of + // current file (Phing.php) + $maybeHomeDir = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'); + $testPath = $maybeHomeDir . DIRECTORY_SEPARATOR . $path; + if (file_exists($testPath)) { + return $testPath; + } } - + return null; - } - - // ------------------------------------------------------------------------------------------- - // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System) - // ------------------------------------------------------------------------------------------- - + } + + // ------------------------------------------------------------------------------------------- + // System-wide methods (moved from System class, which had namespace conflicts w/ PEAR System) + // ------------------------------------------------------------------------------------------- + /** * Set System constants which can be retrieved by calling Phing::getProperty($propName). * @return void @@ -944,20 +1142,20 @@ class Phing { * Windows 98SE => WIN32 * FreeBSD 4.5p7 => FreeBSD * Redhat Linux => Linux - * Mac OS X => Darwin + * Mac OS X => Darwin */ self::setProperty('host.os', PHP_OS); - - // this is used by some tasks too + + // this is used by some tasks too self::setProperty('os.name', PHP_OS); - + // it's still possible this won't be defined, // e.g. if Phing is being included in another app w/o // using the phing.php script. if (!defined('PHP_CLASSPATH')) { define('PHP_CLASSPATH', get_include_path()); } - + self::setProperty('php.classpath', PHP_CLASSPATH); // try to determine the host filesystem and set system property @@ -976,24 +1174,26 @@ class Phing { break; } + self::setProperty('php.interpreter', getenv('PHP_COMMAND')); + self::setProperty('line.separator', PHP_EOL); self::setProperty('php.version', PHP_VERSION); self::setProperty('user.home', getenv('HOME')); self::setProperty('application.startdir', getcwd()); - self::setProperty('line.separator', "\n"); + self::setProperty('phing.startTime', gmdate('D, d M Y H:i:s', time()) . ' GMT'); // try to detect machine dependent information $sysInfo = array(); if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' && function_exists("posix_uname")) { - $sysInfo = posix_uname(); + $sysInfo = posix_uname(); } else { - $sysInfo['nodename'] = php_uname('n'); - $sysInfo['machine']= php_uname('m') ; - //this is a not so ideal substition, but maybe better than nothing - $sysInfo['domain'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : "unknown"; - $sysInfo['release'] = php_uname('r'); - $sysInfo['version'] = php_uname('v'); - } - + $sysInfo['nodename'] = php_uname('n'); + $sysInfo['machine']= php_uname('m') ; + //this is a not so ideal substition, but maybe better than nothing + $sysInfo['domain'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : "unknown"; + $sysInfo['release'] = php_uname('r'); + $sysInfo['version'] = php_uname('v'); + } + self::setProperty("host.name", isset($sysInfo['nodename']) ? $sysInfo['nodename'] : "unknown"); self::setProperty("host.arch", isset($sysInfo['machine']) ? $sysInfo['machine'] : "unknown"); @@ -1002,55 +1202,55 @@ class Phing { self::setProperty("host.os.version", isset($sysInfo['version']) ? $sysInfo['version'] : "unknown"); unset($sysInfo); } - + /** * This gets a property that was set via command line or otherwise passed into Phing. * "Defined" in this case means "externally defined". The reason this method exists is to - * provide a public means of accessing commandline properties for (e.g.) logger or listener + * provide a public means of accessing commandline properties for (e.g.) logger or listener * scripts. E.g. to specify which logfile to use, PearLogger needs to be able to access * the pear.log.name property. - * + * * @param string $name * @return string value of found property (or null, if none found). */ public static function getDefinedProperty($name) { return self::$definedProps->getProperty($name); } - + /** * This sets a property that was set via command line or otherwise passed into Phing. - * + * * @param string $name * @return string value of found property (or null, if none found). */ public static function setDefinedProperty($name, $value) { return self::$definedProps->setProperty($name, $value); } - + /** * Returns property value for a System property. - * System properties are "global" properties like line.separator, + * System properties are "global" properties like application.startdir, * and user.dir. Many of these correspond to similar properties in Java * or Ant. - * + * * @param string $paramName * @return string Value of found property (or null, if none found). */ public static function getProperty($propName) { - + // some properties are detemined on each access // some are cached, see below // default is the cached value: $val = isset(self::$properties[$propName]) ? self::$properties[$propName] : null; - - // special exceptions + + // special exceptions switch($propName) { case 'user.dir': $val = getcwd(); - break; + break; } - + return $val; } @@ -1059,56 +1259,106 @@ class Phing { return self::$properties; } - public static function setProperty($propName, $propValue) { + public static function setProperty($propName, $propValue) { $propName = (string) $propName; $oldValue = self::getProperty($propName); self::$properties[$propName] = $propValue; return $oldValue; } - + public static function currentTimeMillis() { list($usec, $sec) = explode(" ",microtime()); return ((float)$usec + (float)$sec); } - + /** - * Sets the include path based on PHP_CLASSPATH constant (set in phing.php). + * Sets the include path to PHP_CLASSPATH constant (if this has been defined). * @return void + * @throws ConfigurationException - if the include_path could not be set (for some bizarre reason) */ private static function setIncludePaths() { - $success = false; - if (defined('PHP_CLASSPATH')) { - $success = ini_set('include_path', PHP_CLASSPATH); - } else { - // don't do anything, just assume that include_path has been properly set. - $success = true; + $result = set_include_path(PHP_CLASSPATH); + if ($result === false) { + throw new ConfigurationException("Could not set PHP include_path."); + } + self::$origIniSettings['include_path'] = $result; // save original value for setting back later } + } + + /** + * Converts shorthand notation values as returned by ini_get() + * @see http://www.php.net/ini_get + * @param string $val + */ + private static function convertShorthand($val) + { + $val = trim($val); + $last = strtolower($val[strlen($val) - 1]); - if ($success === false) { - print("SYSTEM FAILURE: Could not set PHP include path\n"); - self::halt(-1); + switch($last) { + // The 'G' modifier is available since PHP 5.1.0 + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; } - } + return $val; + } + /** * Sets PHP INI values that Phing needs. * @return void */ private static function setIni() { - error_reporting(E_ALL); + + self::$origIniSettings['error_reporting'] = error_reporting(E_ALL); + + // We won't bother storing original max_execution_time, since 1) the value in + // php.ini may be wrong (and there's no way to get the current value) and + // 2) it would mean something very strange to set it to a value less than time script + // has already been running, which would be the likely change. + set_time_limit(0); - ini_set('magic_quotes_gpc', 'off'); - ini_set('short_open_tag', 'off'); - ini_set('default_charset', 'iso-8859-1'); - ini_set('register_globals', 'off'); - ini_set('allow_call_time_pass_reference', 'on'); - - // should return memory limit in MB - $mem_limit = (int) ini_get('memory_limit'); - if ($mem_limit < 32) { + + self::$origIniSettings['magic_quotes_gpc'] = ini_set('magic_quotes_gpc', 'off'); + self::$origIniSettings['short_open_tag'] = ini_set('short_open_tag', 'off'); + self::$origIniSettings['default_charset'] = ini_set('default_charset', 'iso-8859-1'); + self::$origIniSettings['register_globals'] = ini_set('register_globals', 'off'); + self::$origIniSettings['allow_call_time_pass_reference'] = ini_set('allow_call_time_pass_reference', 'on'); + self::$origIniSettings['track_errors'] = ini_set('track_errors', 1); + + $mem_limit = (int) self::convertShorthand(ini_get('memory_limit')); + if ($mem_limit < (32 * 1024 * 1024) && $mem_limit > -1) { + // We do *not* need to save the original value here, since we don't plan to restore + // this after shutdown (we don't trust the effectiveness of PHP's garbage collection). ini_set('memory_limit', '32M'); // nore: this may need to be higher for many projects - } + } + } + + /** + * Restores [most] PHP INI values to their pre-Phing state. + * + * Currently the following settings are not restored: + * - max_execution_time (because getting current time limit is not possible) + * - memory_limit (which may have been increased by Phing) + * + * @return void + */ + private static function restoreIni() + { + foreach(self::$origIniSettings as $settingName => $settingValue) { + switch($settingName) { + case 'error_reporting': + error_reporting($settingValue); + break; + default: + ini_set($settingName, $settingValue); + } + } } /** @@ -1122,15 +1372,17 @@ class Phing { } return self::$timer; } - - /** + + /** * Start up Phing. - * Sets up the Phing environment -- does NOT initiate the build process. + * Sets up the Phing environment but does not initiate the build process. * @return void + * @throws Exception - If the Phing environment cannot be initialized. */ public static function startup() { - - register_shutdown_function(array('Phing', 'shutdown')); + + // setup STDOUT and STDERR defaults + self::initializeOutputStreams(); // some init stuff self::getTimer()->start(); @@ -1139,23 +1391,24 @@ class Phing { self::setIncludePaths(); self::setIni(); } - + /** * Halts the system. + * @deprecated This method is deprecated and is no longer called by Phing internally. Any + * normal shutdown routines are handled by the shutdown() method. * @see shutdown() */ - public static function halt($code=0) { - self::shutdown($code); + public static function halt() { + self::shutdown(); } /** - * Stops timers & exits. + * Performs any shutdown routines, such as stopping timers. * @return void */ - public static function shutdown($exitcode = 0) { - //print("[AUTOMATIC SYSTEM SHUTDOWN]\n"); + public static function shutdown() { + self::restoreIni(); self::getTimer()->stop(); - exit($exitcode); // final point where everything stops } - + } |