From 5761228068baa934f6312b471ce309591611cdf2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jun 2020 10:20:03 +0200 Subject: [PATCH 1/4] Make installer classes forward promises from downloaders to InstallationManager --- .../Installer/InstallationManager.php | 5 +- src/Composer/Installer/LibraryInstaller.php | 83 +++++++++++++------ src/Composer/Installer/PluginInstaller.php | 58 +++++++++---- src/Composer/Installer/ProjectInstaller.php | 6 +- 4 files changed, 105 insertions(+), 47 deletions(-) diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index bd1bf8aee..059ea7169 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -26,6 +26,7 @@ use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\StreamContextFactory; use Composer\Util\Loop; +use React\Promise\PromiseInterface; /** * Package operation manager. @@ -296,8 +297,8 @@ class InstallationManager $io = $this->io; $promise = $installer->prepare($opType, $package, $initialPackage); - if (null === $promise) { - $promise = new \React\Promise\Promise(function ($resolve, $reject) { $resolve(); }); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } $promise = $promise->then(function () use ($opType, $installManager, $repo, $operation) { diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 5e99e1f47..d70cd4f63 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Util\Filesystem; use Composer\Util\Silencer; use Composer\Util\Platform; +use React\Promise\PromiseInterface; /** * Package installation manager. @@ -131,11 +132,19 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->binaryInstaller->removeBinaries($package); } - $this->installCode($package); - $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package)); - if (!$repo->hasPackage($package)) { - $repo->addPackage(clone $package); + $promise = $this->installCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($package); + return $promise->then(function () use ($binaryInstaller, $installPath, $package, $repo) { + $binaryInstaller->installBinaries($package, $installPath); + if (!$repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + }); } /** @@ -150,12 +159,20 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface $this->initializeVendorDir(); $this->binaryInstaller->removeBinaries($initial); - $this->updateCode($initial, $target); - $this->binaryInstaller->installBinaries($target, $this->getInstallPath($target)); - $repo->removePackage($initial); - if (!$repo->hasPackage($target)) { - $repo->addPackage(clone $target); + $promise = $this->updateCode($initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $binaryInstaller = $this->binaryInstaller; + $installPath = $this->getInstallPath($target); + return $promise->then(function () use ($binaryInstaller, $installPath, $target, $initial, $repo) { + $binaryInstaller->installBinaries($target, $installPath); + $repo->removePackage($initial); + if (!$repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + }); } /** @@ -167,17 +184,25 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface throw new \InvalidArgumentException('Package is not installed: '.$package); } - $this->removeCode($package); - $this->binaryInstaller->removeBinaries($package); - $repo->removePackage($package); + $promise = $this->removeCode($package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); + } + $binaryInstaller = $this->binaryInstaller; $downloadPath = $this->getPackageBasePath($package); - if (strpos($package->getName(), '/')) { - $packageVendorDir = dirname($downloadPath); - if (is_dir($packageVendorDir) && $this->filesystem->isDirEmpty($packageVendorDir)) { - Silencer::call('rmdir', $packageVendorDir); + $filesystem = $this->filesystem; + return $promise->then(function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo) { + $binaryInstaller->removeBinaries($package); + $repo->removePackage($package); + + if (strpos($package->getName(), '/')) { + $packageVendorDir = dirname($downloadPath); + if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) { + Silencer::call('rmdir', $packageVendorDir); + } } - } + }); } /** @@ -227,7 +252,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface protected function installCode(PackageInterface $package) { $downloadPath = $this->getInstallPath($package); - $this->downloadManager->install($package, $downloadPath); + return $this->downloadManager->install($package, $downloadPath); } protected function updateCode(PackageInterface $initial, PackageInterface $target) @@ -240,21 +265,31 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath ) { - $this->removeCode($initial); - $this->installCode($target); - - return; + $promise = $this->removeCode($initial); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); + } + + $self = $this; + return $promise->then(function () use ($self, $target) { + $reflMethod = new \ReflectionMethod($self, 'installCode'); + $reflMethod->setAccessible(true); + + // equivalent of $this->installCode($target) with php 5.3 support + // TODO remove this once 5.3 support is dropped + return $reflMethod->invoke($self, $target); + }); } $this->filesystem->rename($initialDownloadPath, $targetDownloadPath); } - $this->downloadManager->update($initial, $target, $targetDownloadPath); + return $this->downloadManager->update($initial, $target, $targetDownloadPath); } protected function removeCode(PackageInterface $package) { $downloadPath = $this->getPackageBasePath($package); - $this->downloadManager->remove($package, $downloadPath); + return $this->downloadManager->remove($package, $downloadPath); } protected function initializeVendorDir() diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 6d5a26be0..324ef5a99 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use React\Promise\PromiseInterface; /** * Installer for plugin packages @@ -65,15 +66,20 @@ class PluginInstaller extends LibraryInstaller */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { - parent::install($repo, $package); - try { - $this->composer->getPluginManager()->registerPackage($package, true); - } catch (\Exception $e) { - // Rollback installation - $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); - parent::uninstall($repo, $package); - throw $e; + $promise = parent::install($repo, $package); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $pluginManager = $this->composer->getPluginManager(); + $self = $this; + return $promise->then(function () use ($self, $pluginManager, $package, $repo) { + try { + $pluginManager->registerPackage($package, true); + } catch (\Exception $e) { + $self->rollbackInstall($e, $repo, $package); + } + }); } /** @@ -81,22 +87,38 @@ class PluginInstaller extends LibraryInstaller */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { - parent::update($repo, $initial, $target); - - try { - $this->composer->getPluginManager()->deactivatePackage($initial, true); - $this->composer->getPluginManager()->registerPackage($target, true); - } catch (\Exception $e) { - // Rollback installation - $this->io->writeError('Plugin initialization failed, uninstalling plugin'); - parent::uninstall($repo, $target); - throw $e; + $promise = parent::update($repo, $initial, $target); + if (!$promise instanceof PromiseInterface) { + $promise = \React\Promise\resolve(); } + + $pluginManager = $this->composer->getPluginManager(); + $self = $this; + return $promise->then(function () use ($self, $pluginManager, $initial, $target, $repo) { + try { + $pluginManager->deactivatePackage($initial, true); + $pluginManager->registerPackage($target, true); + } catch (\Exception $e) { + $self->rollbackInstall($e, $repo, $target); + } + }); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->composer->getPluginManager()->uninstallPackage($package, true); + + return parent::uninstall($repo, $package); + } + + /** + * TODO v3 should make this private once we can drop PHP 5.3 support + * @private + */ + public function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin'); parent::uninstall($repo, $package); + throw $e; } } diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index 069c741ec..2875e0a65 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -76,7 +76,7 @@ class ProjectInstaller implements InstallerInterface */ public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) { - $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); + return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); } /** @@ -84,7 +84,7 @@ class ProjectInstaller implements InstallerInterface */ public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) { - $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); + return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); } /** @@ -92,7 +92,7 @@ class ProjectInstaller implements InstallerInterface */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { - $this->downloadManager->install($package, $this->installPath); + return $this->downloadManager->install($package, $this->installPath); } /** From a4a617abb46cd1bb55591f9f5b31a4ff3e643ba0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jun 2020 15:30:20 +0200 Subject: [PATCH 2/4] Reduce amount of Filesystem/ProcessExecutor instantiations, add lots of docblocks --- src/Composer/Command/CreateProjectCommand.php | 11 ++-- src/Composer/Downloader/DownloadManager.php | 6 ++ src/Composer/Downloader/FileDownloader.php | 8 ++- src/Composer/Downloader/GzipDownloader.php | 5 +- src/Composer/Downloader/PathDownloader.php | 31 +++++++--- src/Composer/Downloader/RarDownloader.php | 5 +- src/Composer/Downloader/SvnDownloader.php | 6 +- src/Composer/Downloader/XzDownloader.php | 5 +- src/Composer/Downloader/ZipDownloader.php | 5 +- .../EventDispatcher/EventDispatcher.php | 7 +++ src/Composer/Factory.php | 57 ++++++++++--------- .../Installer/InstallationManager.php | 8 ++- src/Composer/Installer/PluginInstaller.php | 5 +- src/Composer/Installer/ProjectInstaller.php | 4 +- .../Package/Archiver/ArchiveManager.php | 2 + .../Package/Loader/RootPackageLoader.php | 2 +- src/Composer/Package/Locker.php | 4 +- src/Composer/Plugin/PluginManager.php | 8 +++ .../Repository/ComposerRepository.php | 4 +- .../Repository/PlatformRepository.php | 45 +++++++++------ src/Composer/Repository/RepositoryFactory.php | 5 +- src/Composer/Repository/RepositoryManager.php | 17 +++++- src/Composer/Repository/Vcs/GitDriver.php | 1 - src/Composer/Repository/Vcs/HgDriver.php | 4 +- src/Composer/Repository/Vcs/SvnDriver.php | 9 ++- src/Composer/Repository/VcsRepository.php | 4 +- src/Composer/Util/Filesystem.php | 10 +++- .../Test/Downloader/ZipDownloaderTest.php | 12 ++-- tests/Composer/Test/Mock/FactoryMock.php | 3 +- .../Package/Archiver/ArchiveManagerTest.php | 4 +- .../Repository/PlatformRepositoryTest.php | 32 ++++++----- 31 files changed, 211 insertions(+), 118 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 07ff8bed8..b872c557a 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder; use Composer\Json\JsonFile; use Composer\Config\JsonConfigSource; use Composer\Util\Filesystem; +use Composer\Util\ProcessExecutor; use Composer\Util\Loop; use Composer\Package\Version\VersionParser; @@ -186,7 +187,8 @@ EOT $composer = Factory::create($io, null, $disablePlugins); } - $fs = new Filesystem(); + $process = new ProcessExecutor($io); + $fs = new Filesystem($process); if ($noScripts === false) { // dispatch event @@ -307,7 +309,8 @@ EOT $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); } - $fs = new Filesystem(); + $process = new ProcessExecutor($io); + $fs = new Filesystem($fs); if (!$fs->isAbsolutePath($directory)) { $directory = getcwd() . DIRECTORY_SEPARATOR . $directory; } @@ -397,11 +400,11 @@ EOT $factory = new Factory(); $httpDownloader = $factory->createHttpDownloader($io, $config); - $dm = $factory->createDownloadManager($io, $config, $httpDownloader); + $dm = $factory->createDownloadManager($io, $config, $httpDownloader, $process); $dm->setPreferSource($preferSource) ->setPreferDist($preferDist); - $projectInstaller = new ProjectInstaller($directory, $dm); + $projectInstaller = new ProjectInstaller($directory, $dm, $fs); $im = $factory->createInstallationManager(new Loop($httpDownloader), $io); $im->addInstaller($projectInstaller); $im->execute(new InstalledFilesystemRepository(new JsonFile('php://memory')), array(new InstallOperation($package))); diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 45467eb4c..182ce88da 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -25,11 +25,17 @@ use React\Promise\PromiseInterface; */ class DownloadManager { + /** @var IOInterface */ private $io; + /** @var bool */ private $preferDist = false; + /** @var bool */ private $preferSource = false; + /** @var array */ private $packagePreferences = array(); + /** @var Filesystem */ private $filesystem; + /** @var array */ private $downloaders = array(); /** diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index c598efd9a..d7c4bcabe 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -39,16 +39,22 @@ use Composer\Downloader\TransportException; */ class FileDownloader implements DownloaderInterface, ChangeReportInterface { + /** @var IOInterface */ protected $io; + /** @var Config */ protected $config; + /** @var HttpDownloader */ protected $httpDownloader; + /** @var Filesystem */ protected $filesystem; + /** @var Cache */ protected $cache; + /** @var EventDispatcher */ + protected $eventDispatcher; /** * @private this is only public for php 5.3 support in closures */ public $lastCacheWrites = array(); - private $eventDispatcher; /** * Constructor. diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index 91be4593d..0b12b4380 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -20,6 +20,7 @@ use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; +use Composer\Util\Filesystem; /** * GZip archive downloader. @@ -31,10 +32,10 @@ class GzipDownloader extends ArchiveDownloader /** @var ProcessExecutor */ protected $process; - public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $fs = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache, $fs); } protected function extract(PackageInterface $package, $file, $path) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 648c987b2..51e9f7709 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -18,10 +18,15 @@ use Composer\Package\PackageInterface; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; use Composer\Util\Platform; +use Composer\IO\IOInterface; +use Composer\Config; +use Composer\Cache; +use Composer\Util\HttpDownloader; use Composer\Util\ProcessExecutor; -use Composer\Util\Filesystem as ComposerFilesystem; +use Composer\Util\Filesystem; +use Composer\EventDispatcher\EventDispatcher; use Symfony\Component\Filesystem\Exception\IOException; -use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; /** * Download a package from a local path. @@ -34,6 +39,15 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter const STRATEGY_SYMLINK = 10; const STRATEGY_MIRROR = 20; + /** @var ProcessExecutor */ + private $process; + + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $fs = null, ProcessExecutor $process = null) + { + $this->process = $process ?: new ProcessExecutor($io); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache, $fs); + } + /** * {@inheritdoc} */ @@ -115,7 +129,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $allowedStrategies = array(self::STRATEGY_MIRROR); } - $fileSystem = new Filesystem(); + $symfonyFilesystem = new SymfonyFilesystem(); $this->filesystem->removeDirectory($path); if ($output) { @@ -142,9 +156,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $path = rtrim($path, "/"); $this->io->writeError(sprintf('Symlinking from %s', $url), false); if ($transportOptions['relative']) { - $fileSystem->symlink($shortestPath, $path); + $symfonyFilesystem->symlink($shortestPath, $path); } else { - $fileSystem->symlink($realUrl, $path); + $symfonyFilesystem->symlink($realUrl, $path); } } } catch (IOException $e) { @@ -161,12 +175,11 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter // Fallback if symlink failed or if symlink is not allowed for the package if (self::STRATEGY_MIRROR == $currentStrategy) { - $fs = new ComposerFilesystem(); - $realUrl = $fs->normalizePath($realUrl); + $realUrl = $this->filesystem->normalizePath($realUrl); $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? ' ' : '', $url), false); $iterator = new ArchivableFilesFinder($realUrl, array()); - $fileSystem->mirror($realUrl, $path, $iterator); + $symfonyFilesystem->mirror($realUrl, $path, $iterator); } if ($output) { @@ -213,7 +226,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter public function getVcsReference(PackageInterface $package, $path) { $parser = new VersionParser; - $guesser = new VersionGuesser($this->config, new ProcessExecutor($this->io), $parser); + $guesser = new VersionGuesser($this->config, $this->process, $parser); $dumper = new ArrayDumper; $packageConfig = $dumper->dump($package); diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index d0fbadcc6..0ca15de1f 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -19,6 +19,7 @@ use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; +use Composer\Util\Filesystem; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use RarArchive; @@ -35,10 +36,10 @@ class RarDownloader extends ArchiveDownloader /** @var ProcessExecutor */ protected $process; - public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $fs = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache, $fs); } protected function extract(PackageInterface $package, $file, $path) diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 634c4a7d5..f73737a7c 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -65,7 +65,7 @@ class SvnDownloader extends VcsDownloader throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } - $util = new SvnUtil($url, $this->io, $this->config); + $util = new SvnUtil($url, $this->io, $this->config, $this->process); $flags = ""; if (version_compare($util->binaryVersion(), '1.7.0', '>=')) { $flags .= ' --ignore-ancestry'; @@ -103,7 +103,7 @@ class SvnDownloader extends VcsDownloader */ protected function execute(PackageInterface $package, $baseUrl, $command, $url, $cwd = null, $path = null) { - $util = new SvnUtil($baseUrl, $this->io, $this->config); + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose()); @@ -202,7 +202,7 @@ class SvnDownloader extends VcsDownloader $command = sprintf('svn log -r%s:%s --incremental', ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision)); - $util = new SvnUtil($baseUrl, $this->io, $this->config); + $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process); $util->setCacheCredentials($this->cacheCredentials); try { return $util->executeLocal($command, $path, null, $this->io->isVerbose()); diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index 371ceda1b..34956d997 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Util\ProcessExecutor; use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; +use Composer\Util\Filesystem; /** * Xz archive downloader. @@ -31,11 +32,11 @@ class XzDownloader extends ArchiveDownloader /** @var ProcessExecutor */ protected $process; - public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $fs = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache, $fs); } protected function extract(PackageInterface $package, $file, $path) diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 29c7fd82a..d0b4e8255 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Util\IniHelper; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; +use Composer\Util\Filesystem; use Composer\Util\HttpDownloader; use Composer\IO\IOInterface; use Symfony\Component\Process\ExecutableFinder; @@ -38,10 +39,10 @@ class ZipDownloader extends ArchiveDownloader /** @var ZipArchive|null */ private $zipArchiveObject; - public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $downloader, EventDispatcher $eventDispatcher = null, Cache $cache = null, Filesystem $fs = null, ProcessExecutor $process = null) { $this->process = $process ?: new ProcessExecutor($io); - parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); + parent::__construct($io, $config, $downloader, $eventDispatcher, $cache, $fs); } /** diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index 2db26eb5f..3aed66f76 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -28,6 +28,7 @@ use Composer\Installer\PackageEvent; use Composer\Installer\BinaryInstaller; use Composer\Util\ProcessExecutor; use Composer\Script\Event as ScriptEvent; +use Composer\ClassLoader; use Symfony\Component\Process\PhpExecutableFinder; /** @@ -45,11 +46,17 @@ use Symfony\Component\Process\PhpExecutableFinder; */ class EventDispatcher { + /** @var Composer */ protected $composer; + /** @var IOInterface */ protected $io; + /** @var ?ClassLoader */ protected $loader; + /** @var ProcessExecutor */ protected $process; + /** @var array>> */ protected $listeners = array(); + /** @var list */ private $eventStack; /** diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 1b084461c..04da08e31 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -335,15 +335,16 @@ class Factory } $httpDownloader = self::createHttpDownloader($io, $config); + $process = new ProcessExecutor($io); $loop = new Loop($httpDownloader); $composer->setLoop($loop); // initialize event dispatcher - $dispatcher = new EventDispatcher($composer, $io); + $dispatcher = new EventDispatcher($composer, $io, $process); $composer->setEventDispatcher($dispatcher); // initialize repository manager - $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher); + $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process); $composer->setRepositoryManager($rm); // force-set the version of the global package if not defined as @@ -354,7 +355,7 @@ class Factory // load package $parser = new VersionParser; - $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser); + $guesser = new VersionGuesser($config, $process, $parser); $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io); $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd); $composer->setPackage($package); @@ -368,7 +369,7 @@ class Factory if ($fullLoad) { // initialize download manager - $dm = $this->createDownloadManager($io, $config, $httpDownloader, $dispatcher); + $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher); $composer->setDownloadManager($dm); // initialize autoload generator @@ -381,7 +382,7 @@ class Factory } // add installers to the manager (must happen after download manager is created since they read it out of $composer) - $this->createDefaultInstallers($im, $composer, $io); + $this->createDefaultInstallers($im, $composer, $io, $process); if ($fullLoad) { $globalComposer = null; @@ -399,7 +400,7 @@ class Factory if ($fullLoad && isset($composerFile)) { $lockFile = self::getLockFile($composerFile); - $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile)); + $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile), $process); $composer->setLocker($locker); } @@ -460,14 +461,16 @@ class Factory * @param EventDispatcher $eventDispatcher * @return Downloader\DownloadManager */ - public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) + public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, EventDispatcher $eventDispatcher = null) { $cache = null; if ($config->get('cache-files-ttl') > 0) { $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./'); } - $dm = new Downloader\DownloadManager($io); + $fs = new Filesystem($process); + + $dm = new Downloader\DownloadManager($io, false, $fs); switch ($preferred = $config->get('preferred-install')) { case 'dist': $dm->setPreferDist(true); @@ -485,22 +488,19 @@ class Factory $dm->setPreferences($preferred); } - $executor = new ProcessExecutor($io); - $fs = new Filesystem($executor); - - $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $executor, $fs)); - $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $executor, $fs)); - $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs)); - $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs)); - $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config)); - $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); - $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); - $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); - $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); - $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $executor)); - $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); - $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); - $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache)); + $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs)); + $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs)); + $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs)); + $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs)); + $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs)); + $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); + $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs)); + $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs)); + $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process)); return $dm; } @@ -544,10 +544,13 @@ class Factory * @param Composer $composer * @param IO\IOInterface $io */ - protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) + protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io, ProcessExecutor $process = null) { - $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null)); - $im->addInstaller(new Installer\PluginInstaller($io, $composer)); + $fs = new Filesystem($process); + $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs); + + $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller)); + $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller)); $im->addInstaller(new Installer\MetapackageInstaller($io)); } diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 059ea7169..06e8d65b9 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -37,11 +37,17 @@ use React\Promise\PromiseInterface; */ class InstallationManager { + /** @var array */ private $installers = array(); + /** @var array */ private $cache = array(); + /** @var array> */ private $notifiablePackages = array(); + /** @var Loop */ private $loop; + /** @var IOInterface */ private $io; + /** @var EventDispatcher */ private $eventDispatcher; public function __construct(Loop $loop, IOInterface $io, EventDispatcher $eventDispatcher = null) @@ -181,7 +187,7 @@ class InstallationManager foreach ($cleanupPromises as $cleanup) { $promises[] = new \React\Promise\Promise(function ($resolve, $reject) use ($cleanup) { $promise = $cleanup(); - if (null === $promise) { + if (!$promise instanceof PromiseInterface) { $resolve(); } else { $promise->then(function () use ($resolve) { diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 324ef5a99..ae281ab08 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Package\PackageInterface; +use Composer\Util\Filesystem; use React\Promise\PromiseInterface; /** @@ -34,9 +35,9 @@ class PluginInstaller extends LibraryInstaller * @param IOInterface $io * @param Composer $composer */ - public function __construct(IOInterface $io, Composer $composer) + public function __construct(IOInterface $io, Composer $composer, Filesystem $fs = null, BinaryInstaller $binaryInstaller = null) { - parent::__construct($io, $composer, 'composer-plugin'); + parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller); $this->installationManager = $composer->getInstallationManager(); } diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index 2875e0a65..5bae889dc 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -29,11 +29,11 @@ class ProjectInstaller implements InstallerInterface private $downloadManager; private $filesystem; - public function __construct($installPath, DownloadManager $dm) + public function __construct($installPath, DownloadManager $dm, Filesystem $fs) { $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/'; $this->downloadManager = $dm; - $this->filesystem = new Filesystem; + $this->filesystem = $fs; } /** diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index aed8ccf80..c0a75f696 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -25,7 +25,9 @@ use Composer\Json\JsonFile; */ class ArchiveManager { + /** @var DownloadManager */ protected $downloadManager; + /** @var Loop */ protected $loop; /** diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 3dfe3b7d2..ac3d8f433 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -58,7 +58,7 @@ class RootPackageLoader extends ArrayLoader $this->manager = $manager; $this->config = $config; - $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser); + $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser); $this->io = $io; } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 367c5e37b..8cc3d1eb8 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -57,7 +57,7 @@ class Locker * @param InstallationManager $installationManager installation manager instance * @param string $composerFileContents The contents of the composer file */ - public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, $composerFileContents) + public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, $composerFileContents, ProcessExecutor $process = null) { $this->lockFile = $lockFile; $this->installationManager = $installationManager; @@ -65,7 +65,7 @@ class Locker $this->contentHash = self::getContentHash($composerFileContents); $this->loader = new ArrayLoader(null, true); $this->dumper = new ArrayDumper(); - $this->process = new ProcessExecutor($io); + $this->process = $process ?: new ProcessExecutor($io); } /** diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 8a7ca86c8..c910098fe 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -34,15 +34,23 @@ use Composer\Util\PackageSorter; */ class PluginManager { + /** @var Composer */ protected $composer; + /** @var IOInterface */ protected $io; + /** @var Composer */ protected $globalComposer; + /** @var VersionParser */ protected $versionParser; + /** @var bool */ protected $disablePlugins = false; + /** @var array */ protected $plugins = array(); + /** @var array */ protected $registeredPlugins = array(); + /** @var int */ private static $classCounter = 0; /** diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index e960f63be..8848452f2 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -1155,12 +1155,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $retries = 3; if (isset($this->packagesNotFoundCache[$filename])) { - return new Promise(function ($resolve, $reject) { $resolve(array('packages' => array())); }); + return \React\Promise\resolve(array('packages' => array())); } if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) { // make it look like we got a 304 response - return new Promise(function ($resolve, $reject) { $resolve(true); }); + return \React\Promise\resolve(true); } $httpDownloader = $this->httpDownloader; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index e003976a1..c88604a3b 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -30,6 +30,7 @@ class PlatformRepository extends ArrayRepository { const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD'; + private static $hhvmVersion; private $versionParser; /** @@ -45,7 +46,7 @@ class PlatformRepository extends ArrayRepository public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null) { - $this->process = $process === null ? (new ProcessExecutor()) : $process; + $this->process = $process; foreach ($overrides as $name => $version) { $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version); } @@ -251,22 +252,7 @@ class PlatformRepository extends ArrayRepository $this->addPackage($lib); } - $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; - if ($hhvmVersion === null && !Platform::isWindows()) { - $finder = new ExecutableFinder(); - $hhvm = $finder->find('hhvm'); - if ($hhvm !== null) { - $exitCode = $this->process->execute( - ProcessExecutor::escape($hhvm). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - $hhvmVersion - ); - if ($exitCode !== 0) { - $hhvmVersion = null; - } - } - } - if ($hhvmVersion) { + if ($hhvmVersion = self::getHHVMVersion($this->process)) { try { $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); @@ -362,4 +348,29 @@ class PlatformRepository extends ArrayRepository { return 'ext-' . str_replace(' ', '-', $name); } + + private static function getHHVMVersion(ProcessExecutor $process = null) + { + if (null !== self::$hhvmVersion) { + return self::$hhvmVersion ?: null; + } + + self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if (self::$hhvmVersion === null && !Platform::isWindows()) { + self::$hhvmVersion = false; + $finder = new ExecutableFinder(); + $hhvmPath = $finder->find('hhvm'); + if ($hhvmPath !== null) { + $process = $process ?: new ProcessExecutor(); + $exitCode = $process->execute( + ProcessExecutor::escape($hhvmPath). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + self::$hhvmVersion + ); + if ($exitCode !== 0) { + self::$hhvmVersion = false; + } + } + } + } } diff --git a/src/Composer/Repository/RepositoryFactory.php b/src/Composer/Repository/RepositoryFactory.php index 97a8ee957..b36664187 100644 --- a/src/Composer/Repository/RepositoryFactory.php +++ b/src/Composer/Repository/RepositoryFactory.php @@ -17,6 +17,7 @@ use Composer\IO\IOInterface; use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; use Composer\Json\JsonFile; /** @@ -114,9 +115,9 @@ class RepositoryFactory * @param HttpDownloader $httpDownloader * @return RepositoryManager */ - public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) + public static function manager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, ProcessExecutor $process = null) { - $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher); + $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 264115c10..717fce4ee 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -17,6 +17,7 @@ use Composer\Config; use Composer\EventDispatcher\EventDispatcher; use Composer\Package\PackageInterface; use Composer\Util\HttpDownloader; +use Composer\Util\ProcessExecutor; /** * Repositories manager. @@ -27,20 +28,30 @@ use Composer\Util\HttpDownloader; */ class RepositoryManager { + /** @var InstalledRepositoryInterface */ private $localRepository; + /** @var list */ private $repositories = array(); + /** @var array */ private $repositoryClasses = array(); + /** @var IOInterface */ private $io; + /** @var Config */ private $config; - private $eventDispatcher; + /** @var HttpDownloader */ private $httpDownloader; + /** @var ?EventDispatcher */ + private $eventDispatcher; + /** @var ProcessExecutor */ + private $process; - public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null) + public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $eventDispatcher = null, ProcessExecutor $process = null) { $this->io = $io; $this->config = $config; $this->httpDownloader = $httpDownloader; $this->eventDispatcher = $eventDispatcher; + $this->process = $process ?: new ProcessExecutor($io); } /** @@ -130,7 +141,7 @@ class RepositoryManager unset($config['only'], $config['exclude'], $config['canonical']); } - $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher); + $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process); if (isset($filterConfig)) { $repository = new FilterRepository($repository, $filterConfig); diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 1b96c4cde..faba0fd1e 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -223,7 +223,6 @@ class GitDriver extends VcsDriver } $process = new ProcessExecutor($io); - return $process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url), $output) === 0; } } diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 04a363442..62e3e4a05 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -228,8 +228,8 @@ class HgDriver extends VcsDriver return false; } - $processExecutor = new ProcessExecutor($io); - $exit = $processExecutor->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored); + $process = new ProcessExecutor($io); + $exit = $process->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored); return $exit === 0; } diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index a8f0c4ad4..097103487 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/src/Composer/Repository/Vcs/SvnDriver.php @@ -307,9 +307,8 @@ class SvnDriver extends VcsDriver return false; } - $processExecutor = new ProcessExecutor($io); - - $exit = $processExecutor->execute( + $process = new ProcessExecutor($io); + $exit = $process->execute( "svn info --non-interactive ".ProcessExecutor::escape($url), $ignoredOutput ); @@ -320,14 +319,14 @@ class SvnDriver extends VcsDriver } // Subversion client 1.7 and older - if (false !== stripos($processExecutor->getErrorOutput(), 'authorization failed:')) { + if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) { // This is likely a remote Subversion repository that requires // authentication. We will handle actual authentication later. return true; } // Subversion client 1.8 and newer - if (false !== stripos($processExecutor->getErrorOutput(), 'Authentication failed')) { + if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) { // This is likely a remote Subversion or newer repository that requires // authentication. We will handle actual authentication later. return true; diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 95bb422d2..1351c4694 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -53,7 +53,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt private $emptyReferences = array(); private $versionTransportExceptions = array(); - public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, EventDispatcher $dispatcher = null, ProcessExecutor $process = null, array $drivers = null, VersionCacheInterface $versionCache = null) { parent::__construct(); $this->drivers = $drivers ?: array( @@ -78,7 +78,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; $this->httpDownloader = $httpDownloader; - $this->processExecutor = new ProcessExecutor($io); + $this->processExecutor = $process ?: new ProcessExecutor($io); } public function getRepoName() diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index fd7ded57e..d5d818556 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -28,7 +28,7 @@ class Filesystem public function __construct(ProcessExecutor $executor = null) { - $this->processExecutor = $executor ?: new ProcessExecutor(); + $this->processExecutor = $executor; } public function remove($file) @@ -320,7 +320,7 @@ class Filesystem if (Platform::isWindows()) { // Try to copy & delete - this is a workaround for random "Access denied" errors. $command = sprintf('xcopy %s %s /E /I /Q /Y', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); - $result = $this->processExecutor->execute($command, $output); + $result = $this->getProcess()->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); @@ -334,7 +334,7 @@ class Filesystem // We do not use PHP's "rename" function here since it does not support // the case where $source, and $target are located on different partitions. $command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target)); - $result = $this->processExecutor->execute($command, $output); + $result = $this->getProcess()->execute($command, $output); // clear stat cache because external processes aren't tracked by the php stat cache clearstatcache(); @@ -546,6 +546,10 @@ class Filesystem */ protected function getProcess() { + if (!$this->processExecutor) { + $this->processExecutor = new ProcessExecutor(); + } + return $this->processExecutor; } diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index e3bbe45a8..4436c6ad7 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -194,7 +194,7 @@ class ZipDownloaderTest extends TestCase ->method('execute') ->will($this->returnValue(1)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } @@ -211,7 +211,7 @@ class ZipDownloaderTest extends TestCase ->method('execute') ->will($this->returnValue(0)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } @@ -238,7 +238,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(true)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } @@ -270,7 +270,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } @@ -298,7 +298,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } @@ -330,7 +330,7 @@ class ZipDownloaderTest extends TestCase ->method('extractTo') ->will($this->returnValue(false)); - $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); + $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, null, $processExecutor); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir'); } diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index 608d572dd..a073f0ab7 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -23,6 +23,7 @@ use Composer\EventDispatcher\EventDispatcher; use Composer\IO\IOInterface; use Composer\Test\TestCase; use Composer\Util\Loop; +use Composer\Util\ProcessExecutor; class FactoryMock extends Factory { @@ -47,7 +48,7 @@ class FactoryMock extends Factory return new InstallationManagerMock(); } - protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) + protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io, ProcessExecutor $process = null) { } diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index 45a635437..9204e6fa6 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -18,6 +18,7 @@ use Composer\Package\Archiver\ArchiveManager; use Composer\Package\PackageInterface; use Composer\Util\Loop; use Composer\Test\Mock\FactoryMock; +use Composer\Util\ProcessExecutor; class ArchiveManagerTest extends ArchiverTest { @@ -36,7 +37,8 @@ class ArchiveManagerTest extends ArchiverTest $dm = $factory->createDownloadManager( $io = new NullIO, $config = FactoryMock::createConfig(), - $httpDownloader = $factory->createHttpDownloader($io, $config) + $httpDownloader = $factory->createHttpDownloader($io, $config), + new ProcessExecutor($io) ); $loop = new Loop($httpDownloader); $this->manager = $factory->createArchiveManager($factory->createConfig(), $dm, $loop); diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index aa51a2fc6..519aadb31 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -14,11 +14,15 @@ namespace Composer\Test\Repository; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; +use Composer\Util\ProcessExecutor; +use Composer\Package\Version\VersionParser; use Composer\Util\Platform; use Symfony\Component\Process\ExecutableFinder; -class PlatformRepositoryTest extends TestCase { - public function testHHVMVersionWhenExecutingInHHVM() { +class PlatformRepositoryTest extends TestCase +{ + public function testHHVMVersionWhenExecutingInHHVM() + { if (!defined('HHVM_VERSION_ID')) { $this->markTestSkipped('Not running with HHVM'); return; @@ -36,7 +40,8 @@ class PlatformRepositoryTest extends TestCase { ); } - public function testHHVMVersionWhenExecutingInPHP() { + public function testHHVMVersionWhenExecutingInPHP() + { if (defined('HHVM_VERSION_ID')) { $this->markTestSkipped('Running with HHVM'); return; @@ -54,17 +59,18 @@ class PlatformRepositoryTest extends TestCase { if ($hhvm === null) { $this->markTestSkipped('HHVM is not installed'); } - $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $process->expects($this->once())->method('execute')->will($this->returnCallback( - function($command, &$out) { - $this->assertContains('HHVM_VERSION', $command); - $out = '4.0.1-dev'; - return 0; - } - )); - $repository = new PlatformRepository(array(), array(), $process); + $repository = new PlatformRepository(array(), array()); $package = $repository->findPackage('hhvm', '*'); $this->assertNotNull($package, 'failed to find HHVM package'); - $this->assertSame('4.0.1.0-dev', $package->getVersion()); + + $process = new ProcessExecutor(); + $exitCode = $process->execute( + ProcessExecutor::escape($hhvm). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + $version + ); + $parser = new VersionParser; + + $this->assertSame($parser->normalize($version), $package->getVersion()); } } From c9571f90b4852879b6edf75704ef65cad1125428 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 4 Jun 2020 16:03:58 +0200 Subject: [PATCH 3/4] Run phpstan with regular output and then run again to cs2pr if there was an error, to keep usable output in CI logs --- .github/workflows/phpstan.yml | 2 +- phpstan/config.neon | 6 ------ src/Composer/Command/CreateProjectCommand.php | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 967f79dcd..87cd149eb 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -53,4 +53,4 @@ jobs: - name: Run PHPStan run: | bin/composer require --dev phpstan/phpstan:^0.12 phpunit/phpunit:^7.5 --with-all-dependencies - vendor/bin/phpstan analyse --configuration=phpstan/config.neon --error-format=checkstyle | cs2pr + vendor/bin/phpstan analyse --configuration=phpstan/config.neon || vendor/bin/phpstan analyse --configuration=phpstan/config.neon --error-format=checkstyle | cs2pr diff --git a/phpstan/config.neon b/phpstan/config.neon index 8b8bf62ce..d9bf7f8c6 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -27,12 +27,6 @@ parameters: # BC with older PHPUnit - '~^Call to an undefined static method PHPUnit\\Framework\\TestCase::setExpectedException\(\)\.$~' - - # hhvm should have support for $this in closures - - - count: 1 - message: '~^Using \$this inside anonymous function is prohibited because of PHP 5\.3 support\.$~' - path: '../tests/Composer/Test/Repository/PlatformRepositoryTest.php' paths: - ../src - ../tests diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index b872c557a..de6701ce4 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -310,7 +310,7 @@ EOT } $process = new ProcessExecutor($io); - $fs = new Filesystem($fs); + $fs = new Filesystem($process); if (!$fs->isAbsolutePath($directory)) { $directory = getcwd() . DIRECTORY_SEPARATOR . $directory; } From 90425a6a50558283a216ba956392426f7e66ced3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 5 Jun 2020 10:36:40 +0200 Subject: [PATCH 4/4] Add upgrade note for custom installers --- UPGRADE-2.0.md | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index 33729371a..2b9ccf24a 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -17,6 +17,7 @@ - `PluginInterface` added a deactivate (so plugin can stop whatever it is doing) and an uninstall (so the plugin can remove any files it created or do general cleanup) method. - Plugins implementing `EventSubscriberInterface` will be deregistered from the EventDispatcher automatically when being deactivated, nothing to do there. - `Pool` objects are now created via the `RepositorySet` class, you should use that in case you were using the `Pool` class directly. +- Custom installers extending from LibraryInstaller should be aware that in Composer 2 it MAY return PromiseInterface instances when calling parent::install/update/uninstall/installCode/removeCode. See [composer/installers](https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb) for an example of how to handle this best. - The `Composer\Installer` class changed quite a bit internally, but the inputs are almost the same: - `setAdditionalInstalledRepository` is now `setAdditionalFixedRepository` - `setUpdateWhitelist` is now `setUpdateAllowList`