LibraryInstaller.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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\Installer;
  12. use Composer\IO\IOInterface;
  13. use Composer\Downloader\DownloadManager;
  14. use Composer\Repository\WritableRepositoryInterface;
  15. use Composer\DependencyResolver\Operation\OperationInterface;
  16. use Composer\Package\PackageInterface;
  17. use Composer\Util\Filesystem;
  18. /**
  19. * Package installation manager.
  20. *
  21. * @author Jordi Boggiano <j.boggiano@seld.be>
  22. * @author Konstantin Kudryashov <ever.zet@gmail.com>
  23. */
  24. class LibraryInstaller implements InstallerInterface
  25. {
  26. protected $vendorDir;
  27. protected $binDir;
  28. protected $downloadManager;
  29. protected $repository;
  30. protected $io;
  31. private $type;
  32. private $filesystem;
  33. /**
  34. * Initializes library installer.
  35. *
  36. * @param string $vendorDir relative path for packages home
  37. * @param string $binDir relative path for binaries
  38. * @param DownloadManager $dm download manager
  39. * @param WritableRepositoryInterface $repository repository controller
  40. * @param IOInterface $io io instance
  41. * @param string $type package type that this installer handles
  42. */
  43. public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, IOInterface $io, $type = 'library')
  44. {
  45. $this->downloadManager = $dm;
  46. $this->repository = $repository;
  47. $this->io = $io;
  48. $this->type = $type;
  49. $this->filesystem = new Filesystem();
  50. $this->vendorDir = rtrim($vendorDir, '/');
  51. $this->binDir = rtrim($binDir, '/');
  52. }
  53. /**
  54. * {@inheritDoc}
  55. */
  56. public function supports($packageType)
  57. {
  58. return $packageType === $this->type || null === $this->type;
  59. }
  60. /**
  61. * {@inheritDoc}
  62. */
  63. public function isInstalled(PackageInterface $package)
  64. {
  65. return $this->repository->hasPackage($package) && is_readable($this->getInstallPath($package));
  66. }
  67. /**
  68. * {@inheritDoc}
  69. */
  70. public function install(PackageInterface $package)
  71. {
  72. $downloadPath = $this->getInstallPath($package);
  73. $this->filesystem->ensureDirectoryExists($this->vendorDir);
  74. $this->filesystem->ensureDirectoryExists($this->binDir);
  75. // remove the binaries if it appears the package files are missing
  76. if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
  77. $this->removeBinaries($package);
  78. }
  79. $this->downloadManager->download($package, $downloadPath);
  80. $this->installBinaries($package);
  81. if (!$this->repository->hasPackage($package)) {
  82. $this->repository->addPackage(clone $package);
  83. }
  84. }
  85. /**
  86. * {@inheritDoc}
  87. */
  88. public function update(PackageInterface $initial, PackageInterface $target)
  89. {
  90. if (!$this->repository->hasPackage($initial)) {
  91. throw new \InvalidArgumentException('Package is not installed: '.$initial);
  92. }
  93. $downloadPath = $this->getInstallPath($initial);
  94. $this->filesystem->ensureDirectoryExists($this->vendorDir);
  95. $this->filesystem->ensureDirectoryExists($this->binDir);
  96. $this->removeBinaries($initial);
  97. $this->downloadManager->update($initial, $target, $downloadPath);
  98. $this->installBinaries($target);
  99. $this->repository->removePackage($initial);
  100. $this->repository->addPackage(clone $target);
  101. }
  102. /**
  103. * {@inheritDoc}
  104. */
  105. public function uninstall(PackageInterface $package)
  106. {
  107. if (!$this->repository->hasPackage($package)) {
  108. // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)
  109. return;
  110. throw new \InvalidArgumentException('Package is not installed: '.$package);
  111. }
  112. $downloadPath = $this->getInstallPath($package);
  113. $this->downloadManager->remove($package, $downloadPath);
  114. $this->removeBinaries($package);
  115. $this->repository->removePackage($package);
  116. }
  117. /**
  118. * {@inheritDoc}
  119. */
  120. public function getInstallPath(PackageInterface $package)
  121. {
  122. $targetDir = $package->getTargetDir();
  123. return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
  124. }
  125. protected function installBinaries(PackageInterface $package)
  126. {
  127. if (!$package->getBinaries()) {
  128. return;
  129. }
  130. foreach ($package->getBinaries() as $bin) {
  131. $link = $this->binDir.'/'.basename($bin);
  132. if (file_exists($link)) {
  133. if (is_link($link)) {
  134. // likely leftover from a previous install, make sure
  135. // that the target is still executable in case this
  136. // is a fresh install of the vendor.
  137. chmod($link, 0755);
  138. }
  139. $this->io->write('Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file');
  140. continue;
  141. }
  142. $bin = $this->getInstallPath($package).'/'.$bin;
  143. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  144. // add unixy support for cygwin and similar environments
  145. if ('.bat' !== substr($bin, -4)) {
  146. file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
  147. chmod($link, 0755);
  148. $link .= '.bat';
  149. }
  150. file_put_contents($link, $this->generateWindowsProxyCode($bin, $link));
  151. } else {
  152. symlink($bin, $link);
  153. }
  154. chmod($link, 0755);
  155. }
  156. }
  157. protected function removeBinaries(PackageInterface $package)
  158. {
  159. if (!$package->getBinaries()) {
  160. return;
  161. }
  162. foreach ($package->getBinaries() as $bin => $os) {
  163. $link = $this->binDir.'/'.basename($bin);
  164. if (!file_exists($link)) {
  165. continue;
  166. }
  167. unlink($link);
  168. }
  169. }
  170. private function generateWindowsProxyCode($bin, $link)
  171. {
  172. $binPath = $this->filesystem->findShortestPath($link, $bin);
  173. if ('.bat' === substr($bin, -4)) {
  174. $caller = 'call';
  175. } else {
  176. $handle = fopen($bin, 'r');
  177. $line = fgets($handle);
  178. fclose($handle);
  179. if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
  180. $caller = $match[1];
  181. } else {
  182. $caller = 'php';
  183. }
  184. }
  185. return "@echo off\r\n".
  186. "pushd .\r\n".
  187. "cd %~dp0\r\n".
  188. "cd ".escapeshellarg(dirname($binPath))."\r\n".
  189. "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
  190. "popd\r\n".
  191. $caller." %BIN_TARGET% %*\r\n";
  192. }
  193. private function generateUnixyProxyCode($bin, $link)
  194. {
  195. $binPath = $this->filesystem->findShortestPath($link, $bin);
  196. return "#!/usr/bin/env sh\n".
  197. 'SRC_DIR=`pwd`'."\n".
  198. 'cd `dirname "$0"`'."\n".
  199. 'cd '.escapeshellarg(dirname($binPath))."\n".
  200. 'BIN_TARGET=`pwd`/'.basename($binPath)."\n".
  201. 'cd $SRC_DIR'."\n".
  202. '$BIN_TARGET "$@"'."\n";
  203. }
  204. }