diff --git a/cabal-install/Distribution/Client/Dependency.hs b/cabal-install/Distribution/Client/Dependency.hs index f13777ab0b5..69f48ba88ef 100644 --- a/cabal-install/Distribution/Client/Dependency.hs +++ b/cabal-install/Distribution/Client/Dependency.hs @@ -64,7 +64,7 @@ module Distribution.Client.Dependency ( ) where import Distribution.Solver.Modular - ( modularResolver, SolverConfig(..), PruneAfterFirstSuccess(..) ) + ( modularResolver, SolverConfig(..) ) import Distribution.Simple.PackageIndex (InstalledPackageIndex) import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex import Distribution.Client.SolverInstallPlan (SolverInstallPlan) @@ -719,7 +719,7 @@ resolveDependencies platform comp pkgConfigDB solver params = $ runSolver solver (SolverConfig reordGoals cntConflicts indGoals noReinstalls shadowing strFlags allowBootLibs maxBkjumps enableBj - solveExes order verbosity (PruneAfterFirstSuccess False)) + solveExes order verbosity) platform comp installedPkgIndex sourcePkgIndex pkgConfigDB preferences constraints targets where diff --git a/cabal-install/Distribution/Solver/Modular.hs b/cabal-install/Distribution/Solver/Modular.hs index b7e2d98430a..21de9cb57dc 100644 --- a/cabal-install/Distribution/Solver/Modular.hs +++ b/cabal-install/Distribution/Solver/Modular.hs @@ -1,5 +1,5 @@ module Distribution.Solver.Modular - ( modularResolver, SolverConfig(..), PruneAfterFirstSuccess(..)) where + ( modularResolver, SolverConfig(..)) where -- Here, we try to map between the external cabal-install solver -- interface and the internal interface that the solver actually @@ -9,39 +9,25 @@ module Distribution.Solver.Modular -- and finally, we have to convert back the resulting install -- plan. -import Data.Map (Map) -import qualified Data.Map as M -import Data.Set (Set) -import Data.Ord +import Data.Map as M + ( fromListWith ) import Distribution.Compat.Graph ( IsNode(..) ) -import Distribution.Compiler - ( CompilerInfo ) import Distribution.Solver.Modular.Assignment - ( Assignment, toCPs ) + ( toCPs ) import Distribution.Solver.Modular.ConfiguredConversion ( convCP ) -import qualified Distribution.Solver.Modular.ConflictSet as CS -import Distribution.Solver.Modular.Dependency -import Distribution.Solver.Modular.Flag -import Distribution.Solver.Modular.Index import Distribution.Solver.Modular.IndexConversion ( convPIs ) import Distribution.Solver.Modular.Log - ( SolverFailure(..), logToProgress ) + ( logToProgress ) import Distribution.Solver.Modular.Package ( PN ) import Distribution.Solver.Modular.Solver - ( SolverConfig(..), PruneAfterFirstSuccess(..), solve ) -import Distribution.Solver.Types.DependencyResolver + ( SolverConfig(..), solve ) import Distribution.Solver.Types.LabeledPackageConstraint import Distribution.Solver.Types.PackageConstraint -import Distribution.Solver.Types.PackagePath -import Distribution.Solver.Types.PackagePreferences -import Distribution.Solver.Types.PkgConfigDb - ( PkgConfigDb ) -import Distribution.Solver.Types.Progress -import Distribution.Solver.Types.Variable +import Distribution.Solver.Types.DependencyResolver import Distribution.System ( Platform(..) ) import Distribution.Simple.Utils @@ -52,8 +38,9 @@ import Distribution.Simple.Utils -- solver. Performs the necessary translations before and after. modularResolver :: SolverConfig -> DependencyResolver loc modularResolver sc (Platform arch os) cinfo iidx sidx pkgConfigDB pprefs pcs pns = - fmap (uncurry postprocess) $ -- convert install plan - solve' sc cinfo idx pkgConfigDB pprefs gcs pns + fmap (uncurry postprocess) $ -- convert install plan + logToProgress (solverVerbosity sc) (maxBackjumps sc) $ -- convert log format into progress format + solve sc cinfo idx pkgConfigDB pprefs gcs pns where -- Indices have to be converted into solver-specific uniform index. idx = convPIs os arch cinfo (shadowPkgs sc) (strongFlags sc) (solveExecutables sc) iidx sidx @@ -71,95 +58,3 @@ modularResolver sc (Platform arch os) cinfo iidx sidx pkgConfigDB pprefs pcs pns -- Helper function to extract the PN from a constraint. pcName :: PackageConstraint -> PN pcName (PackageConstraint scope _) = scopeToPackageName scope - --- | Run 'D.S.Modular.Solver.solve' and then produce a summarized log to display --- in the error case. --- --- When there is no solution, we produce the error message by rerunning the --- solver but making it prefer the goals from the final conflict set from the --- first run. We also set the backjump limit to 0, so that the log stops at the --- first backjump and is relatively short. Preferring goals from the final --- conflict set increases the probability that the log to the first backjump --- contains package, flag, and stanza choices that are relevant to the final --- failure. The solver shouldn't need to choose any packages that aren't in the --- final conflict set. (For every variable in the final conflict set, the final --- conflict set should also contain the variable that introduced that variable. --- The solver can then follow that chain of variables in reverse order from the --- user target to the conflict.) However, it is possible that the conflict set --- contains unnecessary variables. --- --- Producing an error message when the solver reaches the backjump limit is more --- complicated. There is no final conflict set, so we create one for the minimal --- subtree containing the path that the solver took to the first backjump. This --- conflict set helps explain why the solver reached the backjump limit, because --- the first backjump contributes to reaching the backjump limit. Additionally, --- the solver is much more likely to be able to finish traversing this subtree --- before the backjump limit, since its size is linear (not exponential) in the --- number of goal choices. We create it by pruning all children after the first --- successful child under each node in the original tree, so that there is at --- most one valid choice at each level. Then we use the final conflict set from --- that run to generate an error message, as in the case where the solver found --- that there was no solution. --- --- Using the full log from a rerun of the solver ensures that the log is --- complete, i.e., it shows the whole chain of dependencies from the user --- targets to the conflicting packages. -solve' :: SolverConfig - -> CompilerInfo - -> Index - -> PkgConfigDb - -> (PN -> PackagePreferences) - -> Map PN [LabeledPackageConstraint] - -> Set PN - -> Progress String String (Assignment, RevDepMap) -solve' sc cinfo idx pkgConfigDB pprefs gcs pns = - foldProgress Step createErrorMsg Done (runSolver sc) - where - runSolver :: SolverConfig - -> Progress String SolverFailure (Assignment, RevDepMap) - runSolver sc' = - logToProgress (solverVerbosity sc') (maxBackjumps sc') $ -- convert log format into progress format - solve sc' cinfo idx pkgConfigDB pprefs gcs pns - - createErrorMsg :: SolverFailure - -> Progress String String (Assignment, RevDepMap) - createErrorMsg (NoSolution cs msg) = - Fail $ rerunSolverForErrorMsg cs msg - createErrorMsg (BackjumpLimitReached msg) = - Step ("Backjump limit reached. Rerunning dependency solver to generate " - ++ "a final conflict set for the search tree containing the " - ++ "first backjump.") $ - foldProgress Step f Done $ - runSolver sc { pruneAfterFirstSuccess = PruneAfterFirstSuccess True } - where - f :: SolverFailure -> Progress String String (Assignment, RevDepMap) - f (NoSolution cs _) = Fail $ rerunSolverForErrorMsg cs msg - f (BackjumpLimitReached _) = - -- This case is possible when the number of goals involved in - -- conflicts is greater than the backjump limit. - Fail $ msg ++ "Failed to generate a summarized dependency solver " - ++ "log due to low backjump limit." - - rerunSolverForErrorMsg :: ConflictSet -> String -> String - rerunSolverForErrorMsg cs finalMsg = - let sc' = sc { - goalOrder = Just (preferGoalsFromConflictSet cs) - , maxBackjumps = Just 0 - } - in unlines ("Could not resolve dependencies:" : messages (runSolver sc')) - ++ finalMsg - - messages :: Progress step fail done -> [step] - messages = foldProgress (:) (const []) (const []) - --- | Goal ordering that chooses goals contained in the conflict set before --- other goals. -preferGoalsFromConflictSet :: ConflictSet - -> Variable QPN -> Variable QPN -> Ordering -preferGoalsFromConflictSet cs = - comparing $ \v -> not $ CS.member (toVar v) cs - where - toVar :: Variable QPN -> Var QPN - toVar (PackageVar qpn) = P qpn - toVar (FlagVar qpn fn) = F (FN qpn fn) - toVar (StanzaVar qpn sn) = S (SN qpn sn) diff --git a/cabal-install/Distribution/Solver/Modular/Log.hs b/cabal-install/Distribution/Solver/Modular/Log.hs index 0490b9366e1..f80352e7406 100644 --- a/cabal-install/Distribution/Solver/Modular/Log.hs +++ b/cabal-install/Distribution/Solver/Modular/Log.hs @@ -1,12 +1,13 @@ module Distribution.Solver.Modular.Log ( Log , logToProgress - , SolverFailure(..) ) where import Prelude () import Distribution.Solver.Compat.Prelude +import Data.List as L + import Distribution.Solver.Types.Progress import Distribution.Solver.Modular.Dependency @@ -22,22 +23,20 @@ import Distribution.Verbosity -- Parameterized over the type of actual messages and the final result. type Log m a = Progress m (ConflictSet, ConflictMap) a -data Exhaustiveness = Exhaustive | BackjumpLimit +messages :: Progress step fail done -> [step] +messages = foldProgress (:) (const []) (const []) --- | Information about a dependency solver failure. It includes an error message --- and a final conflict set, if available. -data SolverFailure = - NoSolution ConflictSet String - | BackjumpLimitReached String +data Exhaustiveness = Exhaustive | BackjumpLimitReached -- | Postprocesses a log file. Takes as an argument a limit on allowed backjumps. -- If the limit is 'Nothing', then infinitely many backjumps are allowed. If the -- limit is 'Just 0', backtracking is completely disabled. -logToProgress :: Verbosity -> Maybe Int -> Log Message a -> Progress String SolverFailure a +logToProgress :: Verbosity -> Maybe Int -> Log Message a -> Progress String String a logToProgress verbosity mbj l = - let ms = proc mbj l - mapFailure f = foldProgress Step (Fail . f) Done - in mapFailure finalError (showMessages ms) -- run with backjump limit applied + let es = proc (Just 0) l -- catch first error (always) + ms = proc mbj l + in go es es -- trace for first error + (showMessages (const True) True ms) -- run with backjump limit applied where -- Proc takes the allowed number of backjumps and a 'Progress' and explores the -- messages until the maximum number of backjumps has been reached. It filters out @@ -49,15 +48,30 @@ logToProgress verbosity mbj l = proc _ (Fail (cs, cm)) = Fail (Exhaustive, cs, cm) proc mbj' (Step x@(Failure cs Backjump) xs@(Step Leave (Step (Failure cs' Backjump) _))) | cs == cs' = Step x (proc mbj' xs) -- repeated backjumps count as one - proc (Just 0) (Step (Failure cs Backjump) _) = Fail (BackjumpLimit, cs, mempty) -- No final conflict map available + proc (Just 0) (Step (Failure cs Backjump) _) = Fail (BackjumpLimitReached, cs, mempty) -- No final conflict map available proc (Just n) (Step x@(Failure _ Backjump) xs) = Step x (proc (Just (n - 1)) xs) proc mbj' (Step x xs) = Step x (proc mbj' xs) - finalError :: (Exhaustiveness, ConflictSet, ConflictMap) -> SolverFailure - finalError (exh, cs, cm) = + -- The first two arguments are both supposed to be the log up to the first error. + -- That's the error that will always be printed in case we do not find a solution. + -- We pass this log twice, because we evaluate it in parallel with the full log, + -- but we also want to retain the reference to its beginning for when we print it. + -- This trick prevents a space leak! + -- + -- The third argument is the full log, ending with either the solution or the + -- exhaustiveness and final conflict set. + go :: Progress Message (Exhaustiveness, ConflictSet, ConflictMap) b + -> Progress Message (Exhaustiveness, ConflictSet, ConflictMap) b + -> Progress String (Exhaustiveness, ConflictSet, ConflictMap) b + -> Progress String String b + go ms (Step _ ns) (Step x xs) = Step x (go ms ns xs) + go ms r (Step x xs) = Step x (go ms r xs) + go ms (Step _ ns) r = go ms ns r + go ms (Fail (_, cs', _)) (Fail (exh, cs, cm)) = Fail $ + "Could not resolve dependencies:\n" ++ + unlines (messages $ showMessages (L.foldr (\ v _ -> v `CS.member` cs') True) False ms) ++ case exh of Exhaustive -> - NoSolution cs $ "After searching the rest of the dependency tree exhaustively, " ++ "these were the goals I've had most trouble fulfilling: " ++ showCS cm cs @@ -65,9 +79,14 @@ logToProgress verbosity mbj l = showCS = if verbosity > normal then CS.showCSWithFrequency else CS.showCSSortedByFrequency - BackjumpLimit -> - BackjumpLimitReached $ + BackjumpLimitReached -> "Backjump limit reached (" ++ currlimit mbj ++ "change with --max-backjumps or try to run with --reorder-goals).\n" where currlimit (Just n) = "currently " ++ show n ++ ", " currlimit Nothing = "" + go _ _ (Done s) = Done s + go _ (Done _) (Fail _) = Fail $ + -- Should not happen: Second argument is the log up to first error, + -- third one is the entire log. Therefore it should never happen that + -- the second log finishes with 'Done' and the third log with 'Fail'. + "Could not resolve dependencies; something strange happened." diff --git a/cabal-install/Distribution/Solver/Modular/Message.hs b/cabal-install/Distribution/Solver/Modular/Message.hs index 5ead5dab254..99eecc843ee 100644 --- a/cabal-install/Distribution/Solver/Modular/Message.hs +++ b/cabal-install/Distribution/Solver/Modular/Message.hs @@ -33,46 +33,59 @@ data Message = -- | Transforms the structured message type to actual messages (strings). -- --- The log contains level numbers, which are useful for any trace that involves --- backtracking, because only the level numbers will allow to keep track of --- backjumps. -showMessages :: Progress Message a b -> Progress String a b -showMessages = go 0 +-- Takes an additional relevance predicate. The predicate gets a stack of goal +-- variables and can decide whether messages regarding these goals are relevant. +-- You can plug in 'const True' if you're interested in a full trace. If you +-- want a slice of the trace concerning a particular conflict set, then plug in +-- a predicate returning 'True' on the empty stack and if the head is in the +-- conflict set. +-- +-- The second argument indicates if the level numbers should be shown. This is +-- recommended for any trace that involves backtracking, because only the level +-- numbers will allow to keep track of backjumps. +showMessages :: ([Var QPN] -> Bool) -> Bool -> Progress Message a b -> Progress String a b +showMessages p sl = go [] 0 where - -- 'go' increments the level for a recursive call when it encounters - -- 'TryP', 'TryF', or 'TryS' and decrements the level when it encounters 'Leave'. - go :: Int -> Progress Message a b -> Progress String a b - go !_ (Done x) = Done x - go !_ (Fail x) = Fail x + -- The stack 'v' represents variables that are currently assigned by the + -- solver. 'go' pushes a variable for a recursive call when it encounters + -- 'TryP', 'TryF', or 'TryS' and pops a variable when it encounters 'Leave'. + -- When 'go' processes a package goal, or a package goal followed by a + -- 'Failure', it calls 'atLevel' with the goal variable at the head of the + -- stack so that the predicate can also select messages relating to package + -- goal choices. + go :: [Var QPN] -> Int -> Progress Message a b -> Progress String a b + go !_ !_ (Done x) = Done x + go !_ !_ (Fail x) = Fail x -- 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 (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)))) = - (atLevel l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go l ms) - go !l (Step (Next (Goal (P _ ) gr)) (Step (TryP qpn' i) ms@(Step Enter (Step (Next _) _)))) = - (atLevel l $ "trying: " ++ showQPNPOpt qpn' i ++ showGR gr) (go l ms) - go !l (Step (Next (Goal (P qpn) gr)) ms@(Fail _)) = - (atLevel l $ "unknown package: " ++ showQPN qpn ++ showGR gr) $ go l ms + go !v !l (Step (TryP qpn i) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = + goPReject v l qpn [i] c fr ms + go !v !l (Step (TryF qfn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = + (atLevel (F qfn : v) l $ "rejecting: " ++ showQFNBool qfn b ++ showFR c fr) (go v l ms) + go !v !l (Step (TryS qsn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = + (atLevel (S qsn : v) l $ "rejecting: " ++ showQSNBool qsn b ++ showFR c fr) (go v l ms) + go !v !l (Step (Next (Goal (P qpn) gr)) (Step (TryP qpn' i) ms@(Step Enter (Step (Next _) _)))) = + (atLevel (P qpn : v) l $ "trying: " ++ showQPNPOpt qpn' i ++ showGR gr) (go (P qpn : v) l ms) + go !v !l (Step (Next (Goal (P qpn) gr)) ms@(Fail _)) = + (atLevel (P qpn : v) l $ "unknown package: " ++ showQPN qpn ++ showGR gr) $ go v l ms -- the previous case potentially arises in the error output, because we remove the backjump itself -- if we cut the log after the first error - go !l (Step (Next (Goal (P qpn) gr)) ms@(Step (Failure _c Backjump) _)) = - (atLevel l $ "unknown package: " ++ showQPN qpn ++ showGR gr) $ go l ms - go !l (Step (Next (Goal (P qpn) gr)) (Step (Failure c fr) ms)) = - (atLevel l $ showPackageGoal qpn gr) $ (atLevel l $ showFailure c fr) (go l ms) - go !l (Step (Failure c Backjump) ms@(Step Leave (Step (Failure c' Backjump) _))) - | c == c' = go l ms + go !v !l (Step (Next (Goal (P qpn) gr)) ms@(Step (Failure _c Backjump) _)) = + (atLevel (P qpn : v) l $ "unknown package: " ++ showQPN qpn ++ showGR gr) $ go v l ms + go !v !l (Step (Next (Goal (P qpn) gr)) (Step (Failure c fr) ms)) = + let v' = P qpn : v + in (atLevel v' l $ showPackageGoal qpn gr) $ (atLevel v' l $ showFailure c fr) (go v l ms) + go !v !l (Step (Failure c Backjump) ms@(Step Leave (Step (Failure c' Backjump) _))) + | c == c' = go v l ms -- standard display - go !l (Step Enter ms) = go (l+1) ms - go !l (Step Leave ms) = go (l-1) ms - go !l (Step (TryP qpn i) ms) = (atLevel l $ "trying: " ++ showQPNPOpt qpn i) (go l ms) - go !l (Step (TryF qfn b) ms) = (atLevel l $ "trying: " ++ showQFNBool qfn b) (go l ms) - 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 (Success) ms) = (atLevel l $ "done") (go l ms) - go !l (Step (Failure c fr) ms) = (atLevel l $ showFailure c fr) (go l ms) + go !v !l (Step Enter ms) = go v (l+1) ms + go !v !l (Step Leave ms) = go (drop 1 v) (l-1) ms + go !v !l (Step (TryP qpn i) ms) = (atLevel (P qpn : v) l $ "trying: " ++ showQPNPOpt qpn i) (go (P qpn : v) l ms) + go !v !l (Step (TryF qfn b) ms) = (atLevel (F qfn : v) l $ "trying: " ++ showQFNBool qfn b) (go (F qfn : v) l ms) + go !v !l (Step (TryS qsn b) ms) = (atLevel (S qsn : v) l $ "trying: " ++ showQSNBool qsn b) (go (S qsn : v) l ms) + go !v !l (Step (Next (Goal (P qpn) gr)) ms) = (atLevel (P qpn : v) l $ showPackageGoal qpn gr) (go v l ms) + go !v !l (Step (Next _) ms) = go v l ms -- ignore flag goals in the log + go !v !l (Step (Success) ms) = (atLevel v l $ "done") (go v l ms) + go !v !l (Step (Failure c fr) ms) = (atLevel v l $ showFailure c fr) (go v l ms) showPackageGoal :: QPN -> QGoalReason -> String showPackageGoal qpn gr = "next goal: " ++ showQPN qpn ++ showGR gr @@ -81,23 +94,26 @@ showMessages = go 0 showFailure c fr = "fail" ++ showFR c fr -- special handler for many subsequent package rejections - goPReject :: Int + goPReject :: [Var QPN] + -> Int -> QPN -> [POption] -> ConflictSet -> FailReason -> Progress Message a b -> Progress String a b - goPReject l qpn is c fr (Step (TryP qpn' i) (Step Enter (Step (Failure _ fr') (Step Leave ms)))) - | qpn == qpn' && fr == fr' = goPReject l qpn (i : is) c fr ms - goPReject l qpn is c fr ms = - (atLevel l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go l ms) + goPReject v l qpn is c fr (Step (TryP qpn' i) (Step Enter (Step (Failure _ fr') (Step Leave ms)))) + | qpn == qpn' && fr == fr' = goPReject v l qpn (i : is) c fr ms + goPReject v l qpn is c fr ms = + (atLevel (P qpn : v) l $ "rejecting: " ++ L.intercalate ", " (map (showQPNPOpt qpn) (reverse is)) ++ showFR c fr) (go v 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 + -- write a message, but only if it's relevant; we can also enable or disable the display of the current level + atLevel :: [Var QPN] -> Int -> String -> Progress String a b -> Progress String a b + atLevel v l x xs + | sl && p v = let s = show l + in Step ("[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ x) xs + | p v = Step x xs + | otherwise = xs showQPNPOpt :: QPN -> POption -> String showQPNPOpt qpn@(Q _pp pn) (POption i linkedTo) = diff --git a/cabal-install/Distribution/Solver/Modular/Preference.hs b/cabal-install/Distribution/Solver/Modular/Preference.hs index e7665ba528a..74816f51f09 100644 --- a/cabal-install/Distribution/Solver/Modular/Preference.hs +++ b/cabal-install/Distribution/Solver/Modular/Preference.hs @@ -14,7 +14,6 @@ module Distribution.Solver.Modular.Preference , preferReallyEasyGoalChoices , requireInstalled , sortGoals - , pruneAfterFirstSuccess ) where import Prelude () @@ -352,17 +351,6 @@ sortGoals variableOrder = trav go varToVariable (F (FN qpn fn)) = FlagVar qpn fn varToVariable (S (SN qpn stanza)) = StanzaVar qpn stanza --- | Reduce the branching degree of the search tree by removing all choices --- after the first successful choice at each level. The returned tree is the --- minimal subtree containing the path to the first backjump. -pruneAfterFirstSuccess :: Tree d c -> Tree d c -pruneAfterFirstSuccess = trav go - where - go (PChoiceF qpn rdm gr ts) = PChoiceF qpn rdm gr (W.takeUntil active ts) - go (FChoiceF qfn rdm gr w m d ts) = FChoiceF qfn rdm gr w m d (W.takeUntil active ts) - go (SChoiceF qsn rdm gr w ts) = SChoiceF qsn rdm gr w (W.takeUntil active ts) - go x = x - -- | Always choose the first goal in the list next, abandoning all -- other choices. -- diff --git a/cabal-install/Distribution/Solver/Modular/Solver.hs b/cabal-install/Distribution/Solver/Modular/Solver.hs index b2b717ca4f6..acfe4def487 100644 --- a/cabal-install/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/Distribution/Solver/Modular/Solver.hs @@ -6,7 +6,6 @@ module Distribution.Solver.Modular.Solver ( SolverConfig(..) , solve - , PruneAfterFirstSuccess(..) ) where import Data.Map as M @@ -54,25 +53,20 @@ import Debug.Trace.Tree.Assoc (Assoc(..)) -- | Various options for the modular solver. data SolverConfig = SolverConfig { - reorderGoals :: ReorderGoals, - countConflicts :: CountConflicts, - independentGoals :: IndependentGoals, - avoidReinstalls :: AvoidReinstalls, - shadowPkgs :: ShadowPkgs, - strongFlags :: StrongFlags, - allowBootLibInstalls :: AllowBootLibInstalls, - maxBackjumps :: Maybe Int, - enableBackjumping :: EnableBackjumping, - solveExecutables :: SolveExecutables, - goalOrder :: Maybe (Variable QPN -> Variable QPN -> Ordering), - solverVerbosity :: Verbosity, - pruneAfterFirstSuccess :: PruneAfterFirstSuccess + reorderGoals :: ReorderGoals, + countConflicts :: CountConflicts, + independentGoals :: IndependentGoals, + avoidReinstalls :: AvoidReinstalls, + shadowPkgs :: ShadowPkgs, + strongFlags :: StrongFlags, + allowBootLibInstalls :: AllowBootLibInstalls, + maxBackjumps :: Maybe Int, + enableBackjumping :: EnableBackjumping, + solveExecutables :: SolveExecutables, + goalOrder :: Maybe (Variable QPN -> Variable QPN -> Ordering), + solverVerbosity :: Verbosity } --- | Whether to remove all choices after the first successful choice at each --- level in the search tree. -newtype PruneAfterFirstSuccess = PruneAfterFirstSuccess Bool - -- | Run all solver phases. -- -- In principle, we have a valid tree after 'validationPhase', which @@ -103,18 +97,15 @@ solve sc cinfo idx pkgConfigDB userPrefs userConstraints userGoals = detectCycles = traceTree "cycles.json" id . detectCyclesPhase heuristicsPhase = let heuristicsTree = traceTree "heuristics.json" id - sortGoals = case goalOrder sc of - Nothing -> goalChoiceHeuristics . - heuristicsTree . - P.deferSetupChoices . - P.deferWeakFlagChoices . - P.preferBaseGoalChoice - Just order -> P.firstGoal . - heuristicsTree . - P.sortGoals order - PruneAfterFirstSuccess prune = pruneAfterFirstSuccess sc - in sortGoals . - (if prune then P.pruneAfterFirstSuccess else id) + in case goalOrder sc of + Nothing -> goalChoiceHeuristics . + heuristicsTree . + P.deferSetupChoices . + P.deferWeakFlagChoices . + P.preferBaseGoalChoice + Just order -> P.firstGoal . + heuristicsTree . + P.sortGoals order preferencesPhase = P.preferLinked . P.preferPackagePreferences userPrefs validationPhase = traceTree "validated.json" id . diff --git a/cabal-install/Distribution/Solver/Modular/Tree.hs b/cabal-install/Distribution/Solver/Modular/Tree.hs index a7ee060e043..91c2f9becc4 100644 --- a/cabal-install/Distribution/Solver/Modular/Tree.hs +++ b/cabal-install/Distribution/Solver/Modular/Tree.hs @@ -13,7 +13,6 @@ module Distribution.Solver.Modular.Tree , para , trav , zeroOrOneChoices - , active ) where import Control.Monad hiding (mapM, sequence) diff --git a/cabal-install/Distribution/Solver/Modular/WeightedPSQ.hs b/cabal-install/Distribution/Solver/Modular/WeightedPSQ.hs index 21216358ac5..91c37a4e6ae 100644 --- a/cabal-install/Distribution/Solver/Modular/WeightedPSQ.hs +++ b/cabal-install/Distribution/Solver/Modular/WeightedPSQ.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-} -{-# LANGUAGE ScopedTypeVariables #-} module Distribution.Solver.Modular.WeightedPSQ ( WeightedPSQ , fromList @@ -12,7 +11,6 @@ module Distribution.Solver.Modular.WeightedPSQ ( , mapWithKey , mapWeightsWithKey , union - , takeUntil ) where import qualified Data.Foldable as F @@ -78,15 +76,6 @@ mapWithKey f (WeightedPSQ xs) = WeightedPSQ $ union :: Ord w => WeightedPSQ w k v -> WeightedPSQ w k v -> WeightedPSQ w k v union (WeightedPSQ xs) (WeightedPSQ ys) = fromList (xs ++ ys) --- | /O(N)/. Return the prefix of values ending with the first element that --- satisfies p, or all elements if none satisfy p. -takeUntil :: forall w k v. (v -> Bool) -> WeightedPSQ w k v -> WeightedPSQ w k v -takeUntil p (WeightedPSQ xs) = WeightedPSQ (go xs) - where - go :: [(w, k, v)] -> [(w, k, v)] - go [] = [] - go (y : ys) = y : if p (triple_3 y) then [] else go ys - triple_1 :: (x, y, z) -> x triple_1 (x, _, _) = x diff --git a/cabal-install/Distribution/Solver/Types/Variable.hs b/cabal-install/Distribution/Solver/Types/Variable.hs index b4e4b1d7549..c33dcb12e4b 100644 --- a/cabal-install/Distribution/Solver/Types/Variable.hs +++ b/cabal-install/Distribution/Solver/Types/Variable.hs @@ -5,7 +5,8 @@ import Distribution.Solver.Types.OptionalStanza import Distribution.PackageDescription (FlagName) -- | Variables used by the dependency solver. This type is similar to the --- internal 'Var' type. +-- internal 'Var' type, except that flags and stanzas are associated with +-- package names instead of package instances. data Variable qpn = PackageVar qpn | FlagVar qpn FlagName 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 379324ab6e3..b242f2f8153 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs @@ -3,7 +3,6 @@ module UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils ( SolverTest , SolverResult(..) - , maxBackjumps , independentGoals , allowBootLibInstalls , disableBackjumping @@ -47,9 +46,6 @@ import Distribution.Client.Dependency (foldProgress) import UnitTests.Distribution.Solver.Modular.DSL import UnitTests.Options -maxBackjumps :: Maybe Int -> SolverTest -> SolverTest -maxBackjumps mbj test = test { testMaxBackjumps = mbj } - -- | Combinator to turn on --independent-goals behavior, i.e. solve -- for the goals as if we were solving for each goal independently. independentGoals :: SolverTest -> SolverTest @@ -87,7 +83,6 @@ data SolverTest = SolverTest { testLabel :: String , testTargets :: [String] , testResult :: SolverResult - , testMaxBackjumps :: Maybe Int , testIndepGoals :: IndependentGoals , testAllowBootLibInstalls :: AllowBootLibInstalls , testEnableBackjumping :: EnableBackjumping @@ -180,7 +175,6 @@ mkTestExtLangPC exts langs pkgConfigDb db label targets result = SolverTest { testLabel = label , testTargets = targets , testResult = result - , testMaxBackjumps = Nothing , testIndepGoals = IndependentGoals False , testAllowBootLibInstalls = AllowBootLibInstalls False , testEnableBackjumping = EnableBackjumping True @@ -200,7 +194,7 @@ runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) -> testCase testLabel $ do let progress = exResolve testDb testSupportedExts testSupportedLangs testPkgConfigDb testTargets - testMaxBackjumps (CountConflicts True) testIndepGoals + Nothing (CountConflicts True) testIndepGoals (ReorderGoals False) testAllowBootLibInstalls testEnableBackjumping testSolveExecutables (sortGoals <$> testGoalOrder) testConstraints diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index f5fa336bad3..720884ec68e 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -292,9 +292,9 @@ tests = [ solverFailure $ isInfixOf $ -- The solver reports the version conflict when a version conflict -- 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)" + "rejecting: H:bt-pkg:exe.bt-pkg-4.0.0 (conflict: H => H:bt-pkg:exe.bt-pkg (exe exe1)==3.0.0)\n" + ++ "rejecting: H:bt-pkg:exe.bt-pkg-3.0.0 (does not contain executable exe1, which is required by H)\n" + ++ "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)" , runTest $ chooseExeAfterBuildToolsPackage True "choose exe after choosing its package - success" @@ -346,25 +346,6 @@ tests = [ ++ "these were the goals I've had most trouble fulfilling: A, B" in mkTest db "exhaustive search failure message" ["A"] $ solverFailure (isInfixOf msg) - , 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" - ++ "After searching the rest of the dependency tree exhaustively, " - ++ "these were the goals I've had most trouble fulfilling: A, D" - , testSummarizedLog "show first conflicts after inexhaustive search" (Just 2) $ - "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" - ++ "Backjump limit reached (currently 2, change with --max-backjumps " - ++ "or try to run with --reorder-goals).\n" - , testSummarizedLog "don't show summarized log when backjump limit is too low" (Just 1) $ - "Backjump limit reached (currently 1, change with --max-backjumps " - ++ "or try to run with --reorder-goals).\n" - ++ "Failed to generate a summarized dependency solver log due to low backjump limit." ] ] where @@ -967,9 +948,9 @@ db18 = [ commonDependencyLogMessage :: String -> SolverTest commonDependencyLogMessage name = mkTest db name ["A"] $ solverFailure $ isInfixOf $ - "[__0] trying: A-1.0.0 (user goal)\n" - ++ "[__1] next goal: B (dependency of A +/-flagA)\n" - ++ "[__1] rejecting: B-2.0.0 (conflict: A +/-flagA => B==1.0.0 || ==3.0.0)" + "trying: A-1.0.0 (user goal)\n" + ++ "next goal: B (dependency of A +/-flagA)\n" + ++ "rejecting: B-2.0.0 (conflict: A +/-flagA => B==1.0.0 || ==3.0.0)" where db :: ExampleDb db = [ @@ -1301,29 +1282,6 @@ dbPC1 = [ , Right $ exAv "C" 1 [ExAny "B"] ] --- | Test for the solver's summarized log. The final conflict set is {A, D}, --- 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. -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 [] - ] - - goals :: [ExampleVar] - goals = [P QualNone pkg | pkg <- ["A", "B", "C", "D"]] - {------------------------------------------------------------------------------- Simple databases for the illustrations for the backjumping blog post -------------------------------------------------------------------------------} @@ -1501,8 +1459,8 @@ chooseExeAfterBuildToolsPackage shouldSucceed name = requireConsistentBuildToolVersions :: String -> SolverTest requireConsistentBuildToolVersions name = mkTest db name ["A"] $ solverFailure $ isInfixOf $ - "[__1] rejecting: A:B:exe.B-2.0.0 (conflict: A => A:B:exe.B (exe exe1)==1.0.0)\n" - ++ "[__1] rejecting: A:B:exe.B-1.0.0 (conflict: A => A:B:exe.B (exe exe2)==2.0.0)" + "rejecting: A:B:exe.B-2.0.0 (conflict: A => A:B:exe.B (exe exe1)==1.0.0)\n" + ++ "rejecting: A:B:exe.B-1.0.0 (conflict: A => A:B:exe.B (exe exe2)==2.0.0)" where db :: ExampleDb db = [ diff --git a/cabal-testsuite/PackageTests/Backpack/Includes2/setup-external.cabal.out b/cabal-testsuite/PackageTests/Backpack/Includes2/setup-external.cabal.out index e91346a7b78..38102e2778f 100644 --- a/cabal-testsuite/PackageTests/Backpack/Includes2/setup-external.cabal.out +++ b/cabal-testsuite/PackageTests/Backpack/Includes2/setup-external.cabal.out @@ -99,10 +99,10 @@ Registering library for src-0.1.0.0.. Resolving dependencies... Warning: solver failed to find a solution: Could not resolve dependencies: -[__0] trying: exe-0.1.0.0 (user goal) -[__1] next goal: src (dependency of exe) -[__1] rejecting: src-/installed-... (conflict: src => mylib==0.1.0.0/installed-0.1..., src => mylib==0.1.0.0/installed-0.1...) -[__1] fail (backjumping, conflict set: exe, src) +trying: exe-0.1.0.0 (user goal) +next goal: src (dependency of exe) +rejecting: src-/installed-... (conflict: src => mylib==0.1.0.0/installed-0.1..., src => mylib==0.1.0.0/installed-0.1...) +fail (backjumping, conflict set: exe, src) After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: exe (2), src (2) Trying configure anyway. Configuring exe-0.1.0.0... diff --git a/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackage/use-local-version-of-package.out b/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackage/use-local-version-of-package.out index f975325ec42..3515de864da 100644 --- a/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackage/use-local-version-of-package.out +++ b/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackage/use-local-version-of-package.out @@ -13,7 +13,7 @@ local pkg-1.0 # cabal new-build Resolving dependencies... cabal: Could not resolve dependencies: -[__0] next goal: pkg (user goal) -[__0] rejecting: pkg-2.0 (constraint from user target requires ==1.0) -[__0] rejecting: pkg-1.0 (constraint from command line flag requires ==2.0) +next goal: pkg (user goal) +rejecting: pkg-2.0 (constraint from user target requires ==1.0) +rejecting: pkg-1.0 (constraint from command line flag requires ==2.0) After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: pkg (3) diff --git a/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/use-local-package-as-setup-dep.out b/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/use-local-package-as-setup-dep.out index db1c3e94a60..c9c06ce0dbf 100644 --- a/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/use-local-package-as-setup-dep.out +++ b/cabal-testsuite/PackageTests/BuildTargets/UseLocalPackageForSetup/use-local-package-as-setup-dep.out @@ -3,11 +3,11 @@ Downloading the latest package list from test-local-repo # cabal new-build Resolving dependencies... cabal: Could not resolve dependencies: -[__0] trying: pkg-1.0 (user goal) -[__1] next goal: setup-dep (user goal) -[__1] rejecting: setup-dep-2.0 (conflict: pkg => setup-dep==1.*) -[__1] rejecting: setup-dep-1.0 (constraint from user target requires ==2.0) -[__1] fail (backjumping, conflict set: pkg, setup-dep) +trying: pkg-1.0 (user goal) +next goal: setup-dep (user goal) +rejecting: setup-dep-2.0 (conflict: pkg => setup-dep==1.*) +rejecting: setup-dep-1.0 (constraint from user target requires ==2.0) +fail (backjumping, conflict set: pkg, setup-dep) After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: setup-dep (3), pkg (2) # pkg my-exe Main.hs: setup-dep from repo diff --git a/cabal-testsuite/PackageTests/ConfigureComponent/Exe/setup.cabal.out b/cabal-testsuite/PackageTests/ConfigureComponent/Exe/setup.cabal.out index 90439a1fdc6..9284a1ad2c5 100644 --- a/cabal-testsuite/PackageTests/ConfigureComponent/Exe/setup.cabal.out +++ b/cabal-testsuite/PackageTests/ConfigureComponent/Exe/setup.cabal.out @@ -2,9 +2,9 @@ Resolving dependencies... Warning: solver failed to find a solution: Could not resolve dependencies: -[__0] trying: Exe-0.1.0.0 (user goal) -[__1] unknown package: totally-impossible-dependency-to-fill (dependency of Exe) -[__1] fail (backjumping, conflict set: Exe, totally-impossible-dependency-to-fill) +trying: Exe-0.1.0.0 (user goal) +unknown package: totally-impossible-dependency-to-fill (dependency of Exe) +fail (backjumping, conflict set: Exe, totally-impossible-dependency-to-fill) After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: Exe (2), totally-impossible-dependency-to-fill (1) Trying configure anyway. Configuring executable 'goodexe' for Exe-0.1.0.0.. diff --git a/cabal-testsuite/PackageTests/InternalVersions/BuildDependsBad/setup.cabal.out b/cabal-testsuite/PackageTests/InternalVersions/BuildDependsBad/setup.cabal.out index 815f6399b4a..32067ddeab2 100644 --- a/cabal-testsuite/PackageTests/InternalVersions/BuildDependsBad/setup.cabal.out +++ b/cabal-testsuite/PackageTests/InternalVersions/BuildDependsBad/setup.cabal.out @@ -2,8 +2,8 @@ Resolving dependencies... Warning: solver failed to find a solution: Could not resolve dependencies: -[__0] next goal: build-depends-bad-version (user goal) -[__0] rejecting: build-depends-bad-version-0.1.0.0 (conflict: build-depends-bad-version==0.1.0.0, build-depends-bad-version => build-depends-bad-version>=2) +next goal: build-depends-bad-version (user goal) +rejecting: build-depends-bad-version-0.1.0.0 (conflict: build-depends-bad-version==0.1.0.0, build-depends-bad-version => build-depends-bad-version>=2) After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: build-depends-bad-version (2) Trying configure anyway. Configuring build-depends-bad-version-0.1.0.0...