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 d1c1cf05..c5a1811b 100644 --- a/src/Patches.php +++ b/src/Patches.php @@ -15,12 +15,14 @@ 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; 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; @@ -272,6 +274,11 @@ public function postInstall(PackageEvent $event) { } $this->io->write(' - Applying patches for ' . $package_name . ''); + $extra = $this->composer->getPackage()->getExtra(); + if (!empty($extra['inexact-constraint-warning'])) { + $this->displayConstraintWarning($package); + } + // Get the install path from the package object. $manager = $event->getComposer()->getInstallationManager(); $install_path = $manager->getInstaller($package->getType())->getInstallPath($package); @@ -287,6 +294,7 @@ public function postInstall(PackageEvent $event) { foreach ($this->patches[$package_name] as $description => $url) { $this->io->write(' ' . $url . ' (' . $description. ')'); + try { $this->eventDispatcher->dispatch(NULL, new PatchEvent(PatchEvents::PRE_PATCH_APPLY, $package, $url, $description)); $this->getAndApplyPatch($downloader, $install_path, $url); @@ -306,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. *