Merge pull request #10546 from cabalism/fix/dedup-using-config-from
[cabal.git] / cabal-install-solver / src / Distribution / Solver / Modular / Builder.hs
blob5d196f4fd9fffc52f18d7fbb022be357443eb5e8
1 {-# LANGUAGE ScopedTypeVariables #-}
2 module Distribution.Solver.Modular.Builder (
3 buildTree
4 , splits -- for testing
5 ) where
7 -- Building the search tree.
8 --
9 -- In this phase, we build a search tree that is too large, i.e, it contains
10 -- invalid solutions. We keep track of the open goals at each point. We
11 -- nondeterministically pick an open goal (via a goal choice node), create
12 -- subtrees according to the index and the available solutions, and extend the
13 -- set of open goals by superficially looking at the dependencies recorded in
14 -- the index.
16 -- For each goal, we keep track of all the *reasons* why it is being
17 -- introduced. These are for debugging and error messages, mainly. A little bit
18 -- of care has to be taken due to the way we treat flags. If a package has
19 -- flag-guarded dependencies, we cannot introduce them immediately. Instead, we
20 -- store the entire dependency.
22 import qualified Data.List as L
23 import qualified Data.Map as M
24 import qualified Data.Set as S
25 import Prelude
27 import qualified Distribution.Solver.Modular.ConflictSet as CS
28 import Distribution.Solver.Modular.Dependency
29 import Distribution.Solver.Modular.Flag
30 import Distribution.Solver.Modular.Index
31 import Distribution.Solver.Modular.Package
32 import qualified Distribution.Solver.Modular.PSQ as P
33 import Distribution.Solver.Modular.Tree
34 import qualified Distribution.Solver.Modular.WeightedPSQ as W
36 import Distribution.Solver.Types.ComponentDeps
37 import Distribution.Solver.Types.PackagePath
38 import Distribution.Solver.Types.Settings
40 -- | All state needed to build and link the search tree. It has a type variable
41 -- because the linking phase doesn't need to know about the state used to build
42 -- the tree.
43 data Linker a = Linker {
44 buildState :: a,
45 linkingState :: LinkingState
48 -- | The state needed to build the search tree without creating any linked nodes.
49 data BuildState = BS {
50 index :: Index, -- ^ information about packages and their dependencies
51 rdeps :: RevDepMap, -- ^ set of all package goals, completed and open, with reverse dependencies
52 open :: [OpenGoal], -- ^ set of still open goals (flag and package goals)
53 next :: BuildType, -- ^ kind of node to generate next
54 qualifyOptions :: QualifyOptions -- ^ qualification options
57 -- | Map of available linking targets.
58 type LinkingState = M.Map (PN, I) [PackagePath]
60 -- | Extend the set of open goals with the new goals listed.
62 -- We also adjust the map of overall goals, and keep track of the
63 -- reverse dependencies of each of the goals.
64 extendOpen :: QPN -> [FlaggedDep QPN] -> BuildState -> BuildState
65 extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs
66 where
67 go :: RevDepMap -> [OpenGoal] -> [FlaggedDep QPN] -> BuildState
68 go g o [] = s { rdeps = g, open = o }
69 go g o ((Flagged fn@(FN qpn _) fInfo t f) : ngs) =
70 go g (FlagGoal fn fInfo t f (flagGR qpn) : o) ngs
71 -- Note: for 'Flagged' goals, we always insert, so later additions win.
72 -- This is important, because in general, if a goal is inserted twice,
73 -- the later addition will have better dependency information.
74 go g o ((Stanza sn@(SN qpn _) t) : ngs) =
75 go g (StanzaGoal sn t (flagGR qpn) : o) ngs
76 go g o ((Simple (LDep dr (Dep (PkgComponent qpn _) _)) c) : ngs)
77 | qpn == qpn' =
78 -- We currently only add a self-dependency to the graph if it is
79 -- between a package and its setup script. The edge creates a cycle
80 -- and causes the solver to backtrack and choose a different
81 -- instance for the setup script. We may need to track other
82 -- self-dependencies once we implement component-based solving.
83 case c of
84 ComponentSetup -> go (M.adjust (addIfAbsent (ComponentSetup, qpn')) qpn g) o ngs
85 _ -> go g o ngs
86 | qpn `M.member` g = go (M.adjust (addIfAbsent (c, qpn')) qpn g) o ngs
87 | otherwise = go (M.insert qpn [(c, qpn')] g) (PkgGoal qpn (DependencyGoal dr) : o) ngs
88 -- code above is correct; insert/adjust have different arg order
89 go g o ((Simple (LDep _dr (Ext _ext )) _) : ngs) = go g o ngs
90 go g o ((Simple (LDep _dr (Lang _lang))_) : ngs) = go g o ngs
91 go g o ((Simple (LDep _dr (Pkg _pn _vr))_) : ngs) = go g o ngs
93 addIfAbsent :: Eq a => a -> [a] -> [a]
94 addIfAbsent x xs = if x `elem` xs then xs else x : xs
96 -- GoalReason for a flag or stanza. Each flag/stanza is introduced only by
97 -- its containing package.
98 flagGR :: qpn -> GoalReason qpn
99 flagGR qpn = DependencyGoal (DependencyReason qpn M.empty S.empty)
101 -- | Given the current scope, qualify all the package names in the given set of
102 -- dependencies and then extend the set of open goals accordingly.
103 scopedExtendOpen :: QPN -> FlaggedDeps PN -> FlagInfo ->
104 BuildState -> BuildState
105 scopedExtendOpen qpn fdeps fdefs s = extendOpen qpn gs s
106 where
107 -- Qualify all package names
108 qfdeps = qualifyDeps (qualifyOptions s) qpn fdeps
109 -- Introduce all package flags
110 qfdefs = L.map (\ (fn, b) -> Flagged (FN qpn fn) b [] []) $ M.toList fdefs
111 -- Combine new package and flag goals
112 gs = qfdefs ++ qfdeps
113 -- NOTE:
115 -- In the expression @qfdefs ++ qfdeps@ above, flags occur potentially
116 -- multiple times, both via the flag declaration and via dependencies.
118 -- | Datatype that encodes what to build next
119 data BuildType =
120 Goals -- ^ build a goal choice node
121 | OneGoal OpenGoal -- ^ build a node for this goal
122 | Instance QPN PInfo -- ^ build a tree for a concrete instance
124 build :: Linker BuildState -> Tree () QGoalReason
125 build = ana go
126 where
127 go :: Linker BuildState -> TreeF () QGoalReason (Linker BuildState)
128 go s = addLinking (linkingState s) $ addChildren (buildState s)
130 addChildren :: BuildState -> TreeF () QGoalReason BuildState
132 -- If we have a choice between many goals, we just record the choice in
133 -- the tree. We select each open goal in turn, and before we descend, remove
134 -- it from the queue of open goals.
135 addChildren bs@(BS { rdeps = rdm, open = gs, next = Goals })
136 | L.null gs = DoneF rdm ()
137 | otherwise = GoalChoiceF rdm $ P.fromList
138 $ L.map (\ (g, gs') -> (close g, bs { next = OneGoal g, open = gs' }))
139 $ splits gs
141 -- If we have already picked a goal, then the choice depends on the kind
142 -- of goal.
144 -- For a package, we look up the instances available in the global info,
145 -- and then handle each instance in turn.
146 addChildren bs@(BS { rdeps = rdm, index = idx, next = OneGoal (PkgGoal qpn@(Q _ pn) gr) }) =
147 case M.lookup pn idx of
148 Nothing -> FailF
149 (varToConflictSet (P qpn) `CS.union` goalReasonToConflictSetWithConflict qpn gr)
150 UnknownPackage
151 Just pis -> PChoiceF qpn rdm gr (W.fromList (L.map (\ (i, info) ->
152 ([], POption i Nothing, bs { next = Instance qpn info }))
153 (M.toList pis)))
154 -- TODO: data structure conversion is rather ugly here
156 -- For a flag, we create only two subtrees, and we create them in the order
157 -- that is indicated by the flag default.
158 addChildren bs@(BS { rdeps = rdm, next = OneGoal (FlagGoal qfn@(FN qpn _) (FInfo b m w) t f gr) }) =
159 FChoiceF qfn rdm gr weak m b (W.fromList
160 [([if b then 0 else 1], True, (extendOpen qpn t bs) { next = Goals }),
161 ([if b then 1 else 0], False, (extendOpen qpn f bs) { next = Goals })])
162 where
163 trivial = L.null t && L.null f
164 weak = WeakOrTrivial $ unWeakOrTrivial w || trivial
166 -- For a stanza, we also create only two subtrees. The order is initially
167 -- False, True. This can be changed later by constraints (force enabling
168 -- the stanza by replacing the False branch with failure) or preferences
169 -- (try enabling the stanza if possible by moving the True branch first).
171 addChildren bs@(BS { rdeps = rdm, next = OneGoal (StanzaGoal qsn@(SN qpn _) t gr) }) =
172 SChoiceF qsn rdm gr trivial (W.fromList
173 [([0], False, bs { next = Goals }),
174 ([1], True, (extendOpen qpn t bs) { next = Goals })])
175 where
176 trivial = WeakOrTrivial (L.null t)
178 -- For a particular instance, we change the state: we update the scope,
179 -- and furthermore we update the set of goals.
181 -- TODO: We could inline this above.
182 addChildren bs@(BS { next = Instance qpn (PInfo fdeps _ fdefs _) }) =
183 addChildren ((scopedExtendOpen qpn fdeps fdefs bs)
184 { next = Goals })
186 {-------------------------------------------------------------------------------
187 Add linking
188 -------------------------------------------------------------------------------}
190 -- | Introduce link nodes into the tree
192 -- Linking is a phase that adapts package choice nodes and adds the option to
193 -- link wherever appropriate: Package goals are called "related" if they are for
194 -- the same instance of the same package (but have different prefixes). A link
195 -- option is available in a package choice node whenever we can choose an
196 -- instance that has already been chosen for a related goal at a higher position
197 -- in the tree. We only create link options for related goals that are not
198 -- themselves linked, because the choice to link to a linked goal is the same as
199 -- the choice to link to the target of that goal's linking.
201 -- The code here proceeds by maintaining a finite map recording choices that
202 -- have been made at higher positions in the tree. For each pair of package name
203 -- and instance, it stores the prefixes at which we have made a choice for this
204 -- package instance. Whenever we make an unlinked choice, we extend the map.
205 -- Whenever we find a choice, we look into the map in order to find out what
206 -- link options we have to add.
208 -- A separate tree traversal would be simpler. However, 'addLinking' creates
209 -- linked nodes from existing unlinked nodes, which leads to sharing between the
210 -- nodes. If we copied the nodes when they were full trees of type
211 -- 'Tree () QGoalReason', then the sharing would cause a space leak during
212 -- exploration of the tree. Instead, we only copy the 'BuildState', which is
213 -- relatively small, while the tree is being constructed. See
214 -- https://github.com/haskell/cabal/issues/2899
215 addLinking :: LinkingState -> TreeF () c a -> TreeF () c (Linker a)
216 -- The only nodes of interest are package nodes
217 addLinking ls (PChoiceF qpn@(Q pp pn) rdm gr cs) =
218 let linkedCs = fmap (\bs -> Linker bs ls) $
219 W.fromList $ concatMap (linkChoices ls qpn) (W.toList cs)
220 unlinkedCs = W.mapWithKey goP cs
221 allCs = unlinkedCs `W.union` linkedCs
223 -- Recurse underneath package choices. Here we just need to make sure
224 -- that we record the package choice so that it is available below
225 goP :: POption -> a -> Linker a
226 goP (POption i Nothing) bs = Linker bs $ M.insertWith (++) (pn, i) [pp] ls
227 goP _ _ = alreadyLinked
228 in PChoiceF qpn rdm gr allCs
229 addLinking ls t = fmap (\bs -> Linker bs ls) t
231 linkChoices :: forall a w . LinkingState
232 -> QPN
233 -> (w, POption, a)
234 -> [(w, POption, a)]
235 linkChoices related (Q _pp pn) (weight, POption i Nothing, subtree) =
236 L.map aux (M.findWithDefault [] (pn, i) related)
237 where
238 aux :: PackagePath -> (w, POption, a)
239 aux pp = (weight, POption i (Just pp), subtree)
240 linkChoices _ _ (_, POption _ (Just _), _) =
241 alreadyLinked
243 alreadyLinked :: a
244 alreadyLinked = error "addLinking called on tree that already contains linked nodes"
246 -------------------------------------------------------------------------------
248 -- | Interface to the tree builder. Just takes an index and a list of package names,
249 -- and computes the initial state and then the tree from there.
250 buildTree :: Index -> IndependentGoals -> [PN] -> Tree () QGoalReason
251 buildTree idx (IndependentGoals ind) igs =
252 build Linker {
253 buildState = BS {
254 index = idx
255 , rdeps = M.fromList (L.map (\ qpn -> (qpn, [])) qpns)
256 , open = L.map topLevelGoal qpns
257 , next = Goals
258 , qualifyOptions = defaultQualifyOptions idx
260 , linkingState = M.empty
262 where
263 topLevelGoal qpn = PkgGoal qpn UserGoal
265 qpns | ind = L.map makeIndependent igs
266 | otherwise = L.map (Q (PackagePath DefaultNamespace QualToplevel)) igs
268 {-------------------------------------------------------------------------------
269 Goals
270 -------------------------------------------------------------------------------}
272 -- | Information needed about a dependency before it is converted into a Goal.
273 data OpenGoal =
274 FlagGoal (FN QPN) FInfo (FlaggedDeps QPN) (FlaggedDeps QPN) QGoalReason
275 | StanzaGoal (SN QPN) (FlaggedDeps QPN) QGoalReason
276 | PkgGoal QPN QGoalReason
278 -- | Closes a goal, i.e., removes all the extraneous information that we
279 -- need only during the build phase.
280 close :: OpenGoal -> Goal QPN
281 close (FlagGoal qfn _ _ _ gr) = Goal (F qfn) gr
282 close (StanzaGoal qsn _ gr) = Goal (S qsn) gr
283 close (PkgGoal qpn gr) = Goal (P qpn) gr
285 {-------------------------------------------------------------------------------
286 Auxiliary
287 -------------------------------------------------------------------------------}
289 -- | Pairs each element of a list with the list resulting from removal of that
290 -- element from the original list.
291 splits :: [a] -> [(a, [a])]
292 splits = go id
293 where
294 go :: ([a] -> [a]) -> [a] -> [(a, [a])]
295 go _ [] = []
296 go f (x : xs) = (x, f xs) : go (f . (x :)) xs