ArchiveManager.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Package\Archiver;
  12. use Composer\Downloader\DownloadManager;
  13. use Composer\Package\PackageInterface;
  14. use Composer\Package\RootPackageInterface;
  15. use Composer\Util\Filesystem;
  16. use Composer\Util\Loop;
  17. use Composer\Json\JsonFile;
  18. /**
  19. * @author Matthieu Moquet <matthieu@moquet.net>
  20. * @author Till Klampaeckel <till@php.net>
  21. */
  22. class ArchiveManager
  23. {
  24. protected $downloadManager;
  25. protected $loop;
  26. protected $archivers = array();
  27. /**
  28. * @var bool
  29. */
  30. protected $overwriteFiles = true;
  31. /**
  32. * @param DownloadManager $downloadManager A manager used to download package sources
  33. */
  34. public function __construct(DownloadManager $downloadManager, Loop $loop)
  35. {
  36. $this->downloadManager = $downloadManager;
  37. $this->loop = $loop;
  38. }
  39. /**
  40. * @param ArchiverInterface $archiver
  41. */
  42. public function addArchiver(ArchiverInterface $archiver)
  43. {
  44. $this->archivers[] = $archiver;
  45. }
  46. /**
  47. * Set whether existing archives should be overwritten
  48. *
  49. * @param bool $overwriteFiles New setting
  50. *
  51. * @return $this
  52. */
  53. public function setOverwriteFiles($overwriteFiles)
  54. {
  55. $this->overwriteFiles = $overwriteFiles;
  56. return $this;
  57. }
  58. /**
  59. * Generate a distinct filename for a particular version of a package.
  60. *
  61. * @param PackageInterface $package The package to get a name for
  62. *
  63. * @return string A filename without an extension
  64. */
  65. public function getPackageFilename(PackageInterface $package)
  66. {
  67. $nameParts = array(preg_replace('#[^a-z0-9-_]#i', '-', $package->getName()));
  68. if (preg_match('{^[a-f0-9]{40}$}', $package->getDistReference())) {
  69. array_push($nameParts, $package->getDistReference(), $package->getDistType());
  70. } else {
  71. array_push($nameParts, $package->getPrettyVersion(), $package->getDistReference());
  72. }
  73. if ($package->getSourceReference()) {
  74. $nameParts[] = substr(sha1($package->getSourceReference()), 0, 6);
  75. }
  76. $name = implode('-', array_filter($nameParts, function ($p) {
  77. return !empty($p);
  78. }));
  79. return str_replace('/', '-', $name);
  80. }
  81. /**
  82. * Create an archive of the specified package.
  83. *
  84. * @param PackageInterface $package The package to archive
  85. * @param string $format The format of the archive (zip, tar, ...)
  86. * @param string $targetDir The directory where to build the archive
  87. * @param string|null $fileName The relative file name to use for the archive, or null to generate
  88. * the package name. Note that the format will be appended to this name
  89. * @param bool $ignoreFilters Ignore filters when looking for files in the package
  90. * @throws \InvalidArgumentException
  91. * @throws \RuntimeException
  92. * @return string The path of the created archive
  93. */
  94. public function archive(PackageInterface $package, $format, $targetDir, $fileName = null, $ignoreFilters = false)
  95. {
  96. if (empty($format)) {
  97. throw new \InvalidArgumentException('Format must be specified');
  98. }
  99. // Search for the most appropriate archiver
  100. $usableArchiver = null;
  101. foreach ($this->archivers as $archiver) {
  102. if ($archiver->supports($format, $package->getSourceType())) {
  103. $usableArchiver = $archiver;
  104. break;
  105. }
  106. }
  107. // Checks the format/source type are supported before downloading the package
  108. if (null === $usableArchiver) {
  109. throw new \RuntimeException(sprintf('No archiver found to support %s format', $format));
  110. }
  111. $filesystem = new Filesystem();
  112. if (null === $fileName) {
  113. $packageName = $this->getPackageFilename($package);
  114. } else {
  115. $packageName = $fileName;
  116. }
  117. // Archive filename
  118. $filesystem->ensureDirectoryExists($targetDir);
  119. $target = realpath($targetDir).'/'.$packageName.'.'.$format;
  120. $filesystem->ensureDirectoryExists(dirname($target));
  121. if (!$this->overwriteFiles && file_exists($target)) {
  122. return $target;
  123. }
  124. if ($package instanceof RootPackageInterface) {
  125. $sourcePath = realpath('.');
  126. } else {
  127. // Directory used to download the sources
  128. $sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid();
  129. $filesystem->ensureDirectoryExists($sourcePath);
  130. // Download sources
  131. $promise = $this->downloadManager->download($package, $sourcePath);
  132. $this->loop->wait(array($promise));
  133. $this->downloadManager->install($package, $sourcePath);
  134. // Check exclude from downloaded composer.json
  135. if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
  136. $jsonFile = new JsonFile($composerJsonPath);
  137. $jsonData = $jsonFile->read();
  138. if (!empty($jsonData['archive']['exclude'])) {
  139. $package->setArchiveExcludes($jsonData['archive']['exclude']);
  140. }
  141. }
  142. }
  143. // Create the archive
  144. $tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format;
  145. $filesystem->ensureDirectoryExists(dirname($tempTarget));
  146. $archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes(), $ignoreFilters);
  147. $filesystem->rename($archivePath, $target);
  148. // cleanup temporary download
  149. if (!$package instanceof RootPackageInterface) {
  150. $filesystem->removeDirectory($sourcePath);
  151. }
  152. $filesystem->remove($tempTarget);
  153. return $target;
  154. }
  155. }