1
0
Fork 0

Add download-only mode (#11041)

composer install --download-only to prime the cache/download archives but not do any actual of the actual installing

Fixes #11035

Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>
pull/11115/head
Jellyfrog 2022-10-12 13:56:35 +02:00 committed by GitHub
parent 7df744531b
commit 8ed7c46179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 44 deletions

View File

@ -107,6 +107,7 @@ resolution.
* **--dry-run:** If you want to run through an installation without actually
installing a package, you can use `--dry-run`. This will simulate the
installation and show you what would happen.
* **--download-only:** Download only, do not install packages.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader
generation skips the `autoload-dev` rules. Also see [COMPOSER_NO_DEV](#composer-no-dev).

View File

@ -46,6 +46,7 @@ class InstallCommand extends BaseCommand
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'),
new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
@ -124,6 +125,7 @@ EOT
$install
->setDryRun($input->getOption('dry-run'))
->setDownloadOnly($input->getOption('download-only'))
->setVerbose($input->getOption('verbose'))
->setPreferSource($preferSource)
->setPreferDist($preferDist)

View File

@ -307,9 +307,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
}
$dirsToCleanUp = [
$path,
$this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0],
$this->config->get('vendor-dir').'/composer/',
$this->config->get('vendor-dir'),
$path,
];
if (isset($this->additionalCleanupPaths[$package->getName()])) {

View File

@ -148,6 +148,8 @@ class Installer
/** @var bool */
protected $dryRun = false;
/** @var bool */
protected $downloadOnly = false;
/** @var bool */
protected $verbose = false;
/** @var bool */
protected $update = false;
@ -258,6 +260,10 @@ class Installer
$this->mockLocalRepositories($this->repositoryManager);
}
if ($this->downloadOnly) {
$this->dumpAutoloader = false;
}
if ($this->update && !$this->install) {
$this->dumpAutoloader = false;
}
@ -783,7 +789,7 @@ class Installer
if ($this->executeOperations) {
$localRepo->setDevPackageNames($this->locker->getDevPackageNames());
$this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts);
$this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly);
} else {
foreach ($localRepoTransaction->getOperations() as $operation) {
// output op, but alias op only in debug verbosity
@ -1088,6 +1094,18 @@ class Installer
return $this->dryRun;
}
/**
* Whether to download only or not.
*
* @return Installer
*/
public function setDownloadOnly(bool $downloadOnly = true): self
{
$this->downloadOnly = $downloadOnly;
return $this;
}
/**
* prefer source installation
*

View File

@ -172,44 +172,20 @@ class InstallationManager
/**
* Executes solver operation.
*
* @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages
* @param OperationInterface[] $operations operations to execute
* @param bool $devMode whether the install is being run in dev mode
* @param bool $runScripts whether to dispatch script events
* @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages
* @param OperationInterface[] $operations operations to execute
* @param bool $devMode whether the install is being run in dev mode
* @param bool $runScripts whether to dispatch script events
* @param bool $downloadOnly whether to only download packages
*/
public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true): void
public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void
{
/** @var PromiseInterface[] */
/** @var array<callable(): ?PromiseInterface> */
$cleanupPromises = [];
$loop = $this->loop;
$io = $this->io;
$runCleanup = static function () use (&$cleanupPromises, $loop): void {
$promises = [];
$loop->abortJobs();
foreach ($cleanupPromises as $cleanup) {
$promises[] = new \React\Promise\Promise(static function ($resolve, $reject) use ($cleanup): void {
$promise = $cleanup();
if (!$promise instanceof PromiseInterface) {
$resolve();
} else {
$promise->then(static function () use ($resolve): void {
$resolve();
});
}
});
}
if (!empty($promises)) {
$loop->wait($promises);
}
};
$signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], static function (string $signal, SignalHandler $handler) use ($io, $runCleanup) {
$io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
$runCleanup();
$signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) {
$this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
$this->runCleanup($cleanupPromises);
$handler->exitWithLastSignal();
});
@ -239,16 +215,20 @@ class InstallationManager
}
foreach ($batches as $batch) {
$this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $operations);
$this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations);
}
} catch (\Exception $e) {
$runCleanup();
$this->runCleanup($cleanupPromises);
throw $e;
} finally {
$signalHandler->unregister();
}
if ($downloadOnly) {
return;
}
// do a last write so that we write the repository even if nothing changed
// as that can trigger an update of some files like InstalledVersions.php if
// running a new composer version
@ -257,10 +237,10 @@ class InstallationManager
/**
* @param OperationInterface[] $operations List of operations to execute in this batch
* @param PromiseInterface[] $cleanupPromises
* @param array<callable(): ?PromiseInterface> $cleanupPromises
* @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
*/
private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void
private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void
{
$promises = [];
@ -283,11 +263,11 @@ class InstallationManager
}
$installer = $this->getInstaller($package->getType());
$cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage) {
$cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface {
// avoid calling cleanup if the download was not even initialized for a package
// as without installation source configured nothing will work
if (!$package->getInstallationSource()) {
return;
return \React\Promise\resolve();
}
return $installer->cleanup($opType, $package, $initialPackage);
@ -306,6 +286,11 @@ class InstallationManager
$this->waitOnPromises($promises);
}
if ($downloadOnly) {
$this->runCleanup($cleanupPromises);
return;
}
// execute operations in batches to make sure every plugin is installed in the
// right order and activated before the packages depending on it are installed
$batches = [];
@ -337,7 +322,7 @@ class InstallationManager
/**
* @param OperationInterface[] $operations List of operations to execute in this batch
* @param PromiseInterface[] $cleanupPromises
* @param array<callable(): ?PromiseInterface> $cleanupPromises
* @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
*/
private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void
@ -368,6 +353,7 @@ class InstallationManager
$package = $operation->getPackage();
$initialPackage = null;
}
$installer = $this->getInstaller($package->getType());
$eventName = [
@ -451,6 +437,19 @@ class InstallationManager
}
}
/**
* Executes download operation.
*
* $param PackageInterface $package
*/
public function download(PackageInterface $package): ?PromiseInterface
{
$installer = $this->getInstaller($package->getType());
$promise = $installer->cleanup("install", $package);
return $promise;
}
/**
* Executes install operation.
*
@ -637,4 +636,32 @@ class InstallationManager
$this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
}
}
/**
* @param array<callable(): ?PromiseInterface> $cleanupPromises
* @return void
*/
private function runCleanup(array $cleanupPromises): void
{
$promises = [];
$this->loop->abortJobs();
foreach ($cleanupPromises as $cleanup) {
$promises[] = new \React\Promise\Promise(static function ($resolve, $reject) use ($cleanup): void {
$promise = $cleanup();
if (!$promise instanceof PromiseInterface) {
$resolve();
} else {
$promise->then(static function () use ($resolve): void {
$resolve();
});
}
});
}
if (!empty($promises)) {
$this->loop->wait($promises);
}
}
}

View File

@ -46,7 +46,7 @@ class InstallationManagerMock extends InstallationManager
{
}
public function execute(InstalledRepositoryInterface $repo, array $operations, $devMode = true, $runScripts = true): void
public function execute(InstalledRepositoryInterface $repo, array $operations, $devMode = true, $runScripts = true, $downloadOnly = false): void
{
foreach ($operations as $operation) {
$method = $operation->getOperationType();