From 05112ddce15bc19c9ed2a809778d5a1893667a40 Mon Sep 17 00:00:00 2001 From: Kristen Kozak Date: Sun, 3 Mar 2019 12:07:46 -0800 Subject: [PATCH 1/5] Remove unused filter function from ConflictSet. --- .../Distribution/Solver/Modular/ConflictSet.hs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs index 28991e3da04..b532af920da 100644 --- a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs +++ b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs @@ -28,11 +28,9 @@ module Distribution.Solver.Modular.ConflictSet ( , singleton , size , member - , filter , fromList ) where -import Prelude hiding (filter) import Data.List (intercalate, sortBy) import Data.Map (Map) import Data.Set (Set) @@ -178,18 +176,6 @@ size = S.size . conflictSetToSet member :: Var QPN -> ConflictSet -> Bool member var = S.member var . conflictSetToSet -filter :: -#ifdef DEBUG_CONFLICT_SETS - (?loc :: CallStack) => -#endif - (Var QPN -> Bool) -> ConflictSet -> ConflictSet -filter p cs = CS { - conflictSetToSet = S.filter p (conflictSetToSet cs) -#ifdef DEBUG_CONFLICT_SETS - , conflictSetOrigin = Node ?loc [conflictSetOrigin cs] -#endif - } - fromList :: #ifdef DEBUG_CONFLICT_SETS (?loc :: CallStack) => From 6d03e9182b015b79dc078c5e7784e3525237a4aa Mon Sep 17 00:00:00 2001 From: Kristen Kozak Date: Sun, 3 Mar 2019 12:07:46 -0800 Subject: [PATCH 2/5] Remove an out of date comment from ConflictSet. --- cabal-install/Distribution/Solver/Modular/ConflictSet.hs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs index b532af920da..3c762369414 100644 --- a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs +++ b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs @@ -47,11 +47,8 @@ import Distribution.Solver.Modular.Var import Distribution.Solver.Types.PackagePath -- | The set of variables involved in a solver conflict --- --- Since these variables should be preprocessed in some way, this type is --- kept abstract. data ConflictSet = CS { - -- | The set of variables involved on the conflict + -- | The set of variables involved in the conflict conflictSetToSet :: !(Set (Var QPN)) #ifdef DEBUG_CONFLICT_SETS From dbd3148222ff5b86a0379058f045995c5942b59b Mon Sep 17 00:00:00 2001 From: Kristen Kozak Date: Sun, 8 Dec 2019 23:14:27 -0800 Subject: [PATCH 3/5] Use >= instead of == to check whether solver reached the backjump limit. --- cabal-install/Distribution/Solver/Modular/Explore.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal-install/Distribution/Solver/Modular/Explore.hs b/cabal-install/Distribution/Solver/Modular/Explore.hs index f3bfb7c30a4..2cb7144c723 100644 --- a/cabal-install/Distribution/Solver/Modular/Explore.hs +++ b/cabal-install/Distribution/Solver/Modular/Explore.hs @@ -86,7 +86,7 @@ logBackjump mbj cs es = where reachedBjLimit = case mbj of Nothing -> const False - Just limit -> (== limit) + Just limit -> (>= limit) -- | Like 'retry', except that it only applies the input function when the -- backjump limit has not been reached. From 63a9e94fbaa4d782bad188787e1b54c0720e62f8 Mon Sep 17 00:00:00 2001 From: Kristen Kozak Date: Sun, 8 Dec 2019 23:14:27 -0800 Subject: [PATCH 4/5] Rename two functions and improve their comments. --- .../Distribution/Solver/Modular/Builder.hs | 2 +- .../Distribution/Solver/Modular/Dependency.hs | 19 +++++++++++-------- .../Distribution/Solver/Modular/Explore.hs | 2 +- .../Distribution/Solver/Modular/Linking.hs | 2 +- .../Distribution/Solver/Modular/Preference.hs | 2 +- .../Distribution/Solver/Modular/Validate.hs | 16 ++++++++-------- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cabal-install/Distribution/Solver/Modular/Builder.hs b/cabal-install/Distribution/Solver/Modular/Builder.hs index 322d1056820..f53910e3b51 100644 --- a/cabal-install/Distribution/Solver/Modular/Builder.hs +++ b/cabal-install/Distribution/Solver/Modular/Builder.hs @@ -145,7 +145,7 @@ addChildren bs@(BS { rdeps = rdm, open = gs, next = Goals }) -- and then handle each instance in turn. addChildren bs@(BS { rdeps = rdm, index = idx, next = OneGoal (PkgGoal qpn@(Q _ pn) gr) }) = case M.lookup pn idx of - Nothing -> FailF (varToConflictSet (P qpn) `CS.union` goalReasonToCS gr) UnknownPackage + Nothing -> FailF (varToConflictSet (P qpn) `CS.union` goalReasonToConflictSet gr) UnknownPackage Just pis -> PChoiceF qpn rdm gr (W.fromList (L.map (\ (i, info) -> ([], POption i Nothing, bs { next = Instance qpn info })) (M.toList pis))) diff --git a/cabal-install/Distribution/Solver/Modular/Dependency.hs b/cabal-install/Distribution/Solver/Modular/Dependency.hs index 6a6bb333c85..a0900c701ae 100644 --- a/cabal-install/Distribution/Solver/Modular/Dependency.hs +++ b/cabal-install/Distribution/Solver/Modular/Dependency.hs @@ -32,8 +32,8 @@ module Distribution.Solver.Modular.Dependency ( , QGoalReason , goalToVar , varToConflictSet - , goalReasonToCS - , dependencyReasonToCS + , goalReasonToConflictSet + , dependencyReasonToConflictSet ) where import Prelude () @@ -279,14 +279,17 @@ goalToVar (Goal v _) = v varToConflictSet :: Var QPN -> ConflictSet varToConflictSet = CS.singleton -goalReasonToCS :: GoalReason QPN -> ConflictSet -goalReasonToCS UserGoal = CS.empty -goalReasonToCS (DependencyGoal dr) = dependencyReasonToCS dr +-- | Convert a 'GoalReason' to a 'ConflictSet' that can be used when the goal +-- leads to a conflict. +goalReasonToConflictSet :: GoalReason QPN -> ConflictSet +goalReasonToConflictSet UserGoal = CS.empty +goalReasonToConflictSet (DependencyGoal dr) = dependencyReasonToConflictSet dr -- | This function returns the solver variables responsible for the dependency. --- It drops the flag and stanza values, which are only needed for log messages. -dependencyReasonToCS :: DependencyReason QPN -> ConflictSet -dependencyReasonToCS (DependencyReason qpn flags stanzas) = +-- It drops the values chosen for flag and stanza variables, which are only +-- needed for log messages. +dependencyReasonToConflictSet :: DependencyReason QPN -> ConflictSet +dependencyReasonToConflictSet (DependencyReason qpn flags stanzas) = CS.fromList $ P qpn : flagVars ++ map stanzaToVar (S.toList stanzas) where -- Filter out any flags that introduced the dependency with both values. diff --git a/cabal-install/Distribution/Solver/Modular/Explore.hs b/cabal-install/Distribution/Solver/Modular/Explore.hs index 2cb7144c723..267a83a8f98 100644 --- a/cabal-install/Distribution/Solver/Modular/Explore.hs +++ b/cabal-install/Distribution/Solver/Modular/Explore.hs @@ -220,7 +220,7 @@ exploreLog mbj enableBj (CountConflicts countConflicts) t = para go t initES -- avoidSet :: Var QPN -> QGoalReason -> ConflictSet avoidSet var gr = - CS.union (CS.singleton var) (goalReasonToCS gr) + CS.union (CS.singleton var) (goalReasonToConflictSet gr) -- | Interface. -- diff --git a/cabal-install/Distribution/Solver/Modular/Linking.hs b/cabal-install/Distribution/Solver/Modular/Linking.hs index 60438e98af7..d5522cd9d89 100644 --- a/cabal-install/Distribution/Solver/Modular/Linking.hs +++ b/cabal-install/Distribution/Solver/Modular/Linking.hs @@ -251,7 +251,7 @@ linkDeps target = \deps -> do vs <- get let lg = M.findWithDefault (lgSingleton qpn Nothing) qpn $ vsLinks vs lg' = M.findWithDefault (lgSingleton qpn' Nothing) qpn' $ vsLinks vs - lg'' <- lift' $ lgMerge ((CS.union `on` dependencyReasonToCS) dr1 dr2) lg lg' + lg'' <- lift' $ lgMerge ((CS.union `on` dependencyReasonToConflictSet) dr1 dr2) lg lg' updateLinkGroup lg'' (Flagged fn _ t f, ~(Flagged _ _ t' f')) -> do vs <- get diff --git a/cabal-install/Distribution/Solver/Modular/Preference.hs b/cabal-install/Distribution/Solver/Modular/Preference.hs index 856993f2ef3..116c3263282 100644 --- a/cabal-install/Distribution/Solver/Modular/Preference.hs +++ b/cabal-install/Distribution/Solver/Modular/Preference.hs @@ -343,7 +343,7 @@ onlyConstrained :: (PN -> Bool) -> Tree d QGoalReason -> Tree d QGoalReason onlyConstrained p = trav go where go (PChoiceF v@(Q _ pn) _ gr _) | not (p pn) - = FailF (varToConflictSet (P v) `CS.union` goalReasonToCS gr) NotExplicit + = FailF (varToConflictSet (P v) `CS.union` goalReasonToConflictSet gr) NotExplicit go x = x diff --git a/cabal-install/Distribution/Solver/Modular/Validate.hs b/cabal-install/Distribution/Solver/Modular/Validate.hs index 4f63f073e5e..900b1c08a8f 100644 --- a/cabal-install/Distribution/Solver/Modular/Validate.hs +++ b/cabal-install/Distribution/Solver/Modular/Validate.hs @@ -320,7 +320,7 @@ checkComponentsInNewPackage required qpn providedComps = -> (ExposedComponent -> DependencyReason QPN -> FailReason) -> Conflict mkConflict comp dr mkFailure = - (CS.insert (P qpn) (dependencyReasonToCS dr), mkFailure comp dr) + (CS.insert (P qpn) (dependencyReasonToConflictSet dr), mkFailure comp dr) buildableProvidedComps :: [ExposedComponent] buildableProvidedComps = [comp | (comp, IsBuildable True) <- M.toList providedComps] @@ -393,13 +393,13 @@ extend extSupported langSupported pkgPresent newactives ppa = foldM extendSingle extendSingle :: PPreAssignment -> LDep QPN -> Either Conflict PPreAssignment extendSingle a (LDep dr (Ext ext )) = if extSupported ext then Right a - else Left (dependencyReasonToCS dr, UnsupportedExtension ext) + else Left (dependencyReasonToConflictSet dr, UnsupportedExtension ext) extendSingle a (LDep dr (Lang lang)) = if langSupported lang then Right a - else Left (dependencyReasonToCS dr, UnsupportedLanguage lang) + else Left (dependencyReasonToConflictSet dr, UnsupportedLanguage lang) extendSingle a (LDep dr (Pkg pn vr)) = if pkgPresent pn vr then Right a - else Left (dependencyReasonToCS dr, MissingPkgconfigPackage pn vr) + else Left (dependencyReasonToConflictSet dr, MissingPkgconfigPackage pn vr) extendSingle a (LDep dr (Dep dep@(PkgComponent qpn _) ci)) = let mergedDep = M.findWithDefault (MergedDepConstrained []) qpn a in case (\ x -> M.insert qpn x a) <$> merge mergedDep (PkgDep dr dep ci) of @@ -448,14 +448,14 @@ merge :: merge (MergedDepFixed comp1 vs1 i1) (PkgDep vs2 (PkgComponent p comp2) ci@(Fixed i2)) | i1 == i2 = Right $ MergedDepFixed comp1 vs1 i1 | otherwise = - Left ( (CS.union `on` dependencyReasonToCS) vs1 vs2 + Left ( (CS.union `on` dependencyReasonToConflictSet) vs1 vs2 , ( ConflictingDep vs1 (PkgComponent p comp1) (Fixed i1) , ConflictingDep vs2 (PkgComponent p comp2) ci ) ) merge (MergedDepFixed comp1 vs1 i@(I v _)) (PkgDep vs2 (PkgComponent p comp2) ci@(Constrained vr)) | checkVR vr v = Right $ MergedDepFixed comp1 vs1 i | otherwise = - Left ( (CS.union `on` dependencyReasonToCS) vs1 vs2 + Left ( (CS.union `on` dependencyReasonToConflictSet) vs1 vs2 , ( ConflictingDep vs1 (PkgComponent p comp1) (Fixed i) , ConflictingDep vs2 (PkgComponent p comp2) ci ) ) @@ -467,7 +467,7 @@ merge (MergedDepConstrained vrOrigins) (PkgDep vs2 (PkgComponent p comp2) ci@(Fi go ((vr, comp1, vs1) : vros) | checkVR vr v = go vros | otherwise = - Left ( (CS.union `on` dependencyReasonToCS) vs1 vs2 + Left ( (CS.union `on` dependencyReasonToConflictSet) vs1 vs2 , ( ConflictingDep vs1 (PkgComponent p comp1) (Constrained vr) , ConflictingDep vs2 (PkgComponent p comp2) ci ) ) @@ -512,7 +512,7 @@ extendRequiredComponents available = foldM extendSingle -> (QPN -> ExposedComponent -> FailReason) -> Conflict mkConflict qpn comp dr mkFailure = - (CS.insert (P qpn) (dependencyReasonToCS dr), mkFailure qpn comp) + (CS.insert (P qpn) (dependencyReasonToConflictSet dr), mkFailure qpn comp) buildableComps :: Map comp IsBuildable -> [comp] buildableComps comps = [comp | (comp, IsBuildable True) <- M.toList comps] From ac84fa2f25505ed604769d6fc3f00ecf3933b248 Mon Sep 17 00:00:00 2001 From: Kristen Kozak Date: Sun, 8 Dec 2019 23:14:27 -0800 Subject: [PATCH 5/5] Solver: Pair conflict set variables with more information about conflicts. Closes #4805. This commit adds a solver optimization to skip a version of a package if it does not resolve any of the conflicts encountered in the last version of that package. It is on by default and is controlled by the flag --fine-grained-conflicts. Conflict sets allow the solver to skip other versions of a package if that package wasn't involved in a conflict. What if the package was involved in a conflict, but the other versions of the package don't resolve the conflict? The solver should be able to skip the other versions in that case, too. This commit adds more information about why each variable was added to the conflict set to make that possible. The optimization is based on the assumption that the next version of a package is likely to have similar dependencies and constraints to the last version that was tried. Here is an example where --fine-grained-conflicts is more effective than backjumping alone: Package A-1.2 depends on B, and all versions of B depend on C. C is not available in the package index. Previously, the solver would have had to try all combinations of versions for A and B until it found a combination that avoided the dependency on C. With --fine-grained-conflicts, the solver only needs to try A-1.2 and each version of B to know that there is no solution for B at this point in the search tree. Then the solver can skip A-1.1 if it also depends on B. Here is the log: [__0] trying: A-1.2 (user goal) [__1] trying: B-5.0 (dependency of A) [__2] unknown package: C (dependency of B) [__2] fail (backjumping, conflict set: B, C) [__1] skipping: B-4.0, B-3.0, B-2.0, B-1.0 (has the same characteristics that caused the previous version to fail: depends on 'C') [__1] fail (backjumping, conflict set: A, B, C) [__0] skipping: A-1.1, A-1.0 (has the same characteristics that caused the previous version to fail: depends on 'B') [__0] trying: A-8.0 [__1] done This commit changes the type that is used for conflict sets from 'Set (Var QPN)' to 'Map (Var QPN) (Set Conflict)', where 'Conflict' represents a single conflict that caused a variable to be added to the conflict set. 'Conflict' currently handles three types of conflicts, though more could be added in the future: 1. The package excluded a specific version of one of its dependencies. 2. A version of the package was excluded by a constraint from one of its reverse dependencies. 3. The package depends on a package that led to conflicts. 'Conflict' also has an 'OtherConflict' data constructor, which covers every other conflict and can never cause a version to be skipped. Since conflicts are paired with variables in the conflict set, they propagate up the search tree using the same logic as conflict sets. When conflict sets are unioned, the conflicts for each variable are also unioned. During backjumping, the solver checks whether each version of a package can be skipped by looking up the conflicts for its variable in the previous conflict set (unless it is the first version to be tried). A version can only be skipped when it does not resolve any of the previous conflicts. One important design choice for this optimization was whether the information that is paired with conflict set variables should be limited to characteristics of the current package's .cabal file. For example, when package X has a dependency "Y >= 1.2 && < 1.3" and the constraint conflicts with Y-1.4, X's conflict could be described in two ways: 1. (limited to characteristics of X) X has a constraint on Y that is as restrictive as ">= 1.2 && < 1.3". 2. (free to reference other packages) X excludes version 1.4 of Y. Referencing other packages is more powerful because it allows the description of the conflict to be more precise, which lets the solver skip more versions. In the example above, the solver could skip a version of X containing the dependency "Y >= 1.1 && < 1.2" with the description in (2) but not with the description in (1). The downside of referencing other packages is that the logic is more complex. When the solver skips a version because if doesn't resolve the previous conflicts, it cannot simply reuse the previous conflict set. The solver may need to add more conflicts specific to the version that was skipped. For example, when the solver skips the second version of X above, it needs to add a conflict saying that Y was rejected by the constraint "Y >= 1.1 && < 1.2". This commit implements the design where conflicts can reference other packages. Results from running hackage-benchmark on master at 0d4ee7ba30f671fbaa1162c2373abb27c460478a (cabal1) and this branch (cabal2): Index-state: 2019-12-09T07:37:06Z Compiler: GHC 8.6.5 Additional benchmark flags: --min-run-time-percentage-difference-to-rerun=10 package result1 result2 mean1 mean2 stddev1 stddev2 speedup AERN-RnToRm-Plot NoInstallPlan NoInstallPlan 3.516s 3.049s 0.031s 0.028s 1.153 AutoForms NoInstallPlan NoInstallPlan 8.705s 3.477s 0.054s 0.039s 2.504 BASIC NoInstallPlan NoInstallPlan 3.073s 2.708s 0.027s 0.028s 1.135 Blobs Solution Solution 5.037s 3.490s 0.041s 0.036s 1.443 CMCompare NoInstallPlan NoInstallPlan 4.311s 3.345s 0.030s 0.038s 1.289 CSPM-Interpreter NoInstallPlan NoInstallPlan 3.357s 3.051s 0.025s 0.027s 1.100 CSPM-cspm NoInstallPlan NoInstallPlan 3.357s 2.910s 0.021s 0.029s 1.153 Cartesian NoInstallPlan NoInstallPlan 4.080s 3.483s 0.034s 0.024s 1.171 Chart-simple NoInstallPlan NoInstallPlan 5.062s 3.677s 0.032s 0.033s 1.377 Elm BackjumpLimit NoInstallPlan 13.716s 4.847s 0.079s 0.026s 2.830 Eternal10Seconds NoInstallPlan NoInstallPlan 2.758s 2.609s 0.046s 0.052s 1.057 Gamgine Solution Solution 4.336s 3.451s 0.029s 0.030s 1.256 GeBoP Solution Solution 5.002s 3.780s 0.026s 0.022s 1.323 GenI NoInstallPlan NoInstallPlan 4.971s 3.213s 0.036s 0.073s 1.547 Graphalyze Solution Solution 8.627s 4.698s 0.045s 0.035s 1.836 GuiTV BackjumpLimit NoInstallPlan 8.704s 6.072s 0.059s 0.036s 1.434 HGamer3D NoInstallPlan NoInstallPlan 3.073s 2.722s 0.022s 0.035s 1.129 HGamer3D-Ogre-Binding NoInstallPlan NoInstallPlan 3.443s 2.787s 0.038s 0.028s 1.235 HGamer3D-SFML-Binding NoInstallPlan NoInstallPlan 3.144s 2.712s 0.023s 0.047s 1.159 HPlot NoInstallPlan NoInstallPlan 3.337s 2.826s 0.026s 0.022s 1.181 HROOT NoInstallPlan NoInstallPlan 3.544s 3.168s 0.019s 0.037s 1.119 HROOT-graf NoInstallPlan NoInstallPlan 3.418s 3.092s 0.035s 0.036s 1.105 HaRe NoInstallPlan NoInstallPlan 3.599s 2.989s 0.032s 0.026s 1.204 Hieroglyph Solution Solution 4.089s 3.299s 0.018s 0.019s 1.240 HipmunkPlayground Solution Solution 3.393s 3.002s 0.049s 0.031s 1.130 INblobs BackjumpLimit NoInstallPlan 6.427s 3.697s 0.035s 0.039s 1.738 JsonGrammar NoInstallPlan NoInstallPlan 3.080s 2.756s 0.044s 0.035s 1.118 Michelangelo NoInstallPlan NoInstallPlan 4.511s 3.573s 0.028s 0.035s 1.263 Monaris NoInstallPlan NoInstallPlan 3.914s 2.966s 0.028s 0.036s 1.319 Nomyx-Language NoInstallPlan NoInstallPlan 3.295s 2.767s 0.026s 0.028s 1.191 Nomyx-Rules NoInstallPlan NoInstallPlan 3.273s 2.777s 0.020s 0.031s 1.179 OpenVG Solution Solution 3.541s 3.092s 0.021s 0.021s 1.145 QuickPlot NoInstallPlan NoInstallPlan 3.672s 3.210s 0.023s 0.023s 1.144 SourceGraph NoInstallPlan NoInstallPlan 6.356s 4.533s 0.049s 0.056s 1.402 Spock-auth Solution Solution 4.613s 4.150s 0.026s 0.030s 1.112 TBC Solution Solution 3.318s 2.882s 0.012s 0.019s 1.151 WXDiffCtrl NoInstallPlan NoInstallPlan 4.880s 3.523s 0.025s 0.028s 1.385 WaveFront NoInstallPlan NoInstallPlan 5.793s 3.746s 0.039s 0.022s 1.547 WxGeneric BackjumpLimit NoInstallPlan 6.340s 3.612s 0.043s 0.029s 1.755 accelerate-cuda NoInstallPlan NoInstallPlan 6.312s 3.287s 0.040s 0.027s 1.920 acme-everything NoInstallPlan NoInstallPlan 6.252s 5.737s 0.034s 0.050s 1.090 aeson-bson NoInstallPlan NoInstallPlan 3.411s 2.811s 0.019s 0.022s 1.214 ag-pictgen NoInstallPlan NoInstallPlan 3.306s 2.743s 0.028s 0.029s 1.205 alga NoInstallPlan NoInstallPlan 3.290s 2.836s 0.017s 0.032s 1.160 alsa-gui NoInstallPlan NoInstallPlan 4.237s 3.333s 0.020s 0.028s 1.271 ampersand NoInstallPlan NoInstallPlan 3.360s 2.949s 0.026s 0.025s 1.139 analyze-client NoInstallPlan NoInstallPlan 3.395s 2.788s 0.019s 0.025s 1.218 anansi-pandoc Solution Solution 4.766s 3.869s 0.034s 0.042s 1.232 apiary-clientsession NoInstallPlan NoInstallPlan 4.434s 3.016s 0.029s 0.036s 1.470 apiary-cookie BackjumpLimit NoInstallPlan 5.106s 2.952s 0.037s 0.026s 1.730 applicative-parsec NoInstallPlan NoInstallPlan 3.429s 2.998s 0.032s 0.034s 1.144 asic NoInstallPlan NoInstallPlan 3.278s 2.868s 0.024s 0.027s 1.143 asil NoInstallPlan NoInstallPlan 4.348s 2.991s 0.029s 0.035s 1.454 astview NoInstallPlan NoInstallPlan 3.317s 2.768s 0.031s 0.037s 1.198 attoparsec-enumerator NoInstallPlan NoInstallPlan 2.952s 2.695s 0.026s 0.013s 1.096 audiovisual NoInstallPlan NoInstallPlan 3.305s 2.875s 0.028s 0.032s 1.149 aws-configuration-tools Solution Solution 4.445s 3.955s 0.031s 0.029s 1.124 aws-kinesis Solution Solution 3.995s 3.544s 0.034s 0.024s 1.127 aws-kinesis-client Solution Solution 5.820s 5.270s 0.029s 0.034s 1.104 aws-performance-tests NoInstallPlan NoInstallPlan 4.495s 3.796s 0.032s 0.018s 1.184 azure-servicebus BackjumpLimit NoInstallPlan 6.367s 3.754s 0.049s 0.035s 1.696 babylon Solution Solution 5.028s 3.652s 0.017s 0.031s 1.377 bamboo BackjumpLimit NoInstallPlan 9.642s 4.139s 0.074s 0.041s 2.330 bamboo-plugin-highlight NoInstallPlan NoInstallPlan 5.368s 2.933s 0.034s 0.017s 1.830 battleships NoInstallPlan NoInstallPlan 3.884s 3.155s 0.031s 0.036s 1.231 bein NoInstallPlan NoInstallPlan 3.652s 3.267s 0.031s 0.021s 1.118 binding-wx Solution Solution 5.070s 3.630s 0.047s 0.038s 1.397 birch-beer BackjumpLimit NoInstallPlan 9.469s 4.206s 0.046s 0.032s 2.251 blosum NoInstallPlan NoInstallPlan 3.354s 2.734s 0.039s 0.025s 1.227 bluetile NoInstallPlan NoInstallPlan 3.593s 3.043s 0.028s 0.041s 1.181 bulmex Solution Solution 5.087s 4.474s 0.037s 0.031s 1.137 cabal-upload NoInstallPlan NoInstallPlan 2.930s 2.668s 0.028s 0.024s 1.098 category-extras NoInstallPlan NoInstallPlan 3.417s 2.916s 0.026s 0.020s 1.172 cellrenderer-cairo NoInstallPlan NoInstallPlan 3.168s 2.756s 0.029s 0.038s 1.150 celtchar NoInstallPlan NoInstallPlan 4.298s 3.373s 0.018s 0.030s 1.274 chu2 NoInstallPlan NoInstallPlan 3.795s 2.825s 0.038s 0.029s 1.343 citeproc-hs-pandoc-filter BackjumpLimit NoInstallPlan 10.445s 4.403s 0.028s 0.022s 2.372 cj-token NoInstallPlan NoInstallPlan 4.258s 3.235s 0.034s 0.059s 1.316 claferwiki NoInstallPlan NoInstallPlan 5.508s 4.316s 0.027s 0.029s 1.276 clash-systemverilog Solution Solution 7.246s 4.862s 0.050s 0.033s 1.490 clash-verilog Solution Solution 7.218s 4.853s 0.058s 0.038s 1.487 clash-vhdl Solution Solution 7.227s 4.853s 0.036s 0.039s 1.489 clckwrks-dot-com BackjumpLimit NoInstallPlan 9.000s 4.187s 0.041s 0.033s 2.150 clckwrks-plugin-bugs NoInstallPlan NoInstallPlan 3.190s 2.742s 0.023s 0.012s 1.163 clckwrks-theme-bootstrap NoInstallPlan NoInstallPlan 4.882s 4.353s 0.037s 0.024s 1.121 clckwrks-theme-clckwrks NoInstallPlan NoInstallPlan 4.525s 3.967s 0.026s 0.023s 1.141 clustertools NoInstallPlan NoInstallPlan 3.039s 2.764s 0.033s 0.037s 1.100 codex Solution Solution 4.980s 4.430s 0.030s 0.020s 1.124 combinator-interactive NoInstallPlan NoInstallPlan 4.495s 3.131s 0.021s 0.023s 1.435 computational-algebra NoInstallPlan NoInstallPlan 4.601s 3.233s 0.063s 0.018s 1.423 concraft-pl BackjumpLimit NoInstallPlan 9.683s 5.402s 0.053s 0.031s 1.793 containers-benchmark NoInstallPlan NoInstallPlan 3.651s 2.688s 0.011s 0.029s 1.358 cqrs-example NoInstallPlan NoInstallPlan 3.295s 2.849s 0.041s 0.030s 1.157 csv-enumerator NoInstallPlan NoInstallPlan 4.344s 2.742s 0.034s 0.026s 1.584 darcsden NoInstallPlan NoInstallPlan 6.133s 4.395s 0.038s 0.038s 1.395 data-object-yaml Unbuildable Unbuildable 3.579s 2.921s 0.025s 0.024s 1.225 dephd NoInstallPlan NoInstallPlan 3.041s 2.763s 0.020s 0.024s 1.101 diagrams-wx NoInstallPlan NoInstallPlan 3.297s 2.905s 0.020s 0.041s 1.135 dialog NoInstallPlan NoInstallPlan 4.014s 3.061s 0.032s 0.046s 1.311 digestive-functors-scotty BackjumpLimit Solution 6.666s 3.443s 0.026s 0.030s 1.936 dingo-core NoInstallPlan NoInstallPlan 3.406s 2.821s 0.032s 0.043s 1.207 distribution-plot NoInstallPlan NoInstallPlan 3.446s 3.102s 0.026s 0.017s 1.111 diversity BackjumpLimit NoInstallPlan 6.946s 3.222s 0.034s 0.030s 2.156 dow Solution Solution 3.260s 2.950s 0.049s 0.028s 1.105 dwarfadt Solution Solution 4.500s 4.010s 0.061s 0.030s 1.122 effect-handlers NoInstallPlan NoInstallPlan 3.400s 2.982s 0.031s 0.025s 1.140 elm-get BackjumpLimit NoInstallPlan 14.550s 6.523s 0.060s 0.026s 2.231 elm-reactor NoInstallPlan NoInstallPlan 4.722s 3.177s 0.021s 0.034s 1.486 elm-repl BackjumpLimit NoInstallPlan 13.642s 5.149s 0.049s 0.028s 2.649 elm-server BackjumpLimit NoInstallPlan 11.681s 7.027s 0.020s 0.031s 1.662 enumerator-fd NoInstallPlan NoInstallPlan 3.304s 2.757s 0.021s 0.033s 1.198 eventful-postgresql Solution Solution 4.152s 3.538s 0.042s 0.025s 1.174 eventful-sqlite Solution Solution 4.069s 3.386s 0.038s 0.038s 1.202 ez-couch Solution Solution 4.356s 3.634s 0.024s 0.020s 1.199 family-tree Solution Solution 4.573s 3.522s 0.048s 0.018s 1.298 fasta NoInstallPlan NoInstallPlan 4.754s 2.925s 0.028s 0.031s 1.625 fb-persistent BackjumpLimit NoInstallPlan 14.438s 3.909s 0.088s 0.033s 3.693 festung NoInstallPlan NoInstallPlan 9.126s 3.914s 0.048s 0.027s 2.332 fibon NoInstallPlan NoInstallPlan 2.856s 2.593s 0.031s 0.048s 1.102 filesystem-enumerator NoInstallPlan NoInstallPlan 3.198s 2.762s 0.019s 0.023s 1.158 forml NoInstallPlan NoInstallPlan 9.324s 5.014s 0.039s 0.052s 1.859 foscam-sort NoInstallPlan NoInstallPlan 4.178s 3.306s 0.013s 0.039s 1.264 fpco-api BackjumpLimit Solution 12.008s 6.003s 0.053s 0.025s 2.000 geek-server NoInstallPlan NoInstallPlan 8.774s 3.491s 0.041s 0.020s 2.513 geni-gui NoInstallPlan NoInstallPlan 6.212s 3.488s 0.027s 0.018s 1.781 ghc-vis NoInstallPlan NoInstallPlan 3.731s 3.203s 0.021s 0.021s 1.165 ghcjs-dom-hello Solution Solution 7.211s 6.613s 0.122s 0.179s 1.090 ghcjs-dom-webkit NoInstallPlan NoInstallPlan 3.717s 3.343s 0.034s 0.031s 1.112 ghclive NoInstallPlan NoInstallPlan 6.898s 3.761s 0.043s 0.032s 1.834 github-backup BackjumpLimit Solution 9.061s 4.806s 0.040s 0.039s 1.885 gmndl NoInstallPlan NoInstallPlan 4.953s 3.935s 0.035s 0.032s 1.259 gnome-desktop NoInstallPlan NoInstallPlan 3.154s 2.730s 0.039s 0.031s 1.155 goal-core NoInstallPlan NoInstallPlan 3.947s 3.474s 0.028s 0.018s 1.136 google-drive BackjumpLimit NoInstallPlan 6.119s 3.896s 0.032s 0.042s 1.571 gps2htmlReport Solution Solution 4.577s 4.126s 0.051s 0.041s 1.109 graphicstools NoInstallPlan NoInstallPlan 4.915s 3.174s 0.025s 0.047s 1.549 gruff NoInstallPlan NoInstallPlan 3.891s 3.281s 0.032s 0.035s 1.186 gtk-mac-integration NoInstallPlan NoInstallPlan 3.343s 2.952s 0.016s 0.023s 1.132 gtk2hs-cast-glade NoInstallPlan NoInstallPlan 3.097s 2.760s 0.021s 0.022s 1.122 gtk2hs-cast-gnomevfs NoInstallPlan NoInstallPlan 3.038s 2.702s 0.039s 0.025s 1.124 gtk2hs-cast-gtkglext NoInstallPlan NoInstallPlan 3.421s 2.962s 0.029s 0.040s 1.155 gtk2hs-cast-gtksourceview2 NoInstallPlan NoInstallPlan 3.947s 3.117s 0.031s 0.028s 1.267 gtk3-mac-integration NoInstallPlan NoInstallPlan 3.488s 2.983s 0.025s 0.037s 1.170 gtkrsync NoInstallPlan NoInstallPlan 3.226s 2.776s 0.016s 0.038s 1.162 hack-handler-evhttp BackjumpLimit NoInstallPlan 5.497s 3.269s 0.040s 0.016s 1.682 hack-handler-simpleserver NoInstallPlan NoInstallPlan 3.699s 2.999s 0.017s 0.017s 1.233 hack-middleware-cleanpath NoInstallPlan NoInstallPlan 3.359s 2.918s 0.022s 0.017s 1.151 hack-middleware-clientsession NoInstallPlan NoInstallPlan 3.297s 2.903s 0.034s 0.022s 1.136 hack-middleware-jsonp NoInstallPlan NoInstallPlan 3.350s 2.794s 0.016s 0.021s 1.199 hack2-handler-happstack-server NoInstallPlan NoInstallPlan 3.317s 2.741s 0.022s 0.017s 1.210 hails Solution Solution 6.896s 4.685s 0.046s 0.030s 1.472 hakyll-blaze-templates NoInstallPlan NoInstallPlan 3.205s 2.815s 0.033s 0.015s 1.139 hakyll-contrib-elm Solution Solution 8.337s 6.351s 0.063s 0.030s 1.313 hakyll-ogmarkup BackjumpLimit NoInstallPlan 16.596s 4.848s 0.057s 0.052s 3.423 halipeto Solution Solution 3.868s 4.575s 0.047s 0.018s 0.845 happindicator NoInstallPlan NoInstallPlan 3.122s 2.723s 0.045s 0.034s 1.147 happs-tutorial NoInstallPlan NoInstallPlan 3.653s 2.846s 0.023s 0.028s 1.284 happstack BackjumpLimit NoInstallPlan 6.011s 3.000s 0.030s 0.032s 2.004 happstack-clientsession BackjumpLimit NoInstallPlan 7.294s 3.242s 0.026s 0.019s 2.250 happstack-data NoInstallPlan NoInstallPlan 3.156s 2.791s 0.030s 0.025s 1.131 happstack-dlg Solution Solution 3.289s 2.886s 0.022s 0.027s 1.140 happstack-facebook NoInstallPlan NoInstallPlan 3.560s 2.822s 0.024s 0.040s 1.261 happstack-hamlet BackjumpLimit NoInstallPlan 6.364s 3.425s 0.023s 0.022s 1.858 happstack-heist BackjumpLimit NoInstallPlan 5.232s 2.974s 0.033s 0.020s 1.759 happstack-helpers NoInstallPlan NoInstallPlan 4.138s 2.972s 0.034s 0.034s 1.393 happstack-hstringtemplate BackjumpLimit NoInstallPlan 5.636s 3.080s 0.027s 0.023s 1.830 happstack-lite BackjumpLimit NoInstallPlan 7.494s 3.648s 0.044s 0.024s 2.054 happstack-server-tls-cryptonite BackjumpLimit NoInstallPlan 8.341s 3.788s 0.040s 0.023s 2.202 haskell-pdf-presenter NoInstallPlan NoInstallPlan 3.254s 2.737s 0.014s 0.024s 1.189 haskellscrabble NoInstallPlan NoInstallPlan 4.524s 2.918s 0.025s 0.025s 1.550 haste-perch NoInstallPlan NoInstallPlan 3.711s 3.244s 0.021s 0.024s 1.144 hawitter NoInstallPlan NoInstallPlan 3.288s 2.810s 0.025s 0.047s 1.170 hbro NoInstallPlan NoInstallPlan 8.560s 4.254s 0.067s 0.078s 2.012 hbro-contrib NoInstallPlan NoInstallPlan 6.557s 4.295s 0.039s 0.024s 1.527 heatitup-complete NoInstallPlan NoInstallPlan 3.143s 2.731s 0.023s 0.035s 1.151 hedgehog-checkers-lens NoInstallPlan NoInstallPlan 3.861s 2.900s 0.032s 0.016s 1.331 hellage BackjumpLimit NoInstallPlan 5.914s 2.990s 0.029s 0.036s 1.978 hellnet NoInstallPlan NoInstallPlan 4.260s 2.909s 0.024s 0.038s 1.465 hermes NoInstallPlan NoInstallPlan 5.062s 3.265s 0.032s 0.023s 1.550 hesh NoInstallPlan NoInstallPlan 4.225s 3.286s 0.031s 0.029s 1.286 hfiar NoInstallPlan NoInstallPlan 6.268s 3.140s 0.034s 0.051s 1.996 hissmetrics NoInstallPlan NoInstallPlan 3.089s 2.768s 0.058s 0.028s 1.116 hist-pl NoInstallPlan NoInstallPlan 3.379s 2.816s 0.028s 0.027s 1.200 hledger-chart NoInstallPlan NoInstallPlan 3.162s 2.732s 0.039s 0.025s 1.158 hledger-vty NoInstallPlan NoInstallPlan 3.335s 2.756s 0.067s 0.020s 1.210 hoodle BackjumpLimit NoInstallPlan 20.758s 5.907s 0.155s 0.033s 3.514 hoodle-core BackjumpLimit NoInstallPlan 19.462s 5.314s 0.160s 0.085s 3.662 hoodle-publish BackjumpLimit NoInstallPlan 13.920s 4.536s 0.089s 0.029s 3.069 hoodle-render NoInstallPlan NoInstallPlan 11.423s 4.392s 0.042s 0.040s 2.601 hpage NoInstallPlan NoInstallPlan 6.998s 3.458s 0.034s 0.033s 2.024 hplayground BackjumpLimit NoInstallPlan 7.878s 4.227s 0.044s 0.030s 1.864 hs-pkpass NoInstallPlan NoInstallPlan 3.191s 2.789s 0.027s 0.030s 1.144 hsignal NoInstallPlan NoInstallPlan 3.489s 2.854s 0.031s 0.027s 1.223 hspresent Solution Solution 3.330s 2.799s 0.027s 0.032s 1.190 hstzaar NoInstallPlan NoInstallPlan 3.422s 2.797s 0.032s 0.029s 1.223 http-client-lens NoInstallPlan NoInstallPlan 3.381s 2.940s 0.023s 0.042s 1.150 http-client-session NoInstallPlan NoInstallPlan 3.241s 2.919s 0.036s 0.025s 1.110 hums NoInstallPlan NoInstallPlan 3.899s 3.336s 0.031s 0.030s 1.169 hunt-server NoInstallPlan NoInstallPlan 3.311s 2.909s 0.021s 0.029s 1.138 hxournal BackjumpLimit NoInstallPlan 6.425s 3.054s 0.073s 0.033s 2.104 hyakko Solution Solution 4.903s 3.872s 0.024s 0.021s 1.266 hyperpublic NoInstallPlan NoInstallPlan 2.755s 2.565s 0.032s 0.039s 1.074 i3blocks-hs-contrib NoInstallPlan NoInstallPlan 3.391s 2.958s 0.023s 0.023s 1.146 ideas-math NoInstallPlan NoInstallPlan 3.040s 2.734s 0.021s 0.024s 1.112 imprevu-happstack NoInstallPlan NoInstallPlan 3.456s 3.076s 0.024s 0.037s 1.123 instapaper-sender Solution Solution 4.099s 3.635s 0.037s 0.015s 1.128 iptadmin BackjumpLimit NoInstallPlan 5.964s 3.152s 0.037s 0.050s 1.892 isotope Solution Solution 3.284s 2.856s 0.032s 0.031s 1.150 jbi Solution Solution 3.387s 3.076s 0.026s 0.013s 1.101 jsaddle-hello NoInstallPlan NoInstallPlan 3.635s 3.209s 0.041s 0.029s 1.133 json-pointer-hasql NoInstallPlan NoInstallPlan 6.663s 3.385s 0.036s 0.020s 1.968 kawaii Solution Solution 8.740s 5.624s 0.025s 0.028s 1.554 keera-hails-reactive-wx Solution Solution 5.056s 3.656s 0.032s 0.023s 1.383 kevin NoInstallPlan NoInstallPlan 3.316s 2.783s 0.030s 0.027s 1.191 lambdabot-xmpp Solution Solution 6.170s 5.441s 0.038s 0.034s 1.134 lambdacube-bullet Solution Solution 3.396s 3.114s 0.037s 0.051s 1.090 lambdacube-engine Solution Solution 3.322s 2.973s 0.020s 0.027s 1.117 lambdiff NoInstallPlan NoInstallPlan 3.804s 2.906s 0.024s 0.037s 1.309 language-ninja NoInstallPlan NoInstallPlan 3.051s 2.711s 0.061s 0.029s 1.125 language-spelling NoInstallPlan NoInstallPlan 3.232s 2.768s 0.025s 0.025s 1.168 layers-game Solution Solution 3.797s 3.279s 0.027s 0.042s 1.158 leaky NoInstallPlan NoInstallPlan 3.668s 2.997s 0.030s 0.037s 1.224 leksah NoInstallPlan NoInstallPlan 4.632s 3.379s 0.036s 0.039s 1.371 lhc NoInstallPlan NoInstallPlan 3.148s 2.876s 0.063s 0.033s 1.095 list-t-attoparsec NoInstallPlan NoInstallPlan 3.583s 3.129s 0.020s 0.029s 1.145 list-t-html-parser NoInstallPlan NoInstallPlan 3.721s 3.011s 0.025s 0.023s 1.236 liveplot NoInstallPlan NoInstallPlan 3.542s 2.826s 0.024s 0.036s 1.253 llvm-general NoInstallPlan NoInstallPlan 3.325s 3.058s 0.028s 0.024s 1.087 log BackjumpLimit Solution 7.669s 4.420s 0.039s 0.026s 1.735 loli BackjumpLimit NoInstallPlan 5.683s 2.835s 0.024s 0.028s 2.004 lsystem BackjumpLimit NoInstallPlan 6.314s 3.683s 0.043s 0.023s 1.714 maid NoInstallPlan NoInstallPlan 4.421s 2.859s 0.024s 0.027s 1.547 manatee NoInstallPlan NoInstallPlan 5.418s 3.353s 0.021s 0.035s 1.616 manatee-all NoInstallPlan NoInstallPlan 3.651s 3.139s 0.022s 0.033s 1.163 manatee-browser NoInstallPlan NoInstallPlan 3.275s 2.855s 0.033s 0.020s 1.147 manatee-curl NoInstallPlan NoInstallPlan 3.341s 2.826s 0.048s 0.036s 1.182 manatee-editor NoInstallPlan NoInstallPlan 3.577s 2.843s 0.023s 0.025s 1.258 manatee-filemanager NoInstallPlan NoInstallPlan 3.685s 2.804s 0.021s 0.013s 1.314 manatee-imageviewer NoInstallPlan NoInstallPlan 3.698s 2.835s 0.019s 0.030s 1.304 manatee-mplayer NoInstallPlan NoInstallPlan 3.136s 2.794s 0.021s 0.025s 1.122 manatee-pdfviewer NoInstallPlan NoInstallPlan 3.458s 2.828s 0.036s 0.029s 1.223 manatee-reader NoInstallPlan NoInstallPlan 3.283s 2.813s 0.026s 0.020s 1.167 manatee-terminal NoInstallPlan NoInstallPlan 3.153s 2.764s 0.023s 0.037s 1.141 mangopay NoInstallPlan NoInstallPlan 3.226s 2.896s 0.028s 0.029s 1.114 markup-preview NoInstallPlan NoInstallPlan 4.598s 3.678s 0.024s 0.052s 1.250 matlab NoInstallPlan NoInstallPlan 3.323s 2.793s 0.023s 0.037s 1.190 matsuri NoInstallPlan NoInstallPlan 3.537s 2.791s 0.032s 0.030s 1.268 mdcat NoInstallPlan NoInstallPlan 4.085s 2.954s 0.036s 0.038s 1.383 mediabus-fdk-aac BackjumpLimit NoInstallPlan 10.903s 4.132s 0.049s 0.018s 2.639 mellon-web NoInstallPlan NoInstallPlan 7.345s 3.736s 0.017s 0.034s 1.966 messente NoInstallPlan NoInstallPlan 4.698s 3.269s 0.021s 0.035s 1.437 micrologger NoInstallPlan NoInstallPlan 3.700s 3.296s 0.038s 0.041s 1.123 midimory NoInstallPlan NoInstallPlan 5.143s 3.483s 0.054s 0.028s 1.476 minesweeper NoInstallPlan NoInstallPlan 3.414s 2.836s 0.024s 0.018s 1.204 modify-fasta NoInstallPlan NoInstallPlan 4.678s 2.791s 0.027s 0.017s 1.676 mongodb-queue BackjumpLimit Solution 7.015s 3.780s 0.044s 0.031s 1.856 monoids NoInstallPlan NoInstallPlan 3.918s 2.805s 0.029s 0.042s 1.397 music-parts Solution Solution 3.962s 3.598s 0.028s 0.035s 1.101 music-util NoInstallPlan NoInstallPlan 4.276s 2.983s 0.023s 0.040s 1.433 mxnet-dataiter NoInstallPlan NoInstallPlan 3.655s 3.231s 0.041s 0.032s 1.131 netease-fm Solution Solution 3.526s 3.079s 0.019s 0.031s 1.145 nomyx-language NoInstallPlan NoInstallPlan 3.482s 3.074s 0.030s 0.045s 1.133 notmuch-web BackjumpLimit NoInstallPlan 14.552s 5.956s 0.046s 0.047s 2.443 null-canvas NoInstallPlan NoInstallPlan 4.468s 2.938s 0.031s 0.033s 1.521 nymphaea NoInstallPlan NoInstallPlan 3.258s 2.794s 0.020s 0.035s 1.166 orchestrate BackjumpLimit Solution 11.511s 5.146s 0.068s 0.045s 2.237 ot NoInstallPlan NoInstallPlan 3.152s 2.862s 0.059s 0.038s 1.101 panda BackjumpLimit Solution 10.148s 5.647s 0.047s 0.039s 1.797 paypal-api NoInstallPlan NoInstallPlan 3.297s 2.844s 0.014s 0.037s 1.159 pdf-slave-server NoInstallPlan NoInstallPlan 3.578s 3.197s 0.018s 0.048s 1.119 perceptual-hash Solution Solution 5.804s 3.876s 0.043s 0.023s 1.498 persistent-protobuf Solution Solution 4.072s 3.175s 0.027s 0.036s 1.283 pgdl Solution Solution 4.226s 3.682s 0.023s 0.021s 1.148 phooey BackjumpLimit NoInstallPlan 8.662s 4.400s 0.046s 0.039s 1.969 pinpon NoInstallPlan NoInstallPlan 5.546s 4.047s 0.052s 0.018s 1.371 pipes-conduit NoInstallPlan NoInstallPlan 2.804s 2.671s 0.026s 0.044s 1.050 pipes-transduce NoInstallPlan NoInstallPlan 3.665s 2.876s 0.022s 0.019s 1.274 planet-mitchell NoInstallPlan NoInstallPlan 4.629s 3.785s 0.016s 0.029s 1.223 plot-gtk NoInstallPlan NoInstallPlan 4.754s 2.916s 0.019s 0.018s 1.630 plot-gtk3 NoInstallPlan NoInstallPlan 3.439s 2.987s 0.026s 0.018s 1.151 pontarius-mediaserver NoInstallPlan NoInstallPlan 3.308s 2.873s 0.064s 0.016s 1.152 pontarius-xpmn NoInstallPlan NoInstallPlan 3.389s 2.905s 0.028s 0.017s 1.167 poppler NoInstallPlan NoInstallPlan 3.933s 3.420s 0.036s 0.031s 1.150 portager NoInstallPlan NoInstallPlan 3.512s 3.100s 0.032s 0.042s 1.133 postgrest-ws BackjumpLimit NoInstallPlan 11.707s 4.717s 0.089s 0.045s 2.482 primula-bot NoInstallPlan NoInstallPlan 3.676s 2.886s 0.023s 0.034s 1.274 printcess NoInstallPlan NoInstallPlan 3.957s 3.137s 0.025s 0.037s 1.261 process-streaming NoInstallPlan NoInstallPlan 4.727s 3.392s 0.040s 0.027s 1.393 proplang NoInstallPlan NoInstallPlan 3.375s 2.835s 0.043s 0.042s 1.191 purescript-tsd-gen Solution Solution 4.792s 4.273s 0.038s 0.032s 1.121 push-notify BackjumpLimit NoInstallPlan 9.536s 4.017s 0.046s 0.033s 2.374 pushme Solution Solution 8.693s 4.317s 0.030s 0.025s 2.014 quickbooks NoInstallPlan NoInstallPlan 6.061s 3.346s 0.016s 0.034s 1.811 quiver-http NoInstallPlan NoInstallPlan 3.405s 3.042s 0.030s 0.028s 1.119 rail-compiler-editor NoInstallPlan NoInstallPlan 7.381s 2.987s 0.027s 0.022s 2.471 rasa-ext-slate Solution Solution 3.755s 3.351s 0.032s 0.026s 1.121 react-haskell NoInstallPlan NoInstallPlan 5.053s 3.391s 0.023s 0.032s 1.490 reactive Solution Solution 4.323s 2.926s 0.029s 0.029s 1.477 reactive-banana-wx NoInstallPlan NoInstallPlan 5.913s 3.508s 0.049s 0.022s 1.686 reactive-fieldtrip BackjumpLimit NoInstallPlan 6.739s 3.095s 0.028s 0.045s 2.177 reactive-glut BackjumpLimit NoInstallPlan 6.486s 2.890s 0.036s 0.026s 2.244 reflex-dom-colonnade Solution Solution 7.034s 6.734s 0.194s 0.190s 1.045 remote-json-server NoInstallPlan NoInstallPlan 3.614s 3.016s 0.043s 0.036s 1.198 rest-client NoInstallPlan NoInstallPlan 3.268s 2.909s 0.027s 0.025s 1.123 restful-snap NoInstallPlan NoInstallPlan 3.956s 3.476s 0.035s 0.028s 1.138 rhythm-game-tutorial NoInstallPlan NoInstallPlan 3.855s 3.300s 0.027s 0.016s 1.168 rob Solution Solution 3.754s 3.389s 0.024s 0.034s 1.108 roguestar-gl NoInstallPlan NoInstallPlan 3.305s 2.762s 0.027s 0.048s 1.197 route-generator NoInstallPlan NoInstallPlan 3.435s 2.731s 0.040s 0.014s 1.258 rsagl-frp NoInstallPlan NoInstallPlan 3.306s 2.854s 0.039s 0.010s 1.158 scotty-rest Solution Solution 4.081s 3.504s 0.031s 0.045s 1.165 semdoc NoInstallPlan NoInstallPlan 4.163s 3.529s 0.018s 0.033s 1.179 seqloc-datafiles NoInstallPlan NoInstallPlan 4.364s 3.127s 0.054s 0.028s 1.395 servant-auth-cookie NoInstallPlan NoInstallPlan 3.296s 2.934s 0.019s 0.020s 1.123 servant-auth-swagger Solution Solution 3.772s 3.460s 0.025s 0.024s 1.090 servant-auth-token NoInstallPlan NoInstallPlan 4.191s 3.607s 0.029s 0.022s 1.162 servant-auth-token-leveldb NoInstallPlan NoInstallPlan 4.018s 3.612s 0.022s 0.033s 1.112 servant-examples BackjumpLimit NoInstallPlan 12.497s 3.746s 0.059s 0.039s 3.336 serversession-frontend-yesod NoInstallPlan NoInstallPlan 3.050s 2.810s 0.029s 0.037s 1.086 sgrep NoInstallPlan NoInstallPlan 3.000s 2.716s 0.021s 0.025s 1.104 shuffle NoInstallPlan NoInstallPlan 3.196s 2.863s 0.019s 0.035s 1.116 simpleprelude NoInstallPlan NoInstallPlan 2.788s 2.601s 0.030s 0.062s 1.072 slidemews NoInstallPlan NoInstallPlan 3.108s 2.808s 0.027s 0.037s 1.107 smtps-gmail NoInstallPlan NoInstallPlan 3.327s 2.962s 0.030s 0.028s 1.123 snap-auth-cli NoInstallPlan NoInstallPlan 3.978s 3.310s 0.037s 0.029s 1.202 snap-elm BackjumpLimit NoInstallPlan 12.454s 4.631s 0.052s 0.020s 2.690 snap-web-routes NoInstallPlan NoInstallPlan 3.382s 2.973s 0.028s 0.030s 1.137 snaplet-acid-state NoInstallPlan NoInstallPlan 3.487s 2.913s 0.021s 0.017s 1.197 snaplet-actionlog NoInstallPlan NoInstallPlan 4.142s 3.330s 0.019s 0.031s 1.244 snaplet-coffee NoInstallPlan NoInstallPlan 4.566s 3.543s 0.036s 0.032s 1.289 snaplet-css-min NoInstallPlan NoInstallPlan 5.301s 2.971s 0.029s 0.020s 1.784 snaplet-customauth NoInstallPlan NoInstallPlan 4.249s 3.591s 0.025s 0.037s 1.183 snaplet-fay NoInstallPlan NoInstallPlan 3.656s 3.071s 0.033s 0.015s 1.191 snaplet-hasql NoInstallPlan NoInstallPlan 6.973s 4.184s 0.030s 0.035s 1.666 snaplet-mysql-simple NoInstallPlan NoInstallPlan 3.670s 2.961s 0.024s 0.016s 1.239 snaplet-persistent NoInstallPlan NoInstallPlan 6.576s 4.441s 0.032s 0.032s 1.481 snaplet-redson NoInstallPlan NoInstallPlan 2.975s 2.701s 0.028s 0.012s 1.101 snaplet-sedna NoInstallPlan NoInstallPlan 3.784s 4.431s 0.020s 0.035s 0.854 snaplet-sqlite-simple NoInstallPlan NoInstallPlan 3.695s 3.032s 0.046s 0.018s 1.219 socketio NoInstallPlan NoInstallPlan 3.091s 2.779s 0.032s 0.033s 1.112 soegtk NoInstallPlan NoInstallPlan 3.315s 3.013s 0.027s 0.019s 1.100 spike NoInstallPlan NoInstallPlan 3.340s 2.770s 0.028s 0.017s 1.206 ssh-tunnel NoInstallPlan NoInstallPlan 2.988s 2.749s 0.017s 0.028s 1.087 sssp NoInstallPlan NoInstallPlan 3.701s 3.209s 0.025s 0.033s 1.153 stack-run-auto NoInstallPlan NoInstallPlan 5.384s 4.049s 0.025s 0.039s 1.330 stackage BackjumpLimit Solution 7.417s 4.820s 0.058s 0.044s 1.539 stackage-build-plan NoInstallPlan NoInstallPlan 5.537s 4.026s 0.059s 0.083s 1.375 stackage-types NoInstallPlan NoInstallPlan 3.482s 2.897s 0.030s 0.029s 1.202 stratux NoInstallPlan NoInstallPlan 10.506s 4.368s 0.062s 0.038s 2.406 stripe Solution Solution 3.511s 3.182s 0.044s 0.032s 1.103 sunroof-examples NoInstallPlan NoInstallPlan 3.221s 2.960s 0.025s 0.045s 1.088 t3-client NoInstallPlan NoInstallPlan 6.026s 3.266s 0.047s 0.022s 1.845 t3-server NoInstallPlan NoInstallPlan 4.534s 3.273s 0.025s 0.026s 1.385 tamarin-prover-term NoInstallPlan NoInstallPlan 2.810s 2.629s 0.014s 0.053s 1.069 tcache-AWS Solution Solution 4.669s 3.733s 0.039s 0.037s 1.251 tellbot BackjumpLimit NoInstallPlan 6.123s 3.330s 0.037s 0.025s 1.839 text-icu-normalized Solution Solution 4.748s 3.629s 0.028s 0.030s 1.308 thumbnail-plus NoInstallPlan NoInstallPlan 3.221s 2.785s 0.037s 0.017s 1.157 tickle NoInstallPlan NoInstallPlan 4.934s 3.991s 0.035s 0.019s 1.236 tiger NoInstallPlan NoInstallPlan 3.066s 2.715s 0.050s 0.034s 1.129 tightrope Solution Solution 4.995s 4.069s 0.031s 0.032s 1.227 tighttp NoInstallPlan NoInstallPlan 3.903s 2.739s 0.030s 0.039s 1.425 tkyprof BackjumpLimit Solution 13.516s 5.429s 0.082s 0.042s 2.490 toktok NoInstallPlan NoInstallPlan 3.373s 2.883s 0.014s 0.038s 1.170 too-many-cells BackjumpLimit NoInstallPlan 10.111s 4.412s 0.054s 0.031s 2.292 travis Solution Solution 3.419s 3.085s 0.026s 0.024s 1.108 traypoweroff NoInstallPlan NoInstallPlan 2.839s 2.590s 0.024s 0.050s 1.096 twidge NoInstallPlan NoInstallPlan 3.424s 2.865s 0.026s 0.022s 1.195 typescript-docs NoInstallPlan NoInstallPlan 3.357s 3.012s 0.035s 0.016s 1.114 unitym-yesod NoInstallPlan NoInstallPlan 4.123s 3.240s 0.031s 0.028s 1.273 unix-process-conduit NoInstallPlan NoInstallPlan 3.027s 2.762s 0.052s 0.023s 1.096 uri-parse NoInstallPlan NoInstallPlan 4.532s 3.390s 0.031s 0.028s 1.337 uu-cco-examples Solution Solution 3.172s 2.760s 0.058s 0.041s 1.149 uuagc NoInstallPlan NoInstallPlan 3.287s 2.718s 0.023s 0.018s 1.209 validate-input NoInstallPlan NoInstallPlan 5.469s 3.165s 0.043s 0.019s 1.728 verify NoInstallPlan NoInstallPlan 4.204s 3.193s 0.037s 0.047s 1.317 vtegtk3 NoInstallPlan NoInstallPlan 3.202s 2.967s 0.032s 0.049s 1.079 vty-ui NoInstallPlan NoInstallPlan 3.656s 2.961s 0.024s 0.029s 1.235 wai-handler-devel NoInstallPlan NoInstallPlan 5.897s 3.232s 0.061s 0.038s 1.825 wai-lite NoInstallPlan NoInstallPlan 3.107s 2.813s 0.039s 0.026s 1.105 wai-middleware-cache-redis NoInstallPlan NoInstallPlan 3.619s 2.734s 0.018s 0.023s 1.324 wai-middleware-route Solution Solution 3.792s 2.902s 0.027s 0.022s 1.307 wai-throttler NoInstallPlan NoInstallPlan 3.445s 2.922s 0.019s 0.037s 1.179 warp-dynamic NoInstallPlan NoInstallPlan 3.040s 2.641s 0.021s 0.033s 1.151 warp-static NoInstallPlan NoInstallPlan 3.395s 2.790s 0.009s 0.030s 1.217 web-browser-in-haskell NoInstallPlan NoInstallPlan 3.749s 2.971s 0.049s 0.028s 1.262 web-encodings NoInstallPlan NoInstallPlan 3.118s 2.880s 0.056s 0.042s 1.082 webkit NoInstallPlan NoInstallPlan 3.840s 3.115s 0.028s 0.057s 1.233 webkitgtk3 NoInstallPlan NoInstallPlan 3.718s 3.098s 0.026s 0.060s 1.200 websnap NoInstallPlan NoInstallPlan 3.730s 2.962s 0.020s 0.040s 1.259 werewolf Solution Solution 3.692s 3.286s 0.034s 0.038s 1.124 wobsurv NoInstallPlan NoInstallPlan 3.292s 2.943s 0.028s 0.040s 1.119 wordchoice Solution Solution 5.822s 4.729s 0.030s 0.020s 1.231 wx BackjumpLimit Solution 8.170s 5.570s 0.046s 0.041s 1.467 wxAsteroids BackjumpLimit Solution 7.319s 3.943s 0.035s 0.022s 1.856 wxFruit Solution Solution 4.706s 3.691s 0.022s 0.036s 1.275 wxc NoInstallPlan NoInstallPlan 4.619s 3.004s 0.041s 0.035s 1.538 wxcore Solution Solution 5.077s 3.650s 0.025s 0.023s 1.391 wxdirect NoInstallPlan NoInstallPlan 3.605s 3.083s 0.054s 0.028s 1.169 wxhnotepad NoInstallPlan NoInstallPlan 4.686s 3.422s 0.054s 0.019s 1.369 xdcc NoInstallPlan NoInstallPlan 3.205s 2.873s 0.021s 0.022s 1.116 xml-pipe NoInstallPlan NoInstallPlan 3.167s 2.674s 0.016s 0.025s 1.184 xmpipe NoInstallPlan NoInstallPlan 3.579s 2.746s 0.037s 0.023s 1.303 xournal-render NoInstallPlan NoInstallPlan 3.638s 2.805s 0.027s 0.022s 1.297 xtc Solution Solution 5.028s 3.634s 0.034s 0.021s 1.384 yesod-auth-account-fork BackjumpLimit NoInstallPlan 7.659s 3.841s 0.040s 0.060s 1.994 yesod-auth-bcrypt NoInstallPlan NoInstallPlan 8.870s 3.970s 0.042s 0.016s 2.234 yesod-auth-deskcom NoInstallPlan NoInstallPlan 4.654s 3.413s 0.027s 0.047s 1.364 yesod-auth-ldap NoInstallPlan NoInstallPlan 2.996s 2.751s 0.020s 0.024s 1.089 yesod-auth-nopassword NoInstallPlan NoInstallPlan 4.579s 3.880s 0.024s 0.027s 1.180 yesod-auth-zendesk NoInstallPlan NoInstallPlan 4.519s 3.111s 0.029s 0.019s 1.452 yesod-comments BackjumpLimit NoInstallPlan 6.942s 3.679s 0.046s 0.018s 1.887 yesod-crud NoInstallPlan NoInstallPlan 3.521s 3.189s 0.034s 0.055s 1.104 yesod-form-richtext NoInstallPlan NoInstallPlan 4.928s 3.741s 0.028s 0.020s 1.317 yesod-goodies NoInstallPlan NoInstallPlan 3.552s 2.733s 0.038s 0.043s 1.299 yesod-job-queue Solution Solution 7.796s 6.027s 0.063s 0.035s 1.293 yesod-links NoInstallPlan NoInstallPlan 4.181s 3.117s 0.014s 0.029s 1.341 yesod-lucid NoInstallPlan NoInstallPlan 3.910s 3.414s 0.026s 0.033s 1.145 yesod-mangopay BackjumpLimit NoInstallPlan 7.016s 3.461s 0.058s 0.026s 2.027 yesod-paypal-rest NoInstallPlan NoInstallPlan 3.867s 3.387s 0.020s 0.016s 1.142 yesod-platform NoInstallPlan NoInstallPlan 3.551s 3.159s 0.035s 0.059s 1.124 yesod-pure Solution Solution 4.620s 3.791s 0.038s 0.037s 1.219 yesod-purescript NoInstallPlan NoInstallPlan 5.426s 3.827s 0.074s 0.027s 1.418 yesod-recaptcha BackjumpLimit NoInstallPlan 15.046s 4.573s 0.085s 0.030s 3.290 yesod-sass NoInstallPlan NoInstallPlan 3.081s 2.820s 0.026s 0.033s 1.092 yesod-session-redis NoInstallPlan NoInstallPlan 5.285s 3.769s 0.066s 0.022s 1.402 yesod-static-angular BackjumpLimit NoInstallPlan 7.646s 3.549s 0.030s 0.026s 2.155 yesod-tls NoInstallPlan NoInstallPlan 4.109s 3.256s 0.014s 0.057s 1.262 yesod-vend NoInstallPlan NoInstallPlan 4.952s 3.769s 0.049s 0.039s 1.314 yi-contrib UnbuildableDep UnbuildableDep 3.663s 2.949s 0.028s 0.032s 1.242 yi-frontend-pango NoInstallPlan NoInstallPlan 3.735s 3.175s 0.034s 0.021s 1.176 z85 NoInstallPlan NoInstallPlan 4.004s 2.927s 0.034s 0.027s 1.368 zephyr Solution Solution 4.482s 4.189s 0.053s 0.053s 1.070 zeroth Solution Solution 3.383s 2.866s 0.021s 0.023s 1.180 zifter-stack Solution Solution 3.338s 3.040s 0.042s 0.035s 1.098 ziptastic-client BackjumpLimit NoInstallPlan 18.042s 4.060s 0.110s 0.034s 4.443 zoom-cache-sndfile NoInstallPlan NoInstallPlan 3.622s 2.844s 0.025s 0.022s 1.274 --- Cabal/doc/nix-local-build.rst | 17 + cabal-install/Distribution/Client/Config.hs | 1 + .../Distribution/Client/Dependency.hs | 20 +- cabal-install/Distribution/Client/Fetch.hs | 3 + cabal-install/Distribution/Client/Freeze.hs | 3 + cabal-install/Distribution/Client/Install.hs | 3 + .../Distribution/Client/ProjectConfig.hs | 2 + .../Client/ProjectConfig/Legacy.hs | 7 +- .../Client/ProjectConfig/Types.hs | 2 + .../Distribution/Client/ProjectPlanning.hs | 2 + cabal-install/Distribution/Client/Setup.hs | 19 +- .../Distribution/Solver/Modular/Builder.hs | 4 +- .../Solver/Modular/ConflictSet.hs | 89 +++++- .../Distribution/Solver/Modular/Dependency.hs | 53 ++++ .../Distribution/Solver/Modular/Explore.hs | 191 +++++++++-- .../Distribution/Solver/Modular/Message.hs | 112 +++++++ .../Distribution/Solver/Modular/Preference.hs | 4 +- .../Distribution/Solver/Modular/Solver.hs | 3 + .../Distribution/Solver/Modular/Validate.hs | 43 ++- .../Distribution/Solver/Types/Settings.hs | 6 + cabal-install/changelog | 3 + .../Distribution/Solver/Modular/DSL.hs | 8 +- .../Distribution/Client/ProjectConfig.hs | 62 ++-- .../Distribution/Client/TreeDiffInstances.hs | 1 + .../Solver/Modular/DSL/TestCaseUtils.hs | 11 +- .../Solver/Modular/MemoryUsage.hs | 23 +- .../Distribution/Solver/Modular/QuickCheck.hs | 63 +++- .../Distribution/Solver/Modular/Solver.hs | 300 ++++++++++++++++-- 28 files changed, 915 insertions(+), 140 deletions(-) diff --git a/Cabal/doc/nix-local-build.rst b/Cabal/doc/nix-local-build.rst index dbba194206f..1535cbb2b37 100644 --- a/Cabal/doc/nix-local-build.rst +++ b/Cabal/doc/nix-local-build.rst @@ -2115,6 +2115,23 @@ Most users generally won't need these. The command line variant of this field is ``--(no-)count-conflicts``. +.. cfg-field:: fine-grained-conflicts: boolean + --fine-grained-conflicts + --no-fine-grained-conflicts + :synopsis: Skip a version of a package if it does not resolve any conflicts + encountered in the last version (solver optimization). + + :default: True + + When enabled, the solver will skip a version of a package if it does not + resolve any of the conflicts encountered in the last version of that + package. For example, if ``foo-1.2`` depended on ``bar``, and the solver + couldn't find consistent versions for ``bar``'s dependencies, then the + solver would skip ``foo-1.1`` if it also depended on ``bar``. + + The command line variant of this field is + ``--(no-)fine-grained-conflicts``. + .. cfg-field:: minimize-conflict-set: boolean --minimize-conflict-set --no-minimize-conflict-set diff --git a/cabal-install/Distribution/Client/Config.hs b/cabal-install/Distribution/Client/Config.hs index 944175725c8..5072cab7189 100644 --- a/cabal-install/Distribution/Client/Config.hs +++ b/cabal-install/Distribution/Client/Config.hs @@ -312,6 +312,7 @@ instance Semigroup SavedConfig where installMaxBackjumps = combine installMaxBackjumps, installReorderGoals = combine installReorderGoals, installCountConflicts = combine installCountConflicts, + installFineGrainedConflicts = combine installFineGrainedConflicts, installMinimizeConflictSet = combine installMinimizeConflictSet, installIndependentGoals = combine installIndependentGoals, installShadowPkgs = combine installShadowPkgs, diff --git a/cabal-install/Distribution/Client/Dependency.hs b/cabal-install/Distribution/Client/Dependency.hs index faef55062a5..f73f2e46086 100644 --- a/cabal-install/Distribution/Client/Dependency.hs +++ b/cabal-install/Distribution/Client/Dependency.hs @@ -47,6 +47,7 @@ module Distribution.Client.Dependency ( setPreferenceDefault, setReorderGoals, setCountConflicts, + setFineGrainedConflicts, setMinimizeConflictSet, setIndependentGoals, setAvoidReinstalls, @@ -159,6 +160,7 @@ data DepResolverParams = DepResolverParams { depResolverSourcePkgIndex :: PackageIndex.PackageIndex UnresolvedSourcePackage, depResolverReorderGoals :: ReorderGoals, depResolverCountConflicts :: CountConflicts, + depResolverFineGrainedConflicts :: FineGrainedConflicts, depResolverMinimizeConflictSet :: MinimizeConflictSet, depResolverIndependentGoals :: IndependentGoals, depResolverAvoidReinstalls :: AvoidReinstalls, @@ -197,6 +199,7 @@ showDepResolverParams p = ++ "\nstrategy: " ++ show (depResolverPreferenceDefault p) ++ "\nreorder goals: " ++ show (asBool (depResolverReorderGoals p)) ++ "\ncount conflicts: " ++ show (asBool (depResolverCountConflicts p)) + ++ "\nfine grained conflicts: " ++ show (asBool (depResolverFineGrainedConflicts p)) ++ "\nminimize conflict set: " ++ show (asBool (depResolverMinimizeConflictSet p)) ++ "\nindependent goals: " ++ show (asBool (depResolverIndependentGoals p)) ++ "\navoid reinstalls: " ++ show (asBool (depResolverAvoidReinstalls p)) @@ -254,6 +257,7 @@ basicDepResolverParams installedPkgIndex sourcePkgIndex = depResolverSourcePkgIndex = sourcePkgIndex, depResolverReorderGoals = ReorderGoals False, depResolverCountConflicts = CountConflicts True, + depResolverFineGrainedConflicts = FineGrainedConflicts True, depResolverMinimizeConflictSet = MinimizeConflictSet False, depResolverIndependentGoals = IndependentGoals False, depResolverAvoidReinstalls = AvoidReinstalls False, @@ -310,6 +314,12 @@ setCountConflicts count params = depResolverCountConflicts = count } +setFineGrainedConflicts :: FineGrainedConflicts -> DepResolverParams -> DepResolverParams +setFineGrainedConflicts fineGrained params = + params { + depResolverFineGrainedConflicts = fineGrained + } + setMinimizeConflictSet :: MinimizeConflictSet -> DepResolverParams -> DepResolverParams setMinimizeConflictSet minimize params = params { @@ -755,7 +765,8 @@ resolveDependencies platform comp pkgConfigDB solver params = Step (showDepResolverParams finalparams) $ fmap (validateSolverResult platform comp indGoals) - $ runSolver solver (SolverConfig reordGoals cntConflicts minimize indGoals noReinstalls + $ runSolver solver (SolverConfig reordGoals cntConflicts fineGrained minimize + indGoals noReinstalls shadowing strFlags allowBootLibs onlyConstrained_ maxBkjumps enableBj solveExes order verbosity (PruneAfterFirstSuccess False)) platform comp installedPkgIndex sourcePkgIndex @@ -769,6 +780,7 @@ resolveDependencies platform comp pkgConfigDB solver params = sourcePkgIndex reordGoals cntConflicts + fineGrained minimize indGoals noReinstalls @@ -1015,9 +1027,9 @@ resolveWithoutDependencies :: DepResolverParams -> Either [ResolveNoDepsError] [UnresolvedSourcePackage] resolveWithoutDependencies (DepResolverParams targets constraints prefs defpref installedPkgIndex sourcePkgIndex - _reorderGoals _countConflicts _minimizeConflictSet - _indGoals _avoidReinstalls _shadowing _strFlags - _maxBjumps _enableBj _solveExes + _reorderGoals _countConflicts _fineGrained + _minimizeConflictSet _indGoals _avoidReinstalls + _shadowing _strFlags _maxBjumps _enableBj _solveExes _allowBootLibInstalls _onlyConstrained _order _verbosity) = collectEithers $ map selectPackage (Set.toList targets) where diff --git a/cabal-install/Distribution/Client/Fetch.hs b/cabal-install/Distribution/Client/Fetch.hs index ac0a74a907d..11d8cfa299e 100644 --- a/cabal-install/Distribution/Client/Fetch.hs +++ b/cabal-install/Distribution/Client/Fetch.hs @@ -162,6 +162,8 @@ planPackages verbosity comp platform fetchFlags . setCountConflicts countConflicts + . setFineGrainedConflicts fineGrainedConflicts + . setMinimizeConflictSet minimizeConflictSet . setShadowPkgs shadowPkgs @@ -199,6 +201,7 @@ planPackages verbosity comp platform fetchFlags reorderGoals = fromFlag (fetchReorderGoals fetchFlags) countConflicts = fromFlag (fetchCountConflicts fetchFlags) + fineGrainedConflicts = fromFlag (fetchFineGrainedConflicts fetchFlags) minimizeConflictSet = fromFlag (fetchMinimizeConflictSet fetchFlags) independentGoals = fromFlag (fetchIndependentGoals fetchFlags) shadowPkgs = fromFlag (fetchShadowPkgs fetchFlags) diff --git a/cabal-install/Distribution/Client/Freeze.hs b/cabal-install/Distribution/Client/Freeze.hs index 3b8b2c193ab..99694ed9d3b 100644 --- a/cabal-install/Distribution/Client/Freeze.hs +++ b/cabal-install/Distribution/Client/Freeze.hs @@ -175,6 +175,8 @@ planPackages verbosity comp platform mSandboxPkgInfo freezeFlags . setCountConflicts countConflicts + . setFineGrainedConflicts fineGrainedConflicts + . setMinimizeConflictSet minimizeConflictSet . setShadowPkgs shadowPkgs @@ -207,6 +209,7 @@ planPackages verbosity comp platform mSandboxPkgInfo freezeFlags reorderGoals = fromFlag (freezeReorderGoals freezeFlags) countConflicts = fromFlag (freezeCountConflicts freezeFlags) + fineGrainedConflicts = fromFlag (freezeFineGrainedConflicts freezeFlags) minimizeConflictSet = fromFlag (freezeMinimizeConflictSet freezeFlags) independentGoals = fromFlag (freezeIndependentGoals freezeFlags) shadowPkgs = fromFlag (freezeShadowPkgs freezeFlags) diff --git a/cabal-install/Distribution/Client/Install.hs b/cabal-install/Distribution/Client/Install.hs index 82f91e83a2d..229816d4457 100644 --- a/cabal-install/Distribution/Client/Install.hs +++ b/cabal-install/Distribution/Client/Install.hs @@ -395,6 +395,8 @@ planPackages verbosity comp platform mSandboxPkgInfo solver . setCountConflicts countConflicts + . setFineGrainedConflicts fineGrainedConflicts + . setMinimizeConflictSet minimizeConflictSet . setAvoidReinstalls avoidReinstalls @@ -463,6 +465,7 @@ planPackages verbosity comp platform mSandboxPkgInfo solver fromFlag (installReinstall installFlags) reorderGoals = fromFlag (installReorderGoals installFlags) countConflicts = fromFlag (installCountConflicts installFlags) + fineGrainedConflicts = fromFlag (installFineGrainedConflicts installFlags) minimizeConflictSet = fromFlag (installMinimizeConflictSet installFlags) independentGoals = fromFlag (installIndependentGoals installFlags) avoidReinstalls = fromFlag (installAvoidReinstalls installFlags) diff --git a/cabal-install/Distribution/Client/ProjectConfig.hs b/cabal-install/Distribution/Client/ProjectConfig.hs index 8282a6beaf2..234c5f13395 100644 --- a/cabal-install/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/Distribution/Client/ProjectConfig.hs @@ -250,6 +250,7 @@ resolveSolverSettings ProjectConfig{ | otherwise -> Just n solverSettingReorderGoals = fromFlag projectConfigReorderGoals solverSettingCountConflicts = fromFlag projectConfigCountConflicts + solverSettingFineGrainedConflicts = fromFlag projectConfigFineGrainedConflicts solverSettingMinimizeConflictSet = fromFlag projectConfigMinimizeConflictSet solverSettingStrongFlags = fromFlag projectConfigStrongFlags solverSettingAllowBootLibInstalls = fromFlag projectConfigAllowBootLibInstalls @@ -271,6 +272,7 @@ resolveSolverSettings ProjectConfig{ projectConfigMaxBackjumps = Flag defaultMaxBackjumps, projectConfigReorderGoals = Flag (ReorderGoals False), projectConfigCountConflicts = Flag (CountConflicts True), + projectConfigFineGrainedConflicts = Flag (FineGrainedConflicts True), projectConfigMinimizeConflictSet = Flag (MinimizeConflictSet False), projectConfigStrongFlags = Flag (StrongFlags False), projectConfigAllowBootLibInstalls = Flag (AllowBootLibInstalls False), diff --git a/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs index 53975930bf2..66f009a5855 100644 --- a/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs +++ b/cabal-install/Distribution/Client/ProjectConfig/Legacy.hs @@ -374,6 +374,7 @@ convertLegacyAllPackageFlags globalFlags configFlags --installUpgradeDeps = projectConfigUpgradeDeps, installReorderGoals = projectConfigReorderGoals, installCountConflicts = projectConfigCountConflicts, + installFineGrainedConflicts = projectConfigFineGrainedConflicts, installMinimizeConflictSet = projectConfigMinimizeConflictSet, installPerComponent = projectConfigPerComponent, installIndependentGoals = projectConfigIndependentGoals, @@ -611,6 +612,7 @@ convertToLegacySharedConfig installUpgradeDeps = mempty, --projectConfigUpgradeDeps, installReorderGoals = projectConfigReorderGoals, installCountConflicts = projectConfigCountConflicts, + installFineGrainedConflicts = projectConfigFineGrainedConflicts, installMinimizeConflictSet = projectConfigMinimizeConflictSet, installIndependentGoals = projectConfigIndependentGoals, installShadowPkgs = mempty, --projectConfigShadowPkgs, @@ -1004,8 +1006,9 @@ legacySharedConfigFieldDescrs = , "one-shot", "jobs", "keep-going", "offline", "per-component" -- solver flags: , "max-backjumps", "reorder-goals", "count-conflicts" - , "minimize-conflict-set", "independent-goals" - , "strong-flags" , "allow-boot-library-installs", "reject-unconstrained-dependencies", "index-state" + , "fine-grained-conflicts" , "minimize-conflict-set", "independent-goals" + , "strong-flags" , "allow-boot-library-installs" + , "reject-unconstrained-dependencies", "index-state" ] . commandOptionsToFields ) (installOptions ParseArgs) diff --git a/cabal-install/Distribution/Client/ProjectConfig/Types.hs b/cabal-install/Distribution/Client/ProjectConfig/Types.hs index 7e02e3863a9..8b504f94568 100644 --- a/cabal-install/Distribution/Client/ProjectConfig/Types.hs +++ b/cabal-install/Distribution/Client/ProjectConfig/Types.hs @@ -195,6 +195,7 @@ data ProjectConfigShared projectConfigMaxBackjumps :: Flag Int, projectConfigReorderGoals :: Flag ReorderGoals, projectConfigCountConflicts :: Flag CountConflicts, + projectConfigFineGrainedConflicts :: Flag FineGrainedConflicts, projectConfigMinimizeConflictSet :: Flag MinimizeConflictSet, projectConfigStrongFlags :: Flag StrongFlags, projectConfigAllowBootLibInstalls :: Flag AllowBootLibInstalls, @@ -400,6 +401,7 @@ data SolverSettings solverSettingMaxBackjumps :: Maybe Int, solverSettingReorderGoals :: ReorderGoals, solverSettingCountConflicts :: CountConflicts, + solverSettingFineGrainedConflicts :: FineGrainedConflicts, solverSettingMinimizeConflictSet :: MinimizeConflictSet, solverSettingStrongFlags :: StrongFlags, solverSettingAllowBootLibInstalls :: AllowBootLibInstalls, diff --git a/cabal-install/Distribution/Client/ProjectPlanning.hs b/cabal-install/Distribution/Client/ProjectPlanning.hs index d139379da06..ae31ebb06da 100644 --- a/cabal-install/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/Distribution/Client/ProjectPlanning.hs @@ -958,6 +958,8 @@ planPackages verbosity comp platform solver SolverSettings{..} . setCountConflicts solverSettingCountConflicts + . setFineGrainedConflicts solverSettingFineGrainedConflicts + . setMinimizeConflictSet solverSettingMinimizeConflictSet --TODO: [required eventually] should only be configurable for diff --git a/cabal-install/Distribution/Client/Setup.hs b/cabal-install/Distribution/Client/Setup.hs index b063c03c80b..9dce8c24dc8 100644 --- a/cabal-install/Distribution/Client/Setup.hs +++ b/cabal-install/Distribution/Client/Setup.hs @@ -1003,6 +1003,7 @@ data FetchFlags = FetchFlags { fetchMaxBackjumps :: Flag Int, fetchReorderGoals :: Flag ReorderGoals, fetchCountConflicts :: Flag CountConflicts, + fetchFineGrainedConflicts :: Flag FineGrainedConflicts, fetchMinimizeConflictSet :: Flag MinimizeConflictSet, fetchIndependentGoals :: Flag IndependentGoals, fetchShadowPkgs :: Flag ShadowPkgs, @@ -1023,6 +1024,7 @@ defaultFetchFlags = FetchFlags { fetchMaxBackjumps = Flag defaultMaxBackjumps, fetchReorderGoals = Flag (ReorderGoals False), fetchCountConflicts = Flag (CountConflicts True), + fetchFineGrainedConflicts = Flag (FineGrainedConflicts True), fetchMinimizeConflictSet = Flag (MinimizeConflictSet False), fetchIndependentGoals = Flag (IndependentGoals False), fetchShadowPkgs = Flag (ShadowPkgs False), @@ -1085,6 +1087,7 @@ fetchCommand = CommandUI { fetchMaxBackjumps (\v flags -> flags { fetchMaxBackjumps = v }) fetchReorderGoals (\v flags -> flags { fetchReorderGoals = v }) fetchCountConflicts (\v flags -> flags { fetchCountConflicts = v }) + fetchFineGrainedConflicts (\v flags -> flags { fetchFineGrainedConflicts = v }) fetchMinimizeConflictSet (\v flags -> flags { fetchMinimizeConflictSet = v }) fetchIndependentGoals (\v flags -> flags { fetchIndependentGoals = v }) fetchShadowPkgs (\v flags -> flags { fetchShadowPkgs = v }) @@ -1106,6 +1109,7 @@ data FreezeFlags = FreezeFlags { freezeMaxBackjumps :: Flag Int, freezeReorderGoals :: Flag ReorderGoals, freezeCountConflicts :: Flag CountConflicts, + freezeFineGrainedConflicts :: Flag FineGrainedConflicts, freezeMinimizeConflictSet :: Flag MinimizeConflictSet, freezeIndependentGoals :: Flag IndependentGoals, freezeShadowPkgs :: Flag ShadowPkgs, @@ -1124,6 +1128,7 @@ defaultFreezeFlags = FreezeFlags { freezeMaxBackjumps = Flag defaultMaxBackjumps, freezeReorderGoals = Flag (ReorderGoals False), freezeCountConflicts = Flag (CountConflicts True), + freezeFineGrainedConflicts = Flag (FineGrainedConflicts True), freezeMinimizeConflictSet = Flag (MinimizeConflictSet False), freezeIndependentGoals = Flag (IndependentGoals False), freezeShadowPkgs = Flag (ShadowPkgs False), @@ -1177,6 +1182,7 @@ freezeCommand = CommandUI { freezeMaxBackjumps (\v flags -> flags { freezeMaxBackjumps = v }) freezeReorderGoals (\v flags -> flags { freezeReorderGoals = v }) freezeCountConflicts (\v flags -> flags { freezeCountConflicts = v }) + freezeFineGrainedConflicts (\v flags -> flags { freezeFineGrainedConflicts = v }) freezeMinimizeConflictSet (\v flags -> flags { freezeMinimizeConflictSet = v }) freezeIndependentGoals (\v flags -> flags { freezeIndependentGoals = v }) freezeShadowPkgs (\v flags -> flags { freezeShadowPkgs = v }) @@ -1749,6 +1755,7 @@ data InstallFlags = InstallFlags { installMaxBackjumps :: Flag Int, installReorderGoals :: Flag ReorderGoals, installCountConflicts :: Flag CountConflicts, + installFineGrainedConflicts :: Flag FineGrainedConflicts, installMinimizeConflictSet :: Flag MinimizeConflictSet, installIndependentGoals :: Flag IndependentGoals, installShadowPkgs :: Flag ShadowPkgs, @@ -1798,6 +1805,7 @@ defaultInstallFlags = InstallFlags { installMaxBackjumps = Flag defaultMaxBackjumps, installReorderGoals = Flag (ReorderGoals False), installCountConflicts = Flag (CountConflicts True), + installFineGrainedConflicts = Flag (FineGrainedConflicts True), installMinimizeConflictSet = Flag (MinimizeConflictSet False), installIndependentGoals= Flag (IndependentGoals False), installShadowPkgs = Flag (ShadowPkgs False), @@ -2028,6 +2036,7 @@ installOptions showOrParseArgs = installMaxBackjumps (\v flags -> flags { installMaxBackjumps = v }) installReorderGoals (\v flags -> flags { installReorderGoals = v }) installCountConflicts (\v flags -> flags { installCountConflicts = v }) + installFineGrainedConflicts (\v flags -> flags { installFineGrainedConflicts = v }) installMinimizeConflictSet (\v flags -> flags { installMinimizeConflictSet = v }) installIndependentGoals (\v flags -> flags { installIndependentGoals = v }) installShadowPkgs (\v flags -> flags { installShadowPkgs = v }) @@ -2860,6 +2869,7 @@ optionSolverFlags :: ShowOrParseArgs -> (flags -> Flag Int ) -> (Flag Int -> flags -> flags) -> (flags -> Flag ReorderGoals) -> (Flag ReorderGoals -> flags -> flags) -> (flags -> Flag CountConflicts) -> (Flag CountConflicts -> flags -> flags) + -> (flags -> Flag FineGrainedConflicts) -> (Flag FineGrainedConflicts -> flags -> flags) -> (flags -> Flag MinimizeConflictSet) -> (Flag MinimizeConflictSet -> flags -> flags) -> (flags -> Flag IndependentGoals) -> (Flag IndependentGoals -> flags -> flags) -> (flags -> Flag ShadowPkgs) -> (Flag ShadowPkgs -> flags -> flags) @@ -2868,8 +2878,8 @@ optionSolverFlags :: ShowOrParseArgs -> (flags -> Flag OnlyConstrained) -> (Flag OnlyConstrained -> flags -> flags) -> [OptionField flags] optionSolverFlags showOrParseArgs getmbj setmbj getrg setrg getcc setcc - getmc setmc getig setig getsip setsip getstrfl setstrfl - getib setib getoc setoc = + getfgc setfgc getmc setmc getig setig getsip setsip + getstrfl setstrfl getib setib getoc setoc = [ option [] ["max-backjumps"] ("Maximum number of backjumps allowed while solving (default: " ++ show defaultMaxBackjumps ++ "). Use a negative number to enable unlimited backtracking. Use 0 to disable backtracking completely.") getmbj setmbj @@ -2885,6 +2895,11 @@ optionSolverFlags showOrParseArgs getmbj setmbj getrg setrg getcc setcc (fmap asBool . getcc) (setcc . fmap CountConflicts) (yesNoOpt showOrParseArgs) + , option [] ["fine-grained-conflicts"] + "Skip a version of a package if it does not resolve the conflicts encountered in the last version, as a solver optimization (default)." + (fmap asBool . getfgc) + (setfgc . fmap FineGrainedConflicts) + (yesNoOpt showOrParseArgs) , option [] ["minimize-conflict-set"] ("When there is no solution, try to improve the error message by finding " ++ "a minimal conflict set (default: false). May increase run time " diff --git a/cabal-install/Distribution/Solver/Modular/Builder.hs b/cabal-install/Distribution/Solver/Modular/Builder.hs index f53910e3b51..eb11a36aa16 100644 --- a/cabal-install/Distribution/Solver/Modular/Builder.hs +++ b/cabal-install/Distribution/Solver/Modular/Builder.hs @@ -145,7 +145,9 @@ addChildren bs@(BS { rdeps = rdm, open = gs, next = Goals }) -- and then handle each instance in turn. addChildren bs@(BS { rdeps = rdm, index = idx, next = OneGoal (PkgGoal qpn@(Q _ pn) gr) }) = case M.lookup pn idx of - Nothing -> FailF (varToConflictSet (P qpn) `CS.union` goalReasonToConflictSet gr) UnknownPackage + Nothing -> FailF + (varToConflictSet (P qpn) `CS.union` goalReasonToConflictSetWithConflict qpn gr) + UnknownPackage Just pis -> PChoiceF qpn rdm gr (W.fromList (L.map (\ (i, info) -> ([], POption i Nothing, bs { next = Instance qpn info })) (M.toList pis))) diff --git a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs index 3c762369414..190e811f06f 100644 --- a/cabal-install/Distribution/Solver/Modular/ConflictSet.hs +++ b/cabal-install/Distribution/Solver/Modular/ConflictSet.hs @@ -10,7 +10,9 @@ -- > import qualified Distribution.Solver.Modular.ConflictSet as CS module Distribution.Solver.Modular.ConflictSet ( ConflictSet -- opaque + , Conflict(..) , ConflictMap + , OrderedVersionRange(..) #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin #endif @@ -26,17 +28,21 @@ module Distribution.Solver.Modular.ConflictSet ( , delete , empty , singleton + , singletonWithConflict , size , member + , lookup + , filter , fromList ) where +import Prelude hiding (lookup) import Data.List (intercalate, sortBy) import Data.Map (Map) import Data.Set (Set) import Data.Function (on) +import qualified Data.Map.Strict as M import qualified Data.Set as S -import qualified Data.Map as M #ifdef DEBUG_CONFLICT_SETS import Data.Tree @@ -44,12 +50,14 @@ import GHC.Stack #endif import Distribution.Solver.Modular.Var +import Distribution.Solver.Modular.Version import Distribution.Solver.Types.PackagePath --- | The set of variables involved in a solver conflict +-- | The set of variables involved in a solver conflict, each paired with +-- details about the conflict. data ConflictSet = CS { -- | The set of variables involved in the conflict - conflictSetToSet :: !(Set (Var QPN)) + conflictSetToMap :: !(Map (Var QPN) (Set Conflict)) #ifdef DEBUG_CONFLICT_SETS -- | The origin of the conflict set @@ -67,11 +75,48 @@ data ConflictSet = CS { } deriving (Show) +-- | More detailed information about how a conflict set variable caused a +-- conflict. This information can be used to determine whether a second value +-- for that variable would lead to the same conflict. +-- +-- TODO: Handle dependencies under flags or stanzas. +data Conflict = + + -- | The conflict set variable represents a package which depends on the + -- specified problematic package. For example, the conflict set entry + -- '(P x, GoalConflict y)' means that package x introduced package y, and y + -- led to a conflict. + GoalConflict QPN + + -- | The conflict set variable represents a package with a constraint that + -- excluded the specified package and version. For example, the conflict set + -- entry '(P x, VersionConstraintConflict y (mkVersion [2, 0]))' means that + -- package x's constraint on y excluded y-2.0. + | VersionConstraintConflict QPN Ver + + -- | The conflict set variable represents a package that was excluded by a + -- constraint from the specified package. For example, the conflict set + -- entry '(P x, VersionConflict y (orLaterVersion (mkVersion [2, 0])))' + -- means that package y's constraint 'x >= 2.0' excluded some version of x. + | VersionConflict QPN OrderedVersionRange + + -- | Any other conflict. + | OtherConflict + deriving (Eq, Ord, Show) + +-- | Version range with an 'Ord' instance. +newtype OrderedVersionRange = OrderedVersionRange VR + deriving (Eq, Show) + +-- TODO: Avoid converting the version ranges to strings. +instance Ord OrderedVersionRange where + compare = compare `on` show + instance Eq ConflictSet where - (==) = (==) `on` conflictSetToSet + (==) = (==) `on` conflictSetToMap instance Ord ConflictSet where - compare = compare `on` conflictSetToSet + compare = compare `on` conflictSetToMap showConflictSet :: ConflictSet -> String showConflictSet = intercalate ", " . map showVar . toList @@ -97,10 +142,10 @@ showCS showCount cm = -------------------------------------------------------------------------------} toSet :: ConflictSet -> Set (Var QPN) -toSet = conflictSetToSet +toSet = M.keysSet . conflictSetToMap toList :: ConflictSet -> [Var QPN] -toList = S.toList . conflictSetToSet +toList = M.keys . conflictSetToMap union :: #ifdef DEBUG_CONFLICT_SETS @@ -108,7 +153,7 @@ union :: #endif ConflictSet -> ConflictSet -> ConflictSet union cs cs' = CS { - conflictSetToSet = S.union (conflictSetToSet cs) (conflictSetToSet cs') + conflictSetToMap = M.unionWith S.union (conflictSetToMap cs) (conflictSetToMap cs') #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc (map conflictSetOrigin [cs, cs']) #endif @@ -120,7 +165,7 @@ unions :: #endif [ConflictSet] -> ConflictSet unions css = CS { - conflictSetToSet = S.unions (map conflictSetToSet css) + conflictSetToMap = M.unionsWith S.union (map conflictSetToMap css) #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc (map conflictSetOrigin css) #endif @@ -132,7 +177,7 @@ insert :: #endif Var QPN -> ConflictSet -> ConflictSet insert var cs = CS { - conflictSetToSet = S.insert var (conflictSetToSet cs) + conflictSetToMap = M.insert var (S.singleton OtherConflict) (conflictSetToMap cs) #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc [conflictSetOrigin cs] #endif @@ -140,7 +185,7 @@ insert var cs = CS { delete :: Var QPN -> ConflictSet -> ConflictSet delete var cs = CS { - conflictSetToSet = S.delete var (conflictSetToSet cs) + conflictSetToMap = M.delete var (conflictSetToMap cs) } empty :: @@ -149,7 +194,7 @@ empty :: #endif ConflictSet empty = CS { - conflictSetToSet = S.empty + conflictSetToMap = M.empty #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc [] #endif @@ -160,18 +205,28 @@ singleton :: (?loc :: CallStack) => #endif Var QPN -> ConflictSet -singleton var = CS { - conflictSetToSet = S.singleton var +singleton var = singletonWithConflict var OtherConflict + +singletonWithConflict :: +#ifdef DEBUG_CONFLICT_SETS + (?loc :: CallStack) => +#endif + Var QPN -> Conflict -> ConflictSet +singletonWithConflict var conflict = CS { + conflictSetToMap = M.singleton var (S.singleton conflict) #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc [] #endif } size :: ConflictSet -> Int -size = S.size . conflictSetToSet +size = M.size . conflictSetToMap member :: Var QPN -> ConflictSet -> Bool -member var = S.member var . conflictSetToSet +member var = M.member var . conflictSetToMap + +lookup :: Var QPN -> ConflictSet -> Maybe (Set Conflict) +lookup var = M.lookup var . conflictSetToMap fromList :: #ifdef DEBUG_CONFLICT_SETS @@ -179,7 +234,7 @@ fromList :: #endif [Var QPN] -> ConflictSet fromList vars = CS { - conflictSetToSet = S.fromList vars + conflictSetToMap = M.fromList [(var, S.singleton OtherConflict) | var <- vars] #ifdef DEBUG_CONFLICT_SETS , conflictSetOrigin = Node ?loc [] #endif diff --git a/cabal-install/Distribution/Solver/Modular/Dependency.hs b/cabal-install/Distribution/Solver/Modular/Dependency.hs index a0900c701ae..8fc55f5724d 100644 --- a/cabal-install/Distribution/Solver/Modular/Dependency.hs +++ b/cabal-install/Distribution/Solver/Modular/Dependency.hs @@ -33,7 +33,10 @@ module Distribution.Solver.Modular.Dependency ( , goalToVar , varToConflictSet , goalReasonToConflictSet + , goalReasonToConflictSetWithConflict , dependencyReasonToConflictSet + , dependencyReasonToConflictSetWithVersionConstraintConflict + , dependencyReasonToConflictSetWithVersionConflict ) where import Prelude () @@ -285,6 +288,19 @@ goalReasonToConflictSet :: GoalReason QPN -> ConflictSet goalReasonToConflictSet UserGoal = CS.empty goalReasonToConflictSet (DependencyGoal dr) = dependencyReasonToConflictSet dr +-- | Convert a 'GoalReason' to a 'ConflictSet' containing the reason that the +-- conflict occurred, namely the conflict set variables caused a conflict by +-- introducing the given package goal. See the documentation for 'GoalConflict'. +-- +-- This function currently only specifies the reason for the conflict in the +-- simple case where the 'GoalReason' does not involve any flags or stanzas. +-- Otherwise, it falls back to calling 'goalReasonToConflictSet'. +goalReasonToConflictSetWithConflict :: QPN -> GoalReason QPN -> ConflictSet +goalReasonToConflictSetWithConflict goal (DependencyGoal (DependencyReason qpn flags stanzas)) + | M.null flags && S.null stanzas = + CS.singletonWithConflict (P qpn) $ CS.GoalConflict goal +goalReasonToConflictSetWithConflict _ gr = goalReasonToConflictSet gr + -- | This function returns the solver variables responsible for the dependency. -- It drops the values chosen for flag and stanza variables, which are only -- needed for log messages. @@ -300,3 +316,40 @@ dependencyReasonToConflictSet (DependencyReason qpn flags stanzas) = stanzaToVar :: Stanza -> Var QPN stanzaToVar = S . SN qpn + +-- | Convert a 'DependencyReason' to a 'ConflictSet' specifying that the +-- conflict occurred because the conflict set variables introduced a problematic +-- version constraint. See the documentation for 'VersionConstraintConflict'. +-- +-- This function currently only specifies the reason for the conflict in the +-- simple case where the 'DependencyReason' does not involve any flags or +-- stanzas. Otherwise, it falls back to calling 'dependencyReasonToConflictSet'. +dependencyReasonToConflictSetWithVersionConstraintConflict :: QPN + -> Ver + -> DependencyReason QPN + -> ConflictSet +dependencyReasonToConflictSetWithVersionConstraintConflict + dependency excludedVersion dr@(DependencyReason qpn flags stanzas) + | M.null flags && S.null stanzas = + CS.singletonWithConflict (P qpn) $ + CS.VersionConstraintConflict dependency excludedVersion + | otherwise = dependencyReasonToConflictSet dr + +-- | Convert a 'DependencyReason' to a 'ConflictSet' specifying that the +-- conflict occurred because the conflict set variables introduced a version of +-- a package that was excluded by a version constraint. See the documentation +-- for 'VersionConflict'. +-- +-- This function currently only specifies the reason for the conflict in the +-- simple case where the 'DependencyReason' does not involve any flags or +-- stanzas. Otherwise, it falls back to calling 'dependencyReasonToConflictSet'. +dependencyReasonToConflictSetWithVersionConflict :: QPN + -> CS.OrderedVersionRange + -> DependencyReason QPN + -> ConflictSet +dependencyReasonToConflictSetWithVersionConflict + pkgWithVersionConstraint constraint dr@(DependencyReason qpn flags stanzas) + | M.null flags && S.null stanzas = + CS.singletonWithConflict (P qpn) $ + CS.VersionConflict pkgWithVersionConstraint constraint + | otherwise = dependencyReasonToConflictSet dr diff --git a/cabal-install/Distribution/Solver/Modular/Explore.hs b/cabal-install/Distribution/Solver/Modular/Explore.hs index 267a83a8f98..3ac28a462ad 100644 --- a/cabal-install/Distribution/Solver/Modular/Explore.hs +++ b/cabal-install/Distribution/Solver/Modular/Explore.hs @@ -7,19 +7,28 @@ import qualified Distribution.Solver.Types.Progress as P import Data.Foldable as F import Data.List as L (foldl') +import Data.Maybe (fromMaybe) import Data.Map.Strict as M +import Data.Set as S + +import Distribution.Simple.Setup (asBool) import Distribution.Solver.Modular.Assignment import Distribution.Solver.Modular.Dependency +import Distribution.Solver.Modular.Index import Distribution.Solver.Modular.Log import Distribution.Solver.Modular.Message +import Distribution.Solver.Modular.Package import qualified Distribution.Solver.Modular.PSQ as P import qualified Distribution.Solver.Modular.ConflictSet as CS import Distribution.Solver.Modular.RetryLog import Distribution.Solver.Modular.Tree +import Distribution.Solver.Modular.Version import qualified Distribution.Solver.Modular.WeightedPSQ as W import Distribution.Solver.Types.PackagePath -import Distribution.Solver.Types.Settings (EnableBackjumping(..), CountConflicts(..)) +import Distribution.Solver.Types.Settings + (CountConflicts(..), EnableBackjumping(..), FineGrainedConflicts(..)) +import Distribution.Types.VersionRange (anyVersion) -- | This function takes the variable we're currently considering, a -- last conflict set and a list of children's logs. Each log yields @@ -43,25 +52,70 @@ import Distribution.Solver.Types.Settings (EnableBackjumping(..), CountConflicts -- with the (virtual) option not to choose anything for the current -- variable. See also the comments for 'avoidSet'. -- -backjump :: Maybe Int -> EnableBackjumping -> Var QPN - -> ConflictSet -> W.WeightedPSQ w k (ExploreState -> ConflictSetLog a) +-- We can also skip a child if it does not resolve any of the conflicts paired +-- with the current variable in the previous child's conflict set. 'backjump' +-- takes a function to determine whether a child can be skipped. If the child +-- can be skipped, the function returns a new conflict set to be merged with the +-- previous conflict set. +-- +backjump :: forall w k a . Maybe Int + -> EnableBackjumping + -> FineGrainedConflicts + + -> (k -> S.Set CS.Conflict -> Maybe ConflictSet) + -- ^ Function that determines whether the given choice could resolve + -- the given conflict. It indicates false by returning 'Just', + -- with the new conflicts to be added to the conflict set. + + -> (k -> ConflictSet -> ExploreState -> ConflictSetLog a) + -- ^ Function that logs the given choice that was skipped. + + -> Var QPN -- ^ The current variable. + + -> ConflictSet -- ^ Conflict set representing the reason that the goal + -- was introduced. + + -> W.WeightedPSQ w k (ExploreState -> ConflictSetLog a) + -- ^ List of children's logs. + -> ExploreState -> ConflictSetLog a -backjump mbj (EnableBackjumping enableBj) var lastCS xs = - F.foldr combine avoidGoal xs CS.empty +backjump mbj enableBj fineGrainedConflicts couldResolveConflicts + logSkippedChoice var lastCS xs = + F.foldr combine avoidGoal [(k, v) | (_, k, v) <- W.toList xs] CS.empty Nothing where - combine :: forall a . (ExploreState -> ConflictSetLog a) - -> (ConflictSet -> ExploreState -> ConflictSetLog a) - -> ConflictSet -> ExploreState -> ConflictSetLog a - combine x f csAcc es = retryNoSolution (x es) next + combine :: (k, ExploreState -> ConflictSetLog a) + -> (ConflictSet -> Maybe ConflictSet -> ExploreState -> ConflictSetLog a) + -> ConflictSet -> Maybe ConflictSet -> ExploreState -> ConflictSetLog a + combine (k, x) f csAcc mPreviousCS es = + case (asBool fineGrainedConflicts, mPreviousCS) of + (True, Just previousCS) -> + case CS.lookup var previousCS of + Just conflicts -> + case couldResolveConflicts k conflicts of + Nothing -> retryNoSolution (x es) next + Just newConflicts -> skipChoice (previousCS `CS.union` newConflicts) + _ -> skipChoice previousCS + _ -> retryNoSolution (x es) next where next :: ConflictSet -> ExploreState -> ConflictSetLog a - next !cs es' = if enableBj && not (var `CS.member` cs) + next !cs es' = if asBool enableBj && not (var `CS.member` cs) then skipLoggingBackjump cs es' - else f (csAcc `CS.union` cs) es' + else f (csAcc `CS.union` cs) (Just cs) es' + + -- This function is for skipping the choice when it cannot resolve any + -- of the previous conflicts. + skipChoice :: ConflictSet -> ConflictSetLog a + skipChoice newCS = + retryNoSolution (logSkippedChoice k newCS es) $ \cs' es' -> + f (csAcc `CS.union` cs') (Just cs') $ + + -- Update the conflict map with the conflict set, to make up for + -- skipping the whole subtree. + es' { esConflictMap = updateCM cs' (esConflictMap es') } -- This function represents the option to not choose a value for this goal. - avoidGoal :: ConflictSet -> ExploreState -> ConflictSetLog a - avoidGoal cs !es = + avoidGoal :: ConflictSet -> Maybe ConflictSet -> ExploreState -> ConflictSetLog a + avoidGoal cs _mPreviousCS !es = logBackjump mbj (cs `CS.union` lastCS) $ -- Use 'lastCS' below instead of 'cs' since we do not want to @@ -144,15 +198,20 @@ assign tree = cata go tree $ A M.empty M.empty M.empty -- | A tree traversal that simultaneously propagates conflict sets up -- the tree from the leaves and creates a log. -exploreLog :: Maybe Int -> EnableBackjumping -> CountConflicts +exploreLog :: Maybe Int + -> EnableBackjumping + -> FineGrainedConflicts + -> CountConflicts + -> Index -> Tree Assignment QGoalReason -> ConflictSetLog (Assignment, RevDepMap) -exploreLog mbj enableBj (CountConflicts countConflicts) t = para go t initES +exploreLog mbj enableBj fineGrainedConflicts (CountConflicts countConflicts) idx t = + para go t initES where getBestGoal' :: P.PSQ (Goal QPN) a -> ConflictMap -> (Goal QPN, a) getBestGoal' - | countConflicts = \ ts cm -> getBestGoal cm ts - | otherwise = \ ts _ -> getFirstGoal ts + | asBool countConflicts = \ ts cm -> getBestGoal cm ts + | otherwise = \ ts _ -> getFirstGoal ts go :: TreeF Assignment QGoalReason (ExploreState -> ConflictSetLog (Assignment, RevDepMap), Tree Assignment QGoalReason) @@ -162,20 +221,29 @@ exploreLog mbj enableBj (CountConflicts countConflicts) t = para go t initES in failWith (Failure c fr) (NoSolution c es') go (DoneF rdm a) = \ _ -> succeedWith Success (a, rdm) go (PChoiceF qpn _ gr ts) = - backjump mbj enableBj (P qpn) (avoidSet (P qpn) gr) $ -- try children in order, - W.mapWithKey -- when descending ... - (\ k r es -> tryWith (TryP qpn k) (r es)) - (fmap fst ts) + backjump mbj enableBj fineGrainedConflicts + (couldResolveConflicts qpn) + (logSkippedPackage qpn) + (P qpn) (avoidSet (P qpn) gr) $ -- try children in order, + W.mapWithKey -- when descending ... + (\ k r es -> tryWith (TryP qpn k) (r es)) + (fmap fst ts) go (FChoiceF qfn _ gr _ _ _ ts) = - backjump mbj enableBj (F qfn) (avoidSet (F qfn) gr) $ -- try children in order, - W.mapWithKey -- when descending ... - (\ k r es -> tryWith (TryF qfn k) (r es)) - (fmap fst ts) + backjump mbj enableBj fineGrainedConflicts + (\_ _ -> Nothing) + (const logSkippedChoiceSimple) + (F qfn) (avoidSet (F qfn) gr) $ -- try children in order, + W.mapWithKey -- when descending ... + (\ k r es -> tryWith (TryF qfn k) (r es)) + (fmap fst ts) go (SChoiceF qsn _ gr _ ts) = - backjump mbj enableBj (S qsn) (avoidSet (S qsn) gr) $ -- try children in order, - W.mapWithKey -- when descending ... - (\ k r es -> tryWith (TryS qsn k) (r es)) - (fmap fst ts) + backjump mbj enableBj fineGrainedConflicts + (\_ _ -> Nothing) + (const logSkippedChoiceSimple) + (S qsn) (avoidSet (S qsn) gr) $ -- try children in order, + W.mapWithKey -- when descending ... + (\ k r es -> tryWith (TryS qsn k) (r es)) + (fmap fst ts) go (GoalChoiceF _ ts) = \ es -> let (k, (v, tree)) = getBestGoal' ts (esConflictMap es) in continueWith (Next k) $ @@ -194,6 +262,59 @@ exploreLog mbj enableBj (CountConflicts countConflicts) t = para go t initES , esBackjumps = 0 } + -- Is it possible for this package instance (QPN and POption) to resolve any + -- of the conflicts that were caused by the previous instance? The default + -- is true, because it is always safe to explore a package instance. + -- Skipping it is an optimization. If false, it returns a new conflict set + -- to be merged with the previous one. + couldResolveConflicts :: QPN -> POption -> S.Set CS.Conflict -> Maybe ConflictSet + couldResolveConflicts currentQPN@(Q _ pn) (POption i@(I v _) _) conflicts = + let (PInfo deps _ _ _) = idx ! pn ! i + qdeps = qualifyDeps (defaultQualifyOptions idx) currentQPN deps + + couldBeResolved :: CS.Conflict -> Maybe ConflictSet + couldBeResolved CS.OtherConflict = Nothing + couldBeResolved (CS.GoalConflict conflictingDep) = + -- Check whether this package instance also has 'conflictingDep' + -- as a dependency (ignoring flag and stanza choices). + if F.null [() | Simple (LDep _ (Dep (PkgComponent qpn _) _)) _ <- qdeps, qpn == conflictingDep] + then Nothing + else Just CS.empty + couldBeResolved (CS.VersionConstraintConflict dep excludedVersion) = + -- Check whether this package instance also excludes version + -- 'excludedVersion' of 'dep' (ignoring flag and stanza choices). + let vrs = [vr | Simple (LDep _ (Dep (PkgComponent qpn _) (Constrained vr))) _ <- qdeps, qpn == dep ] + vrIntersection = L.foldl' (.&&.) anyVersion vrs + in if checkVR vrIntersection excludedVersion + then Nothing + else -- If we skip this package instance, we need to update the + -- conflict set to say that 'dep' was also excluded by + -- this package instance's constraint. + Just $ CS.singletonWithConflict (P dep) $ + CS.VersionConflict currentQPN (CS.OrderedVersionRange vrIntersection) + couldBeResolved (CS.VersionConflict reverseDep (CS.OrderedVersionRange excludingVR)) = + -- Check whether this package instance's version is also excluded + -- by 'excludingVR'. + if checkVR excludingVR v + then Nothing + else -- If we skip this version, we need to update the conflict + -- set to say that the reverse dependency also excluded this + -- version. + Just $ CS.singletonWithConflict (P reverseDep) (CS.VersionConstraintConflict currentQPN v) + in fmap CS.unions $ traverse couldBeResolved (S.toList conflicts) + + logSkippedPackage :: QPN -> POption -> ConflictSet -> ExploreState -> ConflictSetLog a + logSkippedPackage qpn pOption cs es = + tryWith (TryP qpn pOption) $ + failWith (Skip (fromMaybe S.empty $ CS.lookup (P qpn) cs)) $ + NoSolution cs es + + -- This function is used for flag and stanza choices, but it should not be + -- called, because there is currently no way to skip a value for a flag or + -- stanza. + logSkippedChoiceSimple :: ConflictSet -> ExploreState -> ConflictSetLog a + logSkippedChoiceSimple cs es = fromProgress $ P.Fail $ NoSolution cs es + -- | Build a conflict set corresponding to the (virtual) option not to -- choose a solution for a goal at all. -- @@ -219,7 +340,9 @@ exploreLog mbj enableBj (CountConflicts countConflicts) t = para go t initES -- conflict set. -- avoidSet :: Var QPN -> QGoalReason -> ConflictSet -avoidSet var gr = +avoidSet var@(P qpn) gr = + CS.union (CS.singleton var) (goalReasonToConflictSetWithConflict qpn gr) +avoidSet var gr = CS.union (CS.singleton var) (goalReasonToConflictSet gr) -- | Interface. @@ -229,11 +352,15 @@ avoidSet var gr = -- backtracking is completely disabled. backjumpAndExplore :: Maybe Int -> EnableBackjumping + -> FineGrainedConflicts -> CountConflicts + -> Index -> Tree d QGoalReason -> RetryLog Message SolverFailure (Assignment, RevDepMap) -backjumpAndExplore mbj enableBj countConflicts = - mapFailure convertFailure . exploreLog mbj enableBj countConflicts . assign +backjumpAndExplore mbj enableBj fineGrainedConflicts countConflicts idx = + mapFailure convertFailure + . exploreLog mbj enableBj fineGrainedConflicts countConflicts idx + . assign where convertFailure (NoSolution cs es) = ExhaustiveSearch cs (esConflictMap es) convertFailure BackjumpLimit = BackjumpLimitReached diff --git a/cabal-install/Distribution/Solver/Modular/Message.hs b/cabal-install/Distribution/Solver/Modular/Message.hs index 24d968bce33..5d642d32c14 100644 --- a/cabal-install/Distribution/Solver/Modular/Message.hs +++ b/cabal-install/Distribution/Solver/Modular/Message.hs @@ -6,10 +6,16 @@ module Distribution.Solver.Modular.Message ( ) where import qualified Data.List as L +import Data.Map (Map) +import qualified Data.Map as M +import Data.Set (Set) +import qualified Data.Set as S +import Data.Maybe (catMaybes, mapMaybe) import Prelude hiding (pi) import Distribution.Pretty (prettyShow) -- from Cabal +import qualified Distribution.Solver.Modular.ConflictSet as CS import Distribution.Solver.Modular.Dependency import Distribution.Solver.Modular.Flag import Distribution.Solver.Modular.Package @@ -28,6 +34,7 @@ data Message = | TryF QFN Bool | TryS QSN Bool | Next (Goal QPN) + | Skip (Set CS.Conflict) | Success | Failure ConflictSet FailReason @@ -47,6 +54,8 @@ showMessages = go 0 -- complex patterns go !l (Step (TryP qpn i) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = goPReject l qpn [i] c fr ms + go !l (Step (TryP qpn i) (Step Enter (Step (Skip conflicts) (Step Leave ms)))) = + goPSkip l qpn [i] conflicts ms go !l (Step (TryF qfn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = (atLevel l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go l ms) go !l (Step (TryS qsn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = @@ -63,6 +72,9 @@ showMessages = go 0 go !l (Step (TryS qsn b) ms) = (atLevel l $ "trying: " ++ showQSNBool qsn b) (go l ms) go !l (Step (Next (Goal (P qpn) gr)) ms) = (atLevel l $ showPackageGoal qpn gr) (go l ms) go !l (Step (Next _) ms) = go l ms -- ignore flag goals in the log + go !l (Step (Skip conflicts) ms) = + -- 'Skip' should always be handled by 'goPSkip' in the case above. + (atLevel l $ "skipping: " ++ showConflicts conflicts) (go l ms) go !l (Step (Success) ms) = (atLevel l $ "done") (go l ms) go !l (Step (Failure c fr) ms) = (atLevel l $ showFailure c fr) (go l ms) @@ -85,12 +97,112 @@ showMessages = go 0 goPReject l qpn is c fr ms = (atLevel l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go l ms) + -- Handle many subsequent skipped package instances. + goPSkip :: Int + -> QPN + -> [POption] + -> Set CS.Conflict + -> Progress Message a b + -> Progress String a b + goPSkip l qpn is conflicts (Step (TryP qpn' i) (Step Enter (Step (Skip conflicts') (Step Leave ms)))) + | qpn == qpn' && conflicts == conflicts' = goPSkip l qpn (i : is) conflicts ms + goPSkip l qpn is conflicts ms = + let msg = "skipping: " + ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) + ++ showConflicts conflicts + in atLevel l msg (go l ms) + -- write a message with the current level number atLevel :: Int -> String -> Progress String a b -> Progress String a b atLevel l x xs = let s = show l in Step ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) xs +-- | Display the set of 'Conflicts' for a skipped package version. +showConflicts :: Set CS.Conflict -> String +showConflicts conflicts = + " (has the same characteristics that caused the previous version to fail: " + ++ conflictMsg ++ ")" + where + conflictMsg :: String + conflictMsg = + if S.member CS.OtherConflict conflicts + then + -- This case shouldn't happen, because an unknown conflict should not + -- cause a version to be skipped. + "unknown conflict" + else let mergedConflicts = + [ showConflict qpn conflict + | (qpn, conflict) <- M.toList (mergeConflicts conflicts) ] + in if L.null mergedConflicts + then + -- This case shouldn't happen unless backjumping is turned off. + "none" + else L.intercalate "; " mergedConflicts + + -- Merge conflicts to simplify the log message. + mergeConflicts :: Set CS.Conflict -> Map QPN MergedPackageConflict + mergeConflicts = M.fromListWith mergeConflict . mapMaybe toMergedConflict . S.toList + where + mergeConflict :: MergedPackageConflict + -> MergedPackageConflict + -> MergedPackageConflict + mergeConflict mergedConflict1 mergedConflict2 = MergedPackageConflict { + isGoalConflict = + isGoalConflict mergedConflict1 || isGoalConflict mergedConflict2 + , versionConstraintConflict = + L.nub $ versionConstraintConflict mergedConflict1 + ++ versionConstraintConflict mergedConflict2 + , versionConflict = + mergeVersionConflicts (versionConflict mergedConflict1) + (versionConflict mergedConflict2) + } + where + mergeVersionConflicts (Just vr1) (Just vr2) = Just (vr1 .||. vr2) + mergeVersionConflicts (Just vr1) Nothing = Just vr1 + mergeVersionConflicts Nothing (Just vr2) = Just vr2 + mergeVersionConflicts Nothing Nothing = Nothing + + toMergedConflict :: CS.Conflict -> Maybe (QPN, MergedPackageConflict) + toMergedConflict (CS.GoalConflict qpn) = + Just (qpn, MergedPackageConflict True [] Nothing) + toMergedConflict (CS.VersionConstraintConflict qpn v) = + Just (qpn, MergedPackageConflict False [v] Nothing) + toMergedConflict (CS.VersionConflict qpn (CS.OrderedVersionRange vr)) = + Just (qpn, MergedPackageConflict False [] (Just vr)) + toMergedConflict CS.OtherConflict = Nothing + + showConflict :: QPN -> MergedPackageConflict -> String + showConflict qpn mergedConflict = L.intercalate "; " conflictStrings + where + conflictStrings = catMaybes [ + case () of + () | isGoalConflict mergedConflict -> Just $ + "depends on '" ++ showQPN qpn ++ "'" ++ + (if null (versionConstraintConflict mergedConflict) + then "" + else " but excludes " + ++ showVersions (versionConstraintConflict mergedConflict)) + | not $ L.null (versionConstraintConflict mergedConflict) -> Just $ + "excludes '" ++ showQPN qpn + ++ "' " ++ showVersions (versionConstraintConflict mergedConflict) + | otherwise -> Nothing + , (\vr -> "excluded by constraint '" ++ showVR vr ++ "' from '" ++ showQPN qpn ++ "'") + <$> versionConflict mergedConflict + ] + + showVersions [] = "no versions" + showVersions [v] = "version " ++ showVer v + showVersions vs = "versions " ++ L.intercalate ", " (map showVer vs) + +-- | All conflicts related to one package, used for simplifying the display of +-- a 'Set CS.Conflict'. +data MergedPackageConflict = MergedPackageConflict { + isGoalConflict :: Bool + , versionConstraintConflict :: [Ver] + , versionConflict :: Maybe VR + } + showQPNPOpt :: QPN -> POption -> String showQPNPOpt qpn@(Q _pp pn) (POption i linkedTo) = case linkedTo of diff --git a/cabal-install/Distribution/Solver/Modular/Preference.hs b/cabal-install/Distribution/Solver/Modular/Preference.hs index 116c3263282..5eb2c6a1bf9 100644 --- a/cabal-install/Distribution/Solver/Modular/Preference.hs +++ b/cabal-install/Distribution/Solver/Modular/Preference.hs @@ -343,7 +343,9 @@ onlyConstrained :: (PN -> Bool) -> Tree d QGoalReason -> Tree d QGoalReason onlyConstrained p = trav go where go (PChoiceF v@(Q _ pn) _ gr _) | not (p pn) - = FailF (varToConflictSet (P v) `CS.union` goalReasonToConflictSet gr) NotExplicit + = FailF + (varToConflictSet (P v) `CS.union` goalReasonToConflictSetWithConflict v gr) + NotExplicit go x = x diff --git a/cabal-install/Distribution/Solver/Modular/Solver.hs b/cabal-install/Distribution/Solver/Modular/Solver.hs index b0e40cb9a0a..32452550556 100644 --- a/cabal-install/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/Distribution/Solver/Modular/Solver.hs @@ -57,6 +57,7 @@ import Debug.Trace.Tree.Assoc (Assoc(..)) data SolverConfig = SolverConfig { reorderGoals :: ReorderGoals, countConflicts :: CountConflicts, + fineGrainedConflicts :: FineGrainedConflicts, minimizeConflictSet :: MinimizeConflictSet, independentGoals :: IndependentGoals, avoidReinstalls :: AvoidReinstalls, @@ -104,7 +105,9 @@ solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = where explorePhase = backjumpAndExplore (maxBackjumps sc) (enableBackjumping sc) + (fineGrainedConflicts sc) (countConflicts sc) + idx detectCycles = traceTree "cycles.json" id . detectCyclesPhase heuristicsPhase = let heuristicsTree = traceTree "heuristics.json" id diff --git a/cabal-install/Distribution/Solver/Modular/Validate.hs b/cabal-install/Distribution/Solver/Modular/Validate.hs index 900b1c08a8f..6195d101b02 100644 --- a/cabal-install/Distribution/Solver/Modular/Validate.hs +++ b/cabal-install/Distribution/Solver/Modular/Validate.hs @@ -455,7 +455,7 @@ merge (MergedDepFixed comp1 vs1 i1) (PkgDep vs2 (PkgComponent p comp2) ci@(Fixed merge (MergedDepFixed comp1 vs1 i@(I v _)) (PkgDep vs2 (PkgComponent p comp2) ci@(Constrained vr)) | checkVR vr v = Right $ MergedDepFixed comp1 vs1 i | otherwise = - Left ( (CS.union `on` dependencyReasonToConflictSet) vs1 vs2 + Left ( createConflictSetForVersionConflict p v vs1 vr vs2 , ( ConflictingDep vs1 (PkgComponent p comp1) (Fixed i) , ConflictingDep vs2 (PkgComponent p comp2) ci ) ) @@ -467,7 +467,7 @@ merge (MergedDepConstrained vrOrigins) (PkgDep vs2 (PkgComponent p comp2) ci@(Fi go ((vr, comp1, vs1) : vros) | checkVR vr v = go vros | otherwise = - Left ( (CS.union `on` dependencyReasonToConflictSet) vs1 vs2 + Left ( createConflictSetForVersionConflict p v vs2 vr vs1 , ( ConflictingDep vs1 (PkgComponent p comp1) (Constrained vr) , ConflictingDep vs2 (PkgComponent p comp2) ci ) ) @@ -479,6 +479,45 @@ merge (MergedDepConstrained vrOrigins) (PkgDep vs2 (PkgComponent _ comp2) (Const -- no negative performance impact. vrOrigins ++ [(vr, comp2, vs2)]) +-- | Creates a conflict set representing a conflict between a version constraint +-- and the fixed version chosen for a package. +createConflictSetForVersionConflict :: QPN + -> Ver + -> DependencyReason QPN + -> VR + -> DependencyReason QPN + -> ConflictSet +createConflictSetForVersionConflict pkg + conflictingVersion + versionDR@(DependencyReason p1 _ _) + conflictingVersionRange + versionRangeDR@(DependencyReason p2 _ _) = + let hasFlagsOrStanzas (DependencyReason _ fs ss) = not (M.null fs) || not (S.null ss) + in + -- The solver currently only optimizes the case where there is a conflict + -- between the version chosen for a package and a version constraint that + -- is not under any flags or stanzas. Here is how we check for this case: + -- + -- (1) Choosing a specific version for a package foo is implemented as + -- adding a dependency from foo to that version of foo (See + -- extendWithPackageChoice), so we check that the DependencyReason + -- contains the current package and no flag or stanza choices. + -- + -- (2) We check that the DependencyReason for the version constraint also + -- contains no flag or stanza choices. + -- + -- When these criteria are not met, we fall back to calling + -- dependencyReasonToConflictSet. + if p1 == pkg && not (hasFlagsOrStanzas versionDR) && not (hasFlagsOrStanzas versionRangeDR) + then let cs1 = dependencyReasonToConflictSetWithVersionConflict + p2 + (CS.OrderedVersionRange conflictingVersionRange) + versionDR + cs2 = dependencyReasonToConflictSetWithVersionConstraintConflict + pkg conflictingVersion versionRangeDR + in cs1 `CS.union` cs2 + else dependencyReasonToConflictSet versionRangeDR `CS.union` dependencyReasonToConflictSet versionDR + -- | Takes a list of new dependencies and uses it to try to update the map of -- known component dependencies. It returns a failure when a new dependency -- requires a component that is missing or unbuildable in a previously chosen diff --git a/cabal-install/Distribution/Solver/Types/Settings.hs b/cabal-install/Distribution/Solver/Types/Settings.hs index 79bc3a017d4..c8a4f7e99fc 100644 --- a/cabal-install/Distribution/Solver/Types/Settings.hs +++ b/cabal-install/Distribution/Solver/Types/Settings.hs @@ -11,6 +11,7 @@ module Distribution.Solver.Types.Settings , OnlyConstrained(..) , EnableBackjumping(..) , CountConflicts(..) + , FineGrainedConflicts(..) , SolveExecutables(..) ) where @@ -30,6 +31,9 @@ newtype ReorderGoals = ReorderGoals Bool newtype CountConflicts = CountConflicts Bool deriving (BooleanFlag, Eq, Generic, Show) +newtype FineGrainedConflicts = FineGrainedConflicts Bool + deriving (BooleanFlag, Eq, Generic, Show) + newtype MinimizeConflictSet = MinimizeConflictSet Bool deriving (BooleanFlag, Eq, Generic, Show) @@ -63,6 +67,7 @@ newtype SolveExecutables = SolveExecutables Bool instance Binary ReorderGoals instance Binary CountConflicts +instance Binary FineGrainedConflicts instance Binary IndependentGoals instance Binary MinimizeConflictSet instance Binary AvoidReinstalls @@ -74,6 +79,7 @@ instance Binary SolveExecutables instance Structured ReorderGoals instance Structured CountConflicts +instance Structured FineGrainedConflicts instance Structured IndependentGoals instance Structured MinimizeConflictSet instance Structured AvoidReinstalls diff --git a/cabal-install/changelog b/cabal-install/changelog index b5d1b72bbb3..93fdd2d6a92 100644 --- a/cabal-install/changelog +++ b/cabal-install/changelog @@ -4,6 +4,9 @@ * `v2-build` (and other `v2-`prefixed commands) now accept the `--benchmark-option(s)` flags, which pass options to benchmark executables (analogous to how `--test-option(s)` works). (#6209) + * Add solver optimization to skip a version of a package if it does not resolve + any conflicts encountered in the last version, controlled by flag + '--fine-grained-conflicts'. (#5918) 3.0.0.0 Mikhail Glushenkov August 2019 * Parse comma-separated lists for extra-prog-path, extra-lib-dirs, extra-framework-dirs, diff --git a/cabal-install/solver-dsl/UnitTests/Distribution/Solver/Modular/DSL.hs b/cabal-install/solver-dsl/UnitTests/Distribution/Solver/Modular/DSL.hs index 87d728e9544..4d356615826 100644 --- a/cabal-install/solver-dsl/UnitTests/Distribution/Solver/Modular/DSL.hs +++ b/cabal-install/solver-dsl/UnitTests/Distribution/Solver/Modular/DSL.hs @@ -655,6 +655,7 @@ exResolve :: ExampleDb -> [ExamplePkgName] -> Maybe Int -> CountConflicts + -> FineGrainedConflicts -> MinimizeConflictSet -> IndependentGoals -> ReorderGoals @@ -669,9 +670,9 @@ exResolve :: ExampleDb -> EnableAllTests -> Progress String String CI.SolverInstallPlan.SolverInstallPlan exResolve db exts langs pkgConfigDb targets mbj countConflicts - minimizeConflictSet indepGoals reorder allowBootLibInstalls - onlyConstrained enableBj solveExes goalOrder constraints - prefs verbosity enableAllTests + fineGrainedConflicts minimizeConflictSet indepGoals reorder + allowBootLibInstalls onlyConstrained enableBj solveExes goalOrder + constraints prefs verbosity enableAllTests = resolveDependencies C.buildPlatform compiler pkgConfigDb Modular params where defaultCompiler = C.unknownCompilerInfo C.buildCompilerId C.NoAbiTag @@ -695,6 +696,7 @@ exResolve db exts langs pkgConfigDb targets mbj countConflicts $ addConstraints (fmap toLpc enableTests) $ addPreferences (fmap toPref prefs) $ setCountConflicts countConflicts + $ setFineGrainedConflicts fineGrainedConflicts $ setMinimizeConflictSet minimizeConflictSet $ setIndependentGoals indepGoals $ setReorderGoals reorder diff --git a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs index ceada5bd7c2..0ef0ff003c0 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/ProjectConfig.hs @@ -472,6 +472,7 @@ instance Arbitrary ProjectConfigShared where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary <*> (toNubList <$> listOf arbitraryShortToken) where arbitraryConstraints :: Gen [(UserConstraint, ConstraintSource)] @@ -498,15 +499,16 @@ instance Arbitrary ProjectConfigShared where , projectConfigMaxBackjumps = x16 , projectConfigReorderGoals = x17 , projectConfigCountConflicts = x18 - , projectConfigMinimizeConflictSet = x19 - , projectConfigStrongFlags = x20 - , projectConfigAllowBootLibInstalls = x21 - , projectConfigOnlyConstrained = x22 - , projectConfigPerComponent = x23 - , projectConfigIndependentGoals = x24 - , projectConfigConfigFile = x25 - , projectConfigProgPathExtra = x26 - , projectConfigStoreDir = x27 } = + , projectConfigFineGrainedConflicts = x19 + , projectConfigMinimizeConflictSet = x20 + , projectConfigStrongFlags = x21 + , projectConfigAllowBootLibInstalls = x22 + , projectConfigOnlyConstrained = x23 + , projectConfigPerComponent = x24 + , projectConfigIndependentGoals = x25 + , projectConfigConfigFile = x26 + , projectConfigProgPathExtra = x27 + , projectConfigStoreDir = x28 } = [ ProjectConfigShared { projectConfigDistDir = x00' , projectConfigProjectFile = x01' , projectConfigHcFlavor = x02' @@ -527,26 +529,27 @@ instance Arbitrary ProjectConfigShared where , projectConfigMaxBackjumps = x16' , projectConfigReorderGoals = x17' , projectConfigCountConflicts = x18' - , projectConfigMinimizeConflictSet = x19' - , projectConfigStrongFlags = x20' - , projectConfigAllowBootLibInstalls = x21' - , projectConfigOnlyConstrained = x22' - , projectConfigPerComponent = x23' - , projectConfigIndependentGoals = x24' - , projectConfigConfigFile = x25' - , projectConfigProgPathExtra = x26' - , projectConfigStoreDir = x27' } - | ((x00', x01', x02', x03', x04'), - (x05', x06', x07', x07b', x08', x09'), - (x10', x11', x12', x13', x14', x15'), - (x16', x17', x18', x19', x20', x21'), - x22', x23', x24', x25', x26', x27') + , projectConfigFineGrainedConflicts = x19' + , projectConfigMinimizeConflictSet = x20' + , projectConfigStrongFlags = x21' + , projectConfigAllowBootLibInstalls = x22' + , projectConfigOnlyConstrained = x23' + , projectConfigPerComponent = x24' + , projectConfigIndependentGoals = x25' + , projectConfigConfigFile = x26' + , projectConfigProgPathExtra = x27' + , projectConfigStoreDir = x28' } + | ((x00', x01', x02', x03', x04', x05'), + (x06', x07', x07b', x08', x09', x10'), + (x11', x12', x13', x14', x15', x16'), + (x17', x18', x19', x20', x21', x22'), + x23', x24', x25', x26', x27', x28') <- shrink - ((x00, x01, x02, fmap NonEmpty x03, fmap NonEmpty x04), - (x05, x06, x07, x07b, x08, preShrink_Constraints x09), - (x10, x11, x12, x13, x14, x15), - (x16, x17, x18, x19, x20, x21), - x22, x23, x24, x25, x26, x27) + ((x00, x01, x02, fmap NonEmpty x03, fmap NonEmpty x04, x05), + (x06, x07, x07b, x08, preShrink_Constraints x09, x10), + (x11, x12, x13, x14, x15, x16), + (x17, x18, x19, x20, x21, x22), + x23, x24, x25, x26, x27, x28) ] where preShrink_Constraints = map fst @@ -877,6 +880,9 @@ instance Arbitrary ReorderGoals where instance Arbitrary CountConflicts where arbitrary = CountConflicts <$> arbitrary +instance Arbitrary FineGrainedConflicts where + arbitrary = FineGrainedConflicts <$> arbitrary + instance Arbitrary MinimizeConflictSet where arbitrary = MinimizeConflictSet <$> arbitrary diff --git a/cabal-install/tests/UnitTests/Distribution/Client/TreeDiffInstances.hs b/cabal-install/tests/UnitTests/Distribution/Client/TreeDiffInstances.hs index a04eb105522..58f6870d28b 100644 --- a/cabal-install/tests/UnitTests/Distribution/Client/TreeDiffInstances.hs +++ b/cabal-install/tests/UnitTests/Distribution/Client/TreeDiffInstances.hs @@ -55,6 +55,7 @@ instance ToExpr CompilerFlavor instance ToExpr ConstraintSource instance ToExpr CountConflicts instance ToExpr DebugInfoLevel +instance ToExpr FineGrainedConflicts instance ToExpr FlagAssignment instance ToExpr FlagName where toExpr = defaultExprViaShow instance ToExpr HaddockTarget diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs index 2f8c7a69f59..6135f571951 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs @@ -4,6 +4,7 @@ module UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils ( SolverTest , SolverResult(..) , maxBackjumps + , disableFineGrainedConflicts , minimizeConflictSet , independentGoals , allowBootLibInstalls @@ -54,6 +55,10 @@ import UnitTests.Options maxBackjumps :: Maybe Int -> SolverTest -> SolverTest maxBackjumps mbj test = test { testMaxBackjumps = mbj } +disableFineGrainedConflicts :: SolverTest -> SolverTest +disableFineGrainedConflicts test = + test { testFineGrainedConflicts = FineGrainedConflicts False } + minimizeConflictSet :: SolverTest -> SolverTest minimizeConflictSet test = test { testMinimizeConflictSet = MinimizeConflictSet True } @@ -105,6 +110,7 @@ data SolverTest = SolverTest { , testTargets :: [String] , testResult :: SolverResult , testMaxBackjumps :: Maybe Int + , testFineGrainedConflicts :: FineGrainedConflicts , testMinimizeConflictSet :: MinimizeConflictSet , testIndepGoals :: IndependentGoals , testAllowBootLibInstalls :: AllowBootLibInstalls @@ -201,6 +207,7 @@ mkTestExtLangPC exts langs pkgConfigDb db label targets result = SolverTest { , testTargets = targets , testResult = result , testMaxBackjumps = Nothing + , testFineGrainedConflicts = FineGrainedConflicts True , testMinimizeConflictSet = MinimizeConflictSet False , testIndepGoals = IndependentGoals False , testAllowBootLibInstalls = AllowBootLibInstalls False @@ -224,8 +231,8 @@ runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) -> let progress = exResolve testDb testSupportedExts testSupportedLangs testPkgConfigDb testTargets testMaxBackjumps (CountConflicts True) - testMinimizeConflictSet testIndepGoals - (ReorderGoals False) testAllowBootLibInstalls + testFineGrainedConflicts testMinimizeConflictSet + testIndepGoals (ReorderGoals False) testAllowBootLibInstalls testOnlyConstrained testEnableBackjumping testSolveExecutables (sortGoals <$> testGoalOrder) testConstraints testSoftConstraints testVerbosity testEnableAllTests diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/MemoryUsage.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/MemoryUsage.hs index f1bc388d40d..93f7c5a5d20 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/MemoryUsage.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/MemoryUsage.hs @@ -17,12 +17,14 @@ tests = [ -- | This test solves for n packages that each have two versions. There is no -- solution, because the nth package depends on another package that doesn't fit --- its version constraint. Backjumping is disabled, so the solver must explore a --- search tree of size 2^n. It should fail if memory usage is proportional to --- the size of the tree. +-- its version constraint. Backjumping and fine grained conflicts are disabled, +-- so the solver must explore a search tree of size 2^n. It should fail if +-- memory usage is proportional to the size of the tree. basicTest :: String -> SolverTest basicTest name = - disableBackjumping $ mkTest pkgs name ["target"] anySolverFailure + disableBackjumping $ + disableFineGrainedConflicts $ + mkTest pkgs name ["target"] anySolverFailure where n :: Int n = 18 @@ -44,6 +46,7 @@ basicTest name = flagsTest :: String -> SolverTest flagsTest name = disableBackjumping $ + disableFineGrainedConflicts $ goalOrder orderedFlags $ mkTest pkgs name ["pkg"] anySolverFailure where n :: Int @@ -69,14 +72,16 @@ flagsTest name = -- has a long chain of dependencies (pkg-1 through pkg-n). However, pkg-n -- depends on pkg-n+1, which doesn't exist, so there is no solution. Since each -- dependency has two versions, the solver must try 2^n combinations when --- backjumping is disabled. These combinations create large search trees under --- each of the two choices for target-setup.setup-dep. Although the choice to --- not link is disallowed by the Single Instance Restriction, the solver doesn't --- know that until it has explored (and evaluated) the whole tree under the --- choice to link. If the two trees are shared, memory usage spikes. +-- backjumping and fine grained conflicts are disabled. These combinations +-- create large search trees under each of the two choices for +-- target-setup.setup-dep. Although the choice to not link is disallowed by the +-- Single Instance Restriction, the solver doesn't know that until it has +-- explored (and evaluated) the whole tree under the choice to link. If the two +-- trees are shared, memory usage spikes. issue2899 :: String -> SolverTest issue2899 name = disableBackjumping $ + disableFineGrainedConflicts $ goalOrder goals $ mkTest pkgs name ["target"] anySolverFailure where n :: Int diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/QuickCheck.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/QuickCheck.hs index 96243613ee6..a1a6412d014 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/QuickCheck.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/QuickCheck.hs @@ -57,8 +57,8 @@ tests = [ let r1 = solve' mGoalOrder1 test r2 = solve' mGoalOrder2 test { testTargets = targets2 } solve' goalOrder = - solve (EnableBackjumping True) (ReorderGoals False) - (CountConflicts True) indepGoals + solve (EnableBackjumping True) (FineGrainedConflicts True) + (ReorderGoals False) (CountConflicts True) indepGoals (getBlind <$> goalOrder) targets = testTargets test targets2 = case targetOrder of @@ -73,8 +73,9 @@ tests = [ \test reorderGoals -> let r1 = solve' (IndependentGoals False) test r2 = solve' (IndependentGoals True) test - solve' indep = solve (EnableBackjumping True) reorderGoals - (CountConflicts True) indep Nothing + solve' indep = + solve (EnableBackjumping True) (FineGrainedConflicts True) + reorderGoals (CountConflicts True) indep Nothing in counterexample (showResults r1 r2) $ noneReachedBackjumpLimit [r1, r2] ==> isRight (resultPlan r1) `implies` isRight (resultPlan r2) @@ -83,26 +84,52 @@ tests = [ \test reorderGoals indepGoals -> let r1 = solve' (EnableBackjumping True) test r2 = solve' (EnableBackjumping False) test - solve' enableBj = solve enableBj reorderGoals - (CountConflicts True) indepGoals Nothing + solve' enableBj = + solve enableBj (FineGrainedConflicts False) reorderGoals + (CountConflicts True) indepGoals Nothing in counterexample (showResults r1 r2) $ noneReachedBackjumpLimit [r1, r2] ==> isRight (resultPlan r1) === isRight (resultPlan r2) - -- This test uses --no-count-conflicts, because the goal order used with - -- --count-conflicts depends on the total set of conflicts seen by the + , testPropertyWithSeed "fine-grained conflicts does not affect solvability" $ + \test reorderGoals indepGoals -> + let r1 = solve' (FineGrainedConflicts True) test + r2 = solve' (FineGrainedConflicts False) test + solve' fineGrainedConflicts = + solve (EnableBackjumping True) fineGrainedConflicts + reorderGoals (CountConflicts True) indepGoals Nothing + in counterexample (showResults r1 r2) $ + noneReachedBackjumpLimit [r1, r2] ==> + isRight (resultPlan r1) === isRight (resultPlan r2) + + -- The next two tests use --no-count-conflicts, because the goal order used + -- with --count-conflicts depends on the total set of conflicts seen by the -- solver. The solver explores more of the tree and encounters more -- conflicts when it doesn't backjump. The different goal orders can lead to -- different solutions and cause the test to fail. -- TODO: Find a faster way to randomly sort goals, and then use a random - -- goal order in this test. + -- goal order in these tests. + , testPropertyWithSeed "backjumping does not affect the result (with static goal order)" $ \test reorderGoals indepGoals -> let r1 = solve' (EnableBackjumping True) test r2 = solve' (EnableBackjumping False) test - solve' enableBj = solve enableBj reorderGoals - (CountConflicts False) indepGoals Nothing + solve' enableBj = + solve enableBj (FineGrainedConflicts False) reorderGoals + (CountConflicts False) indepGoals Nothing + in counterexample (showResults r1 r2) $ + noneReachedBackjumpLimit [r1, r2] ==> + resultPlan r1 === resultPlan r2 + + , testPropertyWithSeed + "fine-grained conflicts does not affect the result (with static goal order)" $ + \test reorderGoals indepGoals -> + let r1 = solve' (FineGrainedConflicts True) test + r2 = solve' (FineGrainedConflicts False) test + solve' fineGrainedConflicts = + solve (EnableBackjumping True) fineGrainedConflicts + reorderGoals (CountConflicts False) indepGoals Nothing in counterexample (showResults r1 r2) $ noneReachedBackjumpLimit [r1, r2] ==> resultPlan r1 === resultPlan r2 @@ -132,10 +159,15 @@ newtype VarOrdering = VarOrdering { unVarOrdering :: Variable P.QPN -> Variable P.QPN -> Ordering } -solve :: EnableBackjumping -> ReorderGoals -> CountConflicts -> IndependentGoals +solve :: EnableBackjumping + -> FineGrainedConflicts + -> ReorderGoals + -> CountConflicts + -> IndependentGoals -> Maybe VarOrdering - -> SolverTest -> Result -solve enableBj reorder countConflicts indep goalOrder test = + -> SolverTest + -> Result +solve enableBj fineGrainedConflicts reorder countConflicts indep goalOrder test = let (lg, result) = runProgress $ exResolve (unTestDb (testDb test)) Nothing Nothing (pkgConfigDbFromList []) @@ -143,7 +175,8 @@ solve enableBj reorder countConflicts indep goalOrder test = -- The backjump limit prevents individual tests from using -- too much time and memory. (Just defaultMaxBackjumps) - countConflicts (MinimizeConflictSet False) indep reorder + countConflicts fineGrainedConflicts + (MinimizeConflictSet False) indep reorder (AllowBootLibInstalls False) OnlyConstrainedNone enableBj (SolveExecutables True) (unVarOrdering <$> goalOrder) (testConstraints test) (testPreferences test) normal diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 3e725c16acf..c4405a9974e 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -331,7 +331,7 @@ tests = [ -- and an executable conflict apply to the same package version. "[__1] rejecting: H:bt-pkg:exe.bt-pkg-4.0.0 (conflict: H => H:bt-pkg:exe.bt-pkg (exe exe1)==3.0.0)\n" ++ "[__1] rejecting: H:bt-pkg:exe.bt-pkg-3.0.0 (does not contain executable 'exe1', which is required by H)\n" - ++ "[__1] rejecting: H:bt-pkg:exe.bt-pkg-2.0.0, H:bt-pkg:exe.bt-pkg-1.0.0 (conflict: H => H:bt-pkg:exe.bt-pkg (exe exe1)==3.0.0)" + ++ "[__1] rejecting: H:bt-pkg:exe.bt-pkg-2.0.0 (conflict: H => H:bt-pkg:exe.bt-pkg (exe exe1)==3.0.0)" , runTest $ chooseExeAfterBuildToolsPackage True "choose exe after choosing its package - success" @@ -408,6 +408,266 @@ tests = [ chooseUnbuildableExeAfterBuildToolsPackage "choose unbuildable exe after choosing its package" ] + + , testGroup "--fine-grained-conflicts" [ + + -- Skipping a version because of a problematic dependency: + -- + -- When the solver explores A-4, it finds that it cannot satisfy B's + -- dependencies. This allows the solver to skip the subsequent + -- versions of A that also depend on B. + runTest $ + let db = [ + Right $ exAv "A" 4 [ExAny "B"] + , Right $ exAv "A" 3 [ExAny "B"] + , Right $ exAv "A" 2 [ExAny "B"] + , Right $ exAv "A" 1 [] + , Right $ exAv "B" 2 [ExAny "unknown1"] + , Right $ exAv "B" 1 [ExAny "unknown2"] + ] + msg = [ + "[__0] trying: A-4.0.0 (user goal)" + , "[__1] trying: B-2.0.0 (dependency of A)" + , "[__2] unknown package: unknown1 (dependency of B)" + , "[__2] fail (backjumping, conflict set: B, unknown1)" + , "[__1] trying: B-1.0.0" + , "[__2] unknown package: unknown2 (dependency of B)" + , "[__2] fail (backjumping, conflict set: B, unknown2)" + , "[__1] fail (backjumping, conflict set: A, B, unknown1, unknown2)" + , "[__0] skipping: A-3.0.0, A-2.0.0 (has the same characteristics that " + ++ "caused the previous version to fail: depends on 'B')" + , "[__0] trying: A-1.0.0" + , "[__1] done" + ] + in setVerbose $ + mkTest db "skip version due to problematic dependency" ["A"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1)] + + , -- Skipping a version because of a restrictive constraint on a + -- dependency: + -- + -- The solver rejects A-4 because its constraint on B excludes B-1. + -- Then the solver is able to skip A-3 and A-2 because they also + -- exclude B-1, even though they don't have the exact same constraints + -- on B. + runTest $ + let db = [ + Right $ exAv "A" 4 [ExFix "B" 14] + , Right $ exAv "A" 3 [ExFix "B" 13] + , Right $ exAv "A" 2 [ExFix "B" 12] + , Right $ exAv "A" 1 [ExFix "B" 11] + , Right $ exAv "B" 11 [] + ] + msg = [ + "[__0] trying: A-4.0.0 (user goal)" + , "[__1] next goal: B (dependency of A)" + , "[__1] rejecting: B-11.0.0 (conflict: A => B==14.0.0)" + , "[__1] fail (backjumping, conflict set: A, B)" + , "[__0] skipping: A-3.0.0, A-2.0.0 (has the same characteristics that " + ++ "caused the previous version to fail: depends on 'B' but excludes " + ++ "version 11.0.0)" + , "[__0] trying: A-1.0.0" + , "[__1] next goal: B (dependency of A)" + , "[__1] trying: B-11.0.0" + , "[__2] done" + ] + in setVerbose $ + mkTest db "skip version due to restrictive constraint on its dependency" ["A"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1), ("B", 11)] + + , -- This test tests the case where the solver chooses a version for one + -- package, B, before choosing a version for one of its reverse + -- dependencies, C. While the solver is exploring the subtree rooted + -- at B-3, it finds that C-2's dependency on B conflicts with B-3. + -- Then the solver is able to skip C-1, because it also excludes B-3. + -- + -- --fine-grained-conflicts could have a benefit in this case even + -- though the solver would have found the conflict between B-3 and C-1 + -- immediately after trying C-1 anyway. It prevents C-1 from + -- introducing any other conflicts which could increase the size of + -- the conflict set. + runTest $ + let db = [ + Right $ exAv "A" 1 [ExAny "B", ExAny "C"] + , Right $ exAv "B" 3 [] + , Right $ exAv "B" 2 [] + , Right $ exAv "B" 1 [] + , Right $ exAv "C" 2 [ExFix "B" 2] + , Right $ exAv "C" 1 [ExFix "B" 1] + ] + goals = [P QualNone pkg | pkg <- ["A", "B", "C"]] + expectedMsg = [ + "[__0] trying: A-1.0.0 (user goal)" + , "[__1] trying: B-3.0.0 (dependency of A)" + , "[__2] next goal: C (dependency of A)" + , "[__2] rejecting: C-2.0.0 (conflict: B==3.0.0, C => B==2.0.0)" + , "[__2] skipping: C-1.0.0 (has the same characteristics that caused the " + ++ "previous version to fail: excludes 'B' version 3.0.0)" + , "[__2] fail (backjumping, conflict set: A, B, C)" + , "[__1] trying: B-2.0.0" + , "[__2] next goal: C (dependency of A)" + , "[__2] trying: C-2.0.0" + , "[__3] done" + ] + in setVerbose $ goalOrder goals $ + mkTest db "skip version that excludes dependency that was already chosen" ["A"] $ + SolverResult (isInfixOf expectedMsg) $ Right [("A", 1), ("B", 2), ("C", 2)] + + , -- This test tests how the solver merges conflicts when it has + -- multiple reasons to add a variable to the conflict set. In this + -- case, package A conflicts with B and C. The solver should take the + -- union of the conflicts and then only skip a version if it does not + -- resolve any of the conflicts. + -- + -- The solver rejects A-3 because it can't find consistent versions for + -- its two dependencies, B and C. Then it skips A-2 because A-2 also + -- depends on B and C. This test ensures that the solver considers + -- A-1 even though A-1 only resolves one of the conflicts (A-1 removes + -- the dependency on C). + runTest $ + let db = [ + Right $ exAv "A" 3 [ExAny "B", ExAny "C"] + , Right $ exAv "A" 2 [ExAny "B", ExAny "C"] + , Right $ exAv "A" 1 [ExAny "B"] + , Right $ exAv "B" 1 [ExFix "D" 1] + , Right $ exAv "C" 1 [ExFix "D" 2] + , Right $ exAv "D" 1 [] + , Right $ exAv "D" 2 [] + ] + goals = [P QualNone pkg | pkg <- ["A", "B", "C", "D"]] + msg = [ + "[__0] trying: A-3.0.0 (user goal)" + , "[__1] trying: B-1.0.0 (dependency of A)" + , "[__2] trying: C-1.0.0 (dependency of A)" + , "[__3] next goal: D (dependency of B)" + , "[__3] rejecting: D-2.0.0 (conflict: B => D==1.0.0)" + , "[__3] rejecting: D-1.0.0 (conflict: C => D==2.0.0)" + , "[__3] fail (backjumping, conflict set: B, C, D)" + , "[__2] fail (backjumping, conflict set: A, B, C, D)" + , "[__1] fail (backjumping, conflict set: A, B, C, D)" + , "[__0] skipping: A-2.0.0 (has the same characteristics that caused the " + ++ "previous version to fail: depends on 'B'; depends on 'C')" + , "[__0] trying: A-1.0.0" + , "[__1] trying: B-1.0.0 (dependency of A)" + , "[__2] next goal: D (dependency of B)" + , "[__2] rejecting: D-2.0.0 (conflict: B => D==1.0.0)" + , "[__2] trying: D-1.0.0" + , "[__3] done" + ] + in setVerbose $ goalOrder goals $ + mkTest db "only skip a version if it resolves none of the previous conflicts" ["A"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1), ("B", 1), ("D", 1)] + + , -- This test ensures that the solver log doesn't show all conflicts + -- that the solver encountered in a subtree. The solver should only + -- show the conflicts that are contained in the current conflict set. + -- + -- The goal order forces the solver to try A-4, encounter a conflict + -- with B-2, try B-1, and then try C. A-4 conflicts with the only + -- version of C, so the solver backjumps with a conflict set of + -- {A, C}. When the solver skips the next version of A, the log should + -- mention the conflict with C but not B. + runTest $ + let db = [ + Right $ exAv "A" 4 [ExFix "B" 1, ExFix "C" 1] + , Right $ exAv "A" 3 [ExFix "B" 1, ExFix "C" 1] + , Right $ exAv "A" 2 [ExFix "C" 1] + , Right $ exAv "A" 1 [ExFix "C" 2] + , Right $ exAv "B" 2 [] + , Right $ exAv "B" 1 [] + , Right $ exAv "C" 2 [] + ] + goals = [P QualNone pkg | pkg <- ["A", "B", "C"]] + msg = [ + "[__0] trying: A-4.0.0 (user goal)" + , "[__1] next goal: B (dependency of A)" + , "[__1] rejecting: B-2.0.0 (conflict: A => B==1.0.0)" + , "[__1] trying: B-1.0.0" + , "[__2] next goal: C (dependency of A)" + , "[__2] rejecting: C-2.0.0 (conflict: A => C==1.0.0)" + , "[__2] fail (backjumping, conflict set: A, C)" + , "[__0] skipping: A-3.0.0, A-2.0.0 (has the same characteristics that caused the " + ++ "previous version to fail: depends on 'C' but excludes version 2.0.0)" + , "[__0] trying: A-1.0.0" + , "[__1] next goal: C (dependency of A)" + , "[__1] trying: C-2.0.0" + , "[__2] done" + ] + in setVerbose $ goalOrder goals $ + mkTest db "don't show conflicts that aren't part of the conflict set" ["A"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1), ("C", 2)] + + , -- Tests that the conflict set is properly updated when a version is + -- skipped due to being excluded by one of its reverse dependencies' + -- constraints. + runTest $ + let db = [ + Right $ exAv "A" 2 [ExFix "B" 3] + , Right $ exAv "A" 1 [ExFix "B" 1] + , Right $ exAv "B" 2 [] + , Right $ exAv "B" 1 [] + ] + msg = [ + "[__0] trying: A-2.0.0 (user goal)" + , "[__1] next goal: B (dependency of A)" + + -- During this step, the solver adds A and B to the + -- conflict set, with the details of each package's + -- conflict: + -- + -- A: A's constraint rejected B-2. + -- B: B was rejected by A's B==3 constraint + , "[__1] rejecting: B-2.0.0 (conflict: A => B==3.0.0)" + + -- When the solver skips B-1, it cannot simply reuse the + -- previous conflict set. It also needs to update A's + -- entry to say that A also rejected B-1. Otherwise, the + -- solver wouldn't know that A-1 could resolve one of + -- the conflicts encountered while exploring A-2. The + -- solver would skip A-1, even though it leads to the + -- solution. + , "[__1] skipping: B-1.0.0 (has the same characteristics that caused " + ++ "the previous version to fail: excluded by constraint '==3.0.0' from 'A')" + + , "[__1] fail (backjumping, conflict set: A, B)" + , "[__0] trying: A-1.0.0" + , "[__1] next goal: B (dependency of A)" + , "[__1] rejecting: B-2.0.0 (conflict: A => B==1.0.0)" + , "[__1] trying: B-1.0.0" + , "[__2] done" + ] + in setVerbose $ + mkTest db "update conflict set after skipping version - 1" ["A"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1), ("B", 1)] + + , -- Tests that the conflict set is properly updated when a version is + -- skipped due to excluding a version of one of its dependencies. + -- This test is similar the previous one, with the goal order reversed. + runTest $ + let db = [ + Right $ exAv "A" 2 [] + , Right $ exAv "A" 1 [] + , Right $ exAv "B" 2 [ExFix "A" 3] + , Right $ exAv "B" 1 [ExFix "A" 1] + ] + goals = [P QualNone pkg | pkg <- ["A", "B"]] + msg = [ + "[__0] trying: A-2.0.0 (user goal)" + , "[__1] next goal: B (user goal)" + , "[__1] rejecting: B-2.0.0 (conflict: A==2.0.0, B => A==3.0.0)" + , "[__1] skipping: B-1.0.0 (has the same characteristics that caused " + ++ "the previous version to fail: excludes 'A' version 2.0.0)" + , "[__1] fail (backjumping, conflict set: A, B)" + , "[__0] trying: A-1.0.0" + , "[__1] next goal: B (user goal)" + , "[__1] rejecting: B-2.0.0 (conflict: A==1.0.0, B => A==3.0.0)" + , "[__1] trying: B-1.0.0" + , "[__2] done" + ] + in setVerbose $ goalOrder goals $ + mkTest db "update conflict set after skipping version - 2" ["A", "B"] $ + SolverResult (isInfixOf msg) $ Right [("A", 1), ("B", 1)] + ] -- Tests for the contents of the solver's log , testGroup "Solver log" [ -- See issue #3203. The solver should only choose a version for A once. @@ -428,16 +688,15 @@ tests = [ , testSummarizedLog "show conflicts from final conflict set after exhaustive search" Nothing $ "Could not resolve dependencies:\n" ++ "[__0] trying: A-1.0.0 (user goal)\n" - ++ "[__1] unknown package: D (dependency of A)\n" - ++ "[__1] fail (backjumping, conflict set: A, D)\n" + ++ "[__1] unknown package: F (dependency of A)\n" + ++ "[__1] fail (backjumping, conflict set: A, F)\n" ++ "After searching the rest of the dependency tree exhaustively, " - ++ "these were the goals I've had most trouble fulfilling: A, D" + ++ "these were the goals I've had most trouble fulfilling: A, F" , testSummarizedLog "show first conflicts after inexhaustive search" (Just 3) $ "Could not resolve dependencies:\n" ++ "[__0] trying: A-1.0.0 (user goal)\n" ++ "[__1] trying: B-3.0.0 (dependency of A)\n" - ++ "[__2] next goal: C (dependency of B)\n" - ++ "[__2] rejecting: C-1.0.0 (conflict: B => C==3.0.0)\n" + ++ "[__2] unknown package: C (dependency of B)\n" ++ "[__2] fail (backjumping, conflict set: B, C)\n" ++ "Backjump limit reached (currently 3, change with --max-backjumps " ++ "or try to run with --reorder-goals).\n" @@ -1399,28 +1658,27 @@ dbPC1 = [ , Right $ exAv "C" 1 [ExAny "B"] ] --- | Test for the solver's summarized log. The final conflict set is {A, D}, +-- | Test for the solver's summarized log. The final conflict set is {A, F}, -- though the goal order forces the solver to find the (avoidable) conflict --- between B >= 2 and C first. When the solver reaches the backjump limit, it --- should only show the log to the first conflict. When the backjump limit is --- high enough to allow an exhaustive search, the solver should make use of the --- final conflict set to only show the conflict between A and D in the --- summarized log. +-- between B and C first. When the solver reaches the backjump limit, it should +-- only show the log to the first conflict. When the backjump limit is high +-- enough to allow an exhaustive search, the solver should make use of the final +-- conflict set to only show the conflict between A and F in the summarized log. testSummarizedLog :: String -> Maybe Int -> String -> TestTree testSummarizedLog testName mbj expectedMsg = runTest $ maxBackjumps mbj $ goalOrder goals $ mkTest db testName ["A"] $ solverFailure (== expectedMsg) where db = [ - Right $ exAv "A" 1 [ExAny "B", ExAny "D"] - , Right $ exAv "B" 3 [ExFix "C" 3] - , Right $ exAv "B" 2 [ExFix "C" 2] - , Right $ exAv "B" 1 [ExAny "C"] - , Right $ exAv "C" 1 [] + Right $ exAv "A" 1 [ExAny "B", ExAny "F"] + , Right $ exAv "B" 3 [ExAny "C"] + , Right $ exAv "B" 2 [ExAny "D"] + , Right $ exAv "B" 1 [ExAny "E"] + , Right $ exAv "E" 1 [] ] goals :: [ExampleVar] - goals = [P QualNone pkg | pkg <- ["A", "B", "C", "D"]] + goals = [P QualNone pkg | pkg <- ["A", "B", "C", "D", "E", "F"]] dbMinimizeConflictSet :: ExampleDb dbMinimizeConflictSet = [ @@ -1453,9 +1711,7 @@ testMinimizeConflictSet testName = , "Trying to remove variable \"A\" from the conflict set." , "Failed to remove \"A\" from the conflict set. Continuing with {A, B, C, D}." , "Trying to remove variable \"B\" from the conflict set." - , "Successfully removed \"B\" from the conflict set. Continuing with {A, C, D}." - , "Trying to remove variable \"C\" from the conflict set." - , "Successfully removed \"C\" from the conflict set. Continuing with {A, D}." + , "Successfully removed \"B\" from the conflict set. Continuing with {A, D}." , "Trying to remove variable \"D\" from the conflict set." , "Failed to remove \"D\" from the conflict set. Continuing with {A, D}." ] @@ -1467,7 +1723,7 @@ testMinimizeConflictSet testName = ++ "[__1] rejecting: D-1.0.0 (conflict: A => D==2.0.0)\n" ++ "[__1] fail (backjumping, conflict set: A, D)\n" ++ "After searching the rest of the dependency tree exhaustively, these " - ++ "were the goals I've had most trouble fulfilling: A (7), D (6)" + ++ "were the goals I've had most trouble fulfilling: A (5), D (4)" goals :: [ExampleVar] goals = [P QualNone pkg | pkg <- ["A", "B", "C", "D"]]