Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve Composer's output reproducibility #11663

Merged
merged 5 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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