1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE TypeFamilies #-}
5 -----------------------------------------------------------------------------
7 -----------------------------------------------------------------------------
10 -- Module : Distribution.Client.SolverInstallPlan
11 -- Copyright : (c) Duncan Coutts 2008
14 -- Maintainer : duncan@community.haskell.org
15 -- Stability : provisional
16 -- Portability : portable
18 -- The 'SolverInstallPlan' is the graph of packages produced by the
19 -- dependency solver, and specifies at the package-granularity what
20 -- things are going to be installed. To put it another way: the
21 -- dependency solver produces a 'SolverInstallPlan', which is then
22 -- consumed by various other parts of Cabal.
23 module Distribution
.Client
.SolverInstallPlan
24 ( SolverInstallPlan
(..)
26 , ResolverPackage
(..)
28 -- * Operations on 'SolverInstallPlan's
36 -- * Checking validity of plans
42 -- ** Details on invalid plans
43 , SolverPlanProblem
(..)
47 -- ** Querying the install plan
49 , reverseDependencyClosure
51 , reverseTopologicalOrder
54 import Distribution
.Client
.Compat
.Prelude
hiding (toList
)
57 import Distribution
.Package
61 , PackageIdentifier
(..)
66 import qualified Distribution
.Solver
.Types
.ComponentDeps
as CD
67 import Distribution
.Types
.Flag
(nullFlagAssignment
)
69 import Distribution
.Client
.Types
72 import Distribution
.Version
76 import Distribution
.Solver
.Types
.ResolverPackage
77 import Distribution
.Solver
.Types
.Settings
78 import Distribution
.Solver
.Types
.SolverId
79 import Distribution
.Solver
.Types
.SolverPackage
81 import Data
.Array ((!))
82 import qualified Data
.Foldable
as Foldable
83 import qualified Data
.Graph
as OldGraph
84 import qualified Data
.Map
as Map
85 import Distribution
.Compat
.Graph
(Graph
, IsNode
(..))
86 import qualified Distribution
.Compat
.Graph
as Graph
88 type SolverPlanPackage
= ResolverPackage UnresolvedPkgLoc
90 type SolverPlanIndex
= Graph SolverPlanPackage
92 data SolverInstallPlan
= SolverInstallPlan
93 { planIndex
:: !SolverPlanIndex
94 , planIndepGoals
:: !IndependentGoals
96 deriving (Typeable
, Generic
)
99 -- | Much like 'planPkgIdOf', but mapping back to full packages.
100 planPkgOf :: SolverInstallPlan
104 case Graph.lookupKey (planIndex plan)
105 (planPkgIdOf plan v) of
107 Nothing -> error "InstallPlan: internal error: planPkgOf lookup failed"
110 instance Binary SolverInstallPlan
111 instance Structured SolverInstallPlan
113 showPlanIndex
:: [SolverPlanPackage
] -> String
114 showPlanIndex
= intercalate
"\n" . map showPlanPackage
116 showInstallPlan
:: SolverInstallPlan
-> String
117 showInstallPlan
= showPlanIndex
. toList
119 showPlanPackage
:: SolverPlanPackage
-> String
120 showPlanPackage
(PreExisting ipkg
) =
122 ++ prettyShow
(packageId ipkg
)
124 ++ prettyShow
(installedUnitId ipkg
)
126 showPlanPackage
(Configured spkg
) =
127 "Configured " ++ prettyShow
(packageId spkg
) ++ flags
++ comps
130 | nullFlagAssignment fa
= ""
131 |
otherwise = " " ++ prettyShow
(solverPkgFlags spkg
)
133 fa
= solverPkgFlags spkg
137 |
otherwise = " " ++ unwords (map prettyShow
$ Foldable
.toList deps
)
139 deps
:: Set CD
.Component
141 CD
.components
(solverPkgLibDeps spkg
)
142 <> CD
.components
(solverPkgExeDeps spkg
)
144 -- | Build an installation plan from a valid set of resolved packages.
148 -> Either [SolverPlanProblem
] SolverInstallPlan
149 new indepGoals
index =
150 case problems indepGoals
index of
151 [] -> Right
(SolverInstallPlan
index indepGoals
)
154 toList
:: SolverInstallPlan
-> [SolverPlanPackage
]
155 toList
= Foldable
.toList
. planIndex
157 toMap
:: SolverInstallPlan
-> Map SolverId SolverPlanPackage
158 toMap
= Graph
.toMap
. planIndex
160 -- | Remove packages from the install plan. This will result in an
161 -- error if there are remaining packages that depend on any matching
162 -- package. This is primarily useful for obtaining an install plan for
163 -- the dependencies of a package or set of packages without actually
164 -- installing the package itself, as when doing development.
166 :: (SolverPlanPackage
-> Bool)
171 remove shouldRemove plan
=
172 new
(planIndepGoals plan
) newIndex
175 Graph
.fromDistinctList
$
176 filter (not . shouldRemove
) (toList plan
)
178 -- ------------------------------------------------------------
180 -- * Checking validity of plans
182 -- ------------------------------------------------------------
184 -- | A valid installation plan is a set of packages that is 'acyclic',
185 -- 'closed' and 'consistent'. Also, every 'ConfiguredPackage' in the
186 -- plan has to have a valid configuration (see 'configuredPackageValid').
188 -- * if the result is @False@ use 'problems' to get a detailed list.
193 valid indepGoals
index =
194 null $ problems indepGoals
index
196 data SolverPlanProblem
200 | PackageCycle
[SolverPlanPackage
]
201 | PackageInconsistency PackageName
[(PackageIdentifier
, Version
)]
202 | PackageStateInvalid SolverPlanPackage SolverPlanPackage
204 showPlanProblem
:: SolverPlanProblem
-> String
205 showPlanProblem
(PackageMissingDeps pkg missingDeps
) =
207 ++ prettyShow
(packageId pkg
)
208 ++ " depends on the following packages which are missing from the plan: "
209 ++ intercalate
", " (map prettyShow missingDeps
)
210 showPlanProblem
(PackageCycle cycleGroup
) =
211 "The following packages are involved in a dependency cycle "
212 ++ intercalate
", " (map (prettyShow
. packageId
) cycleGroup
)
213 showPlanProblem
(PackageInconsistency name inconsistencies
) =
216 ++ " is required by several packages,"
217 ++ " but they require inconsistent versions:\n"
222 ++ prettyShow
(PackageIdentifier name ver
)
223 |
(pkg
, ver
) <- inconsistencies
225 showPlanProblem
(PackageStateInvalid pkg pkg
') =
227 ++ prettyShow
(packageId pkg
)
230 ++ " state but it depends on package "
231 ++ prettyShow
(packageId pkg
')
232 ++ " which is in the "
233 ++ showPlanState pkg
'
236 showPlanState
(PreExisting _
) = "pre-existing"
237 showPlanState
(Configured _
) = "configured"
239 -- | For an invalid plan, produce a detailed list of problems as human readable
240 -- error messages. This is mainly intended for debugging purposes.
241 -- Use 'showPlanProblem' for a human readable explanation.
245 -> [SolverPlanProblem
]
246 problems indepGoals
index =
250 (fmap packageId
. flip Graph
.lookup index)
253 |
(pkg
, missingDeps
) <- Graph
.broken
index
255 ++ [ PackageCycle cycleGroup
256 | cycleGroup
<- Graph
.cycles
index
258 ++ [ PackageInconsistency name inconsistencies
259 |
(name
, inconsistencies
) <-
260 dependencyInconsistencies indepGoals
index
262 ++ [ PackageStateInvalid pkg pkg
'
263 | pkg
<- Foldable
.toList
index
266 (flip Graph
.lookup index)
268 , not (stateDependencyRelation pkg pkg
')
271 -- | Compute all roots of the install plan, and verify that the transitive
272 -- plans from those roots are all consistent.
274 -- NOTE: This does not check for dependency cycles. Moreover, dependency cycles
275 -- may be absent from the subplans even if the larger plan contains a dependency
276 -- cycle. Such cycles may or may not be an issue; either way, we don't check
278 dependencyInconsistencies
281 -> [(PackageName
, [(PackageIdentifier
, Version
)])]
282 dependencyInconsistencies indepGoals
index =
283 concatMap dependencyInconsistencies
' subplans
285 subplans
:: [SolverPlanIndex
]
287 -- Not Graph.closure!!
289 (nonSetupClosure
index)
290 (rootSets indepGoals
index)
292 -- NB: When we check for inconsistencies, packages from the setup
293 -- scripts don't count as part of the closure (this way, we
294 -- can build, e.g., Cabal-1.24.1 even if its setup script is
295 -- built with Cabal-1.24.0).
297 -- This is a best effort function that swallows any non-existent
303 nonSetupClosure
index pkgids0
= closure Graph
.empty pkgids0
305 closure
:: Graph SolverPlanPackage
-> [SolverId
] -> SolverPlanIndex
306 closure completed
[] = completed
307 closure completed
(pkgid
: pkgids
) =
308 case Graph
.lookup pkgid
index of
309 Nothing
-> closure completed pkgids
311 case Graph
.lookup (nodeKey pkg
) completed
of
312 Just _
-> closure completed pkgids
313 Nothing
-> closure completed
' pkgids
'
315 completed
' = Graph
.insert pkg completed
316 pkgids
' = CD
.nonSetupDeps
(resolverPackageLibDeps pkg
) ++ pkgids
318 -- | Compute the root sets of a plan
320 -- A root set is a set of packages whose dependency closure must be consistent.
321 -- This is the set of all top-level library roots (taken together normally, or
322 -- as singletons sets if we are considering them as independent goals), along
323 -- with all setup dependencies of all packages.
324 rootSets
:: IndependentGoals
-> SolverPlanIndex
-> [[SolverId
]]
325 rootSets
(IndependentGoals indepGoals
) index =
327 then map (: []) libRoots
332 libRoots
:: [SolverId
]
333 libRoots
= libraryRoots
index
335 -- | Compute the library roots of a plan
337 -- The library roots are the set of packages with no reverse dependencies
338 -- (no reverse library dependencies but also no reverse setup dependencies).
339 libraryRoots
:: SolverPlanIndex
-> [SolverId
]
341 map (nodeKey
. toPkgId
) roots
343 (graph
, toPkgId
, _
) = Graph
.toGraph
index
344 indegree
= OldGraph
.indegree graph
345 roots
= filter isRoot
(OldGraph
.vertices graph
)
346 isRoot v
= indegree
! v
== 0
348 -- | The setup dependencies of each package in the plan
349 setupRoots
:: SolverPlanIndex
-> [[SolverId
]]
352 . map (CD
.setupDeps
. resolverPackageLibDeps
)
355 -- | Given a package index where we assume we want to use all the packages
356 -- (use 'dependencyClosure' if you need to get such a index subset) find out
357 -- if the dependencies within it use consistent versions of each package.
358 -- Return all cases where multiple packages depend on different versions of
359 -- some other package.
361 -- Each element in the result is a package name along with the packages that
362 -- depend on it and the versions they require. These are guaranteed to be
364 dependencyInconsistencies
'
366 -> [(PackageName
, [(PackageIdentifier
, Version
)])]
367 dependencyInconsistencies
' index =
368 [ (name
, [(pid
, packageVersion dep
) |
(dep
, pids
) <- uses
, pid
<- pids
])
369 |
(name
, ipid_map
) <- Map
.toList inverseIndex
370 , let uses
= Map
.elems ipid_map
371 , reallyIsInconsistent
(map fst uses
)
374 -- For each package name (of a dependency, somewhere)
375 -- and each installed ID of that package
376 -- the associated package instance
377 -- and a list of reverse dependencies (as source IDs)
378 inverseIndex
:: Map PackageName
(Map SolverId
(SolverPlanPackage
, [PackageId
]))
381 (Map
.unionWith
(\(a
, b
) (_
, b
') -> (a
, b
++ b
')))
382 [ (packageName dep
, Map
.fromList
[(sid
, (dep
, [packageId pkg
]))])
383 |
-- For each package @pkg@
384 pkg
<- Foldable
.toList
index
385 , -- Find out which @sid@ @pkg@ depends on
386 sid
<- CD
.nonSetupDeps
(resolverPackageLibDeps pkg
)
387 , -- And look up those @sid@ (i.e., @sid@ is the ID of @dep@)
388 Just dep
<- [Graph
.lookup sid
index]
391 -- If, in a single install plan, we depend on more than one version of a
392 -- package, then this is ONLY okay in the (rather special) case that we
393 -- depend on precisely two versions of that package, and one of them
394 -- depends on the other. This is necessary for example for the base where
395 -- we have base-3 depending on base-4.
396 reallyIsInconsistent
:: [SolverPlanPackage
] -> Bool
397 reallyIsInconsistent
[] = False
398 reallyIsInconsistent
[_p
] = False
399 reallyIsInconsistent
[p1
, p2
] =
400 let pid1
= nodeKey p1
402 in pid1 `
notElem` CD
.nonSetupDeps
(resolverPackageLibDeps p2
)
403 && pid2 `
notElem` CD
.nonSetupDeps
(resolverPackageLibDeps p1
)
404 reallyIsInconsistent _
= True
406 -- | The graph of packages (nodes) and dependencies (edges) must be acyclic.
408 -- * if the result is @False@ use 'PackageIndex.dependencyCycles' to find out
409 -- which packages are involved in dependency cycles.
410 acyclic
:: SolverPlanIndex
-> Bool
411 acyclic
= null . Graph
.cycles
413 -- | An installation plan is closed if for every package in the set, all of
414 -- its dependencies are also in the set. That is, the set is closed under the
415 -- dependency relation.
417 -- * if the result is @False@ use 'PackageIndex.brokenPackages' to find out
418 -- which packages depend on packages not in the index.
419 closed
:: SolverPlanIndex
-> Bool
420 closed
= null . Graph
.broken
422 -- | An installation plan is consistent if all dependencies that target a
423 -- single package name, target the same version.
425 -- This is slightly subtle. It is not the same as requiring that there be at
426 -- most one version of any package in the set. It only requires that of
427 -- packages which have more than one other package depending on them. We could
428 -- actually make the condition even more precise and say that different
429 -- versions are OK so long as they are not both in the transitive closure of
430 -- any other package (or equivalently that their inverse closures do not
431 -- intersect). The point is we do not want to have any packages depending
432 -- directly or indirectly on two different versions of the same package. The
433 -- current definition is just a safe approximation of that.
435 -- * if the result is @False@ use 'PackageIndex.dependencyInconsistencies' to
436 -- find out which packages are.
437 consistent
:: SolverPlanIndex
-> Bool
438 consistent
= null . dependencyInconsistencies
(IndependentGoals
False)
440 -- | The states of packages have that depend on each other must respect
441 -- this relation. That is for very case where package @a@ depends on
442 -- package @b@ we require that @dependencyStatesOk a b = True@.
443 stateDependencyRelation
447 stateDependencyRelation PreExisting
{} PreExisting
{} = True
448 stateDependencyRelation
(Configured _
) PreExisting
{} = True
449 stateDependencyRelation
(Configured _
) (Configured _
) = True
450 stateDependencyRelation _ _
= False
452 -- | Compute the dependency closure of a package in a install plan
456 -> [SolverPlanPackage
]
457 dependencyClosure plan
= fromMaybe [] . Graph
.closure
(planIndex plan
)
459 reverseDependencyClosure
462 -> [SolverPlanPackage
]
463 reverseDependencyClosure plan
= fromMaybe [] . Graph
.revClosure
(planIndex plan
)
467 -> [SolverPlanPackage
]
468 topologicalOrder plan
= Graph
.topSort
(planIndex plan
)
470 reverseTopologicalOrder
472 -> [SolverPlanPackage
]
473 reverseTopologicalOrder plan
= Graph
.revTopSort
(planIndex plan
)