root/tags/2.2.0RC1/classes/phing/Phing.php

Revision 11, 41.2 kB (checked in by hans, 3 years ago)

Moved VERSION.TXT into top-level etc/ dir.

Line 
1 <?php
2 /*
3  * $Id: Phing.php,v 1.51 2006/01/06 15:12:33 hlellelid Exp $
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 require_once 'phing/Project.php';
23 require_once 'phing/ProjectComponent.php';
24 require_once 'phing/Target.php';
25 require_once 'phing/Task.php';
26
27 include_once 'phing/BuildException.php';
28 include_once 'phing/BuildEvent.php';
29
30 include_once 'phing/parser/Location.php';
31 include_once 'phing/parser/ExpatParser.php';
32 include_once 'phing/parser/AbstractHandler.php';
33 include_once 'phing/parser/ProjectConfigurator.php';
34 include_once 'phing/parser/RootHandler.php';
35 include_once 'phing/parser/ProjectHandler.php';
36 include_once 'phing/parser/TaskHandler.php';
37 include_once 'phing/parser/TargetHandler.php';
38 include_once 'phing/parser/DataTypeHandler.php';
39 include_once 'phing/parser/NestedElementHandler.php';
40
41 include_once 'phing/system/util/Properties.php';
42 include_once 'phing/util/StringHelper.php';
43 include_once 'phing/system/io/PhingFile.php';
44 include_once 'phing/system/io/FileReader.php';
45 include_once 'phing/system/util/Register.php';
46
47 /**
48  * Entry point into Phing.  This class handles the full lifecycle of a build -- from
49  * parsing & handling commandline arguments to assembling the project to shutting down
50  * and cleaning up in the end.
51  *
52  * If you are invoking Phing from an external application, this is still
53  * the class to use.  Your applicaiton can invoke the start() method, passing
54  * any commandline arguments or additional properties.
55  *
56  * @author    Andreas Aderhold <andi@binarycloud.com>
57  * @author    Hans Lellelid <hans@xmpl.org>
58  * @version   $Revision: 1.51 $
59  * @package   phing
60  */
61 class Phing {
62
63     /** The default build file name */
64     const DEFAULT_BUILD_FILENAME = "build.xml";
65
66     /** Our current message output status. Follows PROJECT_MSG_XXX */
67     private static $msgOutputLevel = PROJECT_MSG_INFO;
68
69     /** PhingFile that we are using for configuration */
70     private $buildFile = null;
71
72     /** The build targets */
73     private $targets = array();
74
75     /**
76      * Set of properties that are passed in from commandline or invoking code.
77      * @var Properties
78      */
79     private static $definedProps;
80
81     /** Names of classes to add as listeners to project */
82     private $listeners = array();
83
84     private $loggerClassname = null;
85
86     /** The class to handle input (can be only one). */
87     private $inputHandlerClassname;
88     
89     /** Indicates if this phing should be run */
90     private $readyToRun = false;
91
92     /** Indicates we should only parse and display the project help information */
93     private $projectHelp = false;
94     
95     /** Used by utility function getResourcePath() */
96     private static $importPaths;
97     
98     /** System-wide static properties (moved from System) */
99     private static $properties = array();
100     
101     /** Static system timer. */
102     private static $timer;
103     
104     /** The current Project */
105     private static $currentProject;
106     
107     /** Whether to capture PHP errors to buffer. */
108     private static $phpErrorCapture = false;
109     
110     /** Array of captured PHP errors */
111     private static $capturedPhpErrors = array();
112     
113     /**
114      * Prints the message of the Exception if it's not null.
115      */
116     function printMessage(Exception $t) {
117         print($t->getMessage() . "\n");
118         if (self::getMsgOutputLevel() === PROJECT_MSG_DEBUG) {
119             print($t->getTraceAsString()."\n");
120             if ($t instanceof Exception) {               
121                 $c = $t->getCause();
122                 if ($c !== null) {
123                     print("Wrapped exception trace:\n");
124                     print($c->getTraceAsString() . "\n");
125                 }
126             }
127         } // if output level is DEBUG
128     }
129
130     /**
131      * Entry point allowing for more options from other front ends.
132      *
133      * This method encapsulates the complete build lifecycle.
134      *
135      * @param array &$args The commandline args passed to phing shell script.
136      * @param array $additionalUserProperties   Any additional properties to be passed to Phing (alternative front-end might implement this).
137      *                                          These additional properties will be available using the getDefinedProperty() method and will
138      *                                          be added to the project's "user" properties.
139      * @return void
140      * @see execute()
141      * @see runBuild()
142      */
143     public static function start(&$args, $additionalUserProperties = null) {
144
145         try {
146             $m = new Phing();
147             $m->execute($args);
148         } catch (Exception $exc) {
149             $m->printMessage($exc);
150             self::halt(-1); // Parameter error
151         }
152
153         if ($additionalUserProperties !== null) {
154             $keys = $m->additionalUserProperties->keys();
155             while(count($keys)) {
156                 $key = array_shift($keys);
157                 $property = $m->additionalUserProperties->getProperty($key);
158                 $m->setDefinedProperty($key, $property);
159             }
160         }
161
162         try {
163             $m->runBuild();
164         } catch(Exception $exc) {
165             self::halt(1); // Errors occured
166         }
167         
168         // everything fine, shutdown
169         self::halt(0); // no errors, everything is cake
170     }
171     
172     /**
173      * Making output level a static property so that this property
174      * can be accessed by other parts of the system, enabling
175      * us to display more information -- e.g. backtraces -- for "debug" level.
176      * @return int
177      */
178     public static function getMsgOutputLevel() {
179         return self::$msgOutputLevel;
180     }       
181     
182     /**
183      * Command line entry point. This method kicks off the building
184      * of a project object and executes a build using either a given
185      * target or the default target.
186      *
187      * @param array $args Command line args.
188      * @return void
189      */
190     public static function fire($args) {
191         self::start($args, null);
192     }
193     
194     /**
195      * Setup/initialize Phing environment from commandline args.
196      * @param array $args commandline args passed to phing shell.
197      * @return void
198      */
199     public function execute($args) {
200     
201         self::$definedProps = new Properties();
202         $this->searchForThis = null;
203
204         // cycle through given args
205         for ($i = 0, $argcount = count($args); $i < $argcount; ++$i) {
206                             // ++$i intentional here, as first param is script name
207             $arg = $args[$i];
208
209             if ($arg == "-help" || $arg == "-h") {
210                 $this->printUsage();
211                 return;
212             } elseif ($arg == "-version" || $arg == "-v") {
213                 $this->printVersion();
214                 return;
215             } elseif ($arg == "-quiet" || $arg == "-q") {
216                 self::$msgOutputLevel = PROJECT_MSG_WARN;
217             } elseif ($arg == "-verbose") {
218                 $this->printVersion();
219                 self::$msgOutputLevel = PROJECT_MSG_VERBOSE;
220             } elseif ($arg == "-debug") {
221                 $this->printVersion();
222                 self::$msgOutputLevel = PROJECT_MSG_DEBUG;
223             } elseif ($arg == "-logfile") {
224                 try { // try to set logfile
225                     if (!isset($args[$i+1])) {
226                         print("You must specify a log file when using the -logfile argument\n");
227                         return;
228                     } else {
229                         $logFile = new PhingFile($args[++$i]);
230                         $this->loggerClassname = 'phing.listener.PearLogger';
231                         $this->setDefinedProperty('pear.log.name', $logFile->getAbsolutePath());
232                     }
233                 } catch (IOException $ioe) {
234                     print("Cannot write on the specified log file. Make sure the path exists and you have write permissions.\n");
235                     throw $ioe;
236                 }
237             } elseif ($arg == "-buildfile" || $arg == "-file" || $arg == "-f") {
238                 if (!isset($args[$i+1])) {
239                     print("You must specify a buildfile when using the -buildfile argument\n");
240                     return;
241                 } else {
242                     $this->buildFile = new PhingFile($args[++$i]);
243                 }
244             } elseif ($arg == "-listener") {
245                 if (!isset($args[$i+1])) {
246                     print("You must specify a listener class when using the -listener argument\n");
247                     return;
248                 } else {
249                     $this->listeners[] = $args[++$i];
250                 }
251                 
252             } elseif (StringHelper::startsWith("-D", $arg)) {
253                 $name = substr($arg, 2);
254                 $value = null;
255                 $posEq = strpos($name, "=");
256                 if ($posEq !== false) {
257                     $value = substr($name, $posEq+1);
258                     $name  = substr($name, 0, $posEq);
259                 } elseif ($i < count($args)-1) {
260                     $value = $args[++$i];
261                 }
262                 self::$definedProps->setProperty($name, $value);
263             } elseif ($arg == "-logger") {
264                 if (!isset($args[$i+1])) {
265                     print("You must specify a classname when using the -logger argument\n");
266                     return;
267                 } else {
268                     $this->loggerClassname = $args[++$i];
269                 }
270             } elseif ($arg == "-inputhandler") {
271                 if ($this->inputHandlerClassname !== null) {
272                     throw new BuildException("Only one input handler class may be specified.");
273                 }
274                 if (!isset($args[$i+1])) {
275                     print("You must specify a classname when using the -inputhandler argument\n");
276                     return;
277                 } else {
278                     $this->inputHandlerClassname = $args[++$i];
279                 }
280             } elseif ($arg == "-projecthelp" || $arg == "-targets" || $arg == "-list" || $arg == "-l") {
281                 // set the flag to display the targets and quit
282                 $this->projectHelp = true;
283             } elseif ($arg == "-find") {
284                 // eat up next arg if present, default to build.xml
285                 if ($i < count($args)-1) {
286                     $this->searchForThis = $args[++$i];
287                 } else {
288                     $this->searchForThis = self::DEFAULT_BUILD_FILENAME;
289                 }
290             } elseif (substr($arg,0,1) == "-") {
291                 // we don't have any more args
292                 print("Unknown argument: $arg\n");
293                 $this->printUsage();
294                 return;
295             } else {
296                 // if it's no other arg, it may be the target
297                 array_push($this->targets, $arg);
298             }
299         }
300
301         // if buildFile was not specified on the command line,
302         if ($this->buildFile === null) {
303             // but -find then search for it
304             if ($this->searchForThis !== null) {
305                 $this->buildFile = $this->_findBuildFile(self::getProperty("user.dir"), $this->searchForThis);
306             } else {
307                 $this->buildFile = new PhingFile(self::DEFAULT_BUILD_FILENAME);
308             }
309         }
310         // make sure buildfile exists
311         if (!$this->buildFile->exists()) {
312             throw new BuildException("Buildfile: " . $this->buildFile->__toString() . " does not exist!");
313         }
314
315         // make sure it's not a directory
316         if ($this->buildFile->isDirectory()) {   
317             throw new BuildException("Buildfile: " . $this->buildFile->__toString() . " is a dir!");
318         }
319
320         $this->readyToRun = true;
321     }
322
323     /**
324      * Helper to get the parent file for a given file.
325      *
326      * @param PhingFile $file
327      * @return PhingFile Parent file or null if none
328      */
329     function _getParentFile(PhingFile $file) {
330         $filename = $file->getAbsolutePath();
331         $file     = new PhingFile($filename);
332         $filename = $file->getParent();
333
334         if ($filename !== null && self::$msgOutputLevel >= PROJECT_MSG_VERBOSE) {
335             print("Searching in $filename\n");
336         }
337
338         return ($filename === null) ? null : new PhingFile($filename);
339     }
340
341     /**
342      * Search parent directories for the build file.
343      *
344      * Takes the given target as a suffix to append to each
345      * parent directory in search of a build file.  Once the
346      * root of the file-system has been reached an exception
347      * is thrown.
348      *
349      * @param string $start Start file path.
350      * @param string $suffix Suffix filename to look for in parents.
351      * @return PhingFile A handle to the build file
352      *
353      * @throws BuildException    Failed to locate a build file
354      */
355     function _findBuildFile($start, $suffix) {
356         if (self::$msgOutputLevel >= PROJECT_MSG_INFO) {
357             print("Searching for $suffix ...\n");
358         }
359         $startf = new PhingFile($start);
360         $parent = new PhingFile($startf->getAbsolutePath());
361         $file   = new PhingFile($parent, $suffix);
362
363         // check if the target file exists in the current directory
364         while (!$file->exists()) {
365             // change to parent directory
366             $parent = $this->_getParentFile($parent);
367
368             // if parent is null, then we are at the root of the fs,
369             // complain that we can't find the build file.
370             if ($parent === null) {
371                 throw new BuildException("Could not locate a build file!");
372             }
373             // refresh our file handle
374             $file = new PhingFile($parent, $suffix);
375         }
376         return $file;
377     }
378
379     /**
380      * Executes the build.
381      * @return void
382      */
383     function runBuild() {
384
385         if (!$this->readyToRun) {
386             return;
387         }
388         
389         $project = new Project();
390         
391         self::setCurrentProject($project);
392         set_error_handler(array('Phing', 'handlePhpError'));
393         
394         $error = null;
395
396         $this->addBuildListeners($project);
397         $this->addInputHandler($project);
398         
399         // set this right away, so that it can be used in logging.
400         $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
401
402         try {
403             $project->fireBuildStarted();
404             $project->init();
405         } catch (Exception $exc) {
406             $project->fireBuildFinished($exc);
407             throw $exc;       
408         }
409
410         $project->setUserProperty("phing.version", $this->getPhingVersion());
411
412         $e = self::$definedProps->keys();
413         while (count($e)) {
414             $arg   = (string) array_shift($e);
415             $value = (string) self::$definedProps->getProperty($arg);
416             $project->setUserProperty($arg, $value);
417         }
418         unset($e);
419
420         $project->setUserProperty("phing.file", $this->buildFile->getAbsolutePath());
421
422         // first use the Configurator to create the project object
423         // from the given build file.
424                 
425         try {
426             ProjectConfigurator::configureProject($project, $this->buildFile);
427         } catch (Exception $exc) {
428             $project->fireBuildFinished($exc);
429             restore_error_handler();
430             self::unsetCurrentProject();
431             throw $exc;
432         }         
433
434         // make sure that we have a target to execute
435         if (count($this->targets) === 0) {
436             $this->targets[] = $project->getDefaultTarget();
437         }
438
439         // execute targets if help param was not given
440         if (!$this->projectHelp) {
441             
442             try {
443                 $project->executeTargets($this->targets);
444             } catch (Exception $exc) {
445                 $project->fireBuildFinished($exc);
446                 restore_error_handler();
447                 self::unsetCurrentProject();
448                 throw $exc;
449             }
450         }
451         // if help is requested print it
452         if ($this->projectHelp) {
453             try {
454                 $this->printDescription($project);
455                 $this->printTargets($project);
456             } catch (Exception $exc) {
457                 $project->fireBuildFinished($exc);
458                 restore_error_handler();
459                 self::unsetCurrentProject();
460                 throw $exc;
461             }
462         }
463                 
464         // finally {
465         if (!$this->projectHelp) {
466             $project->fireBuildFinished(null);
467         }
468         
469         restore_error_handler();
470         self::unsetCurrentProject();
471     }
472     
473     /**
474      * Bind any default build listeners to this project.
475      * Currently this means adding the logger.
476      * @param Project $project
477      * @return void
478      */
479     private function addBuildListeners(Project $project) {
480         // Add the default listener
481         $project->addBuildListener($this->createLogger());
482     }
483     
484     /**
485      * Creates the InputHandler and adds it to the project.
486      *
487      * @param Project $project the project instance.
488      *
489      * @throws BuildException if a specified InputHandler
490      *                           class could not be loaded.
491      */
492     private function addInputHandler(Project $project) {
493         if ($this->inputHandlerClassname === null) {
494             $handler = new DefaultInputHandler();
495         } else {
496             try {
497                 $clz = Phing::import($this->inputHandlerClassname);
498                 $handler = new $clz();
499                 if ($project !== null && method_exists($handler, 'setProject')) {
500                     $handler->setProject($project);
501                 }
502             } catch (Exception $e) {
503                 $msg = "Unable to instantiate specified input handler "
504                     . "class " . $this->inputHandlerClassname . " : "
505                     . $e->getMessage();
506                 throw new BuildException($msg);
507             }
508         }
509         $project->setInputHandler($handler);
510     }
511
512     /**
513      * Creates the default build logger for sending build events to the log.
514      * @return BuildListener The created Logger
515      */
516     private function createLogger() {
517         if ($this->loggerClassname !== null) {
518             self::import($this->loggerClassname);
519             // get class name part           
520             $classname = self::import($this->loggerClassname);
521             $logger = new $classname;
522         } else {
523             require_once 'phing/listener/DefaultLogger.php';
524             $logger = new DefaultLogger();
525         }
526         $logger->setMessageOutputLevel(self::$msgOutputLevel);
527         return $logger;
528     }
529     
530     /**
531      * Sets the current Project
532      * @param Project $p
533      */
534     public static function setCurrentProject($p) {
535