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
parent
7df744531b
commit
8ed7c46179
|
@ -107,6 +107,7 @@ resolution.
|
||||||
* **--dry-run:** If you want to run through an installation without actually
|
* **--dry-run:** If you want to run through an installation without actually
|
||||||
installing a package, you can use `--dry-run`. This will simulate the
|
installing a package, you can use `--dry-run`. This will simulate the
|
||||||
installation and show you what would happen.
|
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).
|
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
|
||||||
* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader
|
* **--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).
|
generation skips the `autoload-dev` rules. Also see [COMPOSER_NO_DEV](#composer-no-dev).
|
||||||
|
|
|
@ -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-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('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('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('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-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.'),
|
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
|
||||||
|
@ -124,6 +125,7 @@ EOT
|
||||||
|
|
||||||
$install
|
$install
|
||||||
->setDryRun($input->getOption('dry-run'))
|
->setDryRun($input->getOption('dry-run'))
|
||||||
|
->setDownloadOnly($input->getOption('download-only'))
|
||||||
->setVerbose($input->getOption('verbose'))
|
->setVerbose($input->getOption('verbose'))
|
||||||
->setPreferSource($preferSource)
|
->setPreferSource($preferSource)
|
||||||
->setPreferDist($preferDist)
|
->setPreferDist($preferDist)
|
||||||
|
|
|
@ -307,9 +307,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
$dirsToCleanUp = [
|
$dirsToCleanUp = [
|
||||||
|
$path,
|
||||||
|
$this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0],
|
||||||
$this->config->get('vendor-dir').'/composer/',
|
$this->config->get('vendor-dir').'/composer/',
|
||||||
$this->config->get('vendor-dir'),
|
$this->config->get('vendor-dir'),
|
||||||
$path,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isset($this->additionalCleanupPaths[$package->getName()])) {
|
if (isset($this->additionalCleanupPaths[$package->getName()])) {
|
||||||
|
|
|
@ -148,6 +148,8 @@ class Installer
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $dryRun = false;
|
protected $dryRun = false;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
|
protected $downloadOnly = false;
|
||||||
|
/** @var bool */
|
||||||
protected $verbose = false;
|
protected $verbose = false;
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $update = false;
|
protected $update = false;
|
||||||
|
@ -258,6 +260,10 @@ class Installer
|
||||||
$this->mockLocalRepositories($this->repositoryManager);
|
$this->mockLocalRepositories($this->repositoryManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->downloadOnly) {
|
||||||
|
$this->dumpAutoloader = false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->update && !$this->install) {
|
if ($this->update && !$this->install) {
|
||||||
$this->dumpAutoloader = false;
|
$this->dumpAutoloader = false;
|
||||||
}
|
}
|
||||||
|
@ -783,7 +789,7 @@ class Installer
|
||||||
|
|
||||||
if ($this->executeOperations) {
|
if ($this->executeOperations) {
|
||||||
$localRepo->setDevPackageNames($this->locker->getDevPackageNames());
|
$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 {
|
} else {
|
||||||
foreach ($localRepoTransaction->getOperations() as $operation) {
|
foreach ($localRepoTransaction->getOperations() as $operation) {
|
||||||
// output op, but alias op only in debug verbosity
|
// output op, but alias op only in debug verbosity
|
||||||
|
@ -1088,6 +1094,18 @@ class Installer
|
||||||
return $this->dryRun;
|
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
|
* prefer source installation
|
||||||
*
|
*
|
||||||
|
|
|
@ -172,44 +172,20 @@ class InstallationManager
|
||||||
/**
|
/**
|
||||||
* Executes solver operation.
|
* Executes solver operation.
|
||||||
*
|
*
|
||||||
* @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages
|
* @param InstalledRepositoryInterface $repo repository in which to add/remove/update packages
|
||||||
* @param OperationInterface[] $operations operations to execute
|
* @param OperationInterface[] $operations operations to execute
|
||||||
* @param bool $devMode whether the install is being run in dev mode
|
* @param bool $devMode whether the install is being run in dev mode
|
||||||
* @param bool $runScripts whether to dispatch script events
|
* @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 = [];
|
$cleanupPromises = [];
|
||||||
|
|
||||||
$loop = $this->loop;
|
$signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) {
|
||||||
$io = $this->io;
|
$this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
|
||||||
$runCleanup = static function () use (&$cleanupPromises, $loop): void {
|
$this->runCleanup($cleanupPromises);
|
||||||
$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();
|
|
||||||
$handler->exitWithLastSignal();
|
$handler->exitWithLastSignal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -239,16 +215,20 @@ class InstallationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($batches as $batch) {
|
foreach ($batches as $batch) {
|
||||||
$this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $operations);
|
$this->downloadAndExecuteBatch($repo, $batch, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$runCleanup();
|
$this->runCleanup($cleanupPromises);
|
||||||
|
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
$signalHandler->unregister();
|
$signalHandler->unregister();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($downloadOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// do a last write so that we write the repository even if nothing changed
|
// 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
|
// as that can trigger an update of some files like InstalledVersions.php if
|
||||||
// running a new composer version
|
// running a new composer version
|
||||||
|
@ -257,10 +237,10 @@ class InstallationManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param OperationInterface[] $operations List of operations to execute in this batch
|
* @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
|
* @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 = [];
|
$promises = [];
|
||||||
|
|
||||||
|
@ -283,11 +263,11 @@ class InstallationManager
|
||||||
}
|
}
|
||||||
$installer = $this->getInstaller($package->getType());
|
$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
|
// avoid calling cleanup if the download was not even initialized for a package
|
||||||
// as without installation source configured nothing will work
|
// as without installation source configured nothing will work
|
||||||
if (!$package->getInstallationSource()) {
|
if (!$package->getInstallationSource()) {
|
||||||
return;
|
return \React\Promise\resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $installer->cleanup($opType, $package, $initialPackage);
|
return $installer->cleanup($opType, $package, $initialPackage);
|
||||||
|
@ -306,6 +286,11 @@ class InstallationManager
|
||||||
$this->waitOnPromises($promises);
|
$this->waitOnPromises($promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($downloadOnly) {
|
||||||
|
$this->runCleanup($cleanupPromises);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// execute operations in batches to make sure every plugin is installed in the
|
// 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
|
// right order and activated before the packages depending on it are installed
|
||||||
$batches = [];
|
$batches = [];
|
||||||
|
@ -337,7 +322,7 @@ class InstallationManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param OperationInterface[] $operations List of operations to execute in this batch
|
* @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
|
* @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
|
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();
|
$package = $operation->getPackage();
|
||||||
$initialPackage = null;
|
$initialPackage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$installer = $this->getInstaller($package->getType());
|
$installer = $this->getInstaller($package->getType());
|
||||||
|
|
||||||
$eventName = [
|
$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.
|
* Executes install operation.
|
||||||
*
|
*
|
||||||
|
@ -637,4 +636,32 @@ class InstallationManager
|
||||||
$this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
|
$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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
foreach ($operations as $operation) {
|
||||||
$method = $operation->getOperationType();
|
$method = $operation->getOperationType();
|
||||||
|
|
Loading…
Reference in New Issue