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 @@
filesetfilelistdirset
+ mappersrcfile
+ targetfile
@@ -1782,7 +1810,8 @@ verbose="true" failonerror="false" />
commandString
- 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/aOne 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 @@
+
+
+
+
+
+