PageRenderTime 26ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Composer/Installer/LibraryInstaller.php

https://github.com/pborreli/composer
PHP | 284 lines | 205 code | 31 blank | 48 comment | 26 complexity | 4ea2bf93cc45056cd472b442b3801c98 MD5 | raw file
  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\Composer;
  13. use Composer\IO\IOInterface;
  14. use Composer\Downloader\DownloadManager;
  15. use Composer\Repository\InstalledRepositoryInterface;
  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 $composer;
  27. protected $vendorDir;
  28. protected $binDir;
  29. protected $downloadManager;
  30. protected $io;
  31. protected $type;
  32. protected $filesystem;
  33. /**
  34. * Initializes library installer.
  35. *
  36. * @param IOInterface $io
  37. * @param Composer $composer
  38. * @param string $type
  39. */
  40. public function __construct(IOInterface $io, Composer $composer, $type = 'library')
  41. {
  42. $this->composer = $composer;
  43. $this->downloadManager = $composer->getDownloadManager();
  44. $this->io = $io;
  45. $this->type = $type;
  46. $this->filesystem = new Filesystem();
  47. $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
  48. $this->binDir = rtrim($composer->getConfig()->get('bin-dir'), '/');
  49. }
  50. /**
  51. * {@inheritDoc}
  52. */
  53. public function supports($packageType)
  54. {
  55. return $packageType === $this->type || null === $this->type;
  56. }
  57. /**
  58. * {@inheritDoc}
  59. */
  60. public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
  61. {
  62. return $repo->hasPackage($package) && is_readable($this->getInstallPath($package));
  63. }
  64. /**
  65. * {@inheritDoc}
  66. */
  67. public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
  68. {
  69. $this->initializeVendorDir();
  70. $downloadPath = $this->getInstallPath($package);
  71. // remove the binaries if it appears the package files are missing
  72. if (!is_readable($downloadPath) && $repo->hasPackage($package)) {
  73. $this->removeBinaries($package);
  74. }
  75. $this->installCode($package);
  76. $this->installBinaries($package);
  77. if (!$repo->hasPackage($package)) {
  78. $repo->addPackage(clone $package);
  79. }
  80. }
  81. /**
  82. * {@inheritDoc}
  83. */
  84. public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
  85. {
  86. if (!$repo->hasPackage($initial)) {
  87. throw new \InvalidArgumentException('Package is not installed: '.$initial);
  88. }
  89. $this->initializeVendorDir();
  90. $this->removeBinaries($initial);
  91. $this->updateCode($initial, $target);
  92. $this->installBinaries($target);
  93. $repo->removePackage($initial);
  94. if (!$repo->hasPackage($target)) {
  95. $repo->addPackage(clone $target);
  96. }
  97. }
  98. /**
  99. * {@inheritDoc}
  100. */
  101. public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
  102. {
  103. if (!$repo->hasPackage($package)) {
  104. // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)
  105. return;
  106. throw new \InvalidArgumentException('Package is not installed: '.$package);
  107. }
  108. $downloadPath = $this->getInstallPath($package);
  109. $this->removeCode($package);
  110. $this->removeBinaries($package);
  111. $repo->removePackage($package);
  112. if (strpos($package->getName(), '/')) {
  113. $packageVendorDir = dirname($downloadPath);
  114. if (is_dir($packageVendorDir) && !glob($packageVendorDir.'/*')) {
  115. @rmdir($packageVendorDir);
  116. }
  117. }
  118. }
  119. /**
  120. * {@inheritDoc}
  121. */
  122. public function getInstallPath(PackageInterface $package)
  123. {
  124. $this->initializeVendorDir();
  125. $targetDir = $package->getTargetDir();
  126. return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
  127. }
  128. protected function installCode(PackageInterface $package)
  129. {
  130. $downloadPath = $this->getInstallPath($package);
  131. $this->downloadManager->download($package, $downloadPath);
  132. }
  133. protected function updateCode(PackageInterface $initial, PackageInterface $target)
  134. {
  135. $downloadPath = $this->getInstallPath($initial);
  136. $this->downloadManager->update($initial, $target, $downloadPath);
  137. }
  138. protected function removeCode(PackageInterface $package)
  139. {
  140. $downloadPath = $this->getInstallPath($package);
  141. $this->downloadManager->remove($package, $downloadPath);
  142. }
  143. protected function getBinaries(PackageInterface $package)
  144. {
  145. return $package->getBinaries();
  146. }
  147. protected function installBinaries(PackageInterface $package)
  148. {
  149. $binaries = $this->getBinaries($package);
  150. if (!$binaries) {
  151. return;
  152. }
  153. foreach ($binaries as $bin) {
  154. $binPath = $this->getInstallPath($package).'/'.$bin;
  155. if (!file_exists($binPath)) {
  156. $this->io->write(' <warning>Skipped installation of '.$bin.' for package '.$package->getName().': file not found in package</warning>');
  157. continue;
  158. }
  159. $this->initializeBinDir();
  160. $link = $this->binDir.'/'.basename($bin);
  161. if (file_exists($link)) {
  162. if (is_link($link)) {
  163. // likely leftover from a previous install, make sure
  164. // that the target is still executable in case this
  165. // is a fresh install of the vendor.
  166. chmod($link, 0777 & ~umask());
  167. }
  168. $this->io->write(' Skipped installation of '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
  169. continue;
  170. }
  171. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  172. // add unixy support for cygwin and similar environments
  173. if ('.bat' !== substr($binPath, -4)) {
  174. file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
  175. chmod($link, 0777 & ~umask());
  176. $link .= '.bat';
  177. }
  178. file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link));
  179. } else {
  180. $cwd = getcwd();
  181. try {
  182. // under linux symlinks are not always supported for example
  183. // when using it in smbfs mounted folder
  184. $relativeBin = $this->filesystem->findShortestPath($link, $binPath);
  185. chdir(dirname($link));
  186. symlink($relativeBin, $link);
  187. } catch (\ErrorException $e) {
  188. file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
  189. }
  190. chdir($cwd);
  191. }
  192. chmod($link, 0777 & ~umask());
  193. }
  194. }
  195. protected function removeBinaries(PackageInterface $package)
  196. {
  197. $binaries = $this->getBinaries($package);
  198. if (!$binaries) {
  199. return;
  200. }
  201. foreach ($binaries as $bin) {
  202. $link = $this->binDir.'/'.basename($bin);
  203. if (file_exists($link)) {
  204. unlink($link);
  205. }
  206. if (file_exists($link.'.bat')) {
  207. unlink($link.'.bat');
  208. }
  209. }
  210. }
  211. protected function initializeVendorDir()
  212. {
  213. $this->filesystem->ensureDirectoryExists($this->vendorDir);
  214. $this->vendorDir = realpath($this->vendorDir);
  215. }
  216. protected function initializeBinDir()
  217. {
  218. $this->filesystem->ensureDirectoryExists($this->binDir);
  219. $this->binDir = realpath($this->binDir);
  220. }
  221. protected function generateWindowsProxyCode($bin, $link)
  222. {
  223. $binPath = $this->filesystem->findShortestPath($link, $bin);
  224. if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
  225. $caller = 'call';
  226. } else {
  227. $handle = fopen($bin, 'r');
  228. $line = fgets($handle);
  229. fclose($handle);
  230. if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
  231. $caller = trim($match[1]);
  232. } else {
  233. $caller = 'php';
  234. }
  235. }
  236. return "@ECHO OFF\r\n".
  237. "SET BIN_TARGET=%~dp0\\".escapeshellarg(dirname($binPath)).'\\'.basename($binPath)."\r\n".
  238. "{$caller} \"%BIN_TARGET%\" %*\r\n";
  239. }
  240. protected function generateUnixyProxyCode($bin, $link)
  241. {
  242. $binPath = $this->filesystem->findShortestPath($link, $bin);
  243. return "#!/usr/bin/env sh\n".
  244. 'SRC_DIR="`pwd`"'."\n".
  245. 'cd "`dirname "$0"`"'."\n".
  246. 'cd '.escapeshellarg(dirname($binPath))."\n".
  247. 'BIN_TARGET="`pwd`/'.basename($binPath)."\"\n".
  248. 'cd "$SRC_DIR"'."\n".
  249. '"$BIN_TARGET" "$@"'."\n";
  250. }
  251. }