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
|
||||
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).
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()])) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -176,40 +176,16 @@ class InstallationManager
|
|||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue