1<?php 2/* 3 * $Id: c3ac5fcdf4d7cdb199d57b021e3f015c9c7fd3f8 $ 4 * 5 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 7 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 8 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 11 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 12 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 13 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 14 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 15 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 * 17 * This software consists of voluntary contributions made by many individuals 18 * and is licensed under the LGPL. For more information please see 19 * <http://phing.info>. 20 */ 21 22require_once 'phing/tasks/system/MatchingTask.php'; 23include_once 'phing/util/SourceFileScanner.php'; 24include_once 'phing/mappers/MergeMapper.php'; 25include_once 'phing/util/StringHelper.php'; 26 27/** 28 * Creates a tar archive using PEAR Archive_Tar. 29 * 30 * @author Hans Lellelid <hans@xmpl.org> (Phing) 31 * @author Stefano Mazzocchi <stefano@apache.org> (Ant) 32 * @author Stefan Bodewig <stefan.bodewig@epost.de> (Ant) 33 * @author Magesh Umasankar 34 * @version $Id: c3ac5fcdf4d7cdb199d57b021e3f015c9c7fd3f8 $ 35 * @package phing.tasks.ext 36 */ 37class TarTask extends MatchingTask { 38 39 const TAR_NAMELEN = 100; 40 41 const WARN = "warn"; 42 const FAIL = "fail"; 43 const OMIT = "omit"; 44 45 private $tarFile; 46 private $baseDir; 47 private $includeEmpty = true; // Whether to include empty dirs in the TAR 48 49 private $longFileMode = "warn"; 50 51 private $filesets = array(); 52 private $fileSetFiles = array(); 53 54 /** 55 * Indicates whether the user has been warned about long files already. 56 */ 57 private $longWarningGiven = false; 58 59 /** 60 * Compression mode. Available options "gzip", "bzip2", "none" (null). 61 */ 62 private $compression = null; 63 64 /** 65 * File path prefix in the tar archive 66 * 67 * @var string 68 */ 69 private $prefix = null; 70 71 /** 72 * Ensures that PEAR lib exists. 73 */ 74 public function init() { 75 include_once 'Archive/Tar.php'; 76 if (!class_exists('Archive_Tar')) { 77 throw new BuildException("You must have installed the PEAR Archive_Tar class in order to use TarTask."); 78 } 79 } 80 81 /** 82 * Add a new fileset 83 * @return FileSet 84 */ 85 public function createTarFileSet() { 86 $this->fileset = new TarFileSet(); 87 $this->filesets[] = $this->fileset; 88 return $this->fileset; 89 } 90 91 /** 92 * Add a new fileset. Alias to createTarFileSet() for backwards compatibility. 93 * @return FileSet 94 * @see createTarFileSet() 95 */ 96 public function createFileSet() { 97 $this->fileset = new TarFileSet(); 98 $this->filesets[] = $this->fileset; 99 return $this->fileset; 100 } 101 102 /** 103 * Set is the name/location of where to create the tar file. 104 * @param PhingFile $destFile The output of the tar 105 */ 106 public function setDestFile(PhingFile $destFile) { 107 $this->tarFile = $destFile; 108 } 109 110 /** 111 * This is the base directory to look in for things to tar. 112 * @param PhingFile $baseDir 113 */ 114 public function setBasedir(PhingFile $baseDir) { 115 $this->baseDir = $baseDir; 116 } 117 118 /** 119 * Set the include empty dirs flag. 120 * @param boolean Flag if empty dirs should be tarred too 121 * @return void 122 * @access public 123 */ 124 public function setIncludeEmptyDirs($bool) { 125 $this->includeEmpty = (boolean) $bool; 126 } 127 128 /** 129 * Set how to handle long files, those with a path>100 chars. 130 * Optional, default=warn. 131 * <p> 132 * Allowable values are 133 * <ul> 134 * <li> truncate - paths are truncated to the maximum length 135 * <li> fail - paths greater than the maximim cause a build exception 136 * <li> warn - paths greater than the maximum cause a warning and GNU is used 137 * <li> gnu - GNU extensions are used for any paths greater than the maximum. 138 * <li> omit - paths greater than the maximum are omitted from the archive 139 * </ul> 140 */ 141 public function setLongfile($mode) { 142 $this->longFileMode = $mode; 143 } 144 145 /** 146 * Set compression method. 147 * Allowable values are 148 * <ul> 149 * <li> none - no compression 150 * <li> gzip - Gzip compression 151 * <li> bzip2 - Bzip2 compression 152 * </ul> 153 */ 154 public function setCompression($mode) { 155 switch($mode) { 156 case "gzip": 157 $this->compression = "gz"; 158 break; 159 case "bzip2": 160 $this->compression = "bz2"; 161 break; 162 case "none": 163 $this->compression = null; 164 break; 165 default: 166 $this->log("Ignoring unknown compression mode: ".$mode, Project::MSG_WARN); 167 $this->compression = null; 168 } 169 } 170 171 /** 172 * Sets the file path prefix for file in the tar file. 173 * 174 * @param string $prefix Prefix 175 * 176 * @return void 177 */ 178 public function setPrefix($prefix) { 179 $this->prefix = $prefix; 180 } 181 182 /** 183 * do the work 184 * @throws BuildException 185 */ 186 public function main() { 187 188 if ($this->tarFile === null) { 189 throw new BuildException("tarfile attribute must be set!", $this->getLocation()); 190 } 191 192 if ($this->tarFile->exists() && $this->tarFile->isDirectory()) { 193 throw new BuildException("tarfile is a directory!", $this->getLocation()); 194 } 195 196 if ($this->tarFile->exists() && !$this->tarFile->canWrite()) { 197 throw new BuildException("Can not write to the specified tarfile!", $this->getLocation()); 198 } 199 200 // shouldn't need to clone, since the entries in filesets 201 // themselves won't be modified -- only elements will be added 202 $savedFileSets = $this->filesets; 203 204 try { 205 if ($this->baseDir !== null) { 206 if (!$this->baseDir->exists()) { 207 throw new BuildException("basedir '" . (string) $this->baseDir . "' does not exist!", $this->getLocation()); 208 } 209 if (empty($this->filesets)) { // if there weren't any explicit filesets specivied, then 210 // create a default, all-inclusive fileset using the specified basedir. 211 $mainFileSet = new TarFileSet($this->fileset); 212 $mainFileSet->setDir($this->baseDir); 213 $this->filesets[] = $mainFileSet; 214 } 215 } 216 217 if (empty($this->filesets)) { 218 throw new BuildException("You must supply either a basedir " 219 . "attribute or some nested filesets.", 220 $this->getLocation()); 221 } 222 223 // check if tar is out of date with respect to each fileset 224 if($this->tarFile->exists()) { 225 $upToDate = true; 226 foreach($this->filesets as $fs) { 227 $files = $fs->getFiles($this->project, $this->includeEmpty); 228 if (!$this->archiveIsUpToDate($files, $fs->getDir($this->project))) { 229 $upToDate = false; 230 } 231 for ($i=0, $fcount=count($files); $i < $fcount; $i++) { 232 if ($this->tarFile->equals(new PhingFile($fs->getDir($this->project), $files[$i]))) { 233 throw new BuildException("A tar file cannot include itself", $this->getLocation()); 234 } 235 } 236 } 237 if ($upToDate) { 238 $this->log("Nothing to do: " . $this->tarFile->__toString() . " is up to date.", Project::MSG_INFO); 239 return; 240 } 241 } 242 243 $this->log("Building tar: " . $this->tarFile->__toString(), Project::MSG_INFO); 244 245 $tar = new Archive_Tar($this->tarFile->getAbsolutePath(), $this->compression); 246 247 if (PEAR::isError($tar->error_object)) { 248 throw new BuildException($tar->error_object->getMessage()); 249 } 250 251 foreach($this->filesets as $fs) { 252 $files = $fs->getFiles($this->project, $this->includeEmpty); 253 if (count($files) > 1 && strlen($fs->getFullpath()) > 0) { 254 throw new BuildException("fullpath attribute may only " 255 . "be specified for " 256 . "filesets that specify a " 257 . "single file."); 258 } 259 $fsBasedir = $fs->getDir($this->project); 260 $filesToTar = array(); 261 for ($i=0, $fcount=count($files); $i < $fcount; $i++) { 262 $f = new PhingFile($fsBasedir, $files[$i]); 263 $filesToTar[] = $f->getAbsolutePath(); 264 $this->log("Adding file " . $f->getPath() . " to archive.", Project::MSG_VERBOSE); 265 } 266 $tar->addModify($filesToTar, $this->prefix, $fsBasedir->getAbsolutePath()); 267 268 if (PEAR::isError($tar->error_object)) { 269 throw new BuildException($tar->error_object->getMessage()); 270 } 271 } 272 273 274 } catch (IOException $ioe) { 275 $msg = "Problem creating TAR: " . $ioe->getMessage(); 276 $this->filesets = $savedFileSets; 277 throw new BuildException($msg, $ioe, $this->getLocation()); 278 } 279 280 $this->filesets = $savedFileSets; 281 } 282 283 /** 284 * @param array $files array of filenames 285 * @param PhingFile $dir 286 * @return boolean 287 */ 288 protected function archiveIsUpToDate($files, $dir) { 289 $sfs = new SourceFileScanner($this); 290 $mm = new MergeMapper(); 291 $mm->setTo($this->tarFile->getAbsolutePath()); 292 return count($sfs->restrict($files, $dir, null, $mm)) == 0; 293 } 294 295} 296 297 298/** 299 * This is a FileSet with the option to specify permissions. 300 * 301 * Permissions are currently not implemented by PEAR Archive_Tar, 302 * but hopefully they will be in the future. 303 * 304 * @package phing.tasks.ext 305 */ 306class TarFileSet extends FileSet { 307 308 private $files = null; 309 310 private $mode = 0100644; 311 312 private $userName = ""; 313 private $groupName = ""; 314 private $prefix = ""; 315 private $fullpath = ""; 316 private $preserveLeadingSlashes = false; 317 318 /** 319 * Get a list of files and directories specified in the fileset. 320 * @return array a list of file and directory names, relative to 321 * the baseDir for the project. 322 */ 323 public function getFiles(Project $p, $includeEmpty = true) { 324 325 if ($this->files === null) { 326 327 $ds = $this->getDirectoryScanner($p); 328 $this->files = $ds->getIncludedFiles(); 329 330 if ($includeEmpty) { 331 332 // first any empty directories that will not be implicitly added by any of the files 333 $implicitDirs = array(); 334 foreach($this->files as $file) { 335 $implicitDirs[] = dirname($file); 336 } 337 338 $incDirs = $ds->getIncludedDirectories(); 339 340 // we'll need to add to that list of implicit dirs any directories 341 // that contain other *directories* (and not files), since otherwise 342 // we get duplicate directories in the resulting tar 343 foreach($incDirs as $dir) { 344 foreach($incDirs as $dircheck) { 345 if (!empty($dir) && $dir == dirname($dircheck)) { 346 $implicitDirs[] = $dir; 347 } 348 } 349 } 350 351 $implicitDirs = array_unique($implicitDirs); 352 353 // Now add any empty dirs (dirs not covered by the implicit dirs) 354 // to the files array. 355 356 foreach($incDirs as $dir) { // we cannot simply use array_diff() since we want to disregard empty/. dirs 357 if ($dir != "" && $dir != "." && !in_array($dir, $implicitDirs)) { 358 // it's an empty dir, so we'll add it. 359 $this->files[] = $dir; 360 } 361 } 362 } // if $includeEmpty 363 364 } // if ($this->files===null) 365 366 return $this->files; 367 } 368 369 /** 370 * A 3 digit octal string, specify the user, group and 371 * other modes in the standard Unix fashion; 372 * optional, default=0644 373 * @param string $octalString 374 */ 375 public function setMode($octalString) { 376 $octal = (int) $octalString; 377 $this->mode = 0100000 | $octal; 378 } 379 380 public function getMode() { 381 return $this->mode; 382 } 383 384 /** 385 * The username for the tar entry 386 * This is not the same as the UID, which is 387 * not currently set by the task. 388 */ 389 public function setUserName($userName) { 390 $this->userName = $userName; 391 } 392 393 public function getUserName() { 394 return $this->userName; 395 } 396 397 /** 398 * The groupname for the tar entry; optional, default="" 399 * This is not the same as the GID, which is 400 * not currently set by the task. 401 */ 402 public function setGroup($groupName) { 403 $this->groupName = $groupName; 404 } 405 406 public function getGroup() { 407 return $this->groupName; 408 } 409 410 /** 411 * If the prefix attribute is set, all files in the fileset 412 * are prefixed with that path in the archive. 413 * optional. 414 */ 415 public function setPrefix($prefix) { 416 $this->prefix = $prefix; 417 } 418 419 public function getPrefix() { 420 return $this->prefix; 421 } 422 423 /** 424 * If the fullpath attribute is set, the file in the fileset 425 * is written with that path in the archive. The prefix attribute, 426 * if specified, is ignored. It is an error to have more than one file specified in 427 * such a fileset. 428 */ 429 public function setFullpath($fullpath) { 430 $this->fullpath = $fullpath; 431 } 432 433 public function getFullpath() { 434 return $this->fullpath; 435 } 436 437 /** 438 * Flag to indicates whether leading `/'s should 439 * be preserved in the file names. 440 * Optional, default is <code>false</code>. 441 * @return void 442 */ 443 public function setPreserveLeadingSlashes($b) { 444 $this->preserveLeadingSlashes = (boolean) $b; 445 } 446 447 public function getPreserveLeadingSlashes() { 448 return $this->preserveLeadingSlashes; 449 } 450} 451