From c6bbf5bfec48588aab0b60c996b28b2e552b2ef5 Mon Sep 17 00:00:00 2001 From: Matthew Grasmick Date: Mon, 5 Dec 2016 19:57:30 -0500 Subject: [PATCH 1/4] Adding warning when inexact version constraints are used. --- src/Patches.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Patches.php b/src/Patches.php index d1c1cf05..6d9dc9e3 100644 --- a/src/Patches.php +++ b/src/Patches.php @@ -285,8 +285,22 @@ public function postInstall(PackageEvent $event) { $extra = $localPackage->getExtra(); $extra['patches_applied'] = array(); + // Gather all packages defined in root composer.json into a single array for version constraint access. + $root_requires = $this->composer->getPackage()->getRequires(); + $root_dev_requires = $this->composer->getPackage()->getDevRequires(); + $root_packages = array_merge($root_requires, $root_dev_requires); + foreach ($this->patches[$package_name] as $description => $url) { $this->io->write(' ' . $url . ' (' . $description. ')'); + + if (!empty($root_packages[$package_name])) { + // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. + $version_constraint = $root_packages[$package_name]; + if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { + $this->io->write("You are patching $package_name with an inexact version constraint, which may cause a patch failure now or in the future when the package is changed."); + } + } + try { $this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::PRE_PATCH_APPLY, $package, $url, $description)); $this->getAndApplyPatch($downloader, $install_path, $url); From 6baa0e95c2618ffa48607f2bd718b6bf3d6751a4 Mon Sep 17 00:00:00 2001 From: Matthew Grasmick Date: Mon, 5 Dec 2016 21:16:02 -0500 Subject: [PATCH 2/4] Fixing version constraint check. --- src/Patches.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Patches.php b/src/Patches.php index 6d9dc9e3..04488776 100644 --- a/src/Patches.php +++ b/src/Patches.php @@ -21,6 +21,7 @@ use Composer\Script\Event; use Composer\Script\ScriptEvents; use Composer\Installer\PackageEvent; +use Composer\Semver\Constraint\MultiConstraint; use Composer\Util\ProcessExecutor; use Composer\Util\RemoteFilesystem; use Symfony\Component\Process\Process; @@ -295,9 +296,11 @@ public function postInstall(PackageEvent $event) { if (!empty($root_packages[$package_name])) { // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. - $version_constraint = $root_packages[$package_name]; + /** @var MultiConstraint $link */ + $link = $root_packages[$package_name]->getConstraint(); + $version_constraint = $link->getPrettyString(); if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { - $this->io->write("You are patching $package_name with an inexact version constraint, which may cause a patch failure now or in the future when the package is changed."); + $this->io->write(" $package_name has inexact version constraint. This may cause a patch failure now or in the future when the package is changed."); } } From d6a9013bc4e08a471e21b1d162c00005f1412055 Mon Sep 17 00:00:00 2001 From: Matthew Grasmick Date: Mon, 5 Dec 2016 21:29:17 -0500 Subject: [PATCH 3/4] Removing duplicate warnings. --- src/Patches.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Patches.php b/src/Patches.php index 04488776..77965aa7 100644 --- a/src/Patches.php +++ b/src/Patches.php @@ -272,6 +272,21 @@ public function postInstall(PackageEvent $event) { return; } $this->io->write(' - Applying patches for ' . $package_name . ''); + + // Gather all packages defined in root composer.json into a single array for version constraint access. + $root_requires = $this->composer->getPackage()->getRequires(); + $root_dev_requires = $this->composer->getPackage()->getDevRequires(); + $root_packages = array_merge($root_requires, $root_dev_requires); + + if (!empty($root_packages[$package_name])) { + // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. + /** @var MultiConstraint $link */ + $link = $root_packages[$package_name]->getConstraint(); + $version_constraint = $link->getPrettyString(); + if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { + $this->io->write(" $package_name has inexact version constraint. This may cause a patch failure now or in the future when the package is changed."); + } + } // Get the install path from the package object. $manager = $event->getComposer()->getInstallationManager(); @@ -286,23 +301,8 @@ public function postInstall(PackageEvent $event) { $extra = $localPackage->getExtra(); $extra['patches_applied'] = array(); - // Gather all packages defined in root composer.json into a single array for version constraint access. - $root_requires = $this->composer->getPackage()->getRequires(); - $root_dev_requires = $this->composer->getPackage()->getDevRequires(); - $root_packages = array_merge($root_requires, $root_dev_requires); - foreach ($this->patches[$package_name] as $description => $url) { $this->io->write(' ' . $url . ' (' . $description. ')'); - - if (!empty($root_packages[$package_name])) { - // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. - /** @var MultiConstraint $link */ - $link = $root_packages[$package_name]->getConstraint(); - $version_constraint = $link->getPrettyString(); - if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { - $this->io->write(" $package_name has inexact version constraint. This may cause a patch failure now or in the future when the package is changed."); - } - } try { $this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::PRE_PATCH_APPLY, $package, $url, $description)); From 3d538c14431434f837804cbca68a1958a031dfa6 Mon Sep 17 00:00:00 2001 From: Matthew Grasmick Date: Tue, 6 Dec 2016 14:12:52 -0500 Subject: [PATCH 4/4] Displaying warning only when inexact-constraint-warning is true. --- README.md | 12 ++++++++++++ src/Patches.php | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 75ee9c34..639d9318 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,18 @@ Composer [blocks](https://getcomposer.org/doc/06-config.md#secure-http) you from However, it's always advised to setup HTTPS to prevent MITM code injection. +## Version constraints + +Typically, Composer best practices dictate that exact versions (e.g., 8.1.1) be avoided. However, using an inexact version constraint (e.g., ^8.1) can cause patching failures when the upstream package is updated. You may optionally display a warning regarding the use of inexact version constraints by using the following configuration in composer.json: + +``` + "extra": { + "inexact-constraint-warning": true + } +``` + +This will be displayed during `composer update`. + ## Error handling If a patch cannot be applied (hunk failed, different line endings, etc.) a message will be shown and the patch will be skipped. diff --git a/src/Patches.php b/src/Patches.php index 77965aa7..c5a1811b 100644 --- a/src/Patches.php +++ b/src/Patches.php @@ -15,6 +15,7 @@ use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Package\AliasPackage; +use Composer\Package\Package; use Composer\Package\PackageInterface; use Composer\Plugin\PluginInterface; use Composer\Installer\PackageEvents; @@ -272,20 +273,10 @@ public function postInstall(PackageEvent $event) { return; } $this->io->write(' - Applying patches for ' . $package_name . ''); - - // Gather all packages defined in root composer.json into a single array for version constraint access. - $root_requires = $this->composer->getPackage()->getRequires(); - $root_dev_requires = $this->composer->getPackage()->getDevRequires(); - $root_packages = array_merge($root_requires, $root_dev_requires); - if (!empty($root_packages[$package_name])) { - // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. - /** @var MultiConstraint $link */ - $link = $root_packages[$package_name]->getConstraint(); - $version_constraint = $link->getPrettyString(); - if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { - $this->io->write(" $package_name has inexact version constraint. This may cause a patch failure now or in the future when the package is changed."); - } + $extra = $this->composer->getPackage()->getExtra(); + if (!empty($extra['inexact-constraint-warning'])) { + $this->displayConstraintWarning($package); } // Get the install path from the package object. @@ -323,6 +314,30 @@ public function postInstall(PackageEvent $event) { $this->writePatchReport($this->patches[$package_name], $install_path); } + /** + * Displays a warning if the package's version constraint is inexact. + * + * @param Composer\Package $package + * The package for which to display the warning. + */ + protected function displayConstraintWarning($package) { + // Gather all packages defined in root composer.json into a single array for version constraint access. + $root_requires = $this->composer->getPackage()->getRequires(); + $root_dev_requires = $this->composer->getPackage()->getDevRequires(); + $root_packages = array_merge($root_requires, $root_dev_requires); + $package_name = $package->getName(); + + if (!empty($root_packages[$package_name])) { + // If ^, ~, or * operators are being used, or this is a dev version without a hash specified, display warning. + /** @var MultiConstraint $link */ + $link = $root_packages[$package_name]->getConstraint(); + $version_constraint = $link->getPrettyString(); + if (preg_match('/[\^~*]|(-dev)|(dev-)/', $version_constraint) && !strstr($version_constraint, '#')) { + $this->io->write(" $package_name has inexact version constraint. This may cause a patch failure now or in the future when the package is changed."); + } + } + } + /** * Get a Package object from an OperationInterface object. *