root/trunk/classes/phing/Phing.php

Revision 307, 38.5 kB (checked in by hans, 1 year ago)

Refs #188 - Adding work-in-progress (still-broken!) namespace support.

  • Property svn:keywords set to author date id revision
Line 
1 <?php
2 /*
3  * $Id$
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information please see
19  * <http://phing.info>.
20  */
21
22 namespace phing;
23 use phing::system::io::OutputStream;
24 use phing::system::util::Properties;
25 use phing::util::StringHelper;
26 use phing::system::io::File;
27 use phing::system::io::FileReader;
28
29 /**
30  * Entry point into Phing.  This class handles the full lifecycle of a build -- from
31  * parsing & handling commandline arguments to assembling the project to shutting down
32  * and cleaning up in the end.
33  *
34  * If you are invoking Phing from an external application, this is still
35  * the class to use.  Your applicaiton can invoke the start() method, passing
36  * any commandline arguments or additional properties.
37  *
38  * @author    Andreas Aderhold <andi@binarycloud.com>
39  * @author    Hans Lellelid <hans@xmpl.org>
40  * @version   $Revision: 1.51 $
41  * @package   phing
42  */
43 class Phing {
44
45     /** The default build file name */
46     const DEFAULT_BUILD_FILENAME = "build.xml";
47
48     /** Our current message output status. Follows Project::MSG_XXX */
49     private static $msgOutputLevel = Project::MSG_INFO;
50
51     /** File that we are using for configuration */
52     private $buildFile = null;
53
54     /** The build targets */
55     private $targets = array();
56
57     /**
58      * Set of properties that are passed in from commandline or invoking code.
59      * @var Properties
60      */
61     private static $definedProps;
62
63     /** Names of classes to add as listeners to project */
64     private $listeners = array();
65
66     private $loggerClassname = null;
67
68     /** The class to handle input (can be only one). */
69     private $inputHandlerClassname;
70
71     /** Indicates if this phing should be run */
72     private $readyToRun = false;
73
74     /** Indicates we should only parse and display the project help information */
75     private $projectHelp = false;
76
77     /** Used by utility function getResourcePath() */
78     private static $importPaths;
79
80     /** System-wide static properties (moved from System) */
81     private static $properties = array();
82
83     /** Static system timer. */
84     private static $timer;
85
86     /** The current Project */
87     private static $currentProject;
88
89     /** Whether to capture PHP errors to buffer. */
90     private static $phpErrorCapture = false;
91
92     /** Array of captured PHP errors */
93     private static $capturedPhpErrors = array();
94
95     /**
96      * @var OUtputStream Stream for standard output.
97      */
98     private static $out;
99
100     /**
101      * @var OutputStream Stream for error output.
102      */
103     private static $err;
104
105     /**
106      * @var boolean Whether we are using a logfile.
107      */
108     private static $isLogFileUsed = false;
109     
110     /**
111      * Enter description here...
112      *
113      * @param string $clazz Class that was requested.
114      */
115     public static function autoload($clazz)
116     {
117         
118         
119         $file = str_replace('::', DIRECTORY_SEPARATOR, $clazz) . '.php';
120         
121         foreach(explode(PATH_SEPARATOR, get_include_path()) as $path) {
122             if (is_readable($path . DIRECTORY_SEPARATOR . $file)) {
123                 // print "Autloaded: $file\n";
124                 require $path . DIRECTORY_SEPARATOR . $file;
125                 return true;
126             }
127         }
128         
129         print "Failed to autoload: " . $clazz . "\n";
130         // print new Exception('trace');
131         
132         return false;
133     }
134     
135     /**
136      * Entry point allowing for more options from other front ends.
137      *
138      * This method encapsulates the complete build lifecycle.
139      *
140      * @param array &$args The commandline args passed to phing shell script.
141      * @param array $additionalUserProperties   Any additional properties to be passed to Phing (alternative front-end might implement this).
142      *                                          These additional properties will be available using the getDefinedProperty() method and will
143      *                                          be added to the project's "user" properties.
144      * @return void
145      * @see execute()
146      * @see runBuild()
147      */
148     public static function start(&$args, $additionalUserProperties = null) {
149
150         try {
151             $m = new Phing();
152             $m->execute($args);
153         } catch (Exception $exc) {
154             self::handleLogfile(); // clean up log file before attempting to print message
155             $m->printMessage($exc);
156             self::halt(-1); // Parameter error
157         }
158
159         if ($additionalUserProperties !== null) {
160             $keys = $m->additionalUserProperties->keys();
161             while(count($keys)) {
162                 $key = array_shift($keys);
163                 $property = $m->additionalUserProperties->getProperty($key);
164                 $m->setDefinedProperty($key, $property);
165             }
166         }
167
168         try {
169             $m->runBuild();
170         } catch(Exception $exc) {
171             if ($exc instanceof ConfigurationException) {
172                 if (self::$msgOutputLevel >= Project::MSG_VERBOSE) {
173                     self::$out->write($exc->__toString() . PHP_EOL);
174                 } else {
175                     self::$out->write($exc->getMessage() . PHP_EOL);
176                 }
177             }
178             self::handleLogfile();
179             self::halt(1); // Errors occured
180         }
181
182         // everything fine, shutdown
183         self::handleLogfile();
184         self::halt(0);
185     }
186
187     /**
188      * Prints the message of the Exception if it's not null.
189      * @param Exception $t
190      */
191     public static function printMessage(Exception $t) {
192         if (self::getMsgOutputLevel() <= Project::MSG_DEBUG) {
193             self::$err->write($t->__toString());
194         } else {
195             self::$err->write($t->getMessage());
196         }
197     }
198
199     /**
200      * Sets the stdout and stderr streams if they are not already set.
201      */
202     private static function initializeOutputStreams() {
203         if (self::$out === null) {
204             self::$out = new OutputStream(fopen("php://stdout", "w"));
205         }
206         if (self::$err === null) {
207             self::$err = new OutputStream(fopen("php://stderr", "w"));
208         }
209     }
210
211     /**
212      * Sets the stream to use for standard (non-error) output.
213      * @param OutputStream $stream The stream to use for standard output.
214      */
215     public static function setOutputStream(OutputStream $stream) {
216         self::$out = $stream;
217     }
218
219     /**
220      * Gets the stream to use for standard (non-error) output.
221      * @return OutputStream
222      */
223     public static function getOutputStream() {
224         return self::$out;
225     }
226
227     /**
228      * Sets the stream to use for error output.
229      * @param OutputStream $stream The stream to use for error output.
230      */
231     public static function setErrorStream(OutputStream $stream) {
232         self::$err = $stream;
233     }
234
235     /**
236      * Gets the stream to use for error output.
237      * @return OutputStream
238      */
239     public static function getErrorStream() {
240         return self::$err;
241     }
242
243     /**
244      * Close logfiles, if we have been writing to them.
245      *
246      * @since Phing 2.3.0
247      */
248     private static function handleLogfile() {
249         if (self::$isLogFileUsed) {
250             self::$err->close();
251             self::$out->close();
252         }
253     }
254
255     /**
256      * Making output level a static property so that this property
257      * can be accessed by other parts of the system, enabling
258      * us to display more information -- e.g. backtraces -- for "debug" level.
259      * @return int
260      */
261     public static function getMsgOutputLevel() {
262         return self::$msgOutputLevel;
263     }
264
265     /**
266      * Command line entry point. This method kicks off the building
267      * of a project object and executes a build using either a given
268      * target or the default target.
269      *
270      * @param array $args Command line args.
271      * @return void
272      */
273     public static function fire($args) {
274         self::start($args, null);
275     }
276
277     /**
278      * Setup/initialize Phing environment from commandline args.
279      * @param array $args commandline args passed to phing shell.
280      * @return void
281      */
282     public function execute($args) {
283
284         self::$definedProps = new Properties();
285         $this->searchForThis = null;
286
287         // cycle through given args
288         for ($i = 0, $argcount = count($args); $i < $argcount; ++$i) {
289             // ++$i intentional here, as first param is script name
290             $arg = $args[$i];
291
292             if ($arg == "-help" || $arg == "-h") {
293                 $this->printUsage();
294                 return;
295             } elseif ($arg == "-version" || $arg == "-v") {
296                 $this->printVersion();
297                 return;
298             } elseif ($arg == "-quiet" || $arg == "-q") {
299                 self::$msgOutputLevel = Project::MSG_WARN;
300             } elseif ($arg == "-verbose") {
301                 $this->printVersion();
302                 self::$msgOutputLevel = Project::MSG_VERBOSE;
303             } elseif ($arg == "-debug") {
304                 $this->printVersion();
305                 self::$msgOutputLevel = Project::MSG_DEBUG;
306             } elseif ($arg == "-logfile") {
307                 try {
308                     // see: http://phing.info/trac/ticket/65
309                     if (!isset($args[$i+1])) {
310                         $msg = "You must specify a log file when using the -logfile argument\n";
311                         throw new ConfigurationException($msg);
312                     } else {
313                         $logFile = new File($args[++$i]);
314                         $out = new FileOutputStream($logFile); // overwrite
315                         self::setOutputStream($out);
316                         self::setErrorStream($out);
317                         self::$isLogFileUsed = true;
318                     }
319                 } catch (IOException $ioe) {
320                     $msg = "Cannot write on the specified log file. Make sure the path exists and you have write permissions.";
321                     throw new ConfigurationException($msg, $ioe);
322                 }
323             } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
324                 if (!isset($args[$i+1])) {
325                     $msg = "You must specify a buildfile when using the -buildfile argument.";
326                     throw new ConfigurationException($msg);
327                 } else {
328                     $this->buildFile = new File($args[++$i]);
329                 }
330             } elseif ($arg == "-listener") {
331                 if (!isset($args[$i+1])) {
332                     $msg = "You must specify a listener class when using the -listener argument";
333                     throw new ConfigurationException($msg);
334                 } else {
335                     $this->listeners[] = $args[++$i];
336                 }
337             } elseif (StringHelper::startsWith("-D", $arg)) {
338                 $name = substr($arg, 2);
339                 $value = null;
340                 $posEq = strpos($name, "=");
341                 if ($posEq !== false) {
342                     $value = substr($name, $posEq+1);
343                     $name  = substr($name, 0, $posEq);
344                 } elseif ($i < count($args)-1) {
345                     $value = $args[++$i];
346                 }
347                 self::$definedProps->setProperty($name, $value);
348             } elseif ($arg == "-logger") {
349                 if (!isset($args[$i+1])) {
350                     $msg = "You must specify a classname when using the -logger argument";
351                     throw new ConfigurationException($msg);
352                 } else {
353                     $this->loggerClassname = $args[++$i];
354                 }
355             } elseif ($arg == "-inputhandler") {
356                 if ($this->inputHandlerClassname !== null) {
357                     throw new ConfigurationException("Only one input handler class may be specified.");
358                 }
359                 if (!isset($args[$i+1])) {
360                     $msg = "You must specify a classname when using the -inputhandler argument";
361                     throw new ConfigurationException($msg);
362                 } else {
363                     $this->inputHandlerClassname = $args[++$i];
364                 }
365             } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l" || $arg == "-p") {
366                 // set the flag to display the targets and quit
367                 $this->projectHelp = true;
368             } elseif ($arg == "-find") {
369                 // eat up next arg if present, default to build.xml
370                 if ($i < count($args)-1) {
371                     $this->searchForThis = $args[++$i];
372                 } else {
373                     $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
374                 }
375             } elseif (substr($arg,0,1) == "-") {
376                 // we don't have any more args
377                 self::$err->write("Unknown argument: $arg");
378                 self::printUsage();
379                 return;
380             } else {
381                 // if it's no other arg, it may be the target
382                 array_push($this->targets, $arg);
383             }
384         }
385
386         // if buildFile was not specified on the command line,
387         if ($this->buildFile === null) {
388             // but -find then search for it
389             if ($this->searchForThis !== null) {
390                 $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
391             } else {
392                 $this->buildFile = new File(self::DEFAULT_BUILD_FILENAME);
393             }
394         }
395         // make sure buildfile exists
396         if (!$this->buildFile->exists()) {
397             throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " does not exist!");
398         }
399
400         // make sure it's not a directory
401         if ($this->buildFile->isDirectory()) {
402             throw new ConfigurationException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
403         }
404
405         $this->readyToRun = true;
406     }
407
408     /**
409      * Helper to get the parent file for a given file.
410      *
411      * @param File $file
412      * @return File Parent file or null if none
413      */
414     private function _getParentFile(File $file) {
415         $filename = $file->getAbsolutePath();
416         $file     = new File($filename);
417         $filename = $file->getParent();
418         return ($filename === null) ? null : new File($filename);
419     }
420
421     /**
422      * Search parent directories for the build file.
423      *
424      * Takes the given target as a suffix to append to each
425      * parent directory in search of a build file.  Once the
426      * root of the file-system has been reached an exception
427      * is thrown.
428      *
429      * @param string $start Start file path.
430      * @param string $suffix Suffix filename to look for in parents.
431      * @return File A handle to the build file
432      *
433      * @throws BuildException    Failed to locate a build file
434      */
435     private function _findBuildFile($start, $suffix) {
436         $startf = new File($start);
437         $parent = new File($startf->getAbsolutePath());
438         $file   = new File($parent, $suffix);
439
440         // check if the target file exists in the current directory
441         while (!$file->exists()) {
442             // change to parent directory
443             $parent = $this->_getParentFile($parent);
444
445             // if parent is null, then we are at the root of the fs,
446             // complain that we can't find the build file.
447             if ($parent === null) {
448                 throw new ConfigurationException("Could not locate a build file!");
449             }
450             // refresh our file handle
451             $file = new File($parent, $suffix);
452         }
453         return $file;
454     }
455
456     /**
457      * Executes the build.
458      * @return void
459      */
460     function runBuild() {
461
462         if (!$this->readyToRun) {
463             return;
464         }
465
466         $project = new Project();
467
468         self::setCurrentProject($project);
469         //set_error_handler(array('phing::Phing', 'handlePhpError'));
470
471         $error = null;
472
473         $this->addBuildListeners($project);
474         $this->addInputHandler($project);
475
476         // set this right away, so that it can be used in logging.
477         $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
478
479         try {
480             $project->fireBuildStarted();
481             $project->init();
482         } catch (Exception $exc) {
483             $project->fireBuildFinished($exc);
484             throw $exc;
485         }
486
487         $project->setUserProperty("phing.version", $this->getPhingVersion());
488
489         $e = self::$definedProps->keys();
490         while (count($e)) {
491             $arg   = (string) array_shift($e);
492             $value = (string) self::$definedProps->getProperty($arg);
493             $project->setUserProperty($arg, $value);
494         }
495         unset($e);
496
497         $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
498
499         // first use the Configurator to create the project object
500         // from the given build file.
501
502         try {
503             phing::parser::ProjectConfigurator::configureProject($project, $this->buildFile);
504         } catch (Exception $exc) {
505             $project->fireBuildFinished($exc);
506             restore_error_handler();
507             self::unsetCurrentProject();
508             throw $exc;
509         }
510
511         // make sure that we have a target to execute
512         if (count($this->targets) === 0) {
513             $this->targets[] = $project->getDefaultTarget();
514         }
515
516         // execute targets if help param was not given
517         if (!$this->projectHelp) {
518
519             try {
520                 $project->executeTargets($this->targets);
521             } catch (Exception $exc) {
522                 $project->fireBuildFinished($exc);
523                 restore_error_handler();
524                 self::unsetCurrentProject();
525                 throw $exc;
526             }
527         }
528         // if help is requested print it
529         if ($this->projectHelp) {
530             try {
531                 $this->printDescription($project);
532                 $this->printTargets($project);