diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 0ff95e042..c7ab9ca1f 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -473,7 +473,7 @@ EOF; /** * @param PackageInterface[] $packages - * @return array + * @return non-empty-array */ public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages) { @@ -485,14 +485,9 @@ EOF; continue; } $this->validatePackage($package); - $installPath = $installationManager->getInstallPath($package); - if ($installPath === null) { - continue; - } - $packageMap[] = [ $package, - $installPath, + $installationManager->getInstallPath($package), ]; } @@ -523,7 +518,7 @@ EOF; /** * Compiles an ordered list of namespace => path mappings * - * @param array $packageMap array of array(package, installDir-relative-to-composer.json) + * @param non-empty-array $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages) * @param RootPackageInterface $rootPackage root package instance * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages * @return array @@ -613,7 +608,7 @@ EOF; } /** - * @param array $packageMap + * @param array $packageMap * @return ?string */ protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode) @@ -623,6 +618,11 @@ EOF; foreach ($packageMap as $item) { [$package, $installPath] = $item; + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) { $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir())); } @@ -716,7 +716,7 @@ EOF; } /** - * @param array $packageMap + * @param array $packageMap * @param bool|'php-only' $checkPlatform * @param string[] $devPackageNames * @return ?string @@ -1149,7 +1149,7 @@ INITIALIZER; } /** - * @param array $packageMap + * @param array $packageMap * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files' * @return array|array>|array */ @@ -1160,6 +1160,11 @@ INITIALIZER; foreach ($packageMap as $item) { [$package, $installPath] = $item; + // packages that are not installed cannot autoload anything + if (null === $installPath) { + continue; + } + $autoload = $package->getAutoload(); if ($this->devMode && $package === $rootPackage) { $autoload = array_merge_recursive($autoload, $package->getDevAutoload()); @@ -1249,10 +1254,8 @@ INITIALIZER; /** * Filters out dev-dependencies * - * @param array $packageMap - * @return array - * - * @phpstan-param array $packageMap + * @param array $packageMap + * @return array */ protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage) { @@ -1305,8 +1308,8 @@ INITIALIZER; * * Packages of equal weight are sorted alphabetically * - * @param array $packageMap - * @return array + * @param array $packageMap + * @return array */ protected function sortPackageMap(array $packageMap) { diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 134bcf222..f1a49b251 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -131,7 +131,11 @@ class AutoloadGeneratorTest extends TestCase ->getMock(); $this->im->expects($this->any()) ->method('getInstallPath') - ->will($this->returnCallback(function ($package): string { + ->will($this->returnCallback(function ($package): ?string { + if ($package->getType() === 'metapackage') { + return null; + } + $targetDir = $package->getTargetDir(); return $this->vendorDir.'/'.$package->getName() . ($targetDir ? '/'.$targetDir : ''); @@ -398,6 +402,40 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testVendorsAutoloadingWithMetapackages(): void + { + $package = new RootPackage('root/a', '1.0', '1.0'); + $package->setRequires([ + 'a/a' => new Link('a', 'a/a', new MatchAllConstraint()), + ]); + + $packages = []; + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $packages[] = $c = new AliasPackage($b, '1.2', '1.2'); + $a->setAutoload(['psr-0' => ['A' => 'src/', 'A\\B' => 'lib/']]); + $b->setAutoload(['psr-0' => ['B\\Sub\\Name' => 'src/']]); + $a->setType('metapackage'); + $a->setRequires([ + 'b/b' => new Link('a/a', 'b/b', new MatchAllConstraint()), + ]); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + // creating a/a files to make sure they would be found by autoloader even tho they are technically not + // needed as the package is a metapackage, but if it fails to be excluded it would find these + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors_meta', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testNonDevAutoloadExclusionWithRecursion(): void { $package = new RootPackage('root/a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_vendors_meta.php b/tests/Composer/Test/Autoload/Fixtures/autoload_vendors_meta.php new file mode 100644 index 000000000..551e64fb8 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_vendors_meta.php @@ -0,0 +1,10 @@ + array($vendorDir . '/b/b/src'), +);