Log.hs 4.99 KB
Newer Older
1
module Distribution.Solver.Modular.Log
2
3
4
    ( Log
    , logToProgress
    ) where
5

6
7
8
import Prelude ()
import Distribution.Client.Compat.Prelude

Andres Löh's avatar
Andres Löh committed
9
import Data.List as L
10

11
import Distribution.Solver.Types.PackagePath
12
import Distribution.Solver.Types.Progress
13

14
15
16
17
import Distribution.Solver.Modular.Dependency
import Distribution.Solver.Modular.Message
import Distribution.Solver.Modular.Tree (FailReason(..))
import qualified Distribution.Solver.Modular.ConflictSet as CS
18
19
20
21
22
23

-- | The 'Log' datatype.
--
-- Represents the progress of a computation lazily.
--
-- Parameterized over the type of actual messages and the final result.
Franz Thoma's avatar
Franz Thoma committed
24
type Log m a = Progress m (ConflictSet QPN, ConflictMap) a
25

26
27
messages :: Progress step fail done -> [step]
messages = foldProgress (:) (const []) (const [])
28

Franz Thoma's avatar
Franz Thoma committed
29
30
data Exhaustive = Exhaustive | NotExhaustive

31
32
33
34
35
-- | 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 :: Maybe Int -> Log Message a -> Progress String String a
logToProgress mbj l = let
36
37
                        es = proc (Just 0) l -- catch first error (always)
                        ms = useFirstError (proc mbj l)
38
                      in go es es -- trace for first error
39
                            (showMessages (const True) True ms) -- run with backjump limit applied
40
  where
41
42
43
44
    -- 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
    -- and ignores repeated backjumps. If proc reaches the backjump limit, it truncates
    -- the 'Progress' and ends it with the last conflict set. Otherwise, it leaves the
Franz Thoma's avatar
Franz Thoma committed
45
46
    -- original result.
    proc :: Maybe Int -> Log Message b -> Progress Message (Exhaustive, ConflictSet QPN, ConflictMap) b
47
    proc _        (Done x)                          = Done x
Franz Thoma's avatar
Franz Thoma committed
48
    proc _        (Fail (cs, cm))                   = Fail (Exhaustive, cs, cm)
49
50
    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
Franz Thoma's avatar
Franz Thoma committed
51
    proc (Just 0) (Step   (Failure cs Backjump)  _) = Fail (NotExhaustive, cs, mempty) -- No final conflict map available
52
53
    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)
54

Franz Thoma's avatar
Franz Thoma committed
55
56
57
58
    -- Sets the conflict set from the first backjump as the final error in case of a
    -- non-exhaustive search.
    useFirstError :: Progress Message (Exhaustive, ConflictSet QPN, ConflictMap) b
                  -> Progress Message (Exhaustive, ConflictSet QPN, ConflictMap) b
59
60
61
    useFirstError = replace Nothing
      where
        replace _       (Done x)                          = Done x
Franz Thoma's avatar
Franz Thoma committed
62
63
        replace _       (Fail (Exhaustive,    cs, cm))    = Fail (Exhaustive, cs, cm)
        replace cs'     (Fail (NotExhaustive, cs, cm))    = -- Backjump limit not reached.
64
                                                            -- Prefer first error over later error.
Franz Thoma's avatar
Franz Thoma committed
65
                                                            Fail (NotExhaustive, fromMaybe cs cs', cm)
66
67
68
69
70
71
72
73
        replace Nothing (Step x@(Failure cs Backjump) xs) = Step x $ replace (Just cs) xs
        replace cs'     (Step x                       xs) = Step x $ replace cs' xs

    -- 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!
74
    --
75
76
77
78
    -- The third argument is the full log, ending with either the solution or the
    -- exhaustiveness and first conflict set.
    go :: Progress Message a b
       -> Progress Message a b
Franz Thoma's avatar
Franz Thoma committed
79
       -> Progress String (Exhaustive, ConflictSet QPN, ConflictMap) b
80
81
82
       -> 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)
Franz Thoma's avatar
Franz Thoma committed
83
    go ms _           (Fail (exh, cs, cm))  = Fail $
Franz Thoma's avatar
Franz Thoma committed
84
85
86
        "Could not resolve dependencies:\n" ++
        unlines (messages $ showMessages (L.foldr (\ v _ -> v `CS.member` cs) True) False ms) ++
        case exh of
Franz Thoma's avatar
Franz Thoma committed
87
88
            Exhaustive ->
                "Dependency tree exhaustively searched.\n" ++
Franz Thoma's avatar
Franz Thoma committed
89
90
                "I've had most trouble fulfilling the following goals: "
                ++ CS.showCSWithFrequency cm cs
Franz Thoma's avatar
Franz Thoma committed
91
92
93
94
95
            NotExhaustive ->
                "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  = ""
96
    go _  _           (Done s)              = Done s