diff --git a/classes/phing/tasks/system/ApplyTask.php b/classes/phing/tasks/system/ApplyTask.php index cea9f518d3..76af02e51a 100644 --- a/classes/phing/tasks/system/ApplyTask.php +++ b/classes/phing/tasks/system/ApplyTask.php @@ -27,54 +27,13 @@ * * @todo Add support for mapper, targetfile expressions */ -class ApplyTask extends Task +class ApplyTask extends ExecTask { use ResourceAware; - /** - * Configuration(s) - * - */ - //[TBA]const TARGETFILE_ID = '__TARGETFILE__'; const SOURCEFILE_ID = '__SOURCEFILE__'; - - /** - * Commandline managing object - * @var Commandline - */ - protected $commandline; - - /** - * Working directory - * @var phingfile - */ - protected $dir; protected $currentdirectory; - /** - * Command to be executed - * @var string - */ - protected $realCommand; - - /** - * Escape (shell) command using 'escapeshellcmd' before execution - * @var boolean - */ - protected $escape = false; - - /** - * Where to direct output - * @var phingfile - */ - protected $output; - - /** - * Where to direct error - * @var phingfile - */ - protected $error; - /** * Whether output should be appended to or overwrite an existing file * @var boolean @@ -94,35 +53,12 @@ class ApplyTask extends Task */ protected $addsourcefile = true; - /** - * Whether to spawn the command execution as a background process - * @var boolean - */ - protected $spawn = false; - - /** - * Property name to set with return value - * @var string - */ - protected $returnProperty; - - /** - * Property name to set with output value - * @var string - */ - protected $outputProperty; - /** * Whether the filenames should be passed on the command line as relative pathnames (relative to the base directory of the corresponding fileset/list) * @var boolean */ protected $relative = false; - /** - * Operating system information - * @var string - */ - protected $os; protected $currentos; protected $osvariant; @@ -132,27 +68,12 @@ class ApplyTask extends Task */ protected $loglevel = null; - /** - * Fail on command that exits with a returncode other than zero - * @var boolean - * - */ - protected $failonerror = false; - - /** - * Whether to use PHP's passthru() function instead of exec() - * @var boolean - */ - protected $passthru = false; - - /** * Whether to use forward-slash as file-separator on the file names * @var boolean */ protected $forwardslash = false; - /** * Limit the amount of parallelism by passing at most this many sourcefiles at once * (Set it to <= 0 for unlimited) @@ -160,52 +81,47 @@ class ApplyTask extends Task */ protected $maxparallel = 0; - /** - * Sets the command executable information - * - * @param string $executable Executable path - * - * @return void - */ - public function setExecutable($executable) - { - $this->commandline->setExecutable((string) $executable); - } + protected static $types = [ + 'FILE' => 'file', + 'DIR' => 'dir', + 'BOTH' => 'both', + ]; - /** - * Specify the working directory for the command execution. - * - * @param PhingFile $dir Set the working directory as specified - * - * @return void - */ - public function setDir(PhingFile $dir) - { - $this->dir = $dir; - } + protected $type = 'file'; + /** @var CommandlineMarker $targetFilePos */ + protected $targetFilePos; + /** @var CommandlineMarker $srcFilePos */ + protected $srcFilePos; + protected $srcIsFirst = true; + + protected $skipEmpty = false; + private $force = false; + private $mapper; + private $destDir; + + /** @var Mapper $mapperElement */ + private $mapperElement; + private $additionalCmds; /** - * Escape command using 'escapeshellcmd' before execution - * - * @param boolean $escape Escape command before execution - * - * @return void + * Set whether empty filesets will be skipped. If true and + * no source files have been found or are newer than their + * corresponding target files, the command will not be run. + * @param boolean $skip whether to skip empty filesets. */ - public function setEscape($escape) + public function setSkipEmptyFilesets(boolean $skip) { - $this->escape = (bool) $escape; + $this->skipEmpty = $skip; } /** - * File to which output should be written + * Specify the directory where target files are to be placed. * - * @param PhingFile $outputfile Output log file - * - * @return void + * @param PhingFile $dest the File object representing the destination directory. */ - public function setOutput(PhingFile $outputfile) + public function setDest(PhingFile $dest) { - $this->output = $outputfile; + $this->destDir = $dest; } /** @@ -245,58 +161,6 @@ public function setAddsourcefile($addsourcefile) $this->addsourcefile = (bool) $addsourcefile; } - /** - * File to which error output should be written - * - * @param PhingFile $errorfile Error log file - * - * @return void - */ - public function setError(PhingFile $errorfile) - { - $this->error = $errorfile; - } - - - /** - * Whether to spawn the command and run as background process - * - * @param boolean $spawn If the command is to be run as a background process - * - * @return void - */ - public function setSpawn($spawn) - { - $this->spawn = (bool) $spawn; - } - - - /** - * The name of property to set to return value - * - * @param string $propertyname Property name - * - * @return void - */ - public function setReturnProperty($propertyname) - { - $this->returnProperty = (string) $propertyname; - } - - - /** - * The name of property to set to output value - * - * @param string $propertyname Property name - * - * @return void - */ - public function setOutputProperty($propertyname) - { - $this->outputProperty = (string) $propertyname; - } - - /** * Whether the filenames should be passed on the command line as relative * pathnames (relative to the base directory of the corresponding fileset/list) @@ -311,32 +175,6 @@ public function setRelative($relative) $this->relative = (bool) $relative; } - - /** - * Specify OS (or multiple OS) that must match in order to execute this command. - * - * @param string $os Operating system string (e.g. "Linux") - * - * @return void - */ - public function setOs($os) - { - $this->os = (string) $os; - } - - - /** - * Whether to use PHP's passthru() function instead of exec() - * - * @param boolean $passthru If passthru shall be used - * - * @return void - */ - public function setPassthru($passthru) - { - $this->passthru = (bool) $passthru; - } - /** * Fail on command exits with a returncode other than zero * @@ -346,15 +184,7 @@ public function setPassthru($passthru) */ public function setFailonerror($failonerror) { - $this->failonerror = (bool) $failonerror; - } - - /** - * @param $failonerror - */ - public function setCheckreturn($failonerror) - { - $this->setFailonerror($failonerror); + $this->checkreturn = (bool) $failonerror; } /** @@ -382,126 +212,197 @@ public function setMaxparallel($max) $this->maxparallel = (int) $max; } - /** [TBA] + public function setForce(bool $force) + { + $this->force = $force; + } + + /** + * Set whether the command works only on files, directories or both. + * @param string $type a FileDirBoth EnumeratedAttribute. + */ + public function setType(string $type) + { + $this->type = $type; + } + + /** * Supports embedded element. * - * @return void + * @return CommandlineMarker + * @throws \BuildException */ - /**public function createTargetfile() { - * return $this->commandline->addArguments( array(self::TARGETFILE_ID) ); - * }*/ + public function createTargetfile() + { + + if ($this->targetFilePos !== null) { + throw new BuildException($this->getTaskType() . " doesn\'t support multiple " + . "targetfile elements.", $this->getLocation()); + } + + $this->targetFilePos = $this->commandline->createMarker(); + $this->srcIsFirst = ($this->srcFilePos !== null); + + return $this->targetFilePos; + } /** * Supports embedded element. * - * @return void + * @return CommandlineMarker + * @throws \BuildException */ public function createSrcfile() { - return $this->commandline->addArguments([self::SOURCEFILE_ID]); + if ($this->srcFilePos !== null) { + throw new BuildException($this->getTaskType() . " doesn\'t support multiple " + . "srcfile elements.", $this->getLocation()); + } + + $this->srcFilePos = $this->commandline->createMarker(); + return $this->srcFilePos; } /** - * Supports embedded element. - * - * @return CommandlineArgument + * @return Mapper + * @throws \BuildException */ - public function createArg() + public function createMapper() { - return $this->commandline->createArgument(); + if ($this->mapperElement !== null) { + throw new BuildException( + 'Cannot define more than one mapper', + $this->getLocation() + ); + } + $this->mapperElement = new Mapper($this->getProject()); + return $this->mapperElement; } /**********************************************************************************/ /**************************** T A S K M E T H O D S ******************************/ /**********************************************************************************/ - /** - * Class Initialization - * @return void - */ - public function init() - { - $this->commandline = new Commandline(); - $this->loglevel = Project::MSG_VERBOSE; - } - /** * Do work - * @throws BuildException + * @throws \BuildException */ public function main() { - - // Log - $this->log('Started ', $this->loglevel); - - // Initialize // - $this->initialize(); - - // Validate O.S. applicability - if ($this->validateOS()) { - - // Build the command // - $this->buildCommand(); - - // Process // - // - FileLists - foreach ($this->filelists as $fl) { - $this->process($fl->getFiles($this->project), $fl->getDir($this->project)); + try { + // Log + $this->log('Started ', $this->loglevel); + // Initialize // + $this->prepare(); + $haveExecuted = false; + // Validate O.S. applicability + if ($this->isValidOs()) { + // Build the command // + $this->buildCommand(); + // Process // + $totalFiles = 0; + $totalDirs = 0; + $fileNames = []; + // - FileSets + foreach ($this->filesets as $fs) { + $currentType = $this->type; + if ($fs instanceof DirSet) { + if ($this->type !== self::$types['DIR']) { + $this->log("Found a nested dirset but type is $this->type . " + . "Temporarily switching to type=\"dir\" on the" + . " assumption that you really did mean" + . " not .", Project::MSG_DEBUG + ); + $currentType = 'dir'; + } + } + $base = $fs->getDir($this->project); + $ds = $fs->getDirectoryScanner($this->project); + if ($currentType !== self::$types['DIR']) { + $s = $this->getFiles($base, $ds); + foreach ($s as $fileName) { + $totalFiles++; + $fileNames[] = $fileName; + $baseDirs[] = $base; + } + } + if ($currentType !== self::$types['FILE']) { + $s = $this->getDirs($base, $ds); + foreach ($s as $fileName) { + $totalDirs++; + $fileNames[] = $fileName; + $baseDirs[] = $base; + } + } + if (count($fileNames) === 0 && $this->skipEmpty) { + $this->logSkippingFileset($currentType, $ds, $base); + continue; + } + $this->process( + $fs->getDirectoryScanner($this->project)->getIncludedFiles(), + $fs->getDir($this->project) + ); + $haveExecuted = true; + } + unset($this->filesets); + // - FileLists + /** @var FileList $fl */ + foreach ($this->filelists as $fl) { + $totalFiles++; + $this->process($fl->getFiles($this->project), $fl->getDir($this->project)); + $haveExecuted = true; + } + unset($this->filelists); } - unset($this->filelists); - - // - FileSets - foreach ($this->filesets as $fs) { - $this->process( - $fs->getDirectoryScanner($this->project)->getIncludedFiles(), - $fs->getDir($this->project) - ); + if ($haveExecuted) { + $this->log( + "Applied " . $this->commandline->getExecutable() . " to " + . $totalFiles . " file" + . ($totalFiles !== 1 ? "s" : "") . " and " + . $totalDirs . " director" + . ($totalDirs !== 1 ? "ies" : "y") . ".", + $this->loglevel); } - unset($this->filesets); + /// Cleanup // + $this->cleanup(); + // Log + $this->log('End ', $this->loglevel); + } catch (IOException | NullPointerException | UnexpectedValueException $e) { + throw new BuildException('Execute failed: ' . $e, $e, $this->getLocation()); } - - /// Cleanup // - $this->cleanup(); - - // Log - $this->log('End ', $this->loglevel); } /**********************************************************************************/ /********************** T A S K C O R E M E T H O D S ***************************/ /**********************************************************************************/ - /** - * Checks whether the current O.S. should be supported - * - * @return boolean False if the exec command shall not be run - */ - protected function validateOS() + protected function getFiles(PhingFile $baseDir, DirectoryScanner $ds) { + return $this->restrict($ds->getIncludedFiles(), $baseDir); + } - // Log - $this->log('Validating Operating System information ', $this->loglevel); - - // Checking whether'os' information is specified - if (empty($this->os)) { - - // Log - $this->log("Operating system information not specified. Skipped checking. ", $this->loglevel); - - return true; - } - - // Validating the operating system information - $matched = stripos($this->os, $this->currentos) !== false; + protected function getDirs(PhingFile $baseDir, DirectoryScanner $ds) + { + return $this->restrict($ds->getIncludedDirectories(), $baseDir); + } - // Log - $this->log( - "Operating system '" . $this->currentos . "' " . ($matched ? '' : 'not ') . "found in " . $this->os, - $this->loglevel - ); + protected function restrict($s, PhingFile $baseDir) + { + $sfs = new SourceFileScanner($this); + return ($this->mapper === null || $this->force) + ? $s + : $sfs->restrict($s, $baseDir, $this->destDir, $this->mapper); + } - return $matched; + private function logSkippingFileset($currentType, DirectoryScanner $ds, PhingFile $base) + { + $includedCount = ( + ($currentType !== self::$types['DIR']) ? $ds->getIncludedFilesCount() : 0 + ) + ( + ($currentType !== self::$types['FILES']) ? $ds->getIncludedDirectoriesCount() : 0); + $this->log("Skipping fileset for directory " . $base . ". It is " + . (($includedCount > 0) ? "up to date." : "empty."), + $this->loglevel); } /** @@ -509,18 +410,21 @@ protected function validateOS() * - Required information validation * - Working directory * - * @param none - * * @return void + * @throws \BuildException + * @throws IOException */ - private function initialize() + protected function prepare() { - // Log $this->log('Initializing started ', $this->loglevel); ///// Validating the required parameters ///// + if (!in_array($this->type, self::$types)) { + throw new BuildException('Type must be one of \'file\', \'dir\' or \'both\'.'); + } + // Executable if ($this->commandline->getExecutable() === null) { $this->throwBuildException('Please provide "executable" information'); @@ -542,8 +446,7 @@ private function initialize() // Log $this->log( - 'Working directory change ' . ($dirchangestatus ? 'successful' : 'failed') . ' to ' . $this->dir->getPath( - ), + 'Working directory change ' . ($dirchangestatus ? 'successful' : 'failed') . ' to ' . $this->dir->getPath(), $this->loglevel ); } @@ -557,9 +460,9 @@ private function initialize() $this->log('Operating System identified : ' . $this->currentos, $this->loglevel); // Getting the O.S. type identifier - // Validating the 'filesystem' for determining the OS type [UNIX, WINDOWS] + // Validating the 'filesystem' for determining the OS type [UNIX, WINNT and WIN32] // (Another usage could be with 'os.name' for determination) - if ('WINDOWS' == Phing::getProperty('host.fstype')) { + if ('WIN' === strtoupper(substr(Phing::getProperty('host.fstype'), 0, 3))) { $this->osvariant = 'WIN'; // Probable Windows flavour } else { $this->osvariant = 'LIN'; // Probable GNU/Linux flavour @@ -568,180 +471,219 @@ private function initialize() // Log $this->log('Operating System variant identified : ' . $this->osvariant, $this->loglevel); + if (count($this->filesets) === 0 && count($this->filelists) === 0 && count($this->getDirSets()) === 0) { + throw new BuildException("no resources specified", + $this->getLocation()); + } + if ($this->targetFilePos !== null && $this->mapperElement === null) { + throw new BuildException("targetfile specified without mapper", + $this->getLocation()); + } + if ($this->destDir !== null && $this->mapperElement === null) { + throw new BuildException("dest specified without mapper", + $this->getLocation()); + } + + if ($this->mapperElement !== null) { + $this->mapper = $this->mapperElement->getImplementation(); + $this->log('Mapper identified : ' . get_class($this->mapper), $this->loglevel); + } + + $this->commandline->setEscape($this->escape); + // Log $this->log('Initializing completed ', $this->loglevel); - - return; } /** * Builds the full command to execute and stores it in $realCommand. * * @return void + * + * @throws \BuildException */ - private function buildCommand() + protected function buildCommand() { // Log $this->log('Command building started ', $this->loglevel); // Building the executable - $this->realCommand = Commandline::toString($this->commandline->getCommandline(), $this->escape); + $this->realCommand = (string) $this->commandline; + + $this->additionalCmds = ''; // Adding the source filename at the end of command, validating the existing // sourcefile position explicit mentioning - if (($this->addsourcefile === true) && (strpos($this->realCommand, self::SOURCEFILE_ID) === false)) { + if ($this->addsourcefile === true) { $this->realCommand .= ' ' . self::SOURCEFILE_ID; } // Setting command output redirection with content appending if ($this->output !== null) { - $this->realCommand .= ' 1>'; - $this->realCommand .= ($this->appendoutput ? '>' : ''); // Append output - $this->realCommand .= ' ' . escapeshellarg($this->output->getPath()); + $this->additionalCmds .= sprintf(' 1>%s %s', $this->appendoutput ? '>' : '', escapeshellarg($this->output->getPath())); } elseif ($this->spawn) { // Validating the 'spawn' configuration, and redirecting the output to 'null' - - // Validating the O.S. variant - if ('WIN' == $this->osvariant) { - $this->realCommand .= ' > NUL'; // MS Windows output nullification - } else { - $this->realCommand .= ' 1>/dev/null'; // GNU/Linux output nullification - } + $this->additionalCmds .= sprintf(' %s', 'WIN' === $this->osvariant ? '> NUL' : '1>/dev/null'); $this->log("For process spawning, setting Output nullification ", $this->loglevel); } // Setting command error redirection with content appending if ($this->error !== null) { - $this->realCommand .= ' 2>'; - $this->realCommand .= ($this->appendoutput ? '>' : ''); // Append error - $this->realCommand .= ' ' . escapeshellarg($this->error->getPath()); + $this->additionalCmds .= sprintf(' 2>%s %s', $this->appendoutput ? '>' : '', escapeshellarg($this->error->getPath())); } // Setting the execution as a background process if ($this->spawn) { // Validating the O.S. variant - if ('WIN' == $this->osvariant) { - $this->realCommand = 'start /b ' . $this->realCommand; // MS Windows background process forking + if ('WIN' === $this->osvariant) { + $this->additionalCmds = 'start /b ' . $this->additionalCmds; // MS Windows background process forking } else { - $this->realCommand .= ' &'; // GNU/Linux background process forking + $this->additionalCmds .= ' &'; // GNU/Linux background process forking } } + $this->additionalCmds = rtrim($this->additionalCmds); + // Log - $this->log('Command built : ' . $this->realCommand, $this->loglevel); + $this->log('Command built : ' . $this->realCommand . $this->additionalCmds, $this->loglevel); // Log $this->log('Command building completed ', $this->loglevel); - - return; } /** * Processes the files list with provided information for execution * - * @param array $files File list for processing + * @param array $srcFiles File list for processing * @param string $basedir Base directory of the file list * * @return void + * @throws \BuildException + * @throws IOException + * @throws NullPointerException */ - private function process($files, $basedir) + private function process($srcFiles, $basedir) { - // Log - $this->log("Processing Filelist with base directory ($basedir) ", $this->loglevel); + $this->log("Processing files with base directory ($basedir) ", $this->loglevel); + $targets = []; + if ($this->targetFilePos !== null) { + $addedFiles = []; + foreach ($srcFiles as $count => $file) { + if ($this->mapper !== null) { + $subTargets = $this->mapper->main($file); + if ($subTargets !== null) { + foreach ($subTargets as $subTarget) { + if ($this->relative) { + $name = $subTarget; + } else { + $name = (new PhingFile($this->destDir, $subTarget))->getAbsolutePath(); + } + if ($this->forwardslash && PhingFile::$separator !== '/') { + $name = str_replace(PhingFile::$separator, '/', $name); + } + if (!isset($addedFiles[$name])) { + $targets[] = $name; + $addedFiles[] = $name; + } + } + } + } + } + } + $targetFiles = $targets; - // Process each file in the list for applying the 'realcommand' - foreach ($files as $count => $file) { + if (!$this->addsourcefile) { + $srcFiles = []; + } + $orig = $this->commandline->getCommandline(); + $result = []; // range(0,count($orig) + count($srcFiles) + count($targetFiles)); - // Preparing the absolute filename with relative path information - $absolutefilename = $this->getFilePath($file, $basedir, $this->relative); + $srcIndex = count($orig); + if ($this->srcFilePos !== null) { + $srcIndex = $this->srcFilePos->getPosition(); + } + if ($this->targetFilePos !== null) { + $targetIndex = $this->targetFilePos->getPosition(); - // Checking whether 'parallel' information is enabled. If enabled, append all - // the file names as arguments, and run only once. - if ($this->parallel) { + if ($srcIndex < $targetIndex || ($srcIndex === $targetIndex && $this->srcIsFirst)) { + // 0 --> srcIndex + $result[] = $orig; - // Checking whether 'maxparallel' setting describes parallelism limitation - // by passing at most 'maxparallel' many sourcefiles at once - $slicedfiles = array_splice( - $files, - 0, - (($this->maxparallel > 0) ? $this->maxparallel : count($files)) - ); + // srcIndex --> targetIndex + $result += array_slice($orig, $srcIndex + count($srcFiles), $targetIndex - $srcIndex, true); - $absolutefilename = implode(' ', $this->getFilePath($slicedfiles, $basedir, $this->relative)); - } + $result[] = $orig; + $result[] = $targetFiles; + $result = array_merge(... $result); - // Checking whether the forward-slash as file-separator has been set. - // (Applicability: The source {and target} file names must use the forward slash as file separator) - if ($this->forwardslash) { - $absolutefilename = str_replace(DIRECTORY_SEPARATOR, '/', $absolutefilename); + // targetIndex --> end + $result = array_merge(array_slice($orig, $targetIndex + count($srcFiles) + count($targetFiles), + count($orig) - $targetIndex, true), $result); + } else { + // 0 --> targetIndex + $result[] = $orig; + $result[] = $targetFiles; + $result = array_merge(... $result); + + // targetIndex --> srcIndex + $result = array_merge(array_slice($orig, $targetIndex + count($targetFiles), $srcIndex - $targetIndex, + true), $result); + + // srcIndex --> end + $result = array_merge(array_slice($orig, $srcIndex + count($srcFiles) + count($targetFiles), + count($orig) - $srcIndex, true), $result); + $srcIndex += count($targetFiles); } - // Preparing the command to be executed - $filecommand = str_replace([self::SOURCEFILE_ID], [$absolutefilename], $this->realCommand); - - // Command execution - list($returncode, $output) = $this->executeCommand($filecommand); - - // Process the stuff on the first command execution only - if (0 == $count) { + } else { // no targetFilePos - // Sets the return property - if ($this->returnProperty) { - $this->project->setProperty($this->returnProperty, $returncode); - } - } - - // Sets the output property - if ($this->outputProperty) { - $previousValue = $this->project->getProperty($this->outputProperty); - if (! empty($previousValue)) { - $previousValue .= "\n"; - } - $this->project->setProperty($this->outputProperty, $previousValue . implode("\n", $output)); + // 0 --> srcIndex + $result = array_merge(array_slice($orig, 0, $srcIndex, true), $result); + // srcIndex --> end + $result = array_merge(array_slice($orig, $srcIndex + count($srcFiles), count($orig) - $srcIndex, true), + $result); + } + // fill in source file names + foreach ($srcFiles as $i => $file) { + if ($this->relative) { + $src = $file; + } else { + $src = (new PhingFile($basedir, $file))->getAbsolutePath(); } - - // Validating the 'return-code' - if (($this->failonerror) && ($returncode != 0)) { - $this->throwBuildException("Task exited with code ($returncode)"); + if ($this->forwardslash && PhingFile::$separator !== '/') { + $src = str_replace(PhingFile::$separator, '/', $src); } - - // Validate the 'parallel' information for command execution. If the command has been - // executed with the filenames as argument, considering 'maxparallel', just break. - if (($this->parallel) && (!array_key_exists($count, $files))) { - break; + if ($this->srcFilePos !== null && (count($this->srcFilePos->getPrefix()) > 0 + || count($this->srcFilePos->getSuffix()) > 0)) { + $src = $this->srcFilePos->getPrefix() . $src . $this->srcFilePos->getSuffix(); } - } // Each file processing loop ends - - return; - } + $result[$srcIndex + $i] = $src; + } - /** - * Executes the specified command and returns the return code & output. - * - * @param string $command - * - * @return array array(return code, array with output) - */ - private function executeCommand($command) - { + $this->commandline = new Commandline(implode(' ', $result)); + $this->commandline->setEscape($this->escape); + $this->realCommand = (string) $this->commandline . $this->additionalCmds; - // Var(s) - $output = []; - $return = null; + [$returncode, $output] = $this->executeCommand(); - // Validating the command executor container - ($this->passthru ? passthru($command, $return) : exec($command, $output, $return)); + $this->maybeSetReturnPropertyValue($returncode); - // Log - $this->log( - 'Command execution : (' . ($this->passthru ? 'passthru' : 'exec') . ') : ' . $command . " : completed with return code ($return) ", - $this->loglevel - ); + // Sets the output property + if ($this->outputProperty) { + $previousValue = $this->project->getProperty($this->outputProperty); + if (!empty($previousValue)) { + $previousValue .= "\n"; + } + $this->project->setProperty($this->outputProperty, $previousValue . implode("\n", $output)); + } - return [$return, $output]; + // Validating the 'return-code' + if ($this->checkreturn && ($returncode !== 0)) { + $this->throwBuildException("Task exited with code ($returncode)"); + } } /** @@ -750,36 +692,39 @@ private function executeCommand($command) * * @return void */ - private function cleanup() + protected function cleanup($return = null, $output = null): void { - // Restore working directory if ($this->dir !== null) { @chdir($this->currentdirectory); } - - return; } /** * Prepares the filename per base directory and relative path information * - * @param $filename + * @param array|string $filename * @param $basedir * @param $relative * * @return mixed processed filenames + * + * @throws IOException */ public function getFilePath($filename, $basedir, $relative) { // Validating the 'file' information - $files = (is_array($filename)) ? $filename : [$filename]; + $files = (array) $filename; // Processing the file information foreach ($files as $index => $file) { - $absolutefilename = (($relative === false) ? ($basedir . DIRECTORY_SEPARATOR) : ''); + $absolutefilename = (($relative === false) ? ($basedir . PhingFile::$separator) : ''); $absolutefilename .= $file; - $files[$index] = $absolutefilename; + if ($relative === false) { + $files[$index] = (new FileUtils())->normalize($absolutefilename); + } else { + $files[$index] = $absolutefilename; + } } return (is_array($filename) ? $files : $files[0]); @@ -788,12 +733,13 @@ public function getFilePath($filename, $basedir, $relative) /** * Throws the exception with specified information * - * @param $information Exception information + * @param string $information Exception information * * @throws BuildException + * * @return void */ - private function throwBuildException($information) + private function throwBuildException($information): void { throw new BuildException('ApplyTask: ' . (string) $information); } diff --git a/classes/phing/tasks/system/AttribTask.php b/classes/phing/tasks/system/AttribTask.php index e554728036..b1c946c92c 100644 --- a/classes/phing/tasks/system/AttribTask.php +++ b/classes/phing/tasks/system/AttribTask.php @@ -133,7 +133,7 @@ protected function checkConfiguration() * @param mixed $e * @throws BuildException */ - public function setExecutable($e) + public function setExecutable($e): void { throw new BuildException( $this->getTaskType() . ' doesn\'t support the executable attribute', diff --git a/classes/phing/tasks/system/ExecTask.php b/classes/phing/tasks/system/ExecTask.php index 50a8866068..f443f5dd2c 100644 --- a/classes/phing/tasks/system/ExecTask.php +++ b/classes/phing/tasks/system/ExecTask.php @@ -38,12 +38,6 @@ class ExecTask extends Task */ protected $realCommand; - /** - * Given command - * @var string - */ - protected $command; - /** * Commandline managing object * @@ -180,11 +174,22 @@ protected function prepare() $this->dir = $this->getProject()->getBasedir(); } + if ($this->commandline->getExecutable() === null) { + throw new BuildException( + 'ExecTask: Please provide "executable"' + ); + } + // expand any symbolic links first try { + if (!$this->dir->getCanonicalFile()->exists()) { + throw new BuildException( + "The directory '" . (string) $this->dir . "' does not exist" + ); + } if (!$this->dir->getCanonicalFile()->isDirectory()) { throw new BuildException( - "'" . (string) $this->dir . "' is not a valid directory" + "'" . (string) $this->dir . "' is not a directory" ); } } catch (IOException $e) { @@ -194,6 +199,8 @@ protected function prepare() } $this->currdir = getcwd(); @chdir($this->dir->getPath()); + + $this->commandline->setEscape($this->escape); } /** @@ -205,34 +212,10 @@ protected function prepare() */ protected function buildCommand() { - if ($this->command === null && $this->commandline->getExecutable() === null) { - throw new BuildException( - 'ExecTask: Please provide "command" OR "executable"' - ); - } else { - if ($this->command === null) { - $this->realCommand = Commandline::toString($this->commandline->getCommandline(), $this->escape); - } else { - if ($this->commandline->getExecutable() === null) { - $this->realCommand = $this->command; - - //we need to escape the command only if it's specified directly - // commandline takes care of "executable" already - if ($this->escape == true) { - $this->realCommand = escapeshellcmd($this->realCommand); - } - } else { - throw new BuildException( - 'ExecTask: Either use "command" OR "executable"' - ); - } - } - } - if ($this->error !== null) { $this->realCommand .= ' 2> ' . escapeshellarg($this->error->getPath()); $this->log( - "Writing error output to: " . $this->error->getPath(), + 'Writing error output to: ' . $this->error->getPath(), $this->logLevel ); } @@ -240,12 +223,12 @@ protected function buildCommand() if ($this->output !== null) { $this->realCommand .= ' 1> ' . escapeshellarg($this->output->getPath()); $this->log( - "Writing standard output to: " . $this->output->getPath(), + 'Writing standard output to: ' . $this->output->getPath(), $this->logLevel ); } elseif ($this->spawn) { $this->realCommand .= ' 1>/dev/null'; - $this->log("Sending output to /dev/null", $this->logLevel); + $this->log('Sending output to /dev/null', $this->logLevel); } // If neither output nor error are being written to file @@ -260,24 +243,29 @@ protected function buildCommand() if ($this->spawn) { $this->realCommand .= ' &'; } + + $this->realCommand = (string) $this->commandline . $this->realCommand; } /** * Executes the command and returns return code and output. * * @return array array(return code, array with output) + * @throws \BuildException */ protected function executeCommand() { - $this->log("Executing command: " . $this->realCommand, $this->logLevel); + $cmdl = $this->realCommand; + + $this->log('Executing command: ' . $cmdl, $this->logLevel); $output = []; $return = null; if ($this->passthru) { - passthru($this->realCommand, $return); + passthru($cmdl, $return); } else { - exec($this->realCommand, $output, $return); + exec($cmdl, $output, $return); } return [$return, $output]; @@ -295,7 +283,7 @@ protected function executeCommand() * @throws BuildException * @return void */ - protected function cleanup($return, $output) + protected function cleanup($return, $output): void { if ($this->dir !== null) { @chdir($this->currdir); @@ -306,9 +294,7 @@ protected function cleanup($return, $output) $this->log($line, $outloglevel); } - if ($this->returnProperty) { - $this->project->setProperty($this->returnProperty, $return); - } + $this->maybeSetReturnPropertyValue($return); if ($this->outputProperty) { $this->project->setProperty( @@ -319,8 +305,11 @@ protected function cleanup($return, $output) $this->setExitValue($return); - if ($return != 0 && $this->checkreturn) { - throw new BuildException("Task exited with code $return"); + if ($return !== 0) { + if ($this->checkreturn) { + throw new BuildException($this->getTaskType() . ' returned: ' . $return, $this->getLocation()); + } + $this->log('Result: ' . $return, Project::MSG_ERR); } } @@ -329,7 +318,7 @@ protected function cleanup($return, $output) * * @param int $value exit value of the process. */ - protected function setExitValue($value) + protected function setExitValue($value): void { $this->exitValue = $value; } @@ -340,7 +329,7 @@ protected function setExitValue($value) * @return int the exit value or self::INVALID if no exit value has * been received. */ - public function getExitValue() + public function getExitValue(): int { return $this->exitValue; } @@ -348,26 +337,33 @@ public function getExitValue() /** * The command to use. * - * @param mixed $command String or string-compatible (e.g. w/ __toString()). + * @param string $command String or string-compatible (e.g. w/ __toString()). * * @return void + * @throws \BuildException */ - public function setCommand($command) + public function setCommand($command): void { - $this->command = "" . $command; + $this->log("The command attribute is deprecated.\nPlease use the executable attribute and nested arg elements.", + Project::MSG_WARN); + $this->commandline = new Commandline($command); + $this->executable = $this->commandline->getExecutable(); } /** * The executable to use. * - * @param mixed $executable String or string-compatible (e.g. w/ __toString()). + * @param string|bool $value String or string-compatible (e.g. w/ __toString()). * * @return void */ - public function setExecutable($executable) + public function setExecutable($value): void { - $this->executable = $executable; - $this->commandline->setExecutable((string) $executable); + if (is_bool($value)) { + $value = $value === true ? 'true' : 'false'; + } + $this->executable = $value; + $this->commandline->setExecutable($value); } /** @@ -377,7 +373,7 @@ public function setExecutable($executable) * * @return void */ - public function setEscape($escape) + public function setEscape($escape): void { $this->escape = (bool) $escape; } @@ -389,7 +385,7 @@ public function setEscape($escape) * * @return void */ - public function setDir(PhingFile $dir) + public function setDir(PhingFile $dir): void { $this->dir = $dir; } @@ -401,7 +397,7 @@ public function setDir(PhingFile $dir) * * @return void */ - public function setOs($os) + public function setOs($os): void { $this->os = (string) $os; } @@ -409,7 +405,7 @@ public function setOs($os) /** * List of operating systems on which the command may be executed. */ - public function getOs() + public function getOs(): string { return $this->os; } @@ -418,7 +414,7 @@ public function getOs() * Restrict this execution to a single OS Family * @param string $osFamily the family to restrict to. */ - public function setOsFamily($osFamily) + public function setOsFamily($osFamily): void { $this->osFamily = strtolower($osFamily); } @@ -438,7 +434,7 @@ public function getOsFamily() * * @return void */ - public function setOutput(PhingFile $f) + public function setOutput(PhingFile $f): void { $this->output = $f; } @@ -450,7 +446,7 @@ public function setOutput(PhingFile $f) * * @return void */ - public function setError(PhingFile $f) + public function setError(PhingFile $f): void { $this->error = $f; } @@ -462,7 +458,7 @@ public function setError(PhingFile $f) * * @return void */ - public function setPassthru($passthru) + public function setPassthru($passthru): void { $this->passthru = $passthru; } @@ -474,7 +470,7 @@ public function setPassthru($passthru) * * @return void */ - public function setLogoutput($logOutput) + public function setLogoutput($logOutput): void { $this->logOutput = $logOutput; } @@ -486,7 +482,7 @@ public function setLogoutput($logOutput) * * @return void */ - public function setSpawn($spawn) + public function setSpawn($spawn): void { $this->spawn = $spawn; } @@ -498,7 +494,7 @@ public function setSpawn($spawn) * * @return void */ - public function setCheckreturn($checkreturn) + public function setCheckreturn($checkreturn): void { $this->checkreturn = $checkreturn; } @@ -510,11 +506,18 @@ public function setCheckreturn($checkreturn) * * @return void */ - public function setReturnProperty($prop) + public function setReturnProperty($prop): void { $this->returnProperty = $prop; } + protected function maybeSetReturnPropertyValue(int $return) + { + if ($this->returnProperty) { + $this->getProject()->setNewProperty($this->returnProperty, $return); + } + } + /** * The name of property to set to output value from exec() call. * @@ -522,7 +525,7 @@ public function setReturnProperty($prop) * * @return void */ - public function setOutputProperty($prop) + public function setOutputProperty($prop): void { $this->outputProperty = $prop; } @@ -535,7 +538,7 @@ public function setOutputProperty($prop) * @throws BuildException * @return void */ - public function setLevel($level) + public function setLevel($level): void { switch ($level) { case 'error': @@ -597,9 +600,9 @@ public function createArg() *
  • false otherwise.
  • * */ - protected function isValidOs() + protected function isValidOs(): bool { - //hand osfamily off to Os class, if set + //hand osfamily off to OsCondition class, if set if ($this->osFamily !== null && !OsCondition::isFamily($this->osFamily)) { return false; } diff --git a/classes/phing/tasks/system/condition/OsCondition.php b/classes/phing/tasks/system/condition/OsCondition.php index c65cfe3426..8f37ddc91b 100644 --- a/classes/phing/tasks/system/condition/OsCondition.php +++ b/classes/phing/tasks/system/condition/OsCondition.php @@ -68,8 +68,18 @@ public static function isOS($family) $osName = strtolower(Phing::getProperty('os.name')); if ($family !== null) { - if ($family === self::FAMILY_WINDOWS) { - return StringHelper::startsWith('win', $osName); + $isWindows = StringHelper::startsWith('win', $osName); + + if ($family === 'windows') { + return $isWindows; + } + + if ($family === 'win32') { + return $isWindows && $osName === 'win32'; + } + + if ($family === 'winnt') { + return $isWindows && $osName === 'winnt'; } if ($family === self::FAMILY_MAC) { diff --git a/classes/phing/types/Commandline.php b/classes/phing/types/Commandline.php index 125b2c3142..d07276910e 100644 --- a/classes/phing/types/Commandline.php +++ b/classes/phing/types/Commandline.php @@ -41,9 +41,8 @@ * @author Stefan Bodewig * @package phing.types */ -class Commandline +class Commandline implements Countable { - /** * @var CommandlineArgument[] */ @@ -56,6 +55,7 @@ class Commandline public $executable; // public so "inner" class can access const DISCLAIMER = "The ' characters around the executable and arguments are not part of the command."; + private $escape = false; /** * @param null $to_process @@ -98,16 +98,17 @@ public function createArgument($insertAtStart = false) /** * Sets the executable to run. - * @param $executable + * @param string $executable + * @param bool $translateFileSeparator */ - public function setExecutable($executable) + public function setExecutable($executable, $translateFileSeparator = true): void { - if (!$executable) { + if ($executable === null || $executable === '') { return; } - $this->executable = $executable; - $this->executable = strtr($this->executable, '/', DIRECTORY_SEPARATOR); - $this->executable = strtr($this->executable, '\\', DIRECTORY_SEPARATOR); + $this->executable = $translateFileSeparator + ? str_replace(['/', '\\'], PhingFile::$separator, $executable) + : $executable; } /** @@ -132,28 +133,28 @@ public function addArguments(array $arguments) * Returns the executable and all defined arguments. * @return array */ - public function getCommandline() + public function getCommandline(): array { $args = $this->getArguments(); - if ($this->executable === null) { - return $args; + if ($this->executable !== null && $this->executable !== '') { + array_unshift($args, $this->executable); } - return array_merge([$this->executable], $args); + return $args; } /** * Returns all arguments defined by addLine, * addValue or the argument object. */ - public function getArguments() + public function getArguments(): array { $result = []; foreach ($this->arguments as $arg) { $parts = $arg->getParts(); if ($parts !== null) { foreach ($parts as $part) { - $result[] = $arg->escape ? self::quoteArgument($part, true) : $part; + $result[] = $part; } } } @@ -161,12 +162,23 @@ public function getArguments() return $result; } + public function setEscape($flag) + { + $this->escape = $flag; + } + /** * @return string */ public function __toString() { - return self::toString($this->getCommandline()); + try { + $cmd = $this->toString($this->getCommandline()); + } catch (BuildException $be) { + $cmd = ''; + } + + return $cmd; } /** @@ -176,67 +188,70 @@ public function __toString() * as is. If it contains double quotes, use single quotes - else * surround the argument by double quotes.

    * - * @exception BuildException if the argument contains both, single - * and double quotes. * @param $argument - * @param bool $escape - * @throws BuildException + * * @return string + * + * @throws BuildException if the argument contains both, single + * and double quotes. */ public static function quoteArgument($argument, $escape = false) { if ($escape) { return escapeshellarg($argument); - } elseif (strpos($argument, "\"") !== false && $argument != '""') { + } + + if (strpos($argument, '"') !== false) { if (strpos($argument, "'") !== false) { throw new BuildException("Can't handle single and double quotes in same argument"); - } else { - return escapeshellarg($argument); } - } elseif (strpos($argument, "'") !== false || strpos($argument, " ") !== false) { - return escapeshellarg($argument); - //return '\"' . $argument . '\"'; - } else { - return $argument; + + return '\'' . $argument . '\''; } + + if (strpos($argument, "'") !== false + || strpos($argument, ' ') !== false + // WIN9x uses a bat file for executing commands + || (OsCondition::isFamily('win32') && strpos($argument, ';') !== false)) { + return '"' . $argument . '"'; + } + + return $argument; } /** * Quotes the parts of the given array in way that makes them * usable as command line arguments. + * * @param $lines - * @param bool $escape - * @throws BuildException + * * @return string + * + * @throws BuildException */ - public static function toString($lines, $escape = false) + private function toString($lines = null): string { // empty path return empty string - if (!$lines) { - return ""; + if ($lines === null || count($lines) === 0) { + return ''; } - // path containing one or more elements - $result = ""; - for ($i = 0, $len = count($lines); $i < $len; $i++) { - if ($i > 0) { - $result .= ' '; - } - $result .= self::quoteArgument($lines[$i], $escape); - } - - return $result; + return implode(' ', array_map(function ($arg) {return self::quoteArgument($arg, $this->escape);}, $lines)); } /** + * Crack a command line. * - * @param string $to_process - * @throws BuildException - * @return array + * @param string $toProcess the command line to process. + * + * @return string[] the command line broken into strings. + * An empty or null toProcess parameter results in a zero sized array. + * + * @throws \BuildException */ - public static function translateCommandline($to_process) + public static function translateCommandline(string $toProcess = null): array { - if (!$to_process) { + if ($toProcess === null || $toProcess === '') { return []; } @@ -251,12 +266,11 @@ public static function translateCommandline($to_process) $current = ""; $lastTokenHasBeenQuoted = false; - $tok = strtok($to_process, ""); - $tokens = preg_split('/(["\' ])/', $to_process, -1, PREG_SPLIT_DELIM_CAPTURE); + $tokens = preg_split('/(["\' ])/', $toProcess, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); while (($nextTok = array_shift($tokens)) !== null) { switch ($state) { case $inQuote: - if ("'" == $nextTok) { + if ("'" === $nextTok) { $lastTokenHasBeenQuoted = true; $state = $normal; } else { @@ -264,7 +278,7 @@ public static function translateCommandline($to_process) } break; case $inDoubleQuote: - if ("\"" == $nextTok) { + if ("\"" === $nextTok) { $lastTokenHasBeenQuoted = true; $state = $normal; } else { @@ -272,12 +286,12 @@ public static function translateCommandline($to_process) } break; default: - if ("'" == $nextTok) { + if ("'" === $nextTok) { $state = $inQuote; - } elseif ("\"" == $nextTok) { + } elseif ("\"" === $nextTok) { $state = $inDoubleQuote; - } elseif (" " == $nextTok) { - if ($lastTokenHasBeenQuoted || strlen($current) != 0) { + } elseif (" " === $nextTok) { + if ($lastTokenHasBeenQuoted || $current !== '') { $args[] = $current; $current = ""; } @@ -289,12 +303,12 @@ public static function translateCommandline($to_process) } } - if ($lastTokenHasBeenQuoted || strlen($current) != 0) { + if ($lastTokenHasBeenQuoted || $current !== '') { $args[] = $current; } - if ($state == $inQuote || $state == $inDoubleQuote) { - throw new BuildException("unbalanced quotes in " . $to_process); + if ($state === $inQuote || $state === $inDoubleQuote) { + throw new BuildException('unbalanced quotes in ' . $toProcess); } return $args; @@ -303,21 +317,18 @@ public static function translateCommandline($to_process) /** * @return int Number of components in current commandline. */ - public function size() + public function count(): int { return count($this->getCommandline()); } /** - * @return Commandline + * @throws \BuildException */ - public function __copy() + public function __clone() { - $c = new Commandline(); - $c->setExecutable($this->executable); + $c = new self(); $c->addArguments($this->getArguments()); - - return $c; } /** @@ -340,24 +351,26 @@ public function createMarker() * *

    This method assumes that the first entry in the array is the * executable to run.

    - * @param array $args CommandlineArgument[] to use + * @param array|Commandline $args CommandlineArgument[] to use * @return string */ public function describeCommand($args = null) { if ($args === null) { $args = $this->getCommandline(); + } elseif ($args instanceof self) { + $args = $args->getCommandline(); } if (!$args) { - return ""; + return ''; } $buf = "Executing '"; $buf .= $args[0]; $buf .= "'"; if (count($args) > 0) { - $buf .= " with "; + $buf .= ' with '; $buf .= $this->describeArguments($args, 1); } else { $buf .= self::DISCLAIMER; @@ -374,14 +387,14 @@ public function describeCommand($args = null) * @param int $offset ignore entries before this index * @return string */ - protected function describeArguments(array $args = null, $offset = 0) + public function describeArguments(array $args = null, $offset = 0) { if ($args === null) { $args = $this->getArguments(); } if ($args === null || count($args) <= $offset) { - return ""; + return ''; } $buf = "argument"; diff --git a/docs/guide/en/source/appendixes/coretasks.xml b/docs/guide/en/source/appendixes/coretasks.xml index 346b8a5bac..42945960c1 100644 --- a/docs/guide/en/source/appendixes/coretasks.xml +++ b/docs/guide/en/source/appendixes/coretasks.xml @@ -496,8 +496,34 @@ No - - + + skipemptyfilesets + Boolean + Don't run the command, if no source files have been found or are newer than their + corresponding target files. Despite its name, this attribute applies to filelists as well. + false + No + + + + type + String + One of file, dir or both. If set to file, only the names of plain files will be sent to + the command. If set to dir, only the names of directories are considered. + Note: The type attribute does not apply to nested dirsets - dirsets always implicitly assume + type to be dir. + file + No + + + + force + Boolean + Whether to bypass timestamp comparisons for target files. + false + No + + @@ -585,7 +611,9 @@ fileset filelist dirset + mapper srcfile + targetfile @@ -1782,7 +1810,8 @@ verbose="true" failonerror="false" /> command String - The command that is to be executed. + NOTE: This attribute is deprecated. Please use executable with nested args. + The command that is to be executed. n/a One of the two diff --git a/test/classes/phing/tasks/system/ApplyTaskTest.php b/test/classes/phing/tasks/system/ApplyTaskTest.php index 2fffb5821e..f63a5dfc3e 100644 --- a/test/classes/phing/tasks/system/ApplyTaskTest.php +++ b/test/classes/phing/tasks/system/ApplyTaskTest.php @@ -61,7 +61,7 @@ public function testPropertySetOs() */ public function testPropertySetDir() { - $this->assertAttributeIsSetTo('dir', new PhingFile('/tmp/')); + $this->assertAttributeIsSetTo('dir', new PhingFile($this->project->getProperty('php.tmpdir'))); } /** @@ -109,7 +109,7 @@ public function testPropertySetOutputProperty() */ public function testPropertySetCheckReturn() { - $this->assertAttributeIsSetTo('checkreturn', true, 'failonerror'); + $this->assertAttributeIsSetTo('checkreturn', true); } /** @@ -117,7 +117,7 @@ public function testPropertySetCheckReturn() */ public function testPropertySetOutput() { - $this->assertAttributeIsSetTo('output', new PhingFile('/tmp/outputfilename')); + $this->assertAttributeIsSetTo('output', new PhingFile($this->project->getProperty('php.tmpdir') . '/outputfilename')); } /** @@ -125,7 +125,7 @@ public function testPropertySetOutput() */ public function testPropertySetError() { - $this->assertAttributeIsSetTo('error', new PhingFile('/tmp/errorfilename')); + $this->assertAttributeIsSetTo('error', new PhingFile($this->project->getProperty('php.tmpdir') . '/errorfilename')); } /** @@ -184,7 +184,7 @@ public function testDoNotExecuteOnWrongOs() // Process $this->executeTarget(__FUNCTION__); - $this->assertInLogs('Not found in unknownos'); + $this->assertInLogs('was not found in the specified list of valid OSes: unknownos'); $this->assertNotContains('this should not be executed', $this->getOutput()); } @@ -203,8 +203,7 @@ public function testExecuteOnCorrectOs() */ public function testFailOnNonExistingDir() { - $nonExistentDir = DIRECTORY_SEPARATOR - . 'tmp' . DIRECTORY_SEPARATOR + $nonExistentDir = $this->project->getProperty('php.tmpdir') . DIRECTORY_SEPARATOR . 'non' . DIRECTORY_SEPARATOR . 'existent' . DIRECTORY_SEPARATOR . 'dir'; @@ -296,7 +295,7 @@ public function testEscape() public function testPassThru() { $this->executeTarget(__FUNCTION__); - $this->assertInLogs('Command execution : (passthru)'); + $this->assertInLogs('Executing command:'); } /** @@ -392,7 +391,7 @@ public function testMissingExecutable() public function testEscapedArg() { $this->executeTarget(__FUNCTION__); - $this->assertPropertyEquals('outval', 'abc$b3!SB'); + $this->assertPropertyEquals('outval', $this->windows ? 'abc$b3 SB' : 'abc$b3!SB'); } /** @@ -404,7 +403,7 @@ public function testRelativeSourceFilenames() if ($this->windows) { $this->markTestSkipped("Windows does not have 'ls'"); } - + $this->executeTarget(__FUNCTION__); $this->assertNotInLogs('/etc/'); } @@ -422,7 +421,7 @@ public function testSourceFilename() $this->executeTarget(__FUNCTION__); // As the addsourcefilename is 'off', only the executable should be processed in the execution - $this->assertInLogs(': ls :'); + $this->assertInLogs('Executing command: ls'); } /** @@ -441,7 +440,7 @@ public function testOutputAppend() // Validating the output $output = @file_get_contents($tempfile); @unlink($tempfile); - $this->assertEquals("Append OK\nAppend OK", rtrim($output)); + $this->assertEquals($this->windows ? "Append OK \r\nAppend OK" : "Append OK\nAppend OK", rtrim($output)); } /** @@ -454,9 +453,26 @@ public function testParallel() foreach($this->logBuffer as $log) { $messages[] = $log['message']; } - $this->assertEquals(1, substr_count(implode("\n", $messages), 'Command execution :')); + $this->assertEquals(1, substr_count(implode("\n", $messages), 'Executing command:')); + } + + public function testMapperSupport() + { + // Getting a temp. file + $tempfile = tempnam(sys_get_temp_dir(), 'phing-exectest-'); + + // Setting the property + $this->project->setProperty('execTmpFile', $tempfile); + + $this->executeTarget(__FUNCTION__); + $messages = []; + foreach($this->logBuffer as $log) { + $messages[] = $log['message']; + } + $this->assertTrue(in_array('Applied echo to 4 files and 0 directories.', $messages)); } + /**********************************************************************************/ /************************** H E L P E R M E T H O D S ****************************/ /**********************************************************************************/ diff --git a/test/classes/phing/tasks/system/ExecTaskTest.php b/test/classes/phing/tasks/system/ExecTaskTest.php index 6589c20cb1..2f0d386861 100644 --- a/test/classes/phing/tasks/system/ExecTaskTest.php +++ b/test/classes/phing/tasks/system/ExecTaskTest.php @@ -94,9 +94,9 @@ protected function assertAttributeIsSetTo($property, $value, $propertyName = nul $this->assertEquals($value, $rprop->getValue($task)); } - public function testPropertySetCommand() + public function testPropertySetCommandline() { - $this->assertAttributeIsSetTo('command', "echo 'foo'"); + $this->assertAttributeIsSetTo('commandline', new Commandline("echo 'foo'")); } public function testPropertySetDir() @@ -239,7 +239,7 @@ public function testFailOnNonExistingDir() $this->fail('Expected BuildException was not thrown'); } catch (BuildException $e) { $this->assertContains( - str_replace('/', DIRECTORY_SEPARATOR, "'/this/dir/does/not/exist' is not a valid directory"), + str_replace('/', DIRECTORY_SEPARATOR, "'/this/dir/does/not/exist' does not exist"), $e->getMessage() ); } @@ -256,8 +256,8 @@ public function testChangeToDir() public function testCheckreturnTrue() { - if ($this->windows) { - $this->markTestSkipped("Windows does not have '/bin/true'"); + if (FileSystem::getFileSystem()->which('true') === false) { + $this->markTestSkipped("'true' not found."); } $this->executeTarget(__FUNCTION__); $this->assertTrue(true); @@ -265,12 +265,12 @@ public function testCheckreturnTrue() /** * @expectedException BuildException - * @expectedExceptionMessage Task exited with code 1 + * @expectedExceptionMessage exec returned: 1 */ public function testCheckreturnFalse() { - if ($this->windows) { - $this->markTestSkipped("Windows does not have '/bin/false'"); + if (FileSystem::getFileSystem()->which('false') === false) { + $this->markTestSkipped("'false' not found."); } $this->executeTarget(__FUNCTION__); } @@ -290,7 +290,7 @@ public function testReturnProperty() public function testEscape() { $this->executeTarget(__FUNCTION__); - $this->assertInLogs($this->windows ? 'foo | cat' : 'foo | cat'); + $this->assertInLogs($this->windows ? '"foo" "|" "cat"' : 'foo | cat'); } public function testPassthru() @@ -315,9 +315,6 @@ public function testOutput() public function testError() { - if ($this->windows) { - $this->markTestSkipped("The script is unlikely to run on Windows"); - } $file = tempnam(sys_get_temp_dir(), 'phing-exectest-'); $this->project->setProperty('execTmpFile', $file); $this->executeTarget(__FUNCTION__); @@ -346,29 +343,28 @@ public function testNestedArg() /** * @expectedException BuildException - * @expectedExceptionMessage ExecTask: Either use "command" OR "executable" + * @expectedExceptionMessage ExecTask: Please provide "executable" */ - public function testExecutableAndCommand() + public function testMissingExecutableAndCommand() { $this->executeTarget(__FUNCTION__); } /** - * @expectedException BuildException - * @expectedExceptionMessage ExecTask: Please provide "command" OR "executable" + * Inspired by {@link http://www.phing.info/trac/ticket/833} */ - public function testMissingExecutableAndCommand() + public function testEscapedArg() { $this->executeTarget(__FUNCTION__); + $this->assertPropertyEquals('outval', $this->windows ? 'abc$b3 SB' : 'abc$b3!SB'); } - /** - * Inspired by {@link http://www.phing.info/trac/ticket/833} - */ - public function testEscapedArg() + public function testEscapedArgWithoutWhitespace() { + $arg = 'foo|bar'; $this->executeTarget(__FUNCTION__); - $this->assertPropertyEquals('outval', 'abc$b3!SB'); + $this->assertInLogs($this->windows ? '"echo" "foo|bar" 2>&1' : '\'echo\' \'foo|bar\' 2>&1'); + $this->assertNotInLogs($this->windows ? 'echo " foo|bar " 2>&1' : 'echo \' foo|bar \' 2>&1'); } public function testNestedEnv() diff --git a/test/etc/tasks/system/ApplyTest.xml b/test/etc/tasks/system/ApplyTest.xml index 1eeace9b12..7a7b2d8747 100644 --- a/test/etc/tasks/system/ApplyTest.xml +++ b/test/etc/tasks/system/ApplyTest.xml @@ -17,39 +17,39 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -76,7 +76,7 @@ - + @@ -85,7 +85,7 @@
    - + @@ -94,9 +94,9 @@ - + - + @@ -104,7 +104,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -126,9 +126,9 @@ - + - + @@ -145,7 +145,7 @@ The return property's value is: "${execReturnProp}" - + @@ -163,9 +163,9 @@ - + - + @@ -180,9 +180,9 @@ - + - + @@ -191,7 +191,7 @@ - + @@ -199,13 +199,13 @@ - + - + @@ -230,7 +230,7 @@ - + @@ -238,11 +238,21 @@ - + - + + + + + + + + + + + \ No newline at end of file diff --git a/test/etc/tasks/system/ExecTest.xml b/test/etc/tasks/system/ExecTest.xml index 4acd1ffc16..725a41ac52 100644 --- a/test/etc/tasks/system/ExecTest.xml +++ b/test/etc/tasks/system/ExecTest.xml @@ -12,7 +12,7 @@ - + @@ -119,7 +119,7 @@ + osfamily="${current}" /> @@ -134,11 +134,11 @@ - + - + @@ -147,7 +147,7 @@ - + The return property's value is: "${execReturnProp}" @@ -166,7 +166,7 @@ - + @@ -181,12 +181,6 @@ - - - - - - @@ -198,6 +192,12 @@ + + + + + +