Skip to content

Commit

Permalink
feat: improve Composer's output reproducibility (#11663)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
drupol authored Sep 28, 2023
1 parent 77fadf0 commit b608b8e
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 56 deletions.
7 changes: 7 additions & 0 deletions doc/01-basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions doc/06-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions src/Composer/Autoload/AutoloadGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Composer\Script\ScriptEvents;
use Composer\Util\PackageSorter;
use Composer\Json\JsonFile;
use Composer\Package\Locker;

/**
* @author Igor Wiedler <igor@wiedler.ch>
Expand Down Expand Up @@ -172,7 +173,7 @@ public function setPlatformRequirementFilter(PlatformRequirementFilterInterface
* @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
Expand Down Expand Up @@ -405,9 +406,8 @@ public static function autoload(\$class)
}
}

// 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));
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/Composer/Command/DumpAutoloadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
$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) {
Expand Down
10 changes: 9 additions & 1 deletion src/Composer/Command/ReinstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$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);
Expand Down
12 changes: 11 additions & 1 deletion src/Composer/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,17 @@ public function run(): int
$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) {
Expand Down
6 changes: 3 additions & 3 deletions src/Composer/Util/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -874,10 +874,8 @@ public function filePutContentsIfModified(string $path, string $content)

/**
* 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');
Expand All @@ -888,6 +886,8 @@ public function safeCopy(string $source, string $target)
stream_copy_to_stream($sourceHandle, $targetHandle);
fclose($sourceHandle);
fclose($targetHandle);

touch($target, (int) filemtime($source), (int) fileatime($source));
}
}

Expand Down
Loading

0 comments on commit b608b8e

Please sign in to comment.