3 namespace Robo\Task\Archive;
5 use Robo\Contract\PrintedInterface;
7 use Robo\Task\BaseTask;
8 use Symfony\Component\Finder\Finder;
11 * Creates a zip or tar archive.
17 * ->add('README') // Puts file 'README' in archive at the root
18 * ->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
19 * ->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
24 class Pack extends BaseTask implements PrintedInterface
27 * The list of items to be packed into the archive.
34 * The full path to the archive to be created.
41 * Construct the class.
43 * @param string $archiveFile The full path and name of the archive file to create.
47 public function __construct($archiveFile)
49 $this->archiveFile = $archiveFile;
53 * Satisfy the parent requirement.
55 * @return bool Always returns true.
59 public function getPrinted()
65 * @param string $archiveFile
69 public function archiveFile($archiveFile)
71 $this->archiveFile = $archiveFile;
76 * Add an item to the archive. Like file_exists(), the parameter
77 * may be a file or a directory.
80 * Relative path and name of item to store in archive
82 * Absolute or relative path to file or directory's location in filesystem
86 public function addFile($placementLocation, $filesystemLocation)
88 $this->items[$placementLocation] = $filesystemLocation;
94 * Alias for addFile, in case anyone has angst about using
95 * addFile with a directory.
98 * Relative path and name of directory to store in archive
100 * Absolute or relative path to directory or directory's location in filesystem
104 public function addDir($placementLocation, $filesystemLocation)
106 $this->addFile($placementLocation, $filesystemLocation);
112 * Add a file or directory, or list of same to the archive.
115 * If given a string, should contain the relative filesystem path to the
116 * the item to store in archive; this will also be used as the item's
117 * path in the archive, so absolute paths should not be used here.
118 * If given an array, the key of each item should be the path to store
119 * in the archive, and the value should be the filesystem path to the
123 public function add($item)
125 if (is_array($item)) {
126 $this->items = array_merge($this->items, $item);
128 $this->addFile($item, $item);
135 * Create a zip archive for distribution.
137 * @return \Robo\Result
141 public function run()
145 // Use the file extension to determine what kind of archive to create.
146 $fileInfo = new \SplFileInfo($this->archiveFile);
147 $extension = strtolower($fileInfo->getExtension());
148 if (empty($extension)) {
149 return Result::error($this, "Archive filename must use an extension (e.g. '.zip') to specify the kind of archive to create.");
153 // Inform the user which archive we are creating
154 $this->printTaskInfo("Creating archive {filename}", ['filename' => $this->archiveFile]);
155 if ($extension == 'zip') {
156 $result = $this->archiveZip($this->archiveFile, $this->items);
158 $result = $this->archiveTar($this->archiveFile, $this->items);
160 $this->printTaskSuccess("{filename} created.", ['filename' => $this->archiveFile]);
161 } catch (\Exception $e) {
162 $this->printTaskError("Could not create {filename}. {exception}", ['filename' => $this->archiveFile, 'exception' => $e->getMessage(), '_style' => ['exception' => '']]);
163 $result = Result::error($this, sprintf('Could not create %s. %s', $this->archiveFile, $e->getMessage()));
166 $result['time'] = $this->getExecutionTime();
172 * @param string $archiveFile
173 * @param array $items
175 * @return \Robo\Result
177 protected function archiveTar($archiveFile, $items)
179 if (!class_exists('Archive_Tar')) {
180 return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
183 $tar_object = new \Archive_Tar($archiveFile);
184 foreach ($items as $placementLocation => $filesystemLocation) {
185 $p_remove_dir = $filesystemLocation;
186 $p_add_dir = $placementLocation;
187 if (is_file($filesystemLocation)) {
188 $p_remove_dir = dirname($filesystemLocation);
189 $p_add_dir = dirname($placementLocation);
190 if (basename($filesystemLocation) != basename($placementLocation)) {
191 return Result::error($this, "Tar archiver does not support renaming files during extraction; could not add $filesystemLocation as $placementLocation.");
195 if (!$tar_object->addModify([$filesystemLocation], $p_add_dir, $p_remove_dir)) {
196 return Result::error($this, "Could not add $filesystemLocation to the archive.");
200 return Result::success($this);
204 * @param string $archiveFile
205 * @param array $items
207 * @return \Robo\Result
209 protected function archiveZip($archiveFile, $items)
211 if (!extension_loaded('zlib')) {
212 return Result::errorMissingExtension($this, 'zlib', 'zip packing');
215 $zip = new \ZipArchive($archiveFile, \ZipArchive::CREATE);
216 if (!$zip->open($archiveFile, \ZipArchive::CREATE)) {
217 return Result::error($this, "Could not create zip archive {$archiveFile}");
219 $result = $this->addItemsToZip($zip, $items);
226 * @param \ZipArchive $zip
227 * @param array $items
229 * @return \Robo\Result
231 protected function addItemsToZip($zip, $items)
233 foreach ($items as $placementLocation => $filesystemLocation) {
234 if (is_dir($filesystemLocation)) {
235 $finder = new Finder();
236 $finder->files()->in($filesystemLocation)->ignoreDotFiles(false);
238 foreach ($finder as $file) {
239 // Replace Windows slashes or resulting zip will have issues on *nixes.
240 $relativePathname = str_replace('\\', '/', $file->getRelativePathname());
242 if (!$zip->addFile($file->getRealpath(), "{$placementLocation}/{$relativePathname}")) {
243 return Result::error($this, "Could not add directory $filesystemLocation to the archive; error adding {$file->getRealpath()}.");
246 } elseif (is_file($filesystemLocation)) {
247 if (!$zip->addFile($filesystemLocation, $placementLocation)) {
248 return Result::error($this, "Could not add file $filesystemLocation to the archive.");
251 return Result::error($this, "Could not find $filesystemLocation for the archive.");
255 return Result::success($this);