ZipTask.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <?php
  2. /*
  3. * $Id: ZipTask.php 905 2010-10-05 16:28:03Z mrook $
  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. require_once 'phing/tasks/system/MatchingTask.php';
  22. include_once 'phing/util/SourceFileScanner.php';
  23. include_once 'phing/mappers/MergeMapper.php';
  24. include_once 'phing/util/StringHelper.php';
  25. /**
  26. * Creates a zip archive using PHP ZipArchive extension/
  27. *
  28. * @author Michiel Rook <michiel.rook@gmail.com>
  29. * @version $Id: ZipTask.php 905 2010-10-05 16:28:03Z mrook $
  30. * @package phing.tasks.ext
  31. * @since 2.1.0
  32. */
  33. class ZipTask extends MatchingTask {
  34. /**
  35. * @var PhingFile
  36. */
  37. private $zipFile;
  38. /**
  39. * @var PhingFile
  40. */
  41. private $baseDir;
  42. /**
  43. * Whether to include empty dirs in the archive.
  44. */
  45. private $includeEmpty = true;
  46. private $filesets = array();
  47. private $fileSetFiles = array();
  48. /**
  49. * File path prefix in zip archive
  50. *
  51. * @var string
  52. */
  53. private $prefix = null;
  54. /**
  55. * Add a new fileset.
  56. * @return FileSet
  57. */
  58. public function createFileSet() {
  59. $this->fileset = new ZipFileSet();
  60. $this->filesets[] = $this->fileset;
  61. return $this->fileset;
  62. }
  63. /**
  64. * Set is the name/location of where to create the zip file.
  65. * @param PhingFile $destFile The output of the zip
  66. */
  67. public function setDestFile(PhingFile $destFile) {
  68. $this->zipFile = $destFile;
  69. }
  70. /**
  71. * This is the base directory to look in for things to zip.
  72. * @param PhingFile $baseDir
  73. */
  74. public function setBasedir(PhingFile $baseDir) {
  75. $this->baseDir = $baseDir;
  76. }
  77. /**
  78. * Sets the file path prefix for file in the zip file.
  79. *
  80. * @param string $prefix Prefix
  81. *
  82. * @return void
  83. */
  84. public function setPrefix($prefix) {
  85. $this->prefix = $prefix;
  86. }
  87. /**
  88. * Set the include empty dirs flag.
  89. * @param boolean Flag if empty dirs should be tarred too
  90. * @return void
  91. * @access public
  92. */
  93. function setIncludeEmptyDirs($bool) {
  94. $this->includeEmpty = (boolean) $bool;
  95. }
  96. /**
  97. * do the work
  98. * @throws BuildException
  99. */
  100. public function main() {
  101. if ($this->zipFile === null) {
  102. throw new BuildException("zipfile attribute must be set!", $this->getLocation());
  103. }
  104. if ($this->zipFile->exists() && $this->zipFile->isDirectory()) {
  105. throw new BuildException("zipfile is a directory!", $this->getLocation());
  106. }
  107. if ($this->zipFile->exists() && !$this->zipFile->canWrite()) {
  108. throw new BuildException("Can not write to the specified zipfile!", $this->getLocation());
  109. }
  110. // shouldn't need to clone, since the entries in filesets
  111. // themselves won't be modified -- only elements will be added
  112. $savedFileSets = $this->filesets;
  113. try {
  114. if ($this->baseDir !== null) {
  115. if (!$this->baseDir->exists()) {
  116. throw new BuildException("basedir does not exist!", $this->getLocation());
  117. }
  118. if (empty($this->filesets))
  119. {
  120. // add the main fileset to the list of filesets to process.
  121. $mainFileSet = new ZipFileSet($this->fileset);
  122. $mainFileSet->setDir($this->baseDir);
  123. $this->filesets[] = $mainFileSet;
  124. }
  125. }
  126. if (empty($this->filesets)) {
  127. throw new BuildException("You must supply either a basedir "
  128. . "attribute or some nested filesets.",
  129. $this->getLocation());
  130. }
  131. // check if zip is out of date with respect to each
  132. // fileset
  133. $upToDate = true;
  134. foreach($this->filesets as $fs) {
  135. $files = $fs->getFiles($this->project, $this->includeEmpty);
  136. if (!$this->archiveIsUpToDate($files, $fs->getDir($this->project))) {
  137. $upToDate = false;
  138. }
  139. for ($i=0, $fcount=count($files); $i < $fcount; $i++) {
  140. if ($this->zipFile->equals(new PhingFile($fs->getDir($this->project), $files[$i]))) {
  141. throw new BuildException("A zip file cannot include itself", $this->getLocation());
  142. }
  143. }
  144. }
  145. if ($upToDate) {
  146. $this->log("Nothing to do: " . $this->zipFile->__toString() . " is up to date.", Project::MSG_INFO);
  147. return;
  148. }
  149. $this->log("Building zip: " . $this->zipFile->__toString(), Project::MSG_INFO);
  150. $zip = new ZipArchive();
  151. $res = $zip->open($this->zipFile->getAbsolutePath(), ZIPARCHIVE::CREATE);
  152. if ($res !== true)
  153. {
  154. throw new Exception("ZipArchive::open() failed with code " . $res);
  155. }
  156. foreach($this->filesets as $fs) {
  157. $fsBasedir = (null != $this->baseDir) ? $this->baseDir :
  158. $fs->getDir($this->project);
  159. $files = $fs->getFiles($this->project, $this->includeEmpty);
  160. $filesToZip = array();
  161. for ($i=0, $fcount=count($files); $i < $fcount; $i++) {
  162. $f = new PhingFile($fsBasedir, $files[$i]);
  163. $pathInZip = $this->prefix
  164. . $f->getPathWithoutBase($fsBasedir);
  165. $pathInZip = str_replace('\\', '/', $pathInZip);
  166. if ($f->isDirectory()) {
  167. if ($pathInZip != '.') {
  168. $zip->addEmptyDir($pathInZip);
  169. }
  170. } else {
  171. $zip->addFile($f->getPath(), $pathInZip);
  172. }
  173. $this->log("Adding " . $f->getPath() . " as " . $pathInZip . " to archive.", Project::MSG_VERBOSE);
  174. }
  175. }
  176. $zip->close();
  177. } catch (IOException $ioe) {
  178. $msg = "Problem creating ZIP: " . $ioe->getMessage();
  179. $this->filesets = $savedFileSets;
  180. throw new BuildException($msg, $ioe, $this->getLocation());
  181. }
  182. $this->filesets = $savedFileSets;
  183. }
  184. /**
  185. * @param array $files array of filenames
  186. * @param PhingFile $dir
  187. * @return boolean
  188. */
  189. protected function archiveIsUpToDate($files, $dir) {
  190. $sfs = new SourceFileScanner($this);
  191. $mm = new MergeMapper();
  192. $mm->setTo($this->zipFile->getAbsolutePath());
  193. return count($sfs->restrict($files, $dir, null, $mm)) == 0;
  194. }
  195. }
  196. /**
  197. * This is a FileSet with the to specify permissions.
  198. *
  199. * Permissions are currently not implemented by PEAR Archive_Tar,
  200. * but hopefully they will be in the future.
  201. *
  202. */
  203. class ZipFileSet extends FileSet {
  204. private $files = null;
  205. /**
  206. * Get a list of files and directories specified in the fileset.
  207. * @return array a list of file and directory names, relative to
  208. * the baseDir for the project.
  209. */
  210. public function getFiles(Project $p, $includeEmpty = true) {
  211. if ($this->files === null) {
  212. $ds = $this->getDirectoryScanner($p);
  213. $this->files = $ds->getIncludedFiles();
  214. // build a list of directories implicitly added by any of the files
  215. $implicitDirs = array();
  216. foreach($this->files as $file) {
  217. $implicitDirs[] = dirname($file);
  218. }
  219. $incDirs = $ds->getIncludedDirectories();
  220. // we'll need to add to that list of implicit dirs any directories
  221. // that contain other *directories* (and not files), since otherwise
  222. // we get duplicate directories in the resulting tar
  223. foreach($incDirs as $dir) {
  224. foreach($incDirs as $dircheck) {
  225. if (!empty($dir) && $dir == dirname($dircheck)) {
  226. $implicitDirs[] = $dir;
  227. }
  228. }
  229. }
  230. $implicitDirs = array_unique($implicitDirs);
  231. $emptyDirectories = array();
  232. if ($includeEmpty) {
  233. // Now add any empty dirs (dirs not covered by the implicit dirs)
  234. // to the files array.
  235. foreach($incDirs as $dir) { // we cannot simply use array_diff() since we want to disregard empty/. dirs
  236. if ($dir != "" && $dir != "." && !in_array($dir, $implicitDirs)) {
  237. // it's an empty dir, so we'll add it.
  238. $emptyDirectories[] = $dir;
  239. }
  240. }
  241. } // if $includeEmpty
  242. $this->files = array_merge($implicitDirs, $emptyDirectories, $this->files);
  243. sort($this->files);
  244. } // if ($this->files===null)
  245. return $this->files;
  246. }
  247. }