1<?php 2 3/** 4 * $Id: f519e4e90fdc87bd40ee563645b5def3e3ff0c0a $ 5 * 6 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 8 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 12 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 13 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 14 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 15 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 16 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 * 18 * This software consists of voluntary contributions made by many individuals 19 * and is licensed under the LGPL. For more information please see 20 * <http://phing.info>. 21 */ 22 23require_once 'phing/Task.php'; 24 25/** 26 * Executes a command on the shell. 27 * 28 * @author Andreas Aderhold <andi@binarycloud.com> 29 * @author Hans Lellelid <hans@xmpl.org> 30 * @author Christian Weiske <cweiske@cweiske.de> 31 * @version $Id: f519e4e90fdc87bd40ee563645b5def3e3ff0c0a $ 32 * @package phing.tasks.system 33 */ 34class ExecTask extends Task 35{ 36 37 /** 38 * Command to be executed 39 * @var string 40 */ 41 protected $realCommand; 42 43 /** 44 * Given command 45 * @var string 46 */ 47 protected $command; 48 49 /** 50 * Commandline managing object 51 * 52 * @var Commandline 53 */ 54 protected $commandline; 55 56 /** 57 * Working directory. 58 * @var PhingFile 59 */ 60 protected $dir; 61 62 /** 63 * Operating system. 64 * @var string 65 */ 66 protected $os; 67 68 /** 69 * Whether to escape shell command using escapeshellcmd(). 70 * @var boolean 71 */ 72 protected $escape = false; 73 74 /** 75 * Where to direct output. 76 * @var File 77 */ 78 protected $output; 79 80 /** 81 * Whether to use PHP's passthru() function instead of exec() 82 * @var boolean 83 */ 84 protected $passthru = false; 85 86 /** 87 * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE 88 * @var boolean 89 */ 90 protected $logOutput = false; 91 92 /** 93 * Logging level for status messages 94 * @var integer 95 */ 96 protected $logLevel = Project::MSG_VERBOSE; 97 98 /** 99 * Where to direct error output. 100 * @var File 101 */ 102 protected $error; 103 104 /** 105 * If spawn is set then [unix] programs will redirect stdout and add '&'. 106 * @var boolean 107 */ 108 protected $spawn = false; 109 110 /** 111 * Property name to set with return value from exec call. 112 * 113 * @var string 114 */ 115 protected $returnProperty; 116 117 /** 118 * Property name to set with output value from exec call. 119 * 120 * @var string 121 */ 122 protected $outputProperty; 123 124 /** 125 * Whether to check the return code. 126 * @var boolean 127 */ 128 protected $checkreturn = false; 129 130 131 132 public function __construct() 133 { 134 $this->commandline = new Commandline(); 135 } 136 137 /** 138 * Main method: wraps execute() command. 139 * 140 * @return void 141 */ 142 public function main() 143 { 144 if (!$this->isApplicable()) { 145 return; 146 } 147 148 $this->prepare(); 149 $this->buildCommand(); 150 list($return, $output) = $this->executeCommand(); 151 $this->cleanup($return, $output); 152 } 153 154 /** 155 * Checks whether the command shall be executed 156 * 157 * @return boolean False if the exec command shall not be run 158 */ 159 protected function isApplicable() 160 { 161 if ($this->os === null) { 162 return true; 163 } 164 165 $myos = Phing::getProperty('os.name'); 166 $this->log('Myos = ' . $myos, Project::MSG_VERBOSE); 167 168 if (strpos($this->os, $myos) !== false) { 169 // this command will be executed only on the specified OS 170 // OS matches 171 return true; 172 } 173 174 $this->log( 175 sprintf( 176 'Operating system %s not found in %s', 177 $myos, $this->os 178 ), 179 Project::MSG_VERBOSE 180 ); 181 return false; 182 } 183 184 /** 185 * Prepares the command building and execution, i.e. 186 * changes to the specified directory. 187 * 188 * @return void 189 */ 190 protected function prepare() 191 { 192 if ($this->dir === null) { 193 return; 194 } 195 196 // expand any symbolic links first 197 if (!$this->dir->getCanonicalFile()->isDirectory()) { 198 throw new BuildException( 199 "'" . (string) $this->dir . "' is not a valid directory" 200 ); 201 } 202 $this->currdir = getcwd(); 203 @chdir($this->dir->getPath()); 204 } 205 206 /** 207 * Builds the full command to execute and stores it in $command. 208 * 209 * @return void 210 * @uses $command 211 */ 212 protected function buildCommand() 213 { 214 if ($this->command === null && $this->commandline->getExecutable() === null) { 215 throw new BuildException( 216 'ExecTask: Please provide "command" OR "executable"' 217 ); 218 } else if ($this->command === null) { 219 $this->realCommand = Commandline::toString($this->commandline->getCommandline(), $this->escape); 220 } else if ($this->commandline->getExecutable() === null) { 221 $this->realCommand = $this->command; 222 223 //we need to escape the command only if it's specified directly 224 // commandline takes care of "executable" already 225 if ($this->escape == true) { 226 $this->realCommand = escapeshellcmd($this->realCommand); 227 } 228 } else { 229 throw new BuildException( 230 'ExecTask: Either use "command" OR "executable"' 231 ); 232 } 233 234 if ($this->error !== null) { 235 $this->realCommand .= ' 2> ' . escapeshellarg($this->error->getPath()); 236 $this->log( 237 "Writing error output to: " . $this->error->getPath(), 238 $this->logLevel 239 ); 240 } 241 242 if ($this->output !== null) { 243 $this->realCommand .= ' 1> ' . escapeshellarg($this->output->getPath()); 244 $this->log( 245 "Writing standard output to: " . $this->output->getPath(), 246 $this->logLevel 247 ); 248 } elseif ($this->spawn) { 249 $this->realCommand .= ' 1>/dev/null'; 250 $this->log("Sending output to /dev/null", $this->logLevel); 251 } 252 253 // If neither output nor error are being written to file 254 // then we'll redirect error to stdout so that we can dump 255 // it to screen below. 256 257 if ($this->output === null && $this->error === null && $this->passthru === false) { 258 $this->realCommand .= ' 2>&1'; 259 } 260 261 // we ignore the spawn boolean for windows 262 if ($this->spawn) { 263 $this->realCommand .= ' &'; 264 } 265 } 266 267 /** 268 * Executes the command and returns return code and output. 269 * 270 * @return array array(return code, array with output) 271 */ 272 protected function executeCommand() 273 { 274 $this->log("Executing command: " . $this->realCommand, $this->logLevel); 275 276 $output = array(); 277 $return = null; 278 279 if ($this->passthru) { 280 passthru($this->realCommand, $return); 281 } else { 282 exec($this->realCommand, $output, $return); 283 } 284 285 return array($return, $output); 286 } 287 288 /** 289 * Runs all tasks after command execution: 290 * - change working directory back 291 * - log output 292 * - verify return value 293 * 294 * @param integer $return Return code 295 * @param array $output Array with command output 296 * 297 * @return void 298 */ 299 protected function cleanup($return, $output) 300 { 301 if ($this->dir !== null) { 302 @chdir($this->currdir); 303 } 304 305 $outloglevel = $this->logOutput ? Project::MSG_INFO : Project::MSG_VERBOSE; 306 foreach ($output as $line) { 307 $this->log($line, $outloglevel); 308 } 309 310 if ($this->returnProperty) { 311 $this->project->setProperty($this->returnProperty, $return); 312 } 313 314 if ($this->outputProperty) { 315 $this->project->setProperty( 316 $this->outputProperty, implode("\n", $output) 317 ); 318 } 319 320 if ($return != 0 && $this->checkreturn) { 321 throw new BuildException("Task exited with code $return"); 322 } 323 } 324 325 326 /** 327 * The command to use. 328 * 329 * @param mixed $command String or string-compatible (e.g. w/ __toString()). 330 * 331 * @return void 332 */ 333 public function setCommand($command) 334 { 335 $this->command = "" . $command; 336 } 337 338 /** 339 * The executable to use. 340 * 341 * @param mixed $executable String or string-compatible (e.g. w/ __toString()). 342 * 343 * @return void 344 */ 345 public function setExecutable($executable) 346 { 347 $this->commandline->setExecutable((string)$executable); 348 } 349 350 /** 351 * Whether to use escapeshellcmd() to escape command. 352 * 353 * @param boolean $escape If the command shall be escaped or not 354 * 355 * @return void 356 */ 357 public function setEscape($escape) 358 { 359 $this->escape = (bool) $escape; 360 } 361 362 /** 363 * Specify the working directory for executing this command. 364 * 365 * @param PhingFile $dir Working directory 366 * 367 * @return void 368 */ 369 public function setDir(PhingFile $dir) 370 { 371 $this->dir = $dir; 372 } 373 374 /** 375 * Specify OS (or muliple OS) that must match in order to execute this command. 376 * 377 * @param string $os Operating system string (e.g. "Linux") 378 * 379 * @return void 380 */ 381 public function setOs($os) 382 { 383 $this->os = (string) $os; 384 } 385 386 /** 387 * File to which output should be written. 388 * 389 * @param PhingFile $f Output log file 390 * 391 * @return void 392 */ 393 public function setOutput(PhingFile $f) 394 { 395 $this->output = $f; 396 } 397 398 /** 399 * File to which error output should be written. 400 * 401 * @param PhingFile $f Error log file 402 * 403 * @return void 404 */ 405 public function setError(PhingFile $f) 406 { 407 $this->error = $f; 408 } 409 410 /** 411 * Whether to use PHP's passthru() function instead of exec() 412 * 413 * @param boolean $passthru If passthru shall be used 414 * 415 * @return void 416 */ 417 public function setPassthru($passthru) 418 { 419 $this->passthru = (bool) $passthru; 420 } 421 422 /** 423 * Whether to log returned output as MSG_INFO instead of MSG_VERBOSE 424 * 425 * @param boolean $logOutput If output shall be logged visibly 426 * 427 * @return void 428 */ 429 public function setLogoutput($logOutput) 430 { 431 $this->logOutput = (bool) $logOutput; 432 } 433 434 /** 435 * Whether to suppress all output and run in the background. 436 * 437 * @param boolean $spawn If the command is to be run in the background 438 * 439 * @return void 440 */ 441 public function setSpawn($spawn) 442 { 443 $this->spawn = (bool) $spawn; 444 } 445 446 /** 447 * Whether to check the return code. 448 * 449 * @param boolean $checkreturn If the return code shall be checked 450 * 451 * @return void 452 */ 453 public function setCheckreturn($checkreturn) 454 { 455 $this->checkreturn = (bool) $checkreturn; 456 } 457 458 /** 459 * The name of property to set to return value from exec() call. 460 * 461 * @param string $prop Property name 462 * 463 * @return void 464 */ 465 public function setReturnProperty($prop) 466 { 467 $this->returnProperty = $prop; 468 } 469 470 /** 471 * The name of property to set to output value from exec() call. 472 * 473 * @param string $prop Property name 474 * 475 * @return void 476 */ 477 public function setOutputProperty($prop) 478 { 479 $this->outputProperty = $prop; 480 } 481 482 /** 483 * Set level of log messages generated (default = verbose) 484 * 485 * @param string $level Log level 486 * 487 * @return void 488 */ 489 public function setLevel($level) 490 { 491 switch ($level) { 492 case 'error': 493 $this->logLevel = Project::MSG_ERR; 494 break; 495 case 'warning': 496 $this->logLevel = Project::MSG_WARN; 497 break; 498 case 'info': 499 $this->logLevel = Project::MSG_INFO; 500 break; 501 case 'verbose': 502 $this->logLevel = Project::MSG_VERBOSE; 503 break; 504 case 'debug': 505 $this->logLevel = Project::MSG_DEBUG; 506 break; 507 default: 508 throw new BuildException( 509 sprintf('Unknown log level "%s"', $level) 510 ); 511 } 512 } 513 514 /** 515 * Creates a nested <arg> tag. 516 * 517 * @return CommandlineArgument Argument object 518 */ 519 public function createArg() 520 { 521 return $this->commandline->createArgument(); 522 } 523} 524 525