From 737c918ee018c1baf5cee32ec8786a2518c1ef16 Mon Sep 17 00:00:00 2001 From: Yvan Sraka Date: Fri, 13 Jun 2025 13:04:44 +1000 Subject: [PATCH 1/4] Refactor cabal-install solver config log output The main goal is to add an intermediate log message type to the processing of the solver log. Changes to the cabal solver's output are very minor. Includes: * Apply some of @grayjay and @mpickering comments * Fix #4251 Co-Authored-By: Erik de Castro Lopo --- .../cabal-install-solver.cabal | 1 + .../src/Distribution/Solver/Modular.hs | 66 ++++--- .../src/Distribution/Solver/Modular/Log.hs | 12 +- .../Distribution/Solver/Modular/Message.hs | 176 +++++++++++------- .../Solver/Types/DependencyResolver.hs | 10 +- .../src/Distribution/Solver/Types/Progress.hs | 5 + .../Solver/Types/SummarizedMessage.hs | 35 ++++ .../src/Distribution/Client/Dependency.hs | 61 +++--- .../Solver/Modular/DSL/TestCaseUtils.hs | 2 +- changelog.d/pr-10854 | 7 + 10 files changed, 245 insertions(+), 130 deletions(-) create mode 100644 cabal-install-solver/src/Distribution/Solver/Types/SummarizedMessage.hs create mode 100644 changelog.d/pr-10854 diff --git a/cabal-install-solver/cabal-install-solver.cabal b/cabal-install-solver/cabal-install-solver.cabal index 4547b74fb14..f5a78f4a30c 100644 --- a/cabal-install-solver/cabal-install-solver.cabal +++ b/cabal-install-solver/cabal-install-solver.cabal @@ -95,6 +95,7 @@ library Distribution.Solver.Types.SolverId Distribution.Solver.Types.SolverPackage Distribution.Solver.Types.SourcePackage + Distribution.Solver.Types.SummarizedMessage Distribution.Solver.Types.Variable build-depends: diff --git a/cabal-install-solver/src/Distribution/Solver/Modular.hs b/cabal-install-solver/src/Distribution/Solver/Modular.hs index 9111b2d78d0..1db5f4c5454 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular.hs @@ -27,8 +27,14 @@ 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 + ( Var(..), + showVar, + ConflictMap, + ConflictSet, + showConflictSet, + RevDepMap ) +import Distribution.Solver.Modular.Flag ( SN(SN), FN(FN) ) +import Distribution.Solver.Modular.Index ( Index ) import Distribution.Solver.Modular.IndexConversion ( convPIs ) import Distribution.Solver.Modular.Log @@ -36,25 +42,36 @@ import Distribution.Solver.Modular.Log import Distribution.Solver.Modular.Package ( PN ) import Distribution.Solver.Modular.RetryLog + ( RetryLog, + toProgress, + fromProgress, + retry, + failWith, + continueWith ) import Distribution.Solver.Modular.Solver ( SolverConfig(..), PruneAfterFirstSuccess(..), solve ) import Distribution.Solver.Types.DependencyResolver + ( DependencyResolver ) import Distribution.Solver.Types.LabeledPackageConstraint + ( LabeledPackageConstraint, unlabelPackageConstraint ) import Distribution.Solver.Types.PackageConstraint -import Distribution.Solver.Types.PackagePath + ( PackageConstraint(..), scopeToPackageName ) +import Distribution.Solver.Types.PackagePath ( QPN ) import Distribution.Solver.Types.PackagePreferences + ( PackagePreferences ) import Distribution.Solver.Types.PkgConfigDb ( PkgConfigDb ) import Distribution.Solver.Types.Progress -import Distribution.Solver.Types.Variable + ( Progress(..), foldProgress, SummarizedMessage(ErrorMsg) ) +import Distribution.Solver.Types.Variable ( Variable(..) ) import Distribution.System ( Platform(..) ) import Distribution.Simple.Setup ( BooleanFlag(..) ) import Distribution.Simple.Utils - ( ordNubBy ) -import Distribution.Verbosity - + ( ordNubBy ) +import Distribution.Verbosity ( normal, verbose ) +import Distribution.Solver.Modular.Message ( renderSummarizedMessage ) -- | Ties the two worlds together: classic cabal-install vs. the modular -- solver. Performs the necessary translations before and after. @@ -120,21 +137,21 @@ solve' :: SolverConfig -> (PN -> PackagePreferences) -> Map PN [LabeledPackageConstraint] -> Set PN - -> Progress String String (Assignment, RevDepMap) + -> Progress SummarizedMessage String (Assignment, RevDepMap) solve' sc cinfo idx pkgConfigDB pprefs gcs pns = toProgress $ retry (runSolver printFullLog sc) createErrorMsg where runSolver :: Bool -> SolverConfig - -> RetryLog String SolverFailure (Assignment, RevDepMap) + -> RetryLog SummarizedMessage SolverFailure (Assignment, RevDepMap) runSolver keepLog sc' = displayLogMessages keepLog $ solve sc' cinfo idx pkgConfigDB pprefs gcs pns createErrorMsg :: SolverFailure - -> RetryLog String String (Assignment, RevDepMap) + -> RetryLog SummarizedMessage String (Assignment, RevDepMap) createErrorMsg failure@(ExhaustiveSearch cs cm) = if asBool $ minimizeConflictSet sc - then continueWith ("Found no solution after exhaustively searching the " + then continueWith (mkErrorMsg $ "Found no solution after exhaustively searching the " ++ "dependency tree. Rerunning the dependency solver " ++ "to minimize the conflict set ({" ++ showConflictSet cs ++ "}).") $ @@ -155,7 +172,7 @@ solve' sc cinfo idx pkgConfigDB pprefs gcs pns = rerunSolverForErrorMsg cs ++ finalErrorMsg sc failure createErrorMsg failure@BackjumpLimitReached = continueWith - ("Backjump limit reached. Rerunning dependency solver to generate " + (mkErrorMsg $ "Backjump limit reached. Rerunning dependency solver to generate " ++ "a final conflict set for the search tree containing the " ++ "first backjump.") $ retry (runSolver printFullLog sc { pruneAfterFirstSuccess = PruneAfterFirstSuccess True }) $ @@ -181,13 +198,16 @@ solve' sc cinfo idx pkgConfigDB pprefs gcs pns = -- original goal order. goalOrder' = preferGoalsFromConflictSet cs <> fromMaybe mempty (goalOrder sc) - in unlines ("Could not resolve dependencies:" : messages (toProgress (runSolver True sc'))) + in unlines ("Could not resolve dependencies:" : map renderSummarizedMessage (messages (toProgress (runSolver True sc')))) printFullLog = solverVerbosity sc >= verbose messages :: Progress step fail done -> [step] messages = foldProgress (:) (const []) (const []) +mkErrorMsg :: String -> SummarizedMessage +mkErrorMsg msg = ErrorMsg msg + -- | Try to remove variables from the given conflict set to create a minimal -- conflict set. -- @@ -219,11 +239,11 @@ solve' sc cinfo idx pkgConfigDB pprefs gcs pns = -- solver to add new unnecessary variables to the conflict set. This function -- discards the result from any run that adds new variables to the conflict -- set, but the end result may not be completely minimized. -tryToMinimizeConflictSet :: forall a . (SolverConfig -> RetryLog String SolverFailure a) +tryToMinimizeConflictSet :: forall a . (SolverConfig -> RetryLog SummarizedMessage SolverFailure a) -> SolverConfig -> ConflictSet -> ConflictMap - -> RetryLog String SolverFailure a + -> RetryLog SummarizedMessage SolverFailure a tryToMinimizeConflictSet runSolver sc cs cm = foldl (\r v -> retryNoSolution r $ tryToRemoveOneVar v) (fromProgress $ Fail $ ExhaustiveSearch cs cm) @@ -249,14 +269,14 @@ tryToMinimizeConflictSet runSolver sc cs cm = tryToRemoveOneVar :: Var QPN -> ConflictSet -> ConflictMap - -> RetryLog String SolverFailure a + -> RetryLog SummarizedMessage SolverFailure a tryToRemoveOneVar v smallestKnownCS smallestKnownCM -- Check whether v is still present, because it may have already been -- removed in a previous solver rerun. | not (v `CS.member` smallestKnownCS) = fromProgress $ Fail $ ExhaustiveSearch smallestKnownCS smallestKnownCM | otherwise = - continueWith ("Trying to remove variable " ++ varStr ++ " from the " + continueWith (mkErrorMsg $ "Trying to remove variable " ++ varStr ++ " from the " ++ "conflict set.") $ retry (runSolver sc') $ \case err@(ExhaustiveSearch cs' _) @@ -268,14 +288,14 @@ tryToMinimizeConflictSet runSolver sc cs cm = ++ "conflict set." in -- Use the new conflict set, even if v wasn't removed, -- because other variables may have been removed. - failWith (msg ++ " Continuing with " ++ showCS cs' ++ ".") err + failWith (mkErrorMsg $ msg ++ " Continuing with " ++ showCS cs' ++ ".") err | otherwise -> - failWith ("Failed to find a smaller conflict set. The new " + failWith (mkErrorMsg $ "Failed to find a smaller conflict set. The new " ++ "conflict set is not a subset of the previous " ++ "conflict set: " ++ showCS cs') $ ExhaustiveSearch smallestKnownCS smallestKnownCM BackjumpLimitReached -> - failWith "Reached backjump limit while minimizing conflict set." + failWith (mkErrorMsg "Reached backjump limit while minimizing conflict set.") BackjumpLimitReached where varStr = "\"" ++ showVar v ++ "\"" @@ -290,9 +310,9 @@ tryToMinimizeConflictSet runSolver sc cs cm = -- Like 'retry', except that it only applies the input function when the -- backjump limit has not been reached. - retryNoSolution :: RetryLog step SolverFailure done - -> (ConflictSet -> ConflictMap -> RetryLog step SolverFailure done) - -> RetryLog step SolverFailure done + retryNoSolution :: RetryLog SummarizedMessage SolverFailure done + -> (ConflictSet -> ConflictMap -> RetryLog SummarizedMessage SolverFailure done) + -> RetryLog SummarizedMessage SolverFailure done retryNoSolution lg f = retry lg $ \case ExhaustiveSearch cs' cm' -> f cs' cm' BackjumpLimitReached -> fromProgress (Fail BackjumpLimitReached) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Log.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Log.hs index 321a051070b..5bb4b72afe7 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Log.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Log.hs @@ -7,10 +7,12 @@ import Prelude () import Distribution.Solver.Compat.Prelude import Distribution.Solver.Types.Progress - -import Distribution.Solver.Modular.Dependency -import Distribution.Solver.Modular.Message + ( Progress(Done, Fail), foldProgress, SummarizedMessage ) +import Distribution.Solver.Modular.ConflictSet + ( ConflictMap, ConflictSet ) import Distribution.Solver.Modular.RetryLog + ( RetryLog, toProgress, fromProgress ) +import Distribution.Solver.Modular.Message (Message, summarizeMessages) -- | Information about a dependency solver failure. data SolverFailure = @@ -22,10 +24,10 @@ data SolverFailure = -- 'keepLog'), for efficiency. displayLogMessages :: Bool -> RetryLog Message SolverFailure a - -> RetryLog String SolverFailure a + -> RetryLog SummarizedMessage SolverFailure a displayLogMessages keepLog lg = fromProgress $ if keepLog - then showMessages progress + then summarizeMessages progress else foldProgress (const id) Fail Done progress where progress = toProgress lg diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index 2bc28286df0..d15dd383f91 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -1,9 +1,9 @@ {-# LANGUAGE BangPatterns #-} -{-# LANGUAGE LambdaCase #-} module Distribution.Solver.Modular.Message ( Message(..), - showMessages + summarizeMessages, + renderSummarizedMessage, ) where import qualified Data.List as L @@ -14,26 +14,51 @@ import qualified Data.Set as S import Data.Maybe (catMaybes, mapMaybe, isJust) import Prelude hiding (pi) -import Distribution.Pretty (prettyShow) -- from Cabal +import Distribution.Pretty ( prettyShow ) -- from Cabal import qualified Distribution.Solver.Modular.ConflictSet as CS import Distribution.Solver.Modular.Dependency -import Distribution.Solver.Modular.Flag ( QFN, QSN ) -import qualified Distribution.Solver.Modular.Flag as Flag ( showQFN, showQFNBool, showQSN, showQSNBool ) + ( Var(P), + ConflictSet, + showConflictSet, + QGoalReason, + GoalReason(DependencyGoal, UserGoal), + Goal(Goal), + DependencyReason(DependencyReason), + ExposedComponent(..), + PkgComponent(PkgComponent), + CI(Constrained, Fixed), + showDependencyReason ) +import Distribution.Solver.Modular.Flag + ( QSN, QFN, showQFNBool, showQSNBool, showQFN, showQSN ) import Distribution.Solver.Modular.MessageUtils - (showUnsupportedExtension, showUnsupportedLanguage) + ( showUnsupportedExtension, showUnsupportedLanguage ) import Distribution.Solver.Modular.Package + ( PI(PI), showI, showPI ) import Distribution.Solver.Modular.Tree - ( FailReason(..), POption(..), ConflictingDep(..) ) + ( FailReason(..), POption(..), ConflictingDep(..) ) import Distribution.Solver.Modular.Version + ( VR, Ver, showVer, showVR, (.||.) ) + import Distribution.Solver.Types.ConstraintSource + ( ConstraintSource (..), showConstraintSource ) import Distribution.Solver.Types.PackagePath + ( QPN, Qualified(Q), showQPN ) import Distribution.Solver.Types.Progress -import Distribution.Solver.Types.ProjectConfigPath (docProjectConfigPathFailReason) + ( Progress(..), + SummarizedMessage(..), + EntryAtLevel(..), + Entry(..) ) +import Distribution.Solver.Types.ProjectConfigPath + ( docProjectConfigPathFailReason) import Distribution.Types.LibraryName + ( LibraryName(LSubLibName, LMainLibName) ) import Distribution.Types.UnqualComponentName -import Text.PrettyPrint (nest, render) + ( unUnqualComponentName ) + +import Text.PrettyPrint ( nest, render ) +-- A data type to hold state information for the modular solver. data Message = Enter -- ^ increase indentation level | Leave -- ^ decrease indentation level @@ -45,51 +70,87 @@ data Message = | Success | Failure ConflictSet FailReason --- | Transforms the structured message type to actual messages (strings). +renderSummarizedMessage :: SummarizedMessage -> String +renderSummarizedMessage (SummarizedMsg i) = displayMessageAtLevel i +renderSummarizedMessage (ErrorMsg s) = s + +displayMessageAtLevel :: EntryAtLevel -> String +displayMessageAtLevel (AtLevel l msg) = + let s = show l + in "[" ++ replicate (3 - length s) '_' ++ s ++ "] " ++ displayMessage msg + +displayMessage :: Entry -> String +displayMessage (EntryPackageGoal qpn gr) = "next goal: " ++ showQPN qpn ++ showGR gr +displayMessage (EntryRejectF qfn b c fr) = "rejecting: " ++ showQFNBool qfn b ++ showFR c fr +displayMessage (EntryRejectS qsn b c fr) = "rejecting: " ++ showQSNBool qsn b ++ showFR c fr +displayMessage (EntrySkipping cs) = "skipping: " ++ showConflicts cs +displayMessage (EntryTryingF qfn b) = "trying: " ++ showQFNBool qfn b +displayMessage (EntryTryingP qpn i) = "trying: " ++ showOption qpn i +displayMessage (EntryTryingNewP qpn i gr) = "trying: " ++ showOption qpn i ++ showGR gr +displayMessage (EntryTryingS qsn b) = "trying: " ++ showQSNBool qsn b +displayMessage (EntryUnknownPackage qpn gr) = "unknown package: " ++ showQPN qpn ++ showGR gr +displayMessage EntrySuccess = "done" +displayMessage (EntryFailure c fr) = "fail" ++ showFR c fr +displayMessage (EntrySkipMany qsn b cs) = "skipping: " ++ showOptions qsn b ++ " " ++ showConflicts cs +-- Instead of displaying `aeson-1.0.2.1, aeson-1.0.2.0, aeson-1.0.1.0, ...`, +-- the following line aims to display `aeson: 1.0.2.1, 1.0.2.0, 1.0.1.0, ...`. +-- +displayMessage (EntryRejectMany qpn is c fr) = "rejecting: " ++ showQPNConflictSet qpn is ++ showFR c fr + +showQPNConflictSet :: QPN -> [POption] -> String +showQPNConflictSet qpn popts = + case popts of + [] -> "" + x:xs -> L.intercalate ", " (showOption qpn x : map (\(POption i _) -> showI i) xs) + +-- | Transforms the structured message type to actual messages (SummarizedMessage s). -- -- 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 +summarizeMessages :: Progress Message a b -> Progress SummarizedMessage a b +summarizeMessages = 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 :: Int -> Progress Message a b -> Progress SummarizedMessage 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 (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 $ blurbQFNBool Rejecting qfn b ++ showFR c fr) (go l ms) + Step (SummarizedMsg $ AtLevel l $ (EntryRejectF qfn b c fr)) (go l ms) + go !l (Step (TryS qsn b) (Step Enter (Step (Failure c fr) (Step Leave ms)))) = - (atLevel l $ blurbQSNBool Rejecting qsn b ++ showFR c fr) (go l ms) + Step (SummarizedMsg $ AtLevel l $ (EntryRejectS qsn b c fr)) (go l ms) + + -- "Trying ..." message when a new goal is started go !l (Step (Next (Goal (P _ ) gr)) (Step (TryP qpn' i) ms@(Step Enter (Step (Next _) _)))) = - (atLevel l $ blurbOption Trying qpn' i ++ showGR gr) (go l ms) + Step (SummarizedMsg $ AtLevel l $ (EntryTryingNewP qpn' i gr)) (go l ms) + go !l (Step (Next (Goal (P qpn) gr)) (Step (Failure _c UnknownPackage) ms)) = - atLevel l ("unknown package: " ++ showQPN qpn ++ showGR gr) $ go l ms + Step (SummarizedMsg $ AtLevel l $ (EntryUnknownPackage qpn gr)) (go 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 $ blurbOption Trying qpn i) (go l ms) - go !l (Step (TryF qfn b) ms) = (atLevel l $ blurbQFNBool Trying qfn b) (go l ms) - go !l (Step (TryS qsn b) ms) = (atLevel l $ blurbQSNBool Trying 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 $ blurb 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) - - showPackageGoal :: QPN -> QGoalReason -> String - showPackageGoal qpn gr = "next goal: " ++ showQPN qpn ++ showGR gr - - showFailure :: ConflictSet -> FailReason -> String - showFailure c fr = "fail" ++ showFR c fr + + go !l (Step (TryP qpn i) ms) = Step (SummarizedMsg $ AtLevel l $ (EntryTryingP qpn i)) (go l ms) + go !l (Step (TryF qfn b) ms) = Step (SummarizedMsg $ AtLevel l $ (EntryTryingF qfn b)) (go l ms) + go !l (Step (TryS qsn b) ms) = Step (SummarizedMsg $ AtLevel l $ (EntryTryingS qsn b)) (go l ms) + go !l (Step (Next (Goal (P qpn) gr)) ms) = Step (SummarizedMsg $ AtLevel l $ (EntryPackageGoal qpn gr)) (go l ms) + go !l (Step (Next _) ms) = go l ms -- ignore flag goals in the log + + -- 'Skip' should always be handled by 'goPSkip' in the case above. + go !l (Step (Skip conflicts) ms) = Step (SummarizedMsg $ AtLevel l $ (EntrySkipping conflicts)) (go l ms) + go !l (Step (Success) ms) = Step (SummarizedMsg $ AtLevel l $ EntrySuccess) (go l ms) + go !l (Step (Failure c fr) ms) = Step (SummarizedMsg $ AtLevel l $ (EntryFailure c fr)) (go l ms) -- special handler for many subsequent package rejections goPReject :: Int @@ -98,14 +159,13 @@ showMessages = go 0 -> ConflictSet -> FailReason -> Progress Message a b - -> Progress String a b + -> Progress SummarizedMessage a b goPReject l qpn is c fr (Step (TryP qpn' i) (Step Enter (Step (Failure _ fr') (Step Leave ms)))) | qpn == qpn' && fr == fr' = -- By prepending (i : is) we reverse the order of the instances. goPReject l qpn (i : is) c fr ms goPReject l qpn is c fr ms = - (atLevel l $ blurbOptions Rejecting qpn (reverse is) ++ showFR c fr) - (go l ms) + Step (SummarizedMsg $ AtLevel l $ (EntryRejectMany qpn is c fr)) (go l ms) -- Handle many subsequent skipped package instances. goPSkip :: Int @@ -113,25 +173,18 @@ showMessages = go 0 -> [POption] -> Set CS.Conflict -> Progress Message a b - -> Progress String a b + -> Progress SummarizedMessage a b goPSkip l qpn is conflicts (Step (TryP qpn' i) (Step Enter (Step (Skip conflicts') (Step Leave ms)))) | qpn == qpn' && conflicts == conflicts' = -- By prepending (i : is) we reverse the order of the instances. goPSkip l qpn (i : is) conflicts ms goPSkip l qpn is conflicts ms = - let msg = blurbOptions Skipping 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 + Step (SummarizedMsg $ AtLevel l $ (EntrySkipMany qpn is conflicts)) (go l ms) -- | 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: " + "(has the same characteristics that caused the previous version to fail: " ++ conflictMsg ++ ")" where conflictMsg :: String @@ -213,29 +266,6 @@ data MergedPackageConflict = MergedPackageConflict { , versionConflict :: Maybe VR } -data ProgressAction = - Trying - | Skipping - | Rejecting - -blurb :: ProgressAction -> String -blurb = \case - Trying -> "trying: " - Skipping -> "skipping: " - Rejecting -> "rejecting: " - -blurbQFNBool :: ProgressAction -> QFN -> Bool -> String -blurbQFNBool a q b = blurb a ++ Flag.showQFNBool q b - -blurbQSNBool :: ProgressAction -> QSN -> Bool -> String -blurbQSNBool a q b = blurb a ++ Flag.showQSNBool q b - -blurbOption :: ProgressAction -> QPN -> POption -> String -blurbOption a q p = blurb a ++ showOption q p - -blurbOptions :: ProgressAction -> QPN -> [POption] -> String -blurbOptions a q ps = blurb a ++ showOptions q ps - showOption :: QPN -> POption -> String showOption qpn@(Q _pp pn) (POption i linkedTo) = case linkedTo of @@ -267,7 +297,7 @@ showOptions q xs = showQPN q ++ "; " ++ (L.intercalate ", " [if isJust linkedTo then showOption q x else showI i -- Don't show the package, just the version - | x@(POption i linkedTo) <- xs + | x@(POption i linkedTo) <- reverse xs ]) showGR :: QGoalReason -> String @@ -306,8 +336,8 @@ showFR _ (UnsupportedSpecVer ver) = " (unsupported spec-version " ++ pre -- The following are internal failures. They should not occur. In the -- interest of not crashing unnecessarily, we still just print an error -- message though. -showFR _ (MalformedFlagChoice qfn) = " (INTERNAL ERROR: MALFORMED FLAG CHOICE: " ++ Flag.showQFN qfn ++ ")" -showFR _ (MalformedStanzaChoice qsn) = " (INTERNAL ERROR: MALFORMED STANZA CHOICE: " ++ Flag.showQSN qsn ++ ")" +showFR _ (MalformedFlagChoice qfn) = " (INTERNAL ERROR: MALFORMED FLAG CHOICE: " ++ showQFN qfn ++ ")" +showFR _ (MalformedStanzaChoice qsn) = " (INTERNAL ERROR: MALFORMED STANZA CHOICE: " ++ showQSN qsn ++ ")" showFR _ EmptyGoalChoice = " (INTERNAL ERROR: EMPTY GOAL CHOICE)" showExposedComponent :: ExposedComponent -> String @@ -332,7 +362,9 @@ showConflictingDep (ConflictingDep dr (PkgComponent qpn comp) ci) = componentStr ++ showVR vr -- $setup +-- >>> import Distribution.Solver.Modular.Package -- >>> import Distribution.Solver.Types.PackagePath +-- >>> import Distribution.Types.PackageName -- >>> import Distribution.Types.Version -- >>> import Distribution.Types.UnitId -- >>> let foobarPN = PackagePath DefaultNamespace QualToplevel diff --git a/cabal-install-solver/src/Distribution/Solver/Types/DependencyResolver.hs b/cabal-install-solver/src/Distribution/Solver/Types/DependencyResolver.hs index 139a6d2b33d..4dc1a265d73 100644 --- a/cabal-install-solver/src/Distribution/Solver/Types/DependencyResolver.hs +++ b/cabal-install-solver/src/Distribution/Solver/Types/DependencyResolver.hs @@ -2,16 +2,20 @@ module Distribution.Solver.Types.DependencyResolver ( DependencyResolver ) where -import Distribution.Solver.Compat.Prelude +import Distribution.Solver.Compat.Prelude ( Maybe, String, Set ) import Prelude () import Distribution.Solver.Types.LabeledPackageConstraint + ( LabeledPackageConstraint ) import Distribution.Solver.Types.PkgConfigDb ( PkgConfigDb ) import Distribution.Solver.Types.PackagePreferences + ( PackagePreferences ) import Distribution.Solver.Types.PackageIndex ( PackageIndex ) import Distribution.Solver.Types.Progress + ( Progress, SummarizedMessage ) import Distribution.Solver.Types.ResolverPackage -import Distribution.Solver.Types.SourcePackage + ( ResolverPackage ) +import Distribution.Solver.Types.SourcePackage ( SourcePackage ) import Distribution.Simple.PackageIndex ( InstalledPackageIndex ) import Distribution.Package ( PackageName ) @@ -34,4 +38,4 @@ type DependencyResolver loc = Platform -> (PackageName -> PackagePreferences) -> [LabeledPackageConstraint] -> Set PackageName - -> Progress String String [ResolverPackage loc] + -> Progress SummarizedMessage String [ResolverPackage loc] diff --git a/cabal-install-solver/src/Distribution/Solver/Types/Progress.hs b/cabal-install-solver/src/Distribution/Solver/Types/Progress.hs index a47e651d1c4..4322126c3f1 100644 --- a/cabal-install-solver/src/Distribution/Solver/Types/Progress.hs +++ b/cabal-install-solver/src/Distribution/Solver/Types/Progress.hs @@ -1,11 +1,16 @@ module Distribution.Solver.Types.Progress ( Progress(..) , foldProgress + , Entry(..) + , EntryAtLevel(..) + , SummarizedMessage(..) ) where import Prelude () import Distribution.Solver.Compat.Prelude hiding (fail) +import Distribution.Solver.Types.SummarizedMessage + -- | A type to represent the unfolding of an expensive long running -- calculation that may fail. We may get intermediate steps before the final -- result which may be used to indicate progress and\/or logging messages. diff --git a/cabal-install-solver/src/Distribution/Solver/Types/SummarizedMessage.hs b/cabal-install-solver/src/Distribution/Solver/Types/SummarizedMessage.hs new file mode 100644 index 00000000000..88f939e66d6 --- /dev/null +++ b/cabal-install-solver/src/Distribution/Solver/Types/SummarizedMessage.hs @@ -0,0 +1,35 @@ +module Distribution.Solver.Types.SummarizedMessage + ( Entry(..) + , EntryAtLevel(..) + , SummarizedMessage(..) + ) where + +import Prelude () +import Distribution.Solver.Compat.Prelude hiding (fail) + +import Distribution.Solver.Modular.Tree + ( FailReason(..), POption(..) ) +import Distribution.Solver.Types.PackagePath ( QPN ) +import Distribution.Solver.Modular.Flag ( QSN, QFN ) +import Distribution.Solver.Modular.Dependency + ( ConflictSet, QGoalReason, GoalReason ) +import qualified Distribution.Solver.Modular.ConflictSet as CS + +data Entry + = EntryPackageGoal QPN QGoalReason + | EntryRejectF QFN Bool ConflictSet FailReason + | EntryRejectS QSN Bool ConflictSet FailReason + | EntrySkipping (Set CS.Conflict) + | EntryTryingF QFN Bool + | EntryTryingP QPN POption + | EntryTryingNewP QPN POption (GoalReason QPN) + | EntryTryingS QSN Bool + | EntryRejectMany QPN [POption] ConflictSet FailReason + | EntrySkipMany QPN [POption] (Set CS.Conflict) + | EntryUnknownPackage QPN (GoalReason QPN) + | EntrySuccess + | EntryFailure ConflictSet FailReason + +data EntryAtLevel = AtLevel Int Entry + +data SummarizedMessage = SummarizedMsg EntryAtLevel | ErrorMsg String diff --git a/cabal-install/src/Distribution/Client/Dependency.hs b/cabal-install/src/Distribution/Client/Dependency.hs index d59bc611c44..f2a476e57fd 100644 --- a/cabal-install/src/Distribution/Client/Dependency.hs +++ b/cabal-install/src/Distribution/Client/Dependency.hs @@ -123,6 +123,9 @@ import Distribution.Solver.Modular , SolverConfig (..) , modularResolver ) +import Distribution.Solver.Modular.Message + ( renderSummarizedMessage + ) import Distribution.System ( Platform ) @@ -152,6 +155,8 @@ import Distribution.Solver.Types.ResolverPackage import Distribution.Solver.Types.Settings import Distribution.Solver.Types.SolverId import Distribution.Solver.Types.SolverPackage + ( SolverPackage (SolverPackage) + ) import Distribution.Solver.Types.SourcePackage import Distribution.Solver.Types.Variable @@ -790,32 +795,33 @@ resolveDependencies resolveDependencies platform comp pkgConfigDB params = Step (showDepResolverParams finalparams) $ fmap (validateSolverResult platform comp indGoals) $ - runSolver - ( SolverConfig - reordGoals - cntConflicts - fineGrained - minimize - indGoals - noReinstalls - shadowing - strFlags - onlyConstrained_ - maxBkjumps - enableBj - solveExes - order - verbosity - (PruneAfterFirstSuccess False) - ) - platform - comp - installedPkgIndex - sourcePkgIndex - pkgConfigDB - preferences - constraints - targets + formatProgress $ + runSolver + ( SolverConfig + reordGoals + cntConflicts + fineGrained + minimize + indGoals + noReinstalls + shadowing + strFlags + onlyConstrained_ + maxBkjumps + enableBj + solveExes + order + verbosity + (PruneAfterFirstSuccess False) + ) + platform + comp + installedPkgIndex + sourcePkgIndex + pkgConfigDB + preferences + constraints + targets where finalparams@( DepResolverParams targets @@ -844,6 +850,9 @@ resolveDependencies platform comp pkgConfigDB params = then params else dontInstallNonReinstallablePackages params + formatProgress :: Progress SummarizedMessage String a -> Progress String String a + formatProgress p = foldProgress (\x xs -> Step (renderSummarizedMessage x) xs) Fail Done p + preferences :: PackageName -> PackagePreferences preferences = interpretPackagesPreference targets defpref prefs 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 afd1419d30c..16d18b54ff9 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs @@ -265,7 +265,7 @@ runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) -> testEnableAllTests printMsg msg = when showSolverLog $ putStrLn msg msgs = foldProgress (:) (const []) (const []) progress - assertBool ("Unexpected solver log:\n" ++ unlines msgs) $ + assertBool ("Unexpected error:\n" ++ unlines msgs) $ resultLogPredicate testResult $ concatMap lines msgs result <- foldProgress ((>>) . printMsg) (return . Left) (return . Right) progress diff --git a/changelog.d/pr-10854 b/changelog.d/pr-10854 new file mode 100644 index 00000000000..5209a98c68c --- /dev/null +++ b/changelog.d/pr-10854 @@ -0,0 +1,7 @@ +synopsis: Refactor cabal-install solver config log output +packages: cabal-install-solver +prs: #10854 + +This refactoring is mainly cosmetic changes to the modular solver's +code base. The main change is separating the detection of errors from +the reporting of errors. From f1e3057f338648743784b8a573dad270983d7290 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Fri, 13 Jun 2025 14:09:20 +1000 Subject: [PATCH 2/4] tests: Fixes to expected message strings These fixes are require due to improvements in solver error reporting. --- .../tests/UnitTests/Distribution/Solver/Modular/Solver.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index a1f5eed3c62..0136e7cacf2 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -208,7 +208,7 @@ tests = solverSuccess [("base", 1), ("ghc-prim", 1), ("integer-gmp", 1), ("integer-simple", 1)] , runTest $ mkTest dbNonupgrade "Refuse to install newer ghc requested by another library" ["A"] $ - solverFailure (isInfixOf "rejecting: ghc-2.0.0 (constraint from non-reinstallable package requires installed instance)") + solverFailure (isInfixOf "rejecting: ghc-1.0.0/installed-1 (conflict: A => ghc==2.0.0)") ] , testGroup "reject-unconstrained" From f41f96b591f6266577fe850fb6f7db1b9246dbba Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Mon, 16 Jun 2025 10:20:15 +1000 Subject: [PATCH 3/4] Add a sort operation --- .../src/Distribution/Solver/Modular/Message.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs index d15dd383f91..5421787180d 100644 --- a/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs +++ b/cabal-install-solver/src/Distribution/Solver/Modular/Message.hs @@ -294,11 +294,10 @@ showOptions :: QPN -> [POption] -> String showOptions _ [] = "unexpected empty list of versions" showOptions q [x] = showOption q x showOptions q xs = showQPN q ++ "; " ++ (L.intercalate ", " - [if isJust linkedTo - then showOption q x - else showI i -- Don't show the package, just the version - | x@(POption i linkedTo) <- reverse xs - ]) + (L.sort + -- Don't show the package, just the version + [if isJust linkedTo then showOption q x else showI i | + x@(POption i linkedTo) <- xs])) showGR :: QGoalReason -> String showGR UserGoal = " (user goal)" From 62e3c59083de2392d2213151e856870116a5d9c0 Mon Sep 17 00:00:00 2001 From: Erik de Castro Lopo Date: Mon, 16 Jun 2025 15:52:57 +1000 Subject: [PATCH 4/4] Adjust expected test output --- .../UnitTests/Distribution/Solver/Modular/Solver.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs index 0136e7cacf2..585656d35fe 100644 --- a/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs +++ b/cabal-install/tests/UnitTests/Distribution/Solver/Modular/Solver.hs @@ -623,7 +623,7 @@ tests = , "[__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, 2.0.0 (has the same characteristics that " + , "[__0] skipping: A; 2.0.0, 3.0.0 (has the same characteristics that " ++ "caused the previous version to fail: depends on 'B')" , "[__0] trying: A-1.0.0" , "[__1] done" @@ -652,7 +652,7 @@ tests = , "[__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, 2.0.0 (has the same characteristics that " + , "[__0] skipping: A; 2.0.0, 3.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" @@ -777,7 +777,7 @@ tests = , "[__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, 2.0.0 (has the same characteristics that caused the " + , "[__0] skipping: A; 2.0.0, 3.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)" @@ -950,7 +950,7 @@ tests = , Right $ exAv "B" 1 [ExFix "A" 4] ] rejecting = "rejecting: A-3.0.0" - skipping = "skipping: A; 2.0.0, 1.0.0" + skipping = "skipping: A; 1.0.0, 2.0.0" in mkTest db "show skipping versions list" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) , runTest $ @@ -961,7 +961,7 @@ tests = , Right $ exAv "B" 1 [ExFix "A" 4] ] rejecting = "rejecting: A-3.0.0/installed-3.0.0" - skipping = "skipping: A; 2.0.0/installed-2.0.0, 1.0.0/installed-1.0.0" + skipping = "skipping: A; 1.0.0/installed-1.0.0, 2.0.0/installed-2.0.0" in mkTest db "show skipping versions list, installed" ["B"] $ solverFailure (\msg -> rejecting `isInfixOf` msg && skipping `isInfixOf` msg) ]