BinaryInstaller.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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\Package\PackageInterface;
  14. use Composer\Util\Filesystem;
  15. use Composer\Util\Platform;
  16. use Composer\Util\ProcessExecutor;
  17. use Composer\Util\Silencer;
  18. /**
  19. * Utility to handle installation of package "bin"/binaries
  20. *
  21. * @author Jordi Boggiano <j.boggiano@seld.be>
  22. * @author Konstantin Kudryashov <ever.zet@gmail.com>
  23. * @author Helmut Hummel <info@helhum.io>
  24. */
  25. class BinaryInstaller
  26. {
  27. protected $binDir;
  28. protected $binCompat;
  29. protected $io;
  30. protected $filesystem;
  31. /**
  32. * @param IOInterface $io
  33. * @param string $binDir
  34. * @param string $binCompat
  35. * @param Filesystem $filesystem
  36. */
  37. public function __construct(IOInterface $io, $binDir, $binCompat, Filesystem $filesystem = null)
  38. {
  39. $this->binDir = $binDir;
  40. $this->binCompat = $binCompat;
  41. $this->io = $io;
  42. $this->filesystem = $filesystem ?: new Filesystem();
  43. }
  44. public function installBinaries(PackageInterface $package, $installPath, $warnOnOverwrite = true)
  45. {
  46. $binaries = $this->getBinaries($package);
  47. if (!$binaries) {
  48. return;
  49. }
  50. foreach ($binaries as $bin) {
  51. $binPath = $installPath.'/'.$bin;
  52. if (!file_exists($binPath)) {
  53. $this->io->writeError(' <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
  54. continue;
  55. }
  56. // in case a custom installer returned a relative path for the
  57. // $package, we can now safely turn it into a absolute path (as we
  58. // already checked the binary's existence). The following helpers
  59. // will require absolute paths to work properly.
  60. $binPath = realpath($binPath);
  61. $this->initializeBinDir();
  62. $link = $this->binDir.'/'.basename($bin);
  63. if (file_exists($link)) {
  64. if (is_link($link)) {
  65. // likely leftover from a previous install, make sure
  66. // that the target is still executable in case this
  67. // is a fresh install of the vendor.
  68. Silencer::call('chmod', $link, 0777 & ~umask());
  69. }
  70. if ($warnOnOverwrite) {
  71. $this->io->writeError(' Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
  72. }
  73. continue;
  74. }
  75. if ($this->binCompat === "auto") {
  76. if (Platform::isWindows()) {
  77. $this->installFullBinaries($binPath, $link, $bin, $package);
  78. } else {
  79. $this->installSymlinkBinaries($binPath, $link);
  80. }
  81. } elseif ($this->binCompat === "full") {
  82. $this->installFullBinaries($binPath, $link, $bin, $package);
  83. }
  84. Silencer::call('chmod', $link, 0777 & ~umask());
  85. }
  86. }
  87. public function removeBinaries(PackageInterface $package)
  88. {
  89. $this->initializeBinDir();
  90. $binaries = $this->getBinaries($package);
  91. if (!$binaries) {
  92. return;
  93. }
  94. foreach ($binaries as $bin) {
  95. $link = $this->binDir.'/'.basename($bin);
  96. if (is_link($link) || file_exists($link)) {
  97. $this->filesystem->unlink($link);
  98. }
  99. if (file_exists($link.'.bat')) {
  100. $this->filesystem->unlink($link.'.bat');
  101. }
  102. }
  103. // attempt removing the bin dir in case it is left empty
  104. if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) {
  105. Silencer::call('rmdir', $this->binDir);
  106. }
  107. }
  108. public static function determineBinaryCaller($bin)
  109. {
  110. if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
  111. return 'call';
  112. }
  113. $handle = fopen($bin, 'r');
  114. $line = fgets($handle);
  115. fclose($handle);
  116. if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
  117. return trim($match[1]);
  118. }
  119. return 'php';
  120. }
  121. protected function getBinaries(PackageInterface $package)
  122. {
  123. return $package->getBinaries();
  124. }
  125. protected function installFullBinaries($binPath, $link, $bin, PackageInterface $package)
  126. {
  127. // add unixy support for cygwin and similar environments
  128. if ('.bat' !== substr($binPath, -4)) {
  129. $this->installUnixyProxyBinaries($binPath, $link);
  130. @chmod($link, 0777 & ~umask());
  131. $link .= '.bat';
  132. if (file_exists($link)) {
  133. $this->io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed');
  134. }
  135. }
  136. if (!file_exists($link)) {
  137. file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link));
  138. }
  139. }
  140. protected function installSymlinkBinaries($binPath, $link)
  141. {
  142. if (!$this->filesystem->relativeSymlink($binPath, $link)) {
  143. $this->installUnixyProxyBinaries($binPath, $link);
  144. }
  145. }
  146. protected function installUnixyProxyBinaries($binPath, $link)
  147. {
  148. file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
  149. }
  150. protected function initializeBinDir()
  151. {
  152. $this->filesystem->ensureDirectoryExists($this->binDir);
  153. $this->binDir = realpath($this->binDir);
  154. }
  155. protected function generateWindowsProxyCode($bin, $link)
  156. {
  157. $binPath = $this->filesystem->findShortestPath($link, $bin);
  158. $caller = self::determineBinaryCaller($bin);
  159. return "@ECHO OFF\r\n".
  160. "setlocal DISABLEDELAYEDEXPANSION\r\n".
  161. "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n".
  162. "{$caller} \"%BIN_TARGET%\" %*\r\n";
  163. }
  164. protected function generateUnixyProxyCode($bin, $link)
  165. {
  166. $binPath = $this->filesystem->findShortestPath($link, $bin);
  167. $binDir = ProcessExecutor::escape(dirname($binPath));
  168. $binFile = basename($binPath);
  169. $proxyCode = <<<PROXY
  170. #!/usr/bin/env sh
  171. dir=\$(cd "\${0%[/\\\\]*}" > /dev/null; cd $binDir && pwd)
  172. if [ -d /proc/cygdrive ] && [[ \$(which php) == \$(readlink -n /proc/cygdrive)/* ]]; then
  173. # We are in Cgywin using Windows php, so the path must be translated
  174. dir=\$(cygpath -m "\$dir");
  175. fi
  176. "\${dir}/$binFile" "\$@"
  177. PROXY;
  178. return $proxyCode;
  179. }
  180. }