From b608b8e87eeafec47fa04a8156ea44fc3f9745b0 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Thu, 28 Sep 2023 11:43:52 +0200 Subject: [PATCH] feat: improve Composer's output reproducibility (#11663) * AutoloadGenerator: add `Locker` parameter to the `dump` method * AutoloadGenerator: do not create a random hash, re-use the one from the lock file if it exists * FileSystem: make sure `safeCopy` copy also the file time metadata --- doc/01-basic-usage.md | 7 ++ doc/06-config.md | 6 +- src/Composer/Autoload/AutoloadGenerator.php | 6 +- src/Composer/Command/DumpAutoloadCommand.php | 10 +- src/Composer/Command/ReinstallCommand.php | 10 +- src/Composer/Installer.php | 12 ++- src/Composer/Util/Filesystem.php | 6 +- .../Test/Autoload/AutoloadGeneratorTest.php | 97 ++++++++++--------- .../Test/Command/DumpAutoloadCommandTest.php | 82 ++++++++++++++++ tests/Composer/Test/TestCase.php | 7 +- 10 files changed, 187 insertions(+), 56 deletions(-) diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index e0ac13c1c..41e43389b 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -152,6 +152,13 @@ a Composer `install` to make sure the vendor directory is up in sync with your php composer.phar install ``` +Composer enables reproducible builds by default. This means that running the +same command multiple times will produce a `vendor/` directory containing files +that are identical (*except their timestamps*), including the autoloader files. +It is especially beneficial for environments that require strict +verification processes, as well as for Linux distributions aiming to package PHP +applications in a secure and predictable manner. + ## Updating dependencies to their latest versions As mentioned above, the `composer.lock` file prevents you from automatically getting diff --git a/doc/06-config.md b/doc/06-config.md index ac0cdc0e6..a39c2872b 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -365,8 +365,10 @@ with other autoloaders. ## autoloader-suffix -Defaults to `null`. Non-empty string to be used as a suffix for the generated -Composer autoloader. When null a random one will be generated. +Defaults to `null`. When set to a non-empty string, this value will be used as a +suffix for the generated Composer autoloader. If set to `null`, the +`content-hash` value from the `composer.lock` file will be used if available; +otherwise, a random suffix will be generated. ## optimize-autoloader diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 3e2faa3eb..8ba82090b 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -33,6 +33,7 @@ use Composer\Util\Platform; use Composer\Script\ScriptEvents; use Composer\Util\PackageSorter; use Composer\Json\JsonFile; +use Composer\Package\Locker; /** * @author Igor Wiedler @@ -172,7 +173,7 @@ class AutoloadGenerator * @throws \Seld\JsonLint\ParsingException * @throws \RuntimeException */ - public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null) + public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, Locker $locker, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null) { if ($this->classMapAuthoritative) { // Force scanPsrPackages when classmap is authoritative @@ -405,9 +406,8 @@ EOF; } } - // generate one if we still haven't got a suffix if (null === $suffix) { - $suffix = md5(uniqid('', true)); + $suffix = $locker->isLocked() ? $locker->getLockData()['content-hash'] : md5(uniqid('', true)); } } diff --git a/src/Composer/Command/DumpAutoloadCommand.php b/src/Composer/Command/DumpAutoloadCommand.php index 41a23cd3a..fcd10f2b2 100644 --- a/src/Composer/Command/DumpAutoloadCommand.php +++ b/src/Composer/Command/DumpAutoloadCommand.php @@ -100,7 +100,15 @@ EOT $generator->setRunScripts(true); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); - $classMap = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + $classMap = $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + $composer->getLocker(), + 'composer', + $optimize + ); $numberOfClasses = count($classMap); if ($authoritative) { diff --git a/src/Composer/Command/ReinstallCommand.php b/src/Composer/Command/ReinstallCommand.php index 990994475..d94ffeb53 100644 --- a/src/Composer/Command/ReinstallCommand.php +++ b/src/Composer/Command/ReinstallCommand.php @@ -163,7 +163,15 @@ EOT $generator->setClassMapAuthoritative($authoritative); $generator->setApcu($apcu, $apcuPrefix); $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input)); - $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); + $generator->dump( + $config, + $localRepo, + $package, + $installationManager, + $composer->getLocker(), + 'composer', + $optimize + ); } $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode); diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 3e0f3a5df..7fc4382d1 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -349,7 +349,17 @@ class Installer $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix); $this->autoloadGenerator->setRunScripts($this->runScripts); $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter); - $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader); + $this + ->autoloadGenerator + ->dump( + $this->config, + $localRepo, + $this->package, + $this->installationManager, + $this->locker, + 'composer', + $this->optimizeAutoloader + ); } if ($this->install && $this->executeOperations) { diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 19e0efa8a..5e90e09f3 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -874,10 +874,8 @@ class Filesystem /** * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463 - * - * @return void */ - public function safeCopy(string $source, string $target) + public function safeCopy(string $source, string $target): void { if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) { $sourceHandle = fopen($source, 'r'); @@ -888,6 +886,8 @@ class Filesystem stream_copy_to_stream($sourceHandle, $targetHandle); fclose($sourceHandle); fclose($targetHandle); + + touch($target, (int) filemtime($source), (int) fileatime($source)); } } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index cc8c2f0ac..3a9544bb5 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -15,7 +15,6 @@ namespace Composer\Test\Autoload; use Composer\Autoload\AutoloadGenerator; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\IO\BufferIO; -use Composer\IO\IOInterface; use Composer\Package\CompletePackage; use Composer\Package\Link; use Composer\Package\Version\VersionParser; @@ -31,6 +30,7 @@ use Composer\Repository\InstalledRepositoryInterface; use Composer\Installer\InstallationManager; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; +use Composer\Package\Locker; use Composer\Util\Platform; use PHPUnit\Framework\MockObject\MockObject; @@ -81,6 +81,11 @@ class AutoloadGeneratorTest extends TestCase */ private $io; + /** + * @var Locker + */ + private $locker; + /** * @var EventDispatcher&MockObject */ @@ -159,6 +164,10 @@ class AutoloadGeneratorTest extends TestCase ->getMock(); $this->generator = new AutoloadGenerator($this->eventDispatcher, $this->io); + + $this->locker = $this->getMockBuilder('Composer\Package\Locker') + ->disableOriginalConstructor() + ->getMock(); } protected function tearDown(): void @@ -208,7 +217,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc'); file_put_contents($this->workingDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); // Assert that autoload_namespaces.php was correctly generated. $this->assertAutoloadFiles('main', $this->vendorDir.'/composer'); @@ -248,7 +257,7 @@ class AutoloadGeneratorTest extends TestCase // generate autoload files with the dev mode set to true $this->generator->setDevMode(true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); // check standard autoload $this->assertAutoloadFiles('main5', $this->vendorDir.'/composer'); @@ -281,7 +290,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->workingDir.'/devfiles'); file_put_contents($this->workingDir.'/devfiles/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); // check standard autoload $this->assertAutoloadFiles('main4', $this->vendorDir.'/composer'); @@ -316,7 +325,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->vendorDir.'/composersrc'); file_put_contents($this->vendorDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_2'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_2'); $this->assertAutoloadFiles('main3', $this->vendorDir.'/composer'); $this->assertAutoloadFiles('psr4_3', $this->vendorDir.'/composer', 'psr4'); $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap'); @@ -345,7 +354,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->workingDir.'/composersrc'); file_put_contents($this->workingDir.'/composersrc/foo.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_3'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_3'); $this->assertAutoloadFiles('main2', $this->vendorDir.'/composer'); $this->assertAutoloadFiles('psr4_2', $this->vendorDir.'/composer', 'psr4'); $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap'); @@ -374,7 +383,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/foo.php', 'workingDir.'/bar.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'TargetDir'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'TargetDir'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_target_dir.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_target_dir.php', $this->vendorDir.'/composer/autoload_static.php'); @@ -400,7 +409,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/foo.php', 'workingDir.'/bar.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'FilesWarning'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'FilesWarning'); self::assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_duplicates.php', $this->vendorDir.'/composer/autoload_files.php'); $expected = 'The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:'.PHP_EOL. ' - $baseDir . \'/foo.php\''.PHP_EOL; @@ -431,7 +440,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_5'); $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } @@ -465,7 +474,7 @@ class AutoloadGeneratorTest extends TestCase $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->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, '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."); } @@ -498,7 +507,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_5'); $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } @@ -526,7 +535,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src/C'); file_put_contents($this->vendorDir.'/b/b/src/C/C.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_5'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_5'); $this->assertEquals( [ @@ -565,7 +574,7 @@ class AutoloadGeneratorTest extends TestCase $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_5'); $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } @@ -617,7 +626,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->vendorDir.'/d/d/src/D.php', 'vendorDir.'/e/e/src/E.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_5'); $this->assertAutoloadFiles('classmap9', $this->vendorDir.'/composer', 'classmap'); } @@ -656,7 +665,7 @@ class AutoloadGeneratorTest extends TestCase ->method('getCanonicalPackages') ->will($this->returnValue([$vendorPackage])); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, 'Phar'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, 'Phar'); $this->assertAutoloadFiles('phar', $this->vendorDir . '/composer'); $this->assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4'); @@ -676,7 +685,7 @@ class AutoloadGeneratorTest extends TestCase ->method('getCanonicalPackages') ->will($this->returnValue([])); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_8'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_8'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( [ @@ -706,7 +715,7 @@ class AutoloadGeneratorTest extends TestCase file_put_contents($this->workingDir.'/psr4/match.php', 'workingDir.'/psr4/badfile.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $expectedClassmap = <<vendorDir.'/b/b/src/b.php', 'vendorDir.'/b/b/lib/c.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_6'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_6'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( [ @@ -794,7 +803,7 @@ EOF; file_put_contents($this->vendorDir.'/a/a/target/lib/b.php', 'vendorDir.'/b/b/src/c.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_6'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_6'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( [ @@ -836,7 +845,7 @@ EOF; file_put_contents($this->vendorDir.'/b/b/test.php', 'vendorDir.'/c/c/foo/test.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_7'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( [ @@ -883,7 +892,7 @@ EOF; $this->generator->setClassMapAuthoritative(true); $this->generator->setApcu(true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_7'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( @@ -932,7 +941,7 @@ EOF; $this->generator->setClassMapAuthoritative(true); $this->generator->setApcu(true, 'custom\'Prefix'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_7'); $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated."); $this->assertEquals( @@ -982,7 +991,7 @@ EOF; file_put_contents($this->vendorDir.'/c/c/foo/bar/test4.php', 'workingDir.'/root.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'FilesAutoload'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'FilesAutoload'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions.php', $this->vendorDir.'/composer/autoload_static.php'); @@ -1047,20 +1056,20 @@ EOF; file_put_contents($this->vendorDir.'/c/c/foo/bar/test4.php', 'workingDir.'/root.php', 'generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, $this->locker, 'composer', false, 'FilesAutoload'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_static.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions.php', $this->vendorDir.'/composer/autoload_files.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions.php', $this->vendorDir.'/composer/include_paths.php'); - $this->generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->generator->dump($this->config, $this->repository, $autoloadPackage, $this->im, $this->locker, 'composer', false, 'FilesAutoload'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_include_paths.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_files_functions_with_removed_extra.php', $this->vendorDir.'/composer/autoload_files.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths_functions_with_removed_extra.php', $this->vendorDir.'/composer/include_paths.php'); - $this->generator->dump($this->config, $this->repository, $notAutoloadPackage, $this->im, 'composer', false, 'FilesAutoload'); + $this->generator->dump($this->config, $this->repository, $notAutoloadPackage, $this->im, $this->locker, 'composer', false, 'FilesAutoload'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_functions.php', $this->vendorDir.'/autoload.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_functions_with_removed_include_paths_and_autolad_files.php', $this->vendorDir.'/composer/autoload_static.php'); @@ -1124,7 +1133,7 @@ EOF; file_put_contents($this->vendorDir . '/e/e/testE.php', 'workingDir . '/root2.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'FilesAutoloadOrder'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'FilesAutoloadOrder'); $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_functions_by_dependency.php', $this->vendorDir . '/autoload.php'); $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_real_files_by_dependency.php', $this->vendorDir . '/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__ . '/Fixtures/autoload_static_files_by_dependency.php', $this->vendorDir . '/composer/autoload_static.php'); @@ -1235,7 +1244,7 @@ return array( EOF; - $this->generator->dump($this->config, $this->repository, $rootPackage, $this->im, 'composer', true, '_9'); + $this->generator->dump($this->config, $this->repository, $rootPackage, $this->im, $this->locker, 'composer', true, '_9'); $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap); @@ -1265,7 +1274,7 @@ EOF; $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, "composer", false, '_10'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_10'); $this->assertFileContentEquals(__DIR__.'/Fixtures/include_paths.php', $this->vendorDir.'/composer/include_paths.php'); $this->assertEquals( @@ -1294,7 +1303,7 @@ EOF; mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, "composer", false, '_11'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_11'); $oldIncludePath = get_include_path(); @@ -1323,7 +1332,7 @@ EOF; mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, "composer", false, '_12'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_12'); $oldIncludePath = get_include_path(); @@ -1352,7 +1361,7 @@ EOF; mkdir($this->vendorDir."/composer", 0777, true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, "composer", false, '_12'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, '_12'); $this->assertFileDoesNotExist($this->vendorDir."/composer/include_paths.php"); } @@ -1381,7 +1390,7 @@ EOF; ->will($this->returnValue([])); $this->generator->setRunScripts(true); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_8'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_8'); } public function testUseGlobalIncludePath(): void @@ -1400,7 +1409,7 @@ EOF; $this->fs->ensureDirectoryExists($this->vendorDir.'/a'); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'IncludePath'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'IncludePath'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_real_include_path.php', $this->vendorDir.'/composer/autoload_real.php'); $this->assertFileContentEquals(__DIR__.'/Fixtures/autoload_static_include_path.php', $this->vendorDir.'/composer/autoload_static.php'); } @@ -1461,7 +1470,7 @@ EOF; $oldVendorDir = $this->vendorDir; $this->vendorDir = $vendorDir; - $this->generator->dump($this->config, $this->repository, $package, $im, 'composer', true, '_13'); + $this->generator->dump($this->config, $this->repository, $package, $im, $this->locker, 'composer', true, '_13'); $this->vendorDir = $oldVendorDir; $expectedNamespace = <<<'EOF' @@ -1551,7 +1560,7 @@ EOF; file_put_contents($this->workingDir.'/working-dir/classmap4/foo/classes.php', 'workingDir.'/test.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_14'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_14'); $expectedNamespace = <<<'EOF' setAutoload([ 'psr-0' => ['Foo' => './src'], ]); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_19'); $expectedNamespace = <<<'EOF' setAutoload([ 'psr-4' => ['Acme\Foo\\' => './src-psr4'], ]); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_19'); $expectedPsr4 = <<<'EOF' ['classmap'], ]); try { - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_19'); } catch (\RuntimeException $e) { $this->assertSame('Could not scan for classes inside "'.$this->vendorDir.'/dep/a/classmap" which does not appear to be a file nor a folder', $e->getMessage()); } @@ -1667,7 +1676,7 @@ EOF; $dep->setAutoload([ 'files' => ['./test.php'], ]); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_19'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_19'); $this->assertStringContainsString("\$vendorDir . '/dep/a/test.php',\n", (string) file_get_contents($this->vendorDir.'/composer/autoload_files.php')); $package->setAutoload([ @@ -1701,7 +1710,7 @@ EOF; file_put_contents($this->workingDir.'/Foo/Bar.php', 'workingDir.'/class.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_15'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_15'); $expectedNamespace = <<<'EOF' generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, 'VendorSubstring'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', false, 'VendorSubstring'); $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_namespaces.php', $expectedNamespace); $this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_psr4.php', $expectedPsr4); } @@ -1862,7 +1871,7 @@ EOF; : 'ln -s "' . $target . '" "' . $link . '"'; exec($command); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); // Assert that autoload_classmap.php was correctly generated. $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap'); @@ -1894,7 +1903,7 @@ EOF; ->will($this->returnValue([])); $this->generator->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs)); - $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1'); + $this->generator->dump($this->config, $this->repository, $package, $this->im, $this->locker, 'composer', true, '_1'); if (null === $expectedFixture) { $this->assertFileDoesNotExist($this->vendorDir . '/composer/platform_check.php'); diff --git a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php index de58f5295..88d128772 100644 --- a/tests/Composer/Test/Command/DumpAutoloadCommandTest.php +++ b/tests/Composer/Test/Command/DumpAutoloadCommandTest.php @@ -102,4 +102,86 @@ class DumpAutoloadCommandTest extends TestCase $this->expectExceptionMessage('You can not use both --no-dev and --dev as they conflict with each other.'); $appTester->run(['command' => 'dump-autoload', '--dev' => true, '--no-dev' => true]); } + + public function testWithCustomAutoloaderSuffix(): void + { + $dir = $this->initTempComposer([ + 'config' => [ + 'autoloader-suffix' => 'Foobar', + ], + ]); + + $appTester = $this->getApplicationTester(); + $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + + self::assertStringContainsString('ComposerAutoloaderInitFoobar', (string) file_get_contents($dir . '/vendor/autoload.php')); + } + + public function testWithExistingComposerLockAndAutoloaderSuffix(): void + { + $dir = $this->initTempComposer( + [ + 'config' => [ + 'autoloader-suffix' => 'Foobar', + ], + ], + [], + [ + "_readme" => [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash" => "d751713988987e9331980363e24189ce", + "packages" => [], + "packages-dev" => [], + "aliases" => [], + "minimum-stability" => "stable", + "stability-flags" => [], + "prefer-stable" => false, + "prefer-lowest" => false, + "platform" => [], + "platform-dev" => [], + "plugin-api-version" => "2.6.0" + ] + ); + + $appTester = $this->getApplicationTester(); + $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + + self::assertStringContainsString('ComposerAutoloaderInitFoobar', (string) file_get_contents($dir . '/vendor/autoload.php')); + } + + public function testWithExistingComposerLockWithoutAutoloaderSuffix(): void + { + $dir = $this->initTempComposer( + [ + 'name' => 'foo/bar', + ], + [], + [ + "_readme" => [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash" => "2d4a6be9a93712c5d6a119b26734a047", + "packages" => [], + "packages-dev" => [], + "aliases" => [], + "minimum-stability" => "stable", + "stability-flags" => [], + "prefer-stable" => false, + "prefer-lowest" => false, + "platform" => [], + "platform-dev" => [], + "plugin-api-version" => "2.6.0" + ] + ); + + $appTester = $this->getApplicationTester(); + $this->assertSame(0, $appTester->run(['command' => 'dump-autoload'])); + + self::assertStringContainsString('ComposerAutoloaderInit2d4a6be9a93712c5d6a119b26734a047', (string) file_get_contents($dir . '/vendor/autoload.php')); + } } diff --git a/tests/Composer/Test/TestCase.php b/tests/Composer/Test/TestCase.php index 357fb9264..a1bbbece1 100644 --- a/tests/Composer/Test/TestCase.php +++ b/tests/Composer/Test/TestCase.php @@ -122,9 +122,10 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase * @see getApplicationTester * @param mixed[] $composerJson * @param mixed[] $authJson + * @param mixed[] $composerLock * @return string the newly created temp dir */ - public function initTempComposer(array $composerJson = [], array $authJson = []): string + public function initTempComposer(array $composerJson = [], array $authJson = [], array $composerLock = []): string { $dir = self::getUniqueTmpDirectory(); @@ -150,6 +151,10 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase file_put_contents($dir.'/composer.json', JsonFile::encode($composerJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); file_put_contents($dir.'/auth.json', JsonFile::encode($authJson, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + if ($composerLock !== []) { + file_put_contents($dir.'/composer.lock', JsonFile::encode($composerLock, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + } + return $dir; }