From b451bcb1ac28a5741d170b3a56b5d6519976a823 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 11:53:52 +0100 Subject: [PATCH 1/7] Fix issue extracting archives into paths that already exist, fixes composer/installers#479 --- src/Composer/Downloader/ArchiveDownloader.php | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 5bfd5cb18..d8fd0bd86 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -109,6 +109,34 @@ abstract class ArchiveDownloader extends FileDownloader return iterator_to_array($finder); }; + $renameRecursively = null; + /** + * Renames (and recursively merges if needed) a folder into another one + * + * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure + * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would + * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/ + * + * @param string $from Directory + * @param string $to Directory + * @return void + */ + $renameRecursively = function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) { + $contentDir = $getFolderContent($from); + + // move files back out of the temp dir + foreach ($contentDir as $file) { + $file = (string) $file; + if (is_dir($to . '/' . basename($file))) { + if (!is_dir($file)) { + throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.'); + } + $renameRecursively($file, $to . '/' . basename($file)); + } else { + $filesystem->rename($file, $to . '/' . basename($file)); + } + } + }; $renameAsOne = false; if (!file_exists($path) || ($filesystem->isDirEmpty($path) && $filesystem->removeDirectory($path))) { @@ -128,15 +156,12 @@ abstract class ArchiveDownloader extends FileDownloader $filesystem->rename($extractedDir, $path); } else { // only one dir in the archive, extract its contents out of it + $from = $temporaryDir; if ($singleDirAtTopLevel) { - $contentDir = $getFolderContent((string) reset($contentDir)); + $from = (string) reset($contentDir); } - // move files back out of the temp dir - foreach ($contentDir as $file) { - $file = (string) $file; - $filesystem->rename($file, $path . '/' . basename($file)); - } + $renameRecursively($from, $path); } $filesystem->removeDirectory($temporaryDir); From e9d405ff18f33da06e858c9e09c6faccf6390d9a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 12:08:38 +0100 Subject: [PATCH 2/7] Avoid using str_replace for dev-master replacement as that may be a valid part of a branch name, fixes #9739 --- src/Composer/Package/Version/VersionParser.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 88b1873cf..e2b70b959 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -76,8 +76,12 @@ class VersionParser extends SemverVersionParser return true; } - $normalizedFrom = str_replace(array('dev-master', 'dev-trunk', 'dev-default'), VersionParser::DEFAULT_BRANCH_ALIAS, $normalizedFrom); - $normalizedTo = str_replace(array('dev-master', 'dev-trunk', 'dev-default'), VersionParser::DEFAULT_BRANCH_ALIAS, $normalizedTo); + if (in_array($normalizedFrom, array('dev-master', 'dev-trunk', 'dev-default'), true)) { + $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS; + } + if (in_array($normalizedTo, array('dev-master', 'dev-trunk', 'dev-default'), true)) { + $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS; + } if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) { return true; From 4bedd8379a5cc3e9e3e02b31ec70d1279a2b7bc4 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 13:57:41 +0100 Subject: [PATCH 3/7] Fix php-proxying of binaries to avoid proxying phar files, fixes #9742 --- src/Composer/Installer/BinaryInstaller.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Composer/Installer/BinaryInstaller.php b/src/Composer/Installer/BinaryInstaller.php index 3ffbb50b3..47022ff42 100644 --- a/src/Composer/Installer/BinaryInstaller.php +++ b/src/Composer/Installer/BinaryInstaller.php @@ -192,14 +192,18 @@ class BinaryInstaller $binFile = basename($binPath); $binContents = file_get_contents($bin); + // For php files, we generate a PHP proxy instead of a shell one, + // which allows calling the proxy with a custom php process if (preg_match('{^(?:#!(?:/usr)?/bin/env php|#!(?:/usr)?/bin/php| Date: Tue, 9 Mar 2021 14:13:19 +0100 Subject: [PATCH 4/7] Make sure that single files installed via file downloader get the executable bit set if they are a binary file, refs #9742 --- src/Composer/Downloader/FileDownloader.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index a99ea79a0..06c080374 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -27,6 +27,7 @@ use Composer\Plugin\PostFileDownloadEvent; use Composer\Plugin\PreFileDownloadEvent; use Composer\EventDispatcher\EventDispatcher; use Composer\Util\Filesystem; +use Composer\Util\Silencer; use Composer\Util\HttpDownloader; use Composer\Util\Url as UrlUtil; use Composer\Util\ProcessExecutor; @@ -313,6 +314,16 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->filesystem->emptyDirectory($path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); + + if ($package->getBinaries()) { + // Single files can not have a mode set like files in archives + // so we make sure if the file is a binary that it is executable + foreach ($package->getBinaries() as $bin) { + if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) { + Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask()); + } + } + } } /** From de8a73701546a3a9e75ddda0c8a863f0e284cd6d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 14:13:28 +0100 Subject: [PATCH 5/7] Fix var shadowing --- src/Composer/Repository/ComposerRepository.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 441e90c20..b4ec499d4 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -1053,9 +1053,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito } } - $packages = $this->loader->loadPackages($packages, 'Composer\Package\CompletePackage'); + $packageInstances = $this->loader->loadPackages($packages, 'Composer\Package\CompletePackage'); - foreach ($packages as $package) { + foreach ($packageInstances as $package) { if (isset($this->sourceMirrors[$package->getSourceType()])) { $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]); } @@ -1063,7 +1063,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->configurePackageTransportOptions($package); } - return $packages; + return $packageInstances; } catch (\Exception $e) { throw new \RuntimeException('Could not load packages '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e); } From ab84916422fcfa565b5e6edb5d257c470ecc470a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 15:01:23 +0100 Subject: [PATCH 6/7] Fix unclear error when a package can be found in lock but not in the remote repo, fixes #9750 --- src/Composer/DependencyResolver/Problem.php | 9 ++- .../installer/alias-solver-problems2.test | 2 +- ...ncies-option-dont-recommend-to-use-it.test | 2 +- ...sent-in-lock-but-not-at-all-in-remote.test | 52 +++++++++++++++++ ...ut-not-in-remote-due-to-min-stability.test | 54 ++++++++++++++++++ ...age-present-in-lock-but-not-in-remote.test | 53 +++++++++++++++++ ...rio-but-not-main-due-to-min-stability.test | 57 +++++++++++++++++++ 7 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test create mode 100644 tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test create mode 100644 tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test create mode 100644 tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index afa1b51b7..c090a34d8 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -268,7 +268,7 @@ class Problem }); if (!$nonLockedPackages) { - return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file.'); + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.'); } return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.'); @@ -404,6 +404,13 @@ class Problem } } + if ($nextRepo instanceof LockArrayRepository) { + $singular = count($higherRepoPackages) === 1; + + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', + 'found '.self::getPackageList($nextRepoPackages, $isVerbose).' in the lock file and '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.'); + } + return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.'); } diff --git a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test index 8f21a285a..3e37004d5 100644 --- a/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test +++ b/tests/Composer/Test/Fixtures/installer/alias-solver-problems2.test @@ -43,7 +43,7 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file. + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. - locked/pkg is locked to version dev-master and an update of this package was not requested. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. diff --git a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test index d1003fd9d..1f5120880 100644 --- a/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test +++ b/tests/Composer/Test/Fixtures/installer/conflict-with-all-dependencies-option-dont-recommend-to-use-it.test @@ -43,7 +43,7 @@ Updating dependencies Your requirements could not be resolved to an installable set of packages. Problem 1 - - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file. + - locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. - locked/pkg is locked to version dev-master and an update of this package was not requested. --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test new file mode 100644 index 000000000..c98d34dc6 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-at-all-in-remote.test @@ -0,0 +1,52 @@ +--TEST-- +Update package which is in lock file but not in remote repo at all should show this error correctly +--COMPOSER-- +{ + "minimum-stability": "dev", + "repositories": [ + {"type": "package", "package": [ + {"name": "main/dep", "version": "1.0.0", "require": {"locked/dep": "^2.1"}} + ]} + ], + "require": { + "main/dep": "*" + } +} +--LOCK-- +{ + "packages": [ + { + "name": "main/dep", "version": "1.0.0", + "require": {"locked/dep": "^2.1"}, + "type": "library" + }, + { + "name": "locked/dep", "version": "2.1.0", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update main/dep --with-all-dependencies + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires main/dep * -> satisfiable by main/dep[1.0.0]. + - main/dep 1.0.0 requires locked/dep ^2.1 -> found locked/dep[2.1.0] in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file. + +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test new file mode 100644 index 000000000..0c66d1dc3 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote-due-to-min-stability.test @@ -0,0 +1,54 @@ +--TEST-- +Update package which is in lock file but not remote repo due to min-stability should show this error correctly +--COMPOSER-- +{ + "minimum-stability": "stable", + "repositories": [ + {"type": "package", "package": [ + {"name": "main/dep", "version": "1.0.0", "require": {"locked/dep": "^2.1"}}, + {"name": "locked/dep", "version": "2.x-dev"}, + {"name": "locked/dep", "version": "2.0.5"} + ]} + ], + "require": { + "main/dep": "*" + } +} +--LOCK-- +{ + "packages": [ + { + "name": "main/dep", "version": "1.0.0", + "require": {"locked/dep": "^2.1"}, + "type": "library" + }, + { + "name": "locked/dep", "version": "2.1.0", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update main/dep --with-all-dependencies + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires main/dep * -> satisfiable by main/dep[1.0.0]. + - main/dep 1.0.0 requires locked/dep ^2.1 -> found locked/dep[2.1.0] in the lock file and locked/dep[2.x-dev] from package repo (defining 3 packages) but it does not match your minimum-stability and is therefore not installable. Make sure you either fix the minimum-stability or avoid updating this package to keep the one from the lock file. + +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test new file mode 100644 index 000000000..0d1e680e7 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lock-but-not-in-remote.test @@ -0,0 +1,53 @@ +--TEST-- +Update package which is in lock file but not in remote repo in the correct version should show this error correctly +--COMPOSER-- +{ + "minimum-stability": "dev", + "repositories": [ + {"type": "package", "package": [ + {"name": "main/dep", "version": "1.0.0", "require": {"locked/dep": "^2.1"}}, + {"name": "locked/dep", "version": "2.0.5"} + ]} + ], + "require": { + "main/dep": "*" + } +} +--LOCK-- +{ + "packages": [ + { + "name": "main/dep", "version": "1.0.0", + "require": {"locked/dep": "^2.1"}, + "type": "library" + }, + { + "name": "locked/dep", "version": "2.1.0", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update main/dep --with-all-dependencies + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires main/dep * -> satisfiable by main/dep[1.0.0]. + - main/dep 1.0.0 requires locked/dep ^2.1 -> found locked/dep[2.1.0] in the lock file and locked/dep[2.0.5] from package repo (defining 2 packages) but it does not match your constraint and is therefore not installable. Make sure you either fix the constraint or avoid updating this package to keep the one from the lock file. + +--EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test new file mode 100644 index 000000000..2ccd48e80 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-package-present-in-lower-repo-prio-but-not-main-due-to-min-stability.test @@ -0,0 +1,57 @@ +--TEST-- +Update package which is in lower prio repo but not main repo due to min-stability should show this error correctly +--COMPOSER-- +{ + "minimum-stability": "stable", + "repositories": [ + {"type": "package", "package": [ + {"name": "main/dep", "version": "1.0.0", "require": {"lower/dep": "^2.1"}}, + {"name": "lower/dep", "version": "2.x-dev"}, + {"name": "lower/dep", "version": "2.0.5"} + ]}, + {"type": "package", "package": [ + {"name": "lower/dep", "version": "2.1.0"} + ]} + ], + "require": { + "main/dep": "*" + } +} +--LOCK-- +{ + "packages": [ + { + "name": "main/dep", "version": "1.0.0", + "require": {"lower/dep": "^2.1"}, + "type": "library" + }, + { + "name": "lower/dep", "version": "2.1.0", + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update main/dep --with-all-dependencies + +--EXPECT-EXIT-CODE-- +2 + +--EXPECT-OUTPUT-- +Loading composer repositories with package information +Updating dependencies +Your requirements could not be resolved to an installable set of packages. + + Problem 1 + - Root composer.json requires main/dep * -> satisfiable by main/dep[1.0.0]. + - main/dep 1.0.0 requires lower/dep ^2.1 -> satisfiable by lower/dep[2.1.0] from package repo (defining 1 package) but lower/dep[2.x-dev] from package repo (defining 3 packages) has higher repository priority. The packages with higher priority do not match your minimum-stability and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance. + +--EXPECT-- From 06003f4da686acbf2df237cd77985516339242b6 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 9 Mar 2021 15:27:35 +0100 Subject: [PATCH 7/7] Update release step to use php8 as it produces slightly different output wrt white-space, fixes #9746 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7255735d8..e2631ed1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: coverage: "none" extensions: "intl" ini-values: "memory_limit=-1" - php-version: "7.4" + php-version: "8.0" - name: "Install dependencies from composer.lock using composer binary provided by system" run: "composer install ${{ env.COMPOSER_FLAGS }}"