From aa94918d509c2829866532c8e373a19873b60f31 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 3 Dec 2011 15:39:06 +0100 Subject: [PATCH 1/5] Add binaries support in composer --- src/Composer/Console/Application.php | 10 +- src/Composer/Downloader/FileDownloader.php | 4 +- src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Downloader/HgDownloader.php | 2 +- src/Composer/Downloader/SvnDownloader.php | 2 +- src/Composer/Downloader/Util/Filesystem.php | 18 +++- src/Composer/Installer/InstallerInstaller.php | 9 +- src/Composer/Installer/LibraryInstaller.php | 91 +++++++++++++++---- src/Composer/Package/Loader/ArrayLoader.php | 8 ++ src/Composer/Package/MemoryPackage.php | 17 ++++ .../Test/Installer/InstallerInstallerTest.php | 6 +- .../Test/Installer/LibraryInstallerTest.php | 53 +++++++---- 12 files changed, 168 insertions(+), 54 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 44a313508..fc9474b12 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -90,7 +90,10 @@ class Application extends BaseApplication } // Configuration defaults - $composerConfig = array('vendor-dir' => 'vendor'); + $composerConfig = array( + 'vendor-dir' => 'vendor', + 'bin-dir' => 'bin', + ); $packageConfig = $file->read(); @@ -101,6 +104,7 @@ class Application extends BaseApplication } $vendorDir = $packageConfig['config']['vendor-dir']; + $binDir = $packageConfig['config']['bin-dir']; // initialize repository manager $rm = new Repository\RepositoryManager(); @@ -120,8 +124,8 @@ class Application extends BaseApplication // initialize installation manager $im = new Installer\InstallationManager($vendorDir); - $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $dm, $rm->getLocalRepository(), null)); - $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $dm, $rm->getLocalRepository(), $im)); + $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), null)); + $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $im)); // load package $loader = new Package\Loader\ArrayLoader($rm); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 09a7a636f..e3bfa5af0 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -96,7 +96,7 @@ abstract class FileDownloader implements DownloaderInterface public function update(PackageInterface $initial, PackageInterface $target, $path) { $fs = new Util\Filesystem(); - $fs->remove($path); + $fs->removeDirectory($path); $this->download($target, $path); } @@ -106,7 +106,7 @@ abstract class FileDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $fs = new Util\Filesystem(); - $fs->remove($path); + $fs->removeDirectory($path); } /** diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index ca48b2a0d..6cb3f3f14 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -61,7 +61,7 @@ class GitDownloader implements DownloaderInterface { $this->enforceCleanDirectory($path); $fs = new Util\Filesystem(); - $fs->remove($path); + $fs->removeDirectory($path); } private function enforceCleanDirectory($path) diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 32d23b770..ccd81d354 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -61,7 +61,7 @@ class HgDownloader implements DownloaderInterface { $this->enforceCleanDirectory($path); $fs = new Util\Filesystem(); - $fs->remove($path); + $fs->removeDirectory($path); } private function enforceCleanDirectory($path) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index b162bb072..89658d961 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -51,6 +51,6 @@ class SvnDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $fs = new Util\Filesystem(); - $fs->remove($path); + $fs->removeDirectory($path); } } \ No newline at end of file diff --git a/src/Composer/Downloader/Util/Filesystem.php b/src/Composer/Downloader/Util/Filesystem.php index ad79a4901..5320299b3 100644 --- a/src/Composer/Downloader/Util/Filesystem.php +++ b/src/Composer/Downloader/Util/Filesystem.php @@ -17,7 +17,7 @@ namespace Composer\Downloader\Util; */ class Filesystem { - public function remove($directory) + public function removeDirectory($directory) { if (defined('PHP_WINDOWS_VERSION_BUILD')) { system(sprintf('rmdir /S /Q %s', escapeshellarg(realpath($directory)))); @@ -25,4 +25,20 @@ class Filesystem system(sprintf('rm -rf %s', escapeshellarg($directory))); } } + + public function ensureDirectoryExists($directory) + { + if (!is_dir($directory)) { + if (file_exists($directory)) { + throw new \RuntimeException( + $directory.' exists and is not a directory.' + ); + } + if (!mkdir($directory, 0777, true)) { + throw new \RuntimeException( + $directory.' does not exist and could not be created.' + ); + } + } + } } diff --git a/src/Composer/Installer/InstallerInstaller.php b/src/Composer/Installer/InstallerInstaller.php index 4177b15a2..3ff87e24b 100644 --- a/src/Composer/Installer/InstallerInstaller.php +++ b/src/Composer/Installer/InstallerInstaller.php @@ -29,13 +29,14 @@ class InstallerInstaller extends LibraryInstaller private static $classCounter = 0; /** - * @param string $dir relative path for packages home + * @param string $vendorDir relative path for packages home + * @param string $binDir relative path for binaries * @param DownloadManager $dm download manager * @param WritableRepositoryInterface $repository repository controller */ - public function __construct($directory, DownloadManager $dm, WritableRepositoryInterface $repository, InstallationManager $im) + public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, InstallationManager $im) { - parent::__construct($directory, $dm, $repository, 'composer-installer'); + parent::__construct($vendorDir, $binDir, $dm, $repository, 'composer-installer'); $this->installationManager = $im; foreach ($repository->getPackages() as $package) { @@ -94,7 +95,7 @@ class InstallerInstaller extends LibraryInstaller } $extra = $package->getExtra(); - $installer = new $class($this->directory, $this->downloadManager, $this->repository); + $installer = new $class($this->vendorDir, $this->binDir, $this->downloadManager, $this->repository); $this->installationManager->addInstaller($installer); } } diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index d67d514ae..12302f129 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager; use Composer\Repository\WritableRepositoryInterface; use Composer\DependencyResolver\Operation\OperationInterface; use Composer\Package\PackageInterface; +use Composer\Downloader\Util\Filesystem; /** * Package installation manager. @@ -25,7 +26,8 @@ use Composer\Package\PackageInterface; */ class LibraryInstaller implements InstallerInterface { - protected $directory; + protected $vendorDir; + protected $binDir; protected $downloadManager; protected $repository; private $type; @@ -33,31 +35,23 @@ class LibraryInstaller implements InstallerInterface /** * Initializes library installer. * - * @param string $dir relative path for packages home + * @param string $vendorDir relative path for packages home + * @param string $binDir relative path for binaries * @param DownloadManager $dm download manager * @param WritableRepositoryInterface $repository repository controller * @param string $type package type that this installer handles */ - public function __construct($directory, DownloadManager $dm, WritableRepositoryInterface $repository, $type = 'library') + public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, $type = 'library') { - $this->directory = $directory; $this->downloadManager = $dm; + $this->repository = $repository; $this->type = $type; - if (!is_dir($this->directory)) { - if (file_exists($this->directory)) { - throw new \UnexpectedValueException( - $this->directory.' exists and is not a directory.' - ); - } - if (!mkdir($this->directory, 0777, true)) { - throw new \UnexpectedValueException( - $this->directory.' does not exist and could not be created.' - ); - } - } - - $this->repository = $repository; + $fs = new Filesystem(); + $fs->ensureDirectoryExists($vendorDir); + $fs->ensureDirectoryExists($binDir); + $this->vendorDir = realpath($vendorDir); + $this->binDir = realpath($binDir); } /** @@ -84,6 +78,7 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($package); $this->downloadManager->download($package, $downloadPath); + $this->installBinaries($package); $this->repository->addPackage(clone $package); } @@ -98,7 +93,9 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($initial); + $this->removeBinaries($initial); $this->downloadManager->update($initial, $target, $downloadPath); + $this->installBinaries($target); $this->repository->removePackage($initial); $this->repository->addPackage(clone $target); } @@ -117,6 +114,7 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($package); $this->downloadManager->remove($package, $downloadPath); + $this->removeBinaries($package); $this->repository->removePackage($package); } @@ -126,6 +124,61 @@ class LibraryInstaller implements InstallerInterface public function getInstallPath(PackageInterface $package) { $targetDir = $package->getTargetDir(); - return ($this->directory ? $this->directory.'/' : '') . $package->getName() . ($targetDir ? '/'.$targetDir : ''); + return ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getName() . ($targetDir ? '/'.$targetDir : ''); + } + + protected function installBinaries(PackageInterface $package) + { + if (!$package->getBinaries()) { + return; + } + foreach ($package->getBinaries() as $bin => $os) { + $link = $this->binDir.'/'.basename($bin); + if (file_exists($link)) { + continue; + } + + // skip windows + if (defined('PHP_WINDOWS_VERSION_BUILD') && false === strpos($os, 'windows') && '*' !== $os) { + continue; + } + + // skip unix + if (!defined('PHP_WINDOWS_VERSION_BUILD') && false === strpos($os, 'unix') && '*' !== $os) { + continue; + } + + $binary = $this->getInstallPath($package).'/'.$bin; + $from = array( + '@php_bin@', + '@bin_dir@', + ); + $to = array( + 'php', + $this->binDir, + ); + file_put_contents($binary, str_replace($from, $to, file_get_contents($binary))); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + copy($binary, $link); + } else { + symlink($this->getInstallPath($package).'/'.$bin, $link); + } + chmod($link, 0777); + } + } + + protected function removeBinaries(PackageInterface $package) + { + if (!$package->getBinaries()) { + return; + } + foreach ($package->getBinaries() as $bin => $os) { + $link = $this->binDir.'/'.basename($bin); + if (!file_exists($link)) { + continue; + } + unlink($link); + } } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index 68afaa7ff..bf8f8ad88 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -78,6 +78,14 @@ class ArrayLoader $package->setExtra($config['extra']); } + if (isset($config['bin']) && is_array($config['bin'])) { + foreach ($config['bin'] as $bin => $os) { + unset($config['bin'][$bin]); + $config['bin'][ltrim($bin, '/')] = $os; + } + $package->setBinaries($config['bin']); + } + if (!empty($config['description']) && is_string($config['description'])) { $package->setDescription($config['description']); } diff --git a/src/Composer/Package/MemoryPackage.php b/src/Composer/Package/MemoryPackage.php index d93081a97..8b4e64ffe 100644 --- a/src/Composer/Package/MemoryPackage.php +++ b/src/Composer/Package/MemoryPackage.php @@ -39,6 +39,7 @@ class MemoryPackage extends BasePackage protected $description; protected $homepage; protected $extra = array(); + protected $binaries = array(); protected $requires = array(); protected $conflicts = array(); @@ -111,6 +112,22 @@ class MemoryPackage extends BasePackage return $this->extra; } + /** + * @param array $binaries + */ + public function setBinaries(array $binaries) + { + $this->binaries = $binaries; + } + + /** + * {@inheritDoc} + */ + public function getBinaries() + { + return $this->binaries; + } + /** * {@inheritDoc} */ diff --git a/tests/Composer/Test/Installer/InstallerInstallerTest.php b/tests/Composer/Test/Installer/InstallerInstallerTest.php index 52006dbeb..2ca75b5cd 100644 --- a/tests/Composer/Test/Installer/InstallerInstallerTest.php +++ b/tests/Composer/Test/Installer/InstallerInstallerTest.php @@ -49,7 +49,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('getPackages') ->will($this->returnValue(array())); - $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', $this->dm, $this->repository, $this->im); + $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->im); $test = $this; $this->im @@ -72,7 +72,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('hasPackage') ->will($this->returnValue(true)); - $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', $this->dm, $this->repository, $this->im); + $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->im); $test = $this; $this->im @@ -95,7 +95,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase ->expects($this->once()) ->method('hasPackage') ->will($this->returnValue(true)); - $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', $this->dm, $this->repository, $this->im); + $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->im); $test = $this; $this->im diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 3a6d8ad76..2140e5cf8 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -14,20 +14,31 @@ namespace Composer\Test\Installer; use Composer\Installer\LibraryInstaller; use Composer\DependencyResolver\Operation; +use Composer\Downloader\Util\Filesystem; class LibraryInstallerTest extends \PHPUnit_Framework_TestCase { - private $dir; + private $vendorDir; + private $binDir; private $dm; private $repository; private $library; protected function setUp() { - $this->dir = sys_get_temp_dir().'/composer'; - if (is_dir($this->dir)) { - rmdir($this->dir); + $fs = new Filesystem; + + $this->vendorDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'composer-test-vendor'; + if (is_dir($this->vendorDir)) { + $fs->removeDirectory($this->vendorDir); } + mkdir($this->vendorDir); + + $this->binDir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'composer-test-bin'; + if (is_dir($this->binDir)) { + $fs->removeDirectory($this->binDir); + } + mkdir($this->binDir); $this->dm = $this->getMockBuilder('Composer\Downloader\DownloadManager') ->disableOriginalConstructor() @@ -40,19 +51,19 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase public function testInstallerCreation() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); - $this->assertTrue(is_dir($this->dir)); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); + $this->assertTrue(is_dir($this->vendorDir)); $file = sys_get_temp_dir().'/file'; touch($file); - $this->setExpectedException('UnexpectedValueException'); - $library = new LibraryInstaller($file, $this->dm, $this->repository); + $this->setExpectedException('RuntimeException'); + $library = new LibraryInstaller($file, $this->binDir, $this->dm, $this->repository); } public function testIsInstalled() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $package = $this->createPackageMock(); $this->repository @@ -67,7 +78,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase public function testInstall() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $package = $this->createPackageMock(); $package @@ -78,7 +89,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $this->dm ->expects($this->once()) ->method('download') - ->with($package, $this->dir.'/some/package'); + ->with($package, $this->vendorDir.'/some/package'); $this->repository ->expects($this->once()) @@ -90,7 +101,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $initial = $this->createPackageMock(); $target = $this->createPackageMock(); @@ -108,7 +119,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $this->dm ->expects($this->once()) ->method('update') - ->with($initial, $target, $this->dir.'/package1'); + ->with($initial, $target, $this->vendorDir.'/package1'); $this->repository ->expects($this->once()) @@ -129,7 +140,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase public function testUninstall() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $package = $this->createPackageMock(); $package @@ -146,7 +157,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $this->dm ->expects($this->once()) ->method('remove') - ->with($package, $this->dir.'/pkg'); + ->with($package, $this->vendorDir.'/pkg'); $this->repository ->expects($this->once()) @@ -163,7 +174,7 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase public function testGetInstallPath() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $package = $this->createPackageMock(); $package @@ -171,20 +182,24 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase ->method('getTargetDir') ->will($this->returnValue(null)); - $this->assertEquals($this->dir.'/'.$package->getName(), $library->getInstallPath($package)); + $this->assertEquals($this->vendorDir.'/'.$package->getName(), $library->getInstallPath($package)); } public function testGetInstallPathWithTargetDir() { - $library = new LibraryInstaller($this->dir, $this->dm, $this->repository); + $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository); $package = $this->createPackageMock(); $package ->expects($this->once()) ->method('getTargetDir') ->will($this->returnValue('Some/Namespace')); + $package + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo/bar')); - $this->assertEquals($this->dir.'/'.$package->getName().'/Some/Namespace', $library->getInstallPath($package)); + $this->assertEquals($this->vendorDir.'/'.$package->getName().'/Some/Namespace', $library->getInstallPath($package)); } private function createPackageMock() From 7e3f8099b1ed23f10ebb53f789d4b47bd248dec8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 3 Dec 2011 20:44:00 +0100 Subject: [PATCH 2/5] Add proxy files for windows instead of copying, removed PEAR-style substitution --- src/Composer/Downloader/Util/Filesystem.php | 79 +++++++++++++++++++ src/Composer/Installer/LibraryInstaller.php | 78 ++++++++++++------ src/Composer/Package/Loader/ArrayLoader.php | 5 +- .../Test/Downloader/Util/FilesystemTest.php | 67 ++++++++++++++++ 4 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 tests/Composer/Test/Downloader/Util/FilesystemTest.php diff --git a/src/Composer/Downloader/Util/Filesystem.php b/src/Composer/Downloader/Util/Filesystem.php index 5320299b3..66e423e1d 100644 --- a/src/Composer/Downloader/Util/Filesystem.php +++ b/src/Composer/Downloader/Util/Filesystem.php @@ -41,4 +41,83 @@ class Filesystem } } } + + /** + * Returns the shortest path from $from to $to + * + * @param string $from + * @param string $to + * @return string + */ + public function findShortestPath($from, $to) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException('from and to must be absolute paths'); + } + + if (dirname($from) === dirname($to)) { + return './'.basename($to); + } + $from = strtr($from, '\\', '/'); + $to = strtr($to, '\\', '/'); + + $commonPath = dirname($to); + while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/$}i', $commonPath)) { + $commonPath = strtr(dirname($commonPath), '\\', '/'); + } + + if (0 !== strpos($from, $commonPath) || '/' === $commonPath) { + return $to; + } + + $commonPath = rtrim($commonPath, '/') . '/'; + $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/'); + $commonPathCode = str_repeat('../', $sourcePathDepth); + return $commonPathCode . substr($to, strlen($commonPath)); + } + + /** + * Returns PHP code that, when executed in $from, will return the path to $to + * + * @param string $from + * @param string $to + * @return string + */ + public function findShortestPathCode($from, $to) + { + if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) { + throw new \InvalidArgumentException('from and to must be absolute paths'); + } + + if ($from === $to) { + return '__FILE__'; + } + $from = strtr($from, '\\', '/'); + $to = strtr($to, '\\', '/'); + + $commonPath = dirname($to); + while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/$}i', $commonPath)) { + $commonPath = strtr(dirname($commonPath), '\\', '/'); + } + + if (0 !== strpos($from, $commonPath) || '/' === $commonPath) { + return var_export($to, true); + } + + $commonPath = rtrim($commonPath, '/') . '/'; + $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/'); + $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth); + return $commonPathCode . '.' . var_export('/' . substr($to, strlen($commonPath)), true); + } + + /** + * Checks if the given path is absolute + * + * @param string $path + * @return Boolean + */ + public function isAbsolutePath($path) + { + return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':'; + } } diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 12302f129..0435d8777 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -31,6 +31,7 @@ class LibraryInstaller implements InstallerInterface protected $downloadManager; protected $repository; private $type; + private $filesystem; /** * Initializes library installer. @@ -47,9 +48,9 @@ class LibraryInstaller implements InstallerInterface $this->repository = $repository; $this->type = $type; - $fs = new Filesystem(); - $fs->ensureDirectoryExists($vendorDir); - $fs->ensureDirectoryExists($binDir); + $this->filesystem = new Filesystem(); + $this->filesystem->ensureDirectoryExists($vendorDir); + $this->filesystem->ensureDirectoryExists($binDir); $this->vendorDir = realpath($vendorDir); $this->binDir = realpath($binDir); } @@ -132,35 +133,20 @@ class LibraryInstaller implements InstallerInterface if (!$package->getBinaries()) { return; } - foreach ($package->getBinaries() as $bin => $os) { + foreach ($package->getBinaries() as $bin) { $link = $this->binDir.'/'.basename($bin); if (file_exists($link)) { continue; } - // skip windows - if (defined('PHP_WINDOWS_VERSION_BUILD') && false === strpos($os, 'windows') && '*' !== $os) { - continue; - } - - // skip unix - if (!defined('PHP_WINDOWS_VERSION_BUILD') && false === strpos($os, 'unix') && '*' !== $os) { - continue; - } - - $binary = $this->getInstallPath($package).'/'.$bin; - $from = array( - '@php_bin@', - '@bin_dir@', - ); - $to = array( - 'php', - $this->binDir, - ); - file_put_contents($binary, str_replace($from, $to, file_get_contents($binary))); - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - copy($binary, $link); + // add unixy support for cygwin and similar environments + if ('.bat' !== substr($bin, -4)) { + file_put_contents($link, $this->generateUnixyProxyCode($this->getInstallPath($package).'/'.$bin)); + chmod($link, 0777); + $link .= '.bat'; + } + file_put_contents($link, $this->generateWindowsProxyCode($this->getInstallPath($package).'/'.$bin)); } else { symlink($this->getInstallPath($package).'/'.$bin, $link); } @@ -181,4 +167,44 @@ class LibraryInstaller implements InstallerInterface unlink($link); } } + + private function generateWindowsProxyCode($bin) + { + $link = $this->binDir.'/'.basename($bin); + $binPath = $this->filesystem->findShortestPath($link, $bin); + if ('.bat' === substr($bin, -4)) { + $caller = 'call'; + } else { + $handle = fopen($bin, 'r'); + $line = fgets($handle); + fclose($handle); + if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) { + $caller = $match[1]; + } else { + $caller = 'php'; + } + } + + return "@echo off\r\n". + "pushd .\r\n". + "cd %~dp0\r\n". + "cd ".escapeshellarg(dirname($binPath))."\r\n". + "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n". + "popd\r\n". + $caller." %BIN_TARGET% %*\r\n"; + } + + private function generateUnixyProxyCode($bin) + { + $link = $this->binDir.'/'.basename($bin); + $binPath = $this->filesystem->findShortestPath($link, $bin); + + return "#!/usr/bin/env sh\n". + 'SRC_DIR=`pwd`'."\n". + 'cd `dirname \\`readlink -m "$0"\\``'."\n". + 'cd '.escapeshellarg(dirname($binPath))."\n". + 'BIN_TARGET=`pwd`/'.basename($binPath)."\n". + 'cd $SRC_DIR'."\n". + '$BIN_TARGET "$@"'."\n"; + } } diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index bf8f8ad88..2f8b4a478 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -79,9 +79,8 @@ class ArrayLoader } if (isset($config['bin']) && is_array($config['bin'])) { - foreach ($config['bin'] as $bin => $os) { - unset($config['bin'][$bin]); - $config['bin'][ltrim($bin, '/')] = $os; + foreach ($config['bin'] as $key => $bin) { + $config['bin'][$key]= ltrim($bin, '/'); } $package->setBinaries($config['bin']); } diff --git a/tests/Composer/Test/Downloader/Util/FilesystemTest.php b/tests/Composer/Test/Downloader/Util/FilesystemTest.php new file mode 100644 index 000000000..f4d9af04f --- /dev/null +++ b/tests/Composer/Test/Downloader/Util/FilesystemTest.php @@ -0,0 +1,67 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Repository; + +use Composer\Downloader\Util\Filesystem; +use Composer\Test\TestCase; + +class FilesystemTest extends TestCase +{ + /** + * @dataProvider providePathCouplesAsCode + */ + public function testFindShortestPathCode($a, $b, $expected) + { + $fs = new Filesystem; + $this->assertEquals($expected, $fs->findShortestPathCode($a, $b)); + } + + public function providePathCouplesAsCode() + { + return array( + array('/foo/bar', '/foo/bar', "__FILE__"), + array('/foo/bar', '/foo/baz', "__DIR__.'/baz'"), + array('/foo/bin/run', '/foo/vendor/acme/bin/run', "dirname(__DIR__).'/vendor/acme/bin/run'"), + array('/foo/bin/run', '/foo/vendor/acme/bin/run', "dirname(__DIR__).'/vendor/acme/bin/run'"), + array('/foo/bin/run', '/bar/bin/run', "'/bar/bin/run'"), + array('c:/bin/run', 'c:/vendor/acme/bin/run', "dirname(__DIR__).'/vendor/acme/bin/run'"), + array('c:\\bin\\run', 'c:/vendor/acme/bin/run', "dirname(__DIR__).'/vendor/acme/bin/run'"), + array('c:/bin/run', 'd:/vendor/acme/bin/run', "'d:/vendor/acme/bin/run'"), + array('c:\\bin\\run', 'd:/vendor/acme/bin/run', "'d:/vendor/acme/bin/run'"), + ); + } + + /** + * @dataProvider providePathCouples + */ + public function testFindShortestPath($a, $b, $expected) + { + $fs = new Filesystem; + $this->assertEquals($expected, $fs->findShortestPath($a, $b)); + } + + public function providePathCouples() + { + return array( + array('/foo/bar', '/foo/bar', "./bar"), + array('/foo/bar', '/foo/baz', "./baz"), + array('/foo/bin/run', '/foo/vendor/acme/bin/run', "../vendor/acme/bin/run"), + array('/foo/bin/run', '/foo/vendor/acme/bin/run', "../vendor/acme/bin/run"), + array('/foo/bin/run', '/bar/bin/run', "/bar/bin/run"), + array('c:/bin/run', 'c:/vendor/acme/bin/run', "../vendor/acme/bin/run"), + array('c:\\bin\\run', 'c:/vendor/acme/bin/run', "../vendor/acme/bin/run"), + array('c:/bin/run', 'd:/vendor/acme/bin/run', "d:/vendor/acme/bin/run"), + array('c:\\bin\\run', 'd:/vendor/acme/bin/run', "d:/vendor/acme/bin/run"), + ); + } +} From 9028546c5aa0d66cce5336faba64a628e2acffec Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 3 Dec 2011 20:47:02 +0100 Subject: [PATCH 3/5] Move bin dir to a subdir of vendor --- src/Composer/Console/Application.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index fc9474b12..c78c9569c 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -92,7 +92,6 @@ class Application extends BaseApplication // Configuration defaults $composerConfig = array( 'vendor-dir' => 'vendor', - 'bin-dir' => 'bin', ); $packageConfig = $file->read(); @@ -104,6 +103,9 @@ class Application extends BaseApplication } $vendorDir = $packageConfig['config']['vendor-dir']; + if (!isset($packageConfig['config']['bin-dir'])) { + $packageConfig['config']['bin-dir'] = $vendorDir.'/bin'; + } $binDir = $packageConfig['config']['bin-dir']; // initialize repository manager From f0d86269930f11ce1552c51561d6afc7cf7e63f7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 3 Dec 2011 21:20:20 +0100 Subject: [PATCH 4/5] Cleanups --- src/Composer/Installer/LibraryInstaller.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 0435d8777..d7cc4d241 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -136,19 +136,21 @@ class LibraryInstaller implements InstallerInterface foreach ($package->getBinaries() as $bin) { $link = $this->binDir.'/'.basename($bin); if (file_exists($link)) { + echo 'Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file'; continue; } + $bin = $this->getInstallPath($package).'/'.$bin; if (defined('PHP_WINDOWS_VERSION_BUILD')) { // add unixy support for cygwin and similar environments if ('.bat' !== substr($bin, -4)) { - file_put_contents($link, $this->generateUnixyProxyCode($this->getInstallPath($package).'/'.$bin)); + file_put_contents($link, $this->generateUnixyProxyCode($bin, $link)); chmod($link, 0777); $link .= '.bat'; } - file_put_contents($link, $this->generateWindowsProxyCode($this->getInstallPath($package).'/'.$bin)); + file_put_contents($link, $this->generateWindowsProxyCode($bin, $link)); } else { - symlink($this->getInstallPath($package).'/'.$bin, $link); + symlink($bin, $link); } chmod($link, 0777); } @@ -168,9 +170,8 @@ class LibraryInstaller implements InstallerInterface } } - private function generateWindowsProxyCode($bin) + private function generateWindowsProxyCode($bin, $link) { - $link = $this->binDir.'/'.basename($bin); $binPath = $this->filesystem->findShortestPath($link, $bin); if ('.bat' === substr($bin, -4)) { $caller = 'call'; @@ -194,9 +195,8 @@ class LibraryInstaller implements InstallerInterface $caller." %BIN_TARGET% %*\r\n"; } - private function generateUnixyProxyCode($bin) + private function generateUnixyProxyCode($bin, $link) { - $link = $this->binDir.'/'.basename($bin); $binPath = $this->filesystem->findShortestPath($link, $bin); return "#!/usr/bin/env sh\n". From 3be6511af7456fdd2688ba2e7891533dc7fad3f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 3 Dec 2011 21:54:54 +0100 Subject: [PATCH 5/5] Fix unixy proxy under git-bash --- src/Composer/Installer/LibraryInstaller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index d7cc4d241..d90d71fc4 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -201,7 +201,7 @@ class LibraryInstaller implements InstallerInterface return "#!/usr/bin/env sh\n". 'SRC_DIR=`pwd`'."\n". - 'cd `dirname \\`readlink -m "$0"\\``'."\n". + 'cd `dirname "$0"`'."\n". 'cd '.escapeshellarg(dirname($binPath))."\n". 'BIN_TARGET=`pwd`/'.basename($binPath)."\n". 'cd $SRC_DIR'."\n".