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()