Browse Source

Merge pull request #8684 from naderman/dev-require-errors

Handle dev extraction exit codes instead of completing broken lock with errors
Jordi Boggiano 5 years ago
parent
commit
d09daa8d5a

+ 3 - 2
src/Composer/DependencyResolver/Problem.php

@@ -251,12 +251,13 @@ class Problem
         if ($providers = $repositorySet->getProviders($packageName)) {
             $maxProviders = 20;
             $providersStr = implode(array_map(function ($p) {
-                return "      - ${p['name']} ".substr($p['description'], 0, 100)."\n";
+                $description = $p['description'] ? ' '.substr($p['description'], 0, 100) : '';
+                return "      - ${p['name']}".$description."\n";
             }, count($providers) > $maxProviders+1 ? array_slice($providers, 0, $maxProviders) : $providers));
             if (count($providers) > $maxProviders+1) {
                 $providersStr .= '      ... and '.(count($providers)-$maxProviders).' more.'."\n";
             }
-            return array("- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it: \n".$providersStr."      Consider requiring one of these to satisfy the $packageName requirement.");
+            return array("- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr."      Consider requiring one of these to satisfy the $packageName requirement.");
         }
 
         return array("- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name.");

+ 2 - 2
src/Composer/DependencyResolver/SolverProblemsException.php

@@ -31,7 +31,7 @@ class SolverProblemsException extends \RuntimeException
         parent::__construct('Failed resolving dependencies with '.count($problems).' problems, call getPrettyString to get formatted details', 2);
     }
 
-    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool)
+    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, $isDevExtraction = false)
     {
         $installedMap = $request->getPresentMap(true);
         $text = "\n";
@@ -44,7 +44,7 @@ class SolverProblemsException extends \RuntimeException
             }
         }
 
-        if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
+        if (!$isDevExtraction && (strpos($text, 'could not be found') || strpos($text, 'no matching package found'))) {
             $text .= "\nPotential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
         }
 

+ 11 - 4
src/Composer/Installer.php

@@ -429,7 +429,10 @@ class Installer
             $this->io->writeError('Nothing to modify in lock file');
         }
 
-        $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy);
+        $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy);
+        if ($exitCode !== 0) {
+            return $exitCode;
+        }
 
         // write lock
         $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
@@ -542,7 +545,7 @@ class Installer
     protected function extractDevPackages(LockTransaction $lockTransaction, $platformRepo, $aliases, $policy)
     {
         if (!$this->package->getDevRequires()) {
-            return array();
+            return 0;
         }
 
         $resultRepo = new ArrayRepository(array());
@@ -562,7 +565,7 @@ class Installer
             $request->requireName($link->getTarget(), $link->getConstraint());
         }
 
-        $pool = $repositorySet->createPool($request, $this->eventDispatcher);
+        $pool = $repositorySet->createPoolWithAllPackages();
 
         $solver = new Solver($policy, $pool, $this->io, $repositorySet);
         try {
@@ -570,12 +573,16 @@ class Installer
             $solver = null;
         } catch (SolverProblemsException $e) {
             $this->io->writeError('<error>Unable to find a compatible set of packages based on your non-dev requirements alone.</error>', true, IOInterface::QUIET);
-            $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool));
+            $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.');
+            $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.');
+            $this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, true));
 
             return max(1, $e->getCode());
         }
 
         $lockTransaction->setNonDevPackages($nonDevLockTransaction);
+
+        return 0;
     }
 
     /**

+ 2 - 0
src/Composer/Repository/ComposerRepository.php

@@ -422,11 +422,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
     public function getProviders($packageName)
     {
         if (!$this->providersApiUrl) {
+            // TODO should this return the info based on getPackages in other cases?
             return array();
         }
 
         $result = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson();
 
+        // TODO filter packageName out here?
         return $result['providers'];
     }
 

+ 39 - 3
src/Composer/Repository/RepositorySet.php

@@ -165,15 +165,29 @@ class RepositorySet
 
     public function getProviders($packageName)
     {
+        $providers = array();
         foreach ($this->repositories as $repository) {
             if ($repository instanceof ComposerRepository) {
-                if ($providers = $repository->getProviders($packageName)) {
-                    return $providers;
+                if ($repoProviders = $repository->getProviders($packageName)) {
+                    $providers = array_merge($providers, $repoProviders);
+                }
+            } else {
+                foreach ($repository->getPackages() as $candidate) {
+                    foreach ($candidate->getProvides() as $link) {
+                        if ($packageName === $link->getTarget()) {
+                            $providers[] = array(
+                                'name' => $candidate->getName(),
+                                'description' => $candidate->getDescription(),
+                                'type' => $candidate->getType(),
+                            );
+                            continue 2;
+                        }
+                    }
                 }
             }
         }
 
-        return array();
+        return $providers;
     }
 
     public function isPackageAcceptable($names, $stability)
@@ -201,6 +215,28 @@ class RepositorySet
         return $poolBuilder->buildPool($this->repositories, $request);
     }
 
+    /**
+     * Create a pool for dependency resolution from the packages in this repository set.
+     *
+     * @return Pool
+     */
+    public function createPoolWithAllPackages()
+    {
+        foreach ($this->repositories as $repo) {
+            if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
+                throw new \LogicException('The pool can not accept packages from an installed repository');
+            }
+        }
+
+        $this->locked = true;
+
+        $packages = array();
+        foreach ($this->repositories as $repository) {
+            $packages = array_merge($packages, $repository->getPackages());
+        }
+        return new Pool($packages);
+    }
+
     // TODO unify this with above in some simpler version without "request"?
     public function createPoolForPackage($packageName, LockArrayRepository $lockedRepo = null)
     {

+ 52 - 0
tests/Composer/Test/Fixtures/installer/provider-dev-require-can-satisfy-require.test

@@ -0,0 +1,52 @@
+--TEST--
+Test that a requirement can be satisfied by a providing package required in require-dev.
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                {"name": "provider/requirer", "version": "1.0.0", "type": "metapackage", "require": {"b/b": "1.0.0"}},
+                {"name": "b/b", "version": "1.0.0", "type": "metapackage", "provide": {"provided/pkg": "1.0.0"}}
+            ]
+        }
+    ],
+    "require": {
+        "provided/pkg": "1.0.0"
+    },
+    "require-dev": {
+        "provider/requirer": "1.0.0"
+    }
+}
+
+--RUN--
+update --no-dev
+
+--EXPECT-LOCK--
+{
+    "packages": [
+        {
+            "name": "b/b",
+            "version": "1.0.0",
+            "type": "metapackage",
+            "provide": {"provided/pkg": "1.0.0"}
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "provider/requirer",
+            "version": "1.0.0",
+            "type": "metapackage",
+            "require": {"b/b": "1.0.0"}
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": []
+}
+--EXPECT--
+Installing b/b (1.0.0)