Merge pull request #10546 from cabalism/fix/dedup-using-config-from
[cabal.git] / cabal-install-solver / src / Distribution / Solver / Modular / Preference.hs
blob9e0d5fb4d2208ea2ac132049ef235cfbfde5da93
1 {-# LANGUAGE ScopedTypeVariables #-}
2 -- | Reordering or pruning the tree in order to prefer or make certain choices.
3 module Distribution.Solver.Modular.Preference
4 ( avoidReinstalls
5 , deferSetupExeChoices
6 , deferWeakFlagChoices
7 , enforceManualFlags
8 , enforcePackageConstraints
9 , enforceSingleInstanceRestriction
10 , firstGoal
11 , preferBaseGoalChoice
12 , preferLinked
13 , preferPackagePreferences
14 , preferReallyEasyGoalChoices
15 , onlyConstrained
16 , sortGoals
17 , pruneAfterFirstSuccess
18 ) where
20 import Prelude ()
21 import Distribution.Solver.Compat.Prelude
23 import qualified Data.List as L
24 import qualified Data.Map as M
25 import Control.Monad.Trans.Reader (Reader, runReader, ask, local)
27 import Distribution.PackageDescription (lookupFlagAssignment, unFlagAssignment) -- from Cabal
29 import Distribution.Solver.Types.Flag
30 import Distribution.Solver.Types.InstalledPreference
31 import Distribution.Solver.Types.LabeledPackageConstraint
32 import Distribution.Solver.Types.OptionalStanza
33 import Distribution.Solver.Types.PackageConstraint
34 import Distribution.Solver.Types.PackagePath
35 import Distribution.Solver.Types.PackagePreferences
36 import Distribution.Solver.Types.Variable
38 import Distribution.Solver.Modular.Dependency
39 import Distribution.Solver.Modular.Flag
40 import Distribution.Solver.Modular.Package
41 import qualified Distribution.Solver.Modular.PSQ as P
42 import Distribution.Solver.Modular.Tree
43 import Distribution.Solver.Modular.Version
44 import qualified Distribution.Solver.Modular.ConflictSet as CS
45 import qualified Distribution.Solver.Modular.WeightedPSQ as W
47 -- | Update the weights of children under 'PChoice' nodes. 'addWeights' takes a
48 -- list of weight-calculating functions in order to avoid sorting the package
49 -- choices multiple times. Each function takes the package name, sorted list of
50 -- children's versions, and package option. 'addWeights' prepends the new
51 -- weights to the existing weights, which gives precedence to preferences that
52 -- are applied later.
53 addWeights :: [PN -> [Ver] -> POption -> Weight] -> EndoTreeTrav d c
54 addWeights fs = go
55 where
56 go :: TreeF d c (Tree d c) -> TreeF d c (Tree d c)
57 go (PChoiceF qpn@(Q _ pn) rdm x cs) =
58 let sortedVersions = L.sortBy (flip compare) $ L.map version (W.keys cs)
59 weights k = [f pn sortedVersions k | f <- fs]
61 elemsToWhnf :: [a] -> ()
62 elemsToWhnf = foldr seq ()
63 in PChoiceF qpn rdm x
64 -- Evaluate the children's versions before evaluating any of the
65 -- subtrees, so that 'sortedVersions' doesn't hold onto all of the
66 -- subtrees (referenced by cs) and cause a space leak.
67 (elemsToWhnf sortedVersions `seq`
68 W.mapWeightsWithKey (\k w -> weights k ++ w) cs)
69 go x = x
71 addWeight :: (PN -> [Ver] -> POption -> Weight) -> EndoTreeTrav d c
72 addWeight f = addWeights [f]
74 version :: POption -> Ver
75 version (POption (I v _) _) = v
77 -- | Prefer to link packages whenever possible.
78 preferLinked :: EndoTreeTrav d c
79 preferLinked = addWeight (const (const linked))
80 where
81 linked (POption _ Nothing) = 1
82 linked (POption _ (Just _)) = 0
84 -- Works by setting weights on choice nodes. Also applies stanza preferences.
85 preferPackagePreferences :: (PN -> PackagePreferences) -> EndoTreeTrav d c
86 preferPackagePreferences pcs =
87 preferPackageStanzaPreferences pcs .
88 -- Each package is assigned a list of weights (currently three of them),
89 -- and options are ordered by comparison of these lists.
91 -- The head of the list (and thus the top priority for ordering)
92 -- is whether the package version is "preferred"
93 -- (https://hackage.haskell.org/packages/preferred-versions).
95 -- The next two elements depend on 'PackagePreferences'.
96 -- For 'PreferInstalled' they are whether the version is installed (0 or 1)
97 -- and how close is the version to the latest one (between 0.0 and 1.0).
98 -- For 'PreferLatest' the weights are the same, but swapped, so that
99 -- ordering considers how new is the package first.
100 -- For 'PreferOldest' one weight measures how close is the version to the
101 -- the oldest one possible (between 0.0 and 1.0) and another checks whether
102 -- the version is installed (0 or 1).
103 addWeights [
104 \pn _ opt -> preferred pn opt
105 , \pn vs opt -> case preference pn of
106 PreferInstalled -> installed opt
107 PreferLatest -> latest vs opt
108 PreferOldest -> oldest vs opt
109 , \pn vs opt -> case preference pn of
110 PreferInstalled -> latest vs opt
111 PreferLatest -> installed opt
112 PreferOldest -> installed opt
114 where
115 -- Prefer packages with higher version numbers over packages with
116 -- lower version numbers.
117 latest :: [Ver] -> POption -> Weight
118 latest sortedVersions opt =
119 let l = length sortedVersions
120 index = fromMaybe l $ L.findIndex (<= version opt) sortedVersions
121 in fromIntegral index / fromIntegral l
123 -- Prefer packages with lower version numbers over packages with
124 -- higher version numbers.
125 oldest :: [Ver] -> POption -> Weight
126 oldest sortedVersions opt = 1 - latest sortedVersions opt
128 preference :: PN -> InstalledPreference
129 preference pn =
130 let PackagePreferences _ ipref _ = pcs pn
131 in ipref
133 -- | Prefer versions satisfying more preferred version ranges.
134 preferred :: PN -> POption -> Weight
135 preferred pn opt =
136 let PackagePreferences vrs _ _ = pcs pn
137 in fromIntegral . negate . L.length $
138 L.filter (flip checkVR (version opt)) vrs
140 -- Prefer installed packages over non-installed packages.
141 installed :: POption -> Weight
142 installed (POption (I _ (Inst _)) _) = 0
143 installed _ = 1
145 -- | Traversal that tries to establish package stanza enable\/disable
146 -- preferences. Works by reordering the branches of stanza choices.
147 -- Note that this works on packages lower in the path as well as at the top level.
148 -- This is because stanza preferences apply to local packages only
149 -- and for local packages, a single version is fixed, which means
150 -- (for now) that all stanza preferences must be uniform at all levels.
151 -- Further, even when we can have multiple versions of the same package,
152 -- the build plan will be more efficient if we can attempt to keep
153 -- stanza preferences aligned at all levels.
154 preferPackageStanzaPreferences :: (PN -> PackagePreferences) -> EndoTreeTrav d c
155 preferPackageStanzaPreferences pcs = go
156 where
157 go (SChoiceF qsn@(SN (Q _pp pn) s) rdm gr _tr ts)
158 | enableStanzaPref pn s =
159 -- move True case first to try enabling the stanza
160 let ts' = W.mapWeightsWithKey (\k w -> weight k : w) ts
161 weight k = if k then 0 else 1
162 -- defer the choice by setting it to weak
163 in SChoiceF qsn rdm gr (WeakOrTrivial True) ts'
164 go x = x
166 enableStanzaPref :: PN -> OptionalStanza -> Bool
167 enableStanzaPref pn s =
168 let PackagePreferences _ _ spref = pcs pn
169 in s `elem` spref
171 -- | Helper function that tries to enforce a single package constraint on a
172 -- given instance for a P-node. Translates the constraint into a
173 -- tree-transformer that either leaves the subtree untouched, or replaces it
174 -- with an appropriate failure node.
175 processPackageConstraintP :: forall d c. QPN
176 -> ConflictSet
177 -> I
178 -> LabeledPackageConstraint
179 -> Tree d c
180 -> Tree d c
181 processPackageConstraintP qpn c i (LabeledPackageConstraint (PackageConstraint scope prop) src) r =
182 if constraintScopeMatches scope qpn
183 then go i prop
184 else r
185 where
186 go :: I -> PackageProperty -> Tree d c
187 go (I v _) (PackagePropertyVersion vr)
188 | checkVR vr v = r
189 | otherwise = Fail c (GlobalConstraintVersion vr src)
190 go _ PackagePropertyInstalled
191 | instI i = r
192 | otherwise = Fail c (GlobalConstraintInstalled src)
193 go _ PackagePropertySource
194 | not (instI i) = r
195 | otherwise = Fail c (GlobalConstraintSource src)
196 go _ _ = r
198 -- | Helper function that tries to enforce a single package constraint on a
199 -- given flag setting for an F-node. Translates the constraint into a
200 -- tree-transformer that either leaves the subtree untouched, or replaces it
201 -- with an appropriate failure node.
202 processPackageConstraintF :: forall d c. QPN
203 -> Flag
204 -> ConflictSet
205 -> Bool
206 -> LabeledPackageConstraint
207 -> Tree d c
208 -> Tree d c
209 processPackageConstraintF qpn f c b' (LabeledPackageConstraint (PackageConstraint scope prop) src) r =
210 if constraintScopeMatches scope qpn
211 then go prop
212 else r
213 where
214 go :: PackageProperty -> Tree d c
215 go (PackagePropertyFlags fa) =
216 case lookupFlagAssignment f fa of
217 Nothing -> r
218 Just b | b == b' -> r
219 | otherwise -> Fail c (GlobalConstraintFlag src)
220 go _ = r
222 -- | Helper function that tries to enforce a single package constraint on a
223 -- given flag setting for an F-node. Translates the constraint into a
224 -- tree-transformer that either leaves the subtree untouched, or replaces it
225 -- with an appropriate failure node.
226 processPackageConstraintS :: forall d c. QPN
227 -> OptionalStanza
228 -> ConflictSet
229 -> Bool
230 -> LabeledPackageConstraint
231 -> Tree d c
232 -> Tree d c
233 processPackageConstraintS qpn s c b' (LabeledPackageConstraint (PackageConstraint scope prop) src) r =
234 if constraintScopeMatches scope qpn
235 then go prop
236 else r
237 where
238 go :: PackageProperty -> Tree d c
239 go (PackagePropertyStanzas ss) =
240 if not b' && s `elem` ss then Fail c (GlobalConstraintFlag src)
241 else r
242 go _ = r
244 -- | Traversal that tries to establish various kinds of user constraints. Works
245 -- by selectively disabling choices that have been ruled out by global user
246 -- constraints.
247 enforcePackageConstraints :: M.Map PN [LabeledPackageConstraint]
248 -> EndoTreeTrav d c
249 enforcePackageConstraints pcs = go
250 where
251 go (PChoiceF qpn@(Q _ pn) rdm gr ts) =
252 let c = varToConflictSet (P qpn)
253 -- compose the transformation functions for each of the relevant constraint
254 g = \ (POption i _) -> foldl (\ h pc -> h . processPackageConstraintP qpn c i pc)
256 (M.findWithDefault [] pn pcs)
257 in PChoiceF qpn rdm gr (W.mapWithKey g ts)
258 go (FChoiceF qfn@(FN qpn@(Q _ pn) f) rdm gr tr m d ts) =
259 let c = varToConflictSet (F qfn)
260 -- compose the transformation functions for each of the relevant constraint
261 g = \ b -> foldl (\ h pc -> h . processPackageConstraintF qpn f c b pc)
263 (M.findWithDefault [] pn pcs)
264 in FChoiceF qfn rdm gr tr m d (W.mapWithKey g ts)
265 go (SChoiceF qsn@(SN qpn@(Q _ pn) f) rdm gr tr ts) =
266 let c = varToConflictSet (S qsn)
267 -- compose the transformation functions for each of the relevant constraint
268 g = \ b -> foldl (\ h pc -> h . processPackageConstraintS qpn f c b pc)
270 (M.findWithDefault [] pn pcs)
271 in SChoiceF qsn rdm gr tr (W.mapWithKey g ts)
272 go x = x
274 -- | Transformation that tries to enforce the rule that manual flags can only be
275 -- set by the user.
277 -- If there are no constraints on a manual flag, this function prunes all but
278 -- the default value. If there are constraints, then the flag is allowed to have
279 -- the values specified by the constraints. Note that the type used for flag
280 -- values doesn't need to be Bool.
282 -- This function makes an exception for the case where there are multiple goals
283 -- for a single package (with different qualifiers), and flag constraints for
284 -- manual flag x only apply to some of those goals. In that case, we allow the
285 -- unconstrained goals to use the default value for x OR any of the values in
286 -- the constraints on x (even though the constraints don't apply), in order to
287 -- allow the unconstrained goals to be linked to the constrained goals. See
288 -- https://github.com/haskell/cabal/issues/4299. Removing the single instance
289 -- restriction (SIR) would also fix #4299, so we may want to remove this
290 -- exception and only let the user toggle manual flags if we remove the SIR.
292 -- This function does not enforce any of the constraints, since that is done by
293 -- 'enforcePackageConstraints'.
294 enforceManualFlags :: M.Map PN [LabeledPackageConstraint] -> EndoTreeTrav d c
295 enforceManualFlags pcs = go
296 where
297 go (FChoiceF qfn@(FN (Q _ pn) fn) rdm gr tr Manual d ts) =
298 FChoiceF qfn rdm gr tr Manual d $
299 let -- A list of all values specified by constraints on 'fn'.
300 -- We ignore the constraint scope in order to handle issue #4299.
301 flagConstraintValues :: [Bool]
302 flagConstraintValues =
303 [ flagVal
304 | let lpcs = M.findWithDefault [] pn pcs
305 , (LabeledPackageConstraint (PackageConstraint _ (PackagePropertyFlags fa)) _) <- lpcs
306 , (fn', flagVal) <- unFlagAssignment fa
307 , fn' == fn ]
309 -- Prune flag values that are not the default and do not match any
310 -- of the constraints.
311 restrictToggling :: Eq a => a -> [a] -> a -> Tree d c -> Tree d c
312 restrictToggling flagDefault constraintVals flagVal r =
313 if flagVal `elem` constraintVals || flagVal == flagDefault
314 then r
315 else Fail (varToConflictSet (F qfn)) ManualFlag
317 in W.mapWithKey (restrictToggling d flagConstraintValues) ts
318 go x = x
320 -- | Avoid reinstalls.
322 -- This is a tricky strategy. If a package version is installed already and the
323 -- same version is available from a repo, the repo version will never be chosen.
324 -- This would result in a reinstall (either destructively, or potentially,
325 -- shadowing). The old instance won't be visible or even present anymore, but
326 -- other packages might have depended on it.
328 -- TODO: It would be better to actually check the reverse dependencies of installed
329 -- packages. If they're not depended on, then reinstalling should be fine. Even if
330 -- they are, perhaps this should just result in trying to reinstall those other
331 -- packages as well. However, doing this all neatly in one pass would require to
332 -- change the builder, or at least to change the goal set after building.
333 avoidReinstalls :: (PN -> Bool) -> EndoTreeTrav d c
334 avoidReinstalls p = go
335 where
336 go (PChoiceF qpn@(Q _ pn) rdm gr cs)
337 | p pn = PChoiceF qpn rdm gr disableReinstalls
338 | otherwise = PChoiceF qpn rdm gr cs
339 where
340 disableReinstalls =
341 let installed = [ v | (_, POption (I v (Inst _)) _, _) <- W.toList cs ]
342 in W.mapWithKey (notReinstall installed) cs
344 notReinstall vs (POption (I v InRepo) _) _ | v `elem` vs =
345 Fail (varToConflictSet (P qpn)) CannotReinstall
346 notReinstall _ _ x =
348 go x = x
350 -- | Require all packages to be mentioned in a constraint or as a goal.
351 onlyConstrained :: (PN -> Bool) -> EndoTreeTrav d QGoalReason
352 onlyConstrained p = go
353 where
354 go (PChoiceF v@(Q _ pn) _ gr _) | not (p pn)
355 = FailF
356 (varToConflictSet (P v) `CS.union` goalReasonToConflictSetWithConflict v gr)
357 NotExplicit
358 go x
361 -- | Sort all goals using the provided function.
362 sortGoals :: (Variable QPN -> Variable QPN -> Ordering) -> EndoTreeTrav d c
363 sortGoals variableOrder = go
364 where
365 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.sortByKeys goalOrder xs)
366 go x = x
368 goalOrder :: Goal QPN -> Goal QPN -> Ordering
369 goalOrder = variableOrder `on` (varToVariable . goalToVar)
371 varToVariable :: Var QPN -> Variable QPN
372 varToVariable (P qpn) = PackageVar qpn
373 varToVariable (F (FN qpn fn)) = FlagVar qpn fn
374 varToVariable (S (SN qpn stanza)) = StanzaVar qpn stanza
376 -- | Reduce the branching degree of the search tree by removing all choices
377 -- after the first successful choice at each level. The returned tree is the
378 -- minimal subtree containing the path to the first backjump.
379 pruneAfterFirstSuccess :: EndoTreeTrav d c
380 pruneAfterFirstSuccess = go
381 where
382 go (PChoiceF qpn rdm gr ts) = PChoiceF qpn rdm gr (W.takeUntil active ts)
383 go (FChoiceF qfn rdm gr w m d ts) = FChoiceF qfn rdm gr w m d (W.takeUntil active ts)
384 go (SChoiceF qsn rdm gr w ts) = SChoiceF qsn rdm gr w (W.takeUntil active ts)
385 go x = x
387 -- | Always choose the first goal in the list next, abandoning all
388 -- other choices.
390 -- This is unnecessary for the default search strategy, because
391 -- it descends only into the first goal choice anyway,
392 -- but may still make sense to just reduce the tree size a bit.
393 firstGoal :: EndoTreeTrav d c
394 firstGoal = go
395 where
396 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.firstOnly xs)
397 go x = x
398 -- Note that we keep empty choice nodes, because they mean success.
400 -- | Transformation that tries to make a decision on base as early as
401 -- possible by pruning all other goals when base is available. In nearly
402 -- all cases, there's a single choice for the base package. Also, fixing
403 -- base early should lead to better error messages.
404 preferBaseGoalChoice :: EndoTreeTrav d c
405 preferBaseGoalChoice = go
406 where
407 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.filterIfAnyByKeys isBase xs)
408 go x = x
410 isBase :: Goal QPN -> Bool
411 isBase (Goal (P (Q _pp pn)) _) = unPN pn == "base"
412 isBase _ = False
414 -- | Deal with setup and build-tool-depends dependencies after regular dependencies,
415 -- so we will link setup/exe dependencies against package dependencies when possible
416 deferSetupExeChoices :: EndoTreeTrav d c
417 deferSetupExeChoices = go
418 where
419 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.preferByKeys noSetupOrExe xs)
420 go x = x
422 noSetupOrExe :: Goal QPN -> Bool
423 noSetupOrExe (Goal (P (Q (PackagePath _ns (QualSetup _)) _)) _) = False
424 noSetupOrExe (Goal (P (Q (PackagePath _ns (QualExe _ _)) _)) _) = False
425 noSetupOrExe _ = True
427 -- | Transformation that tries to avoid making weak flag choices early.
428 -- Weak flags are trivial flags (not influencing dependencies) or such
429 -- flags that are explicitly declared to be weak in the index.
430 deferWeakFlagChoices :: EndoTreeTrav d c
431 deferWeakFlagChoices = go
432 where
433 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.prefer noWeakFlag (P.prefer noWeakStanza xs))
434 go x = x
436 noWeakStanza :: Tree d c -> Bool
437 noWeakStanza (SChoice _ _ _ (WeakOrTrivial True) _) = False
438 noWeakStanza _ = True
440 noWeakFlag :: Tree d c -> Bool
441 noWeakFlag (FChoice _ _ _ (WeakOrTrivial True) _ _ _) = False
442 noWeakFlag _ = True
444 -- | Transformation that prefers goals with lower branching degrees.
446 -- When a goal choice node has at least one goal with zero or one children, this
447 -- function prunes all other goals. This transformation can help the solver find
448 -- a solution in fewer steps by allowing it to backtrack sooner when it is
449 -- exploring a subtree with no solutions. However, each step is more expensive.
450 preferReallyEasyGoalChoices :: EndoTreeTrav d c
451 preferReallyEasyGoalChoices = go
452 where
453 go (GoalChoiceF rdm xs) = GoalChoiceF rdm (P.filterIfAny zeroOrOneChoices xs)
454 go x = x
456 -- | Monad used internally in enforceSingleInstanceRestriction
458 -- For each package instance we record the goal for which we picked a concrete
459 -- instance. The SIR means that for any package instance there can only be one.
460 type EnforceSIR = Reader (Map (PI PN) QPN)
462 -- | Enforce ghc's single instance restriction
464 -- From the solver's perspective, this means that for any package instance
465 -- (that is, package name + package version) there can be at most one qualified
466 -- goal resolving to that instance (there may be other goals _linking_ to that
467 -- instance however).
468 enforceSingleInstanceRestriction :: Tree d c -> Tree d c
469 enforceSingleInstanceRestriction = (`runReader` M.empty) . go
470 where
471 go :: Tree d c -> EnforceSIR (Tree d c)
473 -- We just verify package choices.
474 go (PChoice qpn rdm gr cs) =
475 PChoice qpn rdm gr <$> sequenceA (W.mapWithKey (goP qpn) (fmap go cs))
476 go (FChoice qfn rdm y t m d ts) =
477 FChoice qfn rdm y t m d <$> traverse go ts
478 go (SChoice qsn rdm y t ts) =
479 SChoice qsn rdm y t <$> traverse go ts
480 go (GoalChoice rdm ts) =
481 GoalChoice rdm <$> traverse go ts
482 go x@(Fail _ _) = return x
483 go x@(Done _ _) = return x
485 -- The check proper
486 goP :: QPN -> POption -> EnforceSIR (Tree d c) -> EnforceSIR (Tree d c)
487 goP qpn@(Q _ pn) (POption i linkedTo) r = do
488 let inst = PI pn i
489 env <- ask
490 case (linkedTo, M.lookup inst env) of
491 (Just _, _) ->
492 -- For linked nodes we don't check anything
494 (Nothing, Nothing) ->
495 -- Not linked, not already used
496 local (M.insert inst qpn) r
497 (Nothing, Just qpn') -> do
498 -- Not linked, already used. This is an error
499 return $ Fail (CS.union (varToConflictSet (P qpn)) (varToConflictSet (P qpn'))) MultipleInstances