diff --git a/src/Unpacker.php b/src/Unpacker.php index af6ae2afe..99b398642 100644 --- a/src/Unpacker.php +++ b/src/Unpacker.php @@ -18,6 +18,7 @@ use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Json\JsonManipulator; +use Composer\Package\Link; use Composer\Package\Locker; use Composer\Package\Version\VersionSelector; use Composer\Plugin\PluginInterface; @@ -60,7 +61,7 @@ public function unpack(Operation $op, Result $result = null, &$links = []): Resu null === $pkg || 'symfony-pack' !== $pkg->getType() || !$op->shouldUnpack() || - 0 === \count($pkg->getRequires()) + \count($pkg->getDevRequires()) + 0 === \count($pkg->getRequires()) + \count($pkg->getDevRequires()) + \count($pkg->getReplaces()) + \count($pkg->getProvides()) ) { $result->addRequired($package['name'].($package['version'] ? ':'.$package['version'] : '')); @@ -72,6 +73,16 @@ public function unpack(Operation $op, Result $result = null, &$links = []): Resu } $versionSelector = null; + + if (!$package['dev']) { + foreach ($pkg->getReplaces() as $link) { + $this->addLinks($link, $links, 'replace'); + } + foreach ($pkg->getProvides() as $link) { + $this->addLinks($link, $links, 'provide'); + } + } + foreach ($pkg->getRequires() as $link) { if ('php' === $link->getTarget()) { continue; @@ -106,7 +117,7 @@ public function unpack(Operation $op, Result $result = null, &$links = []): Resu if (isset($links[$linkName])) { $links[$linkName]['constraints'][] = $constraint; - if ('require' === $linkType) { + if ('require' === $linkType && !\in_array($links['linkName']['type'],['replace', 'provide'])) { $links[$linkName]['type'] = 'require'; } } else { @@ -200,4 +211,20 @@ public function updateLock(Result $result, IOInterface $io): void } $this->composer->setLocker($locker); } + + + private function addLinks(Link $link, array &$links, string $linkType): void + { + $constraint = $link->getPrettyConstraint(); + $constraint = substr($this->resolver->parseVersion($link->getTarget(), $constraint, false), 1) ?: $constraint; + + $linkName = $link->getTarget(); + $constraint = $this->versionParser->parseConstraints($constraint); + + $links[$linkName] = [ + 'type' => $linkType, + 'name' => $linkName, + 'constraints' => [$constraint], + ]; + } } diff --git a/tests/UnpackerTest.php b/tests/UnpackerTest.php index f9e9e4ee5..04013c0eb 100644 --- a/tests/UnpackerTest.php +++ b/tests/UnpackerTest.php @@ -90,4 +90,158 @@ public function testDoNotDuplicateEntry(): void putenv('COMPOSER='.$originalEnvComposer); @unlink($composerJsonPath); } + + /** + * Replaces are added from the packs into the main composer.json. + * + * Context: + * + * - There are two packs: "pack_foo" and "pack_bar" + * - Both points to a package named "real" + * - "pack_foo" is present in the "require" section + * - "pack_bar" is present in the "replace" section + * + * Expected result: + * + * - "real" package MUST be present ONLY in "replace" section + */ + public function testReplacesAreAdded(): void + { + // Setup project + + $composerJsonPath = FLEX_TEST_DIR.'/composer.json'; + + @mkdir(FLEX_TEST_DIR); + @unlink($composerJsonPath); + file_put_contents($composerJsonPath, '{}'); + + $originalEnvComposer = getenv('COMPOSER'); + putenv('COMPOSER='.$composerJsonPath); + + // Setup packages + + $realPkg = new Package('real', '1.0.0', '1.0.0'); + $realPkgLink = new Link('lorem', 'real', class_exists(MatchAllConstraint::class) ? new MatchAllConstraint() : null, 'wraps', '1.0.0'); + + $virtualPkgFoo = new Package('pack_foo', '1.0.0', '1.0.0'); + $virtualPkgFoo->setType('symfony-pack'); + $virtualPkgFoo->setRequires([$realPkgLink]); + + $virtualPkgBar = new Package('pack_bar', '1.0.0', '1.0.0'); + $virtualPkgBar->setType('symfony-pack'); + $virtualPkgBar->setReplaces([$realPkgLink]); + + $packages = [$realPkg, $virtualPkgFoo, $virtualPkgBar]; + + // Setup Composer + + $repManager = $this->getMockBuilder(RepositoryManager::class)->disableOriginalConstructor()->getMock(); + $repManager->expects($this->any())->method('getLocalRepository')->willReturn(new InstalledArrayRepository($packages)); + + $composer = new Composer(); + $composer->setRepositoryManager($repManager); + + // Unpack + + $resolver = $this->getMockBuilder(PackageResolver::class)->disableOriginalConstructor()->getMock(); + + $unpacker = new Unpacker($composer, $resolver, false); + + $operation = new Operation(true, false); + $operation->addPackage('pack_foo', '*', false); + $operation->addPackage('pack_bar', '*', false); + + $unpacker->unpack($operation); + + // Check + + $composerJson = json_decode(file_get_contents($composerJsonPath), true); + + $this->assertArrayHasKey('replace', $composerJson); + $this->assertArrayHasKey('real', $composerJson['replace']); + $this->assertArrayNotHasKey('require-dev', $composerJson); + $this->assertArrayNotHasKey('require', $composerJson); + + // Restore + + putenv('COMPOSER='.$originalEnvComposer); + @unlink($composerJsonPath); + } + + /** + * Replaces from dev dependencies are not added. + * + * Context: + * + * - There are two packs: "pack_foo" and "pack_bar" + * - Both points to a package named "real" + * - "pack_foo" is present in the "require" section + * - "pack_bar" is present in the "replace" section + * + * Expected result: + * + * - "real" package MUST be present ONLY in "require" section + */ + public function testReplacesAreNotAddedForDevDependencies(): void + { + // Setup project + + $composerJsonPath = FLEX_TEST_DIR.'/composer.json'; + + @mkdir(FLEX_TEST_DIR); + @unlink($composerJsonPath); + file_put_contents($composerJsonPath, '{}'); + + $originalEnvComposer = getenv('COMPOSER'); + putenv('COMPOSER='.$composerJsonPath); + + // Setup packages + + $realPkg = new Package('real', '1.0.0', '1.0.0'); + $realPkgLink = new Link('lorem', 'real', class_exists(MatchAllConstraint::class) ? new MatchAllConstraint() : null, 'wraps', '1.0.0'); + + $virtualPkgFoo = new Package('pack_foo', '1.0.0', '1.0.0'); + $virtualPkgFoo->setType('symfony-pack'); + $virtualPkgFoo->setRequires([$realPkgLink]); + + $virtualPkgBar = new Package('pack_bar', '1.0.0', '1.0.0'); + $virtualPkgBar->setType('symfony-pack'); + $virtualPkgBar->setReplaces([$realPkgLink]); + + $packages = [$realPkg, $virtualPkgFoo, $virtualPkgBar]; + + // Setup Composer + + $repManager = $this->getMockBuilder(RepositoryManager::class)->disableOriginalConstructor()->getMock(); + $repManager->expects($this->any())->method('getLocalRepository')->willReturn(new InstalledArrayRepository($packages)); + + $composer = new Composer(); + $composer->setRepositoryManager($repManager); + + // Unpack + + $resolver = $this->getMockBuilder(PackageResolver::class)->disableOriginalConstructor()->getMock(); + + $unpacker = new Unpacker($composer, $resolver, false); + + $operation = new Operation(true, false); + $operation->addPackage('pack_foo', '*', false); + $operation->addPackage('pack_bar', '*', true); + + $unpacker->unpack($operation); + + // Check + + $composerJson = json_decode(file_get_contents($composerJsonPath), true); + + $this->assertArrayHasKey('require', $composerJson); + $this->assertArrayHasKey('real', $composerJson['require']); + $this->assertArrayNotHasKey('require-dev', $composerJson); + $this->assertArrayNotHasKey('replace', $composerJson); + + // Restore + + putenv('COMPOSER='.$originalEnvComposer); + @unlink($composerJsonPath); + } }