From 0995933183dba55fa98772179c20ce181ad76fbc Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Fri, 27 Apr 2012 11:42:58 +0200 Subject: [PATCH] Do not install root package as a vendor if some dependency requires it, closes #480 Also add some tests for the installer. --- src/Composer/Installer.php | 32 ++++- .../Installer/InstallationManager.php | 4 +- src/Composer/Repository/ArrayRepository.php | 7 ++ tests/Composer/Test/InstallerTest.php | 115 ++++++++++++++++++ .../Test/Mock/InstallationManagerMock.php | 65 ++++++++++ .../Test/Mock/WritableRepositoryMock.php | 26 ++++ tests/Composer/Test/TestCase.php | 15 +-- 7 files changed, 245 insertions(+), 19 deletions(-) create mode 100644 tests/Composer/Test/InstallerTest.php create mode 100644 tests/Composer/Test/Mock/InstallationManagerMock.php create mode 100644 tests/Composer/Test/Mock/WritableRepositoryMock.php diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 4ef8ea848..933d563e7 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -27,6 +27,7 @@ use Composer\Package\Link; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\Locker; use Composer\Package\PackageInterface; +use Composer\Repository\ArrayRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; @@ -76,6 +77,11 @@ class Installer */ protected $eventDispatcher; + /** + * @var AutoloadGenerator + */ + protected $autoloadGenerator; + protected $preferSource = false; protected $devMode = false; protected $dryRun = false; @@ -102,8 +108,9 @@ class Installer * @param Locker $locker * @param InstallationManager $installationManager * @param EventDispatcher $eventDispatcher + * @param AutoloadGenerator $autoloadGenerator */ - public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher) + public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, autoloadGenerator $autoloadGenerator) { $this->io = $io; $this->package = $package; @@ -112,6 +119,7 @@ class Installer $this->locker = $locker; $this->installationManager = $installationManager; $this->eventDispatcher = $eventDispatcher; + $this->autoloadGenerator = $autoloadGenerator; } /** @@ -128,7 +136,13 @@ class Installer } // create installed repo, this contains all local packages + platform packages (php & extensions) - $repos = array_merge($this->repositoryManager->getLocalRepositories(), array(new PlatformRepository())); + $repos = array_merge( + $this->repositoryManager->getLocalRepositories(), + array( + new ArrayRepository(array($this->package)), + new PlatformRepository(), + ) + ); $installedRepo = new CompositeRepository($repos); if ($this->additionalInstalledRepository) { $installedRepo->addRepository($this->additionalInstalledRepository); @@ -172,9 +186,8 @@ class Installer // write autoloader $this->io->write('Generating autoload files'); - $generator = new AutoloadGenerator; $localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories()); - $generator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath() . '/composer', true); + $this->autoloadGenerator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath() . '/composer', true); // dispatch post event $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD; @@ -201,6 +214,10 @@ class Installer // creating requirements request $installFromLock = false; $request = new Request($pool); + + $constraint = new VersionConstraint('=', $this->package->getVersion()); + $request->install($this->package->getName(), $constraint); + if ($this->update) { $this->io->write('Updating '.($devMode ? 'dev ': '').'dependencies'); @@ -406,11 +423,13 @@ class Installer * @param IOInterface $io * @param Composer $composer * @param EventDispatcher $eventDispatcher + * @param AutoloadGenerator $autoloadGenerator * @return Installer */ - static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null) + static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null, AutoloadGenerator $autoloadGenerator = null) { $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io); + $autoloadGenerator = $autoloadGenerator ?: new AutoloadGenerator; return new static( $io, @@ -419,7 +438,8 @@ class Installer $composer->getRepositoryManager(), $composer->getLocker(), $composer->getInstallationManager(), - $eventDispatcher + $eventDispatcher, + $autoloadGenerator ); } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index e5080e0ef..3bf09de4f 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -33,7 +33,7 @@ class InstallationManager { private $installers = array(); private $cache = array(); - private $vendorPath; + protected $vendorPath; /** * Creates an instance of InstallationManager @@ -204,7 +204,7 @@ class InstallationManager } } - private function antiAlias(PackageInterface $package) + protected function antiAlias(PackageInterface $package) { if ($package instanceof AliasPackage) { $alias = $package; diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 1af73d311..7a6336393 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -25,6 +25,13 @@ class ArrayRepository implements RepositoryInterface { protected $packages; + public function __construct(array $packages = array()) + { + foreach ($packages as $package) { + $this->addPackage($package); + } + } + /** * {@inheritDoc} */ diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php new file mode 100644 index 000000000..179d0fe47 --- /dev/null +++ b/tests/Composer/Test/InstallerTest.php @@ -0,0 +1,115 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Installer; +use Composer\Repository\ArrayRepository; +use Composer\Repository\RepositoryManager; +use Composer\Repository\RepositoryInterface; +use Composer\Package\PackageInterface; +use Composer\Package\Link; +use Composer\Test\Mock\WritableRepositoryMock; +use Composer\Test\Mock\InstallationManagerMock; + +class InstallerTest extends TestCase +{ + /** + * @dataProvider provideInstaller + */ + public function testInstaller(array $expectedInstalled, array $expectedUpdated, array $expectedUninstalled, PackageInterface $package, RepositoryInterface $repository) + { + $io = $this->getMock('Composer\IO\IOInterface'); + + $package = $this->getPackage('A', '1.0.0'); + $package->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')), + )); + + $downloadManager = $this->getMock('Composer\Downloader\DownloadManager'); + $config = $this->getMock('Composer\Config'); + + $repositoryManager = new RepositoryManager($io, $config); + $repositoryManager->setLocalRepository(new WritableRepositoryMock()); + $repositoryManager->setLocalDevRepository(new WritableRepositoryMock()); + $repositoryManager->addRepository($repository); + + $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock(); + $installationManager = new InstallationManagerMock(); + $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock(); + $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator'); + + $installer = new Installer($io, $package, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator); + $result = $installer->run(); + $this->assertTrue($result); + + $installed = $installationManager->getInstalledPackages(); + $this->assertSame($expectedInstalled, array_map(array($this, 'getPackageString'), $installed)); + + $updated = $installationManager->getUpdatedPackages(); + $this->assertSame($expectedUpdated, array_map(array($this, 'getPackageString'), $updated)); + + $uninstalled = $installationManager->getUninstalledPackages(); + $this->assertSame($expectedUninstalled, array_map(array($this, 'getPackageString'), $uninstalled)); + } + + public function provideInstaller() + { + $cases = array(); + + // when A requires B and B requires A, and A is a non-published root package + // the install of B should succeed + + $a = $this->getPackage('A', '1.0.0'); + $a->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')), + )); + $b = $this->getPackage('B', '1.0.0'); + $b->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')), + )); + + $cases[] = array( + array('b-1.0.0.0'), + array(), + array(), + $a, + new ArrayRepository(array($b)), + ); + + // #480: when A requires B and B requires A, and A is a published root package + // only B should be installed, as A is the root + + $a = $this->getPackage('A', '1.0.0'); + $a->setRequires(array( + new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')), + )); + $b = $this->getPackage('B', '1.0.0'); + $b->setRequires(array( + new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')), + )); + + $cases[] = array( + array('b-1.0.0.0'), + array(), + array(), + $a, + new ArrayRepository(array($a, $b)), + ); + + return $cases; + } + + public function getPackageString(PackageInterface $package) + { + return (string) $package; + } +} diff --git a/tests/Composer/Test/Mock/InstallationManagerMock.php b/tests/Composer/Test/Mock/InstallationManagerMock.php new file mode 100644 index 000000000..8f0f83008 --- /dev/null +++ b/tests/Composer/Test/Mock/InstallationManagerMock.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\Installer\InstallationManager; +use Composer\Repository\RepositoryInterface; +use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\DependencyResolver\Operation\UpdateOperation; +use Composer\DependencyResolver\Operation\UninstallOperation; + +class InstallationManagerMock extends InstallationManager +{ + private $installed = array(); + private $updated = array(); + private $uninstalled = array(); + + public function __construct($vendorDir = 'vendor') + { + $this->vendorPath = $vendorDir; + } + + public function install(RepositoryInterface $repo, InstallOperation $operation) + { + $package = $this->antiAlias($operation->getPackage()); + $this->installed[] = $package; + } + + public function update(RepositoryInterface $repo, UpdateOperation $operation) + { + $initial = $this->antiAlias($operation->getInitialPackage()); + $target = $this->antiAlias($operation->getTargetPackage()); + $this->updated[] = $target; + } + + public function uninstall(RepositoryInterface $repo, UninstallOperation $operation) + { + $package = $this->antiAlias($operation->getPackage()); + $this->uninstalled[] = $package; + } + + public function getInstalledPackages() + { + return $this->installed; + } + + public function getUpdatedPackages() + { + return $this->updated; + } + + public function getUninstalledPackages() + { + return $this->uninstalled; + } +} diff --git a/tests/Composer/Test/Mock/WritableRepositoryMock.php b/tests/Composer/Test/Mock/WritableRepositoryMock.php new file mode 100644 index 000000000..59bb19f88 --- /dev/null +++ b/tests/Composer/Test/Mock/WritableRepositoryMock.php @@ -0,0 +1,26 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\Repository\ArrayRepository; +use Composer\Repository\WritableRepositoryInterface; + +class WritableRepositoryMock extends ArrayRepository implements WritableRepositoryInterface +{ + public function reload() + { + } + + public function write() + { + } +} diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index f0c8ab8fd..b2c2cc9cd 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -19,26 +19,19 @@ use Composer\Util\Filesystem; abstract class TestCase extends \PHPUnit_Framework_TestCase { - private static $versionParser; - - public static function setUpBeforeClass() - { - if (!self::$versionParser) { - self::$versionParser = new VersionParser(); - } - } - protected function getVersionConstraint($operator, $version) { + $versionParser = new VersionParser(); return new VersionConstraint( $operator, - self::$versionParser->normalize($version) + $versionParser->normalize($version) ); } protected function getPackage($name, $version) { - $normVersion = self::$versionParser->normalize($version); + $versionParser = new VersionParser(); + $normVersion = $versionParser->normalize($version); return new MemoryPackage($name, $normVersion, $version); }