1 {-# LANGUAGE DeriveGeneric #-}
2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE TypeFamilies #-}
4 -----------------------------------------------------------------------------
6 -- Module : Distribution.Client.SolverInstallPlan
7 -- Copyright : (c) Duncan Coutts 2008
10 -- Maintainer : duncan@community.haskell.org
11 -- Stability : provisional
12 -- Portability : portable
14 -- The 'SolverInstallPlan' is the graph of packages produced by the
15 -- dependency solver, and specifies at the package-granularity what
16 -- things are going to be installed. To put it another way: the
17 -- dependency solver produces a 'SolverInstallPlan', which is then
18 -- consumed by various other parts of Cabal.
20 -----------------------------------------------------------------------------
21 module Distribution
.Client
.SolverInstallPlan
(
22 SolverInstallPlan
(..),
26 -- * 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
58 ( PackageIdentifier
(..), Package
(..), PackageName
59 , HasUnitId
(..), PackageId
, packageVersion
, packageName
)
60 import Distribution
.Types
.Flag
(nullFlagAssignment
)
61 import qualified Distribution
.Solver
.Types
.ComponentDeps
as CD
63 import Distribution
.Client
.Types
65 import Distribution
.Version
68 import Distribution
.Solver
.Types
.Settings
69 import Distribution
.Solver
.Types
.ResolverPackage
70 import Distribution
.Solver
.Types
.SolverId
71 import Distribution
.Solver
.Types
.SolverPackage
73 import Distribution
.Compat
.Graph
(Graph
, IsNode
(..))
74 import qualified Data
.Foldable
as Foldable
75 import qualified Data
.Graph
as OldGraph
76 import qualified Distribution
.Compat
.Graph
as Graph
77 import qualified Data
.Map
as Map
78 import Data
.Array ((!))
80 type SolverPlanPackage
= ResolverPackage UnresolvedPkgLoc
82 type SolverPlanIndex
= Graph SolverPlanPackage
84 data SolverInstallPlan
= SolverInstallPlan
{
85 planIndex
:: !SolverPlanIndex
,
86 planIndepGoals
:: !IndependentGoals
88 deriving (Typeable
, Generic
)
91 -- | Much like 'planPkgIdOf', but mapping back to full packages.
92 planPkgOf :: SolverInstallPlan
96 case Graph.lookupKey (planIndex plan)
97 (planPkgIdOf plan v) of
99 Nothing -> error "InstallPlan: internal error: planPkgOf lookup failed"
104 instance Binary SolverInstallPlan
105 instance Structured SolverInstallPlan
107 showPlanIndex
:: [SolverPlanPackage
] -> String
108 showPlanIndex
= intercalate
"\n" . map showPlanPackage
110 showInstallPlan
:: SolverInstallPlan
-> String
111 showInstallPlan
= showPlanIndex
. toList
113 showPlanPackage
:: SolverPlanPackage
-> String
114 showPlanPackage
(PreExisting ipkg
) = "PreExisting " ++ prettyShow
(packageId ipkg
)
115 ++ " (" ++ prettyShow
(installedUnitId ipkg
)
117 showPlanPackage
(Configured spkg
) =
118 "Configured " ++ prettyShow
(packageId spkg
) ++ flags
++ comps
121 | nullFlagAssignment fa
= ""
122 |
otherwise = " " ++ prettyShow
(solverPkgFlags spkg
)
124 fa
= solverPkgFlags spkg
126 comps |
null deps
= ""
127 |
otherwise = " " ++ unwords (map prettyShow
$ Foldable
.toList deps
)
129 deps
:: Set CD
.Component
130 deps
= CD
.components
(solverPkgLibDeps spkg
)
131 <> CD
.components
(solverPkgExeDeps spkg
)
133 -- | Build an installation plan from a valid set of resolved packages.
135 new
:: IndependentGoals
137 -> Either [SolverPlanProblem
] SolverInstallPlan
138 new indepGoals
index =
139 case problems indepGoals
index of
140 [] -> Right
(SolverInstallPlan
index indepGoals
)
143 toList
:: SolverInstallPlan
-> [SolverPlanPackage
]
144 toList
= Foldable
.toList
. planIndex
146 toMap
:: SolverInstallPlan
-> Map SolverId SolverPlanPackage
147 toMap
= Graph
.toMap
. planIndex
149 -- | Remove packages from the install plan. This will result in an
150 -- error if there are remaining packages that depend on any matching
151 -- package. This is primarily useful for obtaining an install plan for
152 -- the dependencies of a package or set of packages without actually
153 -- installing the package itself, as when doing development.
155 remove
:: (SolverPlanPackage
-> Bool)
157 -> Either [SolverPlanProblem
]
159 remove shouldRemove plan
=
160 new
(planIndepGoals plan
) newIndex
162 newIndex
= Graph
.fromDistinctList
$
163 filter (not . shouldRemove
) (toList plan
)
165 -- ------------------------------------------------------------
166 -- * Checking validity of plans
167 -- ------------------------------------------------------------
169 -- | A valid installation plan is a set of packages that is 'acyclic',
170 -- 'closed' and 'consistent'. Also, every 'ConfiguredPackage' in the
171 -- plan has to have a valid configuration (see 'configuredPackageValid').
173 -- * if the result is @False@ use 'problems' to get a detailed list.
175 valid
:: IndependentGoals
178 valid indepGoals
index =
179 null $ problems indepGoals
index
181 data SolverPlanProblem
=
182 PackageMissingDeps SolverPlanPackage
184 | PackageCycle
[SolverPlanPackage
]
185 | PackageInconsistency PackageName
[(PackageIdentifier
, Version
)]
186 | PackageStateInvalid SolverPlanPackage SolverPlanPackage
188 showPlanProblem
:: SolverPlanProblem
-> String
189 showPlanProblem
(PackageMissingDeps pkg missingDeps
) =
190 "Package " ++ prettyShow
(packageId pkg
)
191 ++ " depends on the following packages which are missing from the plan: "
192 ++ intercalate
", " (map prettyShow missingDeps
)
194 showPlanProblem
(PackageCycle cycleGroup
) =
195 "The following packages are involved in a dependency cycle "
196 ++ intercalate
", " (map (prettyShow
.packageId
) cycleGroup
)
198 showPlanProblem
(PackageInconsistency name inconsistencies
) =
199 "Package " ++ prettyShow name
200 ++ " is required by several packages,"
201 ++ " but they require inconsistent versions:\n"
202 ++ unlines [ " package " ++ prettyShow pkg
++ " requires "
203 ++ prettyShow
(PackageIdentifier name ver
)
204 |
(pkg
, ver
) <- inconsistencies
]
206 showPlanProblem
(PackageStateInvalid pkg pkg
') =
207 "Package " ++ prettyShow
(packageId pkg
)
208 ++ " is in the " ++ showPlanState pkg
209 ++ " state but it depends on package " ++ prettyShow
(packageId pkg
')
210 ++ " which is in the " ++ showPlanState pkg
'
213 showPlanState
(PreExisting _
) = "pre-existing"
214 showPlanState
(Configured _
) = "configured"
216 -- | For an invalid plan, produce a detailed list of problems as human readable
217 -- error messages. This is mainly intended for debugging purposes.
218 -- Use 'showPlanProblem' for a human readable explanation.
220 problems
:: IndependentGoals
222 -> [SolverPlanProblem
]
223 problems indepGoals
index =
225 [ PackageMissingDeps pkg
227 (fmap packageId
. flip Graph
.lookup index)
229 |
(pkg
, missingDeps
) <- Graph
.broken
index ]
231 ++ [ PackageCycle cycleGroup
232 | cycleGroup
<- Graph
.cycles
index ]
234 ++ [ PackageInconsistency name inconsistencies
235 |
(name
, inconsistencies
) <-
236 dependencyInconsistencies indepGoals
index ]
238 ++ [ PackageStateInvalid pkg pkg
'
239 | pkg
<- Foldable
.toList
index
240 , Just pkg
' <- map (flip Graph
.lookup index)
242 , not (stateDependencyRelation pkg pkg
') ]
245 -- | Compute all roots of the install plan, and verify that the transitive
246 -- plans from those roots are all consistent.
248 -- NOTE: This does not check for dependency cycles. Moreover, dependency cycles
249 -- may be absent from the subplans even if the larger plan contains a dependency
250 -- cycle. Such cycles may or may not be an issue; either way, we don't check
252 dependencyInconsistencies
:: IndependentGoals
254 -> [(PackageName
, [(PackageIdentifier
, Version
)])]
255 dependencyInconsistencies indepGoals
index =
256 concatMap dependencyInconsistencies
' subplans
258 subplans
:: [SolverPlanIndex
]
259 subplans
= -- Not Graph.closure!!
260 map (nonSetupClosure
index)
261 (rootSets indepGoals
index)
263 -- NB: When we check for inconsistencies, packages from the setup
264 -- scripts don't count as part of the closure (this way, we
265 -- can build, e.g., Cabal-1.24.1 even if its setup script is
266 -- built with Cabal-1.24.0).
268 -- This is a best effort function that swallows any non-existent
270 nonSetupClosure
:: SolverPlanIndex
273 nonSetupClosure
index pkgids0
= closure Graph
.empty pkgids0
275 closure
:: Graph SolverPlanPackage
-> [SolverId
] -> SolverPlanIndex
276 closure completed
[] = completed
277 closure completed
(pkgid
:pkgids
) =
278 case Graph
.lookup pkgid
index of
279 Nothing
-> closure completed pkgids
281 case Graph
.lookup (nodeKey pkg
) completed
of
282 Just _
-> closure completed pkgids
283 Nothing
-> closure completed
' pkgids
'
284 where completed
' = Graph
.insert pkg completed
285 pkgids
' = CD
.nonSetupDeps
(resolverPackageLibDeps pkg
) ++ pkgids
287 -- | Compute the root sets of a plan
289 -- A root set is a set of packages whose dependency closure must be consistent.
290 -- This is the set of all top-level library roots (taken together normally, or
291 -- as singletons sets if we are considering them as independent goals), along
292 -- with all setup dependencies of all packages.
293 rootSets
:: IndependentGoals
-> SolverPlanIndex
-> [[SolverId
]]
294 rootSets
(IndependentGoals indepGoals
) index =
295 if indepGoals
then map (:[]) libRoots
else [libRoots
]
298 libRoots
:: [SolverId
]
299 libRoots
= libraryRoots
index
301 -- | Compute the library roots of a plan
303 -- The library roots are the set of packages with no reverse dependencies
304 -- (no reverse library dependencies but also no reverse setup dependencies).
305 libraryRoots
:: SolverPlanIndex
-> [SolverId
]
307 map (nodeKey
. toPkgId
) roots
309 (graph
, toPkgId
, _
) = Graph
.toGraph
index
310 indegree
= OldGraph
.indegree graph
311 roots
= filter isRoot
(OldGraph
.vertices graph
)
312 isRoot v
= indegree
! v
== 0
314 -- | The setup dependencies of each package in the plan
315 setupRoots
:: SolverPlanIndex
-> [[SolverId
]]
316 setupRoots
= filter (not . null)
317 . map (CD
.setupDeps
. resolverPackageLibDeps
)
320 -- | Given a package index where we assume we want to use all the packages
321 -- (use 'dependencyClosure' if you need to get such a index subset) find out
322 -- if the dependencies within it use consistent versions of each package.
323 -- Return all cases where multiple packages depend on different versions of
324 -- some other package.
326 -- Each element in the result is a package name along with the packages that
327 -- depend on it and the versions they require. These are guaranteed to be
330 dependencyInconsistencies
' :: SolverPlanIndex
331 -> [(PackageName
, [(PackageIdentifier
, Version
)])]
332 dependencyInconsistencies
' index =
333 [ (name
, [ (pid
, packageVersion dep
) |
(dep
,pids
) <- uses
, pid
<- pids
])
334 |
(name
, ipid_map
) <- Map
.toList inverseIndex
335 , let uses
= Map
.elems ipid_map
336 , reallyIsInconsistent
(map fst uses
)
339 -- For each package name (of a dependency, somewhere)
340 -- and each installed ID of that package
341 -- the associated package instance
342 -- and a list of reverse dependencies (as source IDs)
343 inverseIndex
:: Map PackageName
(Map SolverId
(SolverPlanPackage
, [PackageId
]))
344 inverseIndex
= Map
.fromListWith
(Map
.unionWith
(\(a
,b
) (_
,b
') -> (a
,b
++b
')))
345 [ (packageName dep
, Map
.fromList
[(sid
,(dep
,[packageId pkg
]))])
346 |
-- For each package @pkg@
347 pkg
<- Foldable
.toList
index
348 -- Find out which @sid@ @pkg@ depends on
349 , sid
<- CD
.nonSetupDeps
(resolverPackageLibDeps pkg
)
350 -- And look up those @sid@ (i.e., @sid@ is the ID of @dep@)
351 , Just dep
<- [Graph
.lookup sid
index]
354 -- If, in a single install plan, we depend on more than one version of a
355 -- package, then this is ONLY okay in the (rather special) case that we
356 -- depend on precisely two versions of that package, and one of them
357 -- depends on the other. This is necessary for example for the base where
358 -- we have base-3 depending on base-4.
359 reallyIsInconsistent
:: [SolverPlanPackage
] -> Bool
360 reallyIsInconsistent
[] = False
361 reallyIsInconsistent
[_p
] = False
362 reallyIsInconsistent
[p1
, p2
] =
363 let pid1
= nodeKey p1
365 in pid1 `
notElem` CD
.nonSetupDeps
(resolverPackageLibDeps p2
)
366 && pid2 `
notElem` CD
.nonSetupDeps
(resolverPackageLibDeps p1
)
367 reallyIsInconsistent _
= True
370 -- | The graph of packages (nodes) and dependencies (edges) must be acyclic.
372 -- * if the result is @False@ use 'PackageIndex.dependencyCycles' to find out
373 -- which packages are involved in dependency cycles.
375 acyclic
:: SolverPlanIndex
-> Bool
376 acyclic
= null . Graph
.cycles
378 -- | An installation plan is closed if for every package in the set, all of
379 -- its dependencies are also in the set. That is, the set is closed under the
380 -- dependency relation.
382 -- * if the result is @False@ use 'PackageIndex.brokenPackages' to find out
383 -- which packages depend on packages not in the index.
385 closed
:: SolverPlanIndex
-> Bool
386 closed
= null . Graph
.broken
388 -- | An installation plan is consistent if all dependencies that target a
389 -- single package name, target the same version.
391 -- This is slightly subtle. It is not the same as requiring that there be at
392 -- most one version of any package in the set. It only requires that of
393 -- packages which have more than one other package depending on them. We could
394 -- actually make the condition even more precise and say that different
395 -- versions are OK so long as they are not both in the transitive closure of
396 -- any other package (or equivalently that their inverse closures do not
397 -- intersect). The point is we do not want to have any packages depending
398 -- directly or indirectly on two different versions of the same package. The
399 -- current definition is just a safe approximation of that.
401 -- * if the result is @False@ use 'PackageIndex.dependencyInconsistencies' to
402 -- find out which packages are.
404 consistent
:: SolverPlanIndex
-> Bool
405 consistent
= null . dependencyInconsistencies
(IndependentGoals
False)
407 -- | The states of packages have that depend on each other must respect
408 -- this relation. That is for very case where package @a@ depends on
409 -- package @b@ we require that @dependencyStatesOk a b = True@.
411 stateDependencyRelation
:: SolverPlanPackage
414 stateDependencyRelation PreExisting
{} PreExisting
{} = True
416 stateDependencyRelation
(Configured _
) PreExisting
{} = True
417 stateDependencyRelation
(Configured _
) (Configured _
) = True
419 stateDependencyRelation _ _
= False
422 -- | Compute the dependency closure of a package in a install plan
424 dependencyClosure
:: SolverInstallPlan
426 -> [SolverPlanPackage
]
427 dependencyClosure plan
= fromMaybe [] . Graph
.closure
(planIndex plan
)
430 reverseDependencyClosure
:: SolverInstallPlan
432 -> [SolverPlanPackage
]
433 reverseDependencyClosure plan
= fromMaybe [] . Graph
.revClosure
(planIndex plan
)
436 topologicalOrder
:: SolverInstallPlan
437 -> [SolverPlanPackage
]
438 topologicalOrder plan
= Graph
.topSort
(planIndex plan
)
441 reverseTopologicalOrder
:: SolverInstallPlan
442 -> [SolverPlanPackage
]
443 reverseTopologicalOrder plan
= Graph
.revTopSort
(planIndex plan
)