ArchiveManager.php 5.8 KB

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