1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE ScopedTypeVariables #-}
3 {-# LANGUAGE DeriveGeneric #-}
4 {-# LANGUAGE DeriveDataTypeable #-}
5 {-# LANGUAGE TypeFamilies #-}
6 {-# LANGUAGE ConstraintKinds #-}
7 {-# LANGUAGE FlexibleContexts #-}
8 -----------------------------------------------------------------------------
10 -- Module : Distribution.Client.InstallPlan
11 -- Copyright : (c) Duncan Coutts 2008
14 -- Maintainer : duncan@community.haskell.org
15 -- Stability : provisional
16 -- Portability : portable
18 -- Package installation plan
20 -----------------------------------------------------------------------------
21 module Distribution
.Client
.InstallPlan
(
25 GenericPlanPackage
(..),
29 -- * Operations on 'InstallPlan's
39 fromSolverInstallPlan
,
40 fromSolverInstallPlanWithProgress
,
53 -- ** Traversal helpers
64 -- * Graph-like operations
66 reverseTopologicalOrder
,
67 reverseDependencyClosure
,
70 import Distribution
.Client
.Compat
.Prelude
hiding (toList
, lookup, tail)
72 import Distribution
.Compat
.Stack
(WithCallStack
)
74 import Distribution
.Client
.Types
hiding (BuildOutcomes
)
75 import qualified Distribution
.PackageDescription
as PD
76 import qualified Distribution
.Simple
.Configure
as Configure
77 import qualified Distribution
.Simple
.Setup
as Cabal
79 import Distribution
.InstalledPackageInfo
80 ( InstalledPackageInfo
)
81 import Distribution
.Package
82 ( Package
(..), HasMungedPackageId
(..)
83 , HasUnitId
(..), UnitId
)
84 import Distribution
.Solver
.Types
.SolverPackage
85 import Distribution
.Client
.JobControl
86 import Distribution
.Pretty
(defaultStyle
)
87 import Text
.PrettyPrint
88 import qualified Distribution
.Client
.SolverInstallPlan
as SolverInstallPlan
89 import Distribution
.Client
.SolverInstallPlan
(SolverInstallPlan
)
91 import qualified Distribution
.Solver
.Types
.ComponentDeps
as CD
92 import Distribution
.Solver
.Types
.Settings
93 import Distribution
.Solver
.Types
.SolverId
94 import Distribution
.Solver
.Types
.InstSolverPackage
96 import Distribution
.Utils
.LogProgress
97 import Distribution
.Utils
.Structured
(Structured
(..), Structure
(Nominal
))
99 -- TODO: Need this when we compute final UnitIds
100 -- import qualified Distribution.Simple.Configure as Configure
102 import qualified Data
.Foldable
as Foldable
(all, toList
)
103 import qualified Distribution
.Compat
.Graph
as Graph
104 import Distribution
.Compat
.Graph
(Graph
, IsNode
(..))
105 import Control
.Exception
107 import qualified Data
.Map
as Map
108 import qualified Data
.Set
as Set
110 -- When cabal tries to install a number of packages, including all their
111 -- dependencies it has a non-trivial problem to solve.
115 -- In general we start with a set of installed packages and a set of source
118 -- Installed packages have fixed dependencies. They have already been built and
119 -- we know exactly what packages they were built against, including their exact
122 -- Source package have somewhat flexible dependencies. They are specified as
123 -- version ranges, though really they're predicates. To make matters worse they
124 -- have conditional flexible dependencies. Configuration flags can affect which
125 -- packages are required and can place additional constraints on their
128 -- These two sets of package can and usually do overlap. There can be installed
129 -- packages that are also available as source packages which means they could
130 -- be re-installed if required, though there will also be packages which are
131 -- not available as source and cannot be re-installed. Very often there will be
132 -- extra versions available than are installed. Sometimes we may like to prefer
133 -- installed packages over source ones or perhaps always prefer the latest
134 -- available version whether installed or not.
136 -- The goal is to calculate an installation plan that is closed, acyclic and
137 -- consistent and where every configured package is valid.
139 -- An installation plan is a set of packages that are going to be used
140 -- together. It will consist of a mixture of installed packages and source
141 -- packages along with their exact version dependencies. An installation plan
142 -- is closed if for every package in the set, all of its dependencies are
143 -- also in the set. It is consistent if for every package in the set, all
144 -- dependencies which target that package have the same version.
146 -- Note that plans do not necessarily compose. You might have a valid plan for
147 -- package A and a valid plan for package B. That does not mean the composition
148 -- is simultaneously valid for A and B. In particular you're most likely to
149 -- have problems with inconsistent dependencies.
150 -- On the other hand it is true that every closed sub plan is valid.
152 -- | Packages in an install plan
154 -- NOTE: 'ConfiguredPackage', 'GenericReadyPackage' and 'GenericPlanPackage'
155 -- intentionally have no 'PackageInstalled' instance. `This is important:
156 -- PackageInstalled returns only library dependencies, but for package that
157 -- aren't yet installed we know many more kinds of dependencies (setup
158 -- dependencies, exe, test-suite, benchmark, ..). Any functions that operate on
159 -- dependencies in cabal-install should consider what to do with these
160 -- dependencies; if we give a 'PackageInstalled' instance it would be too easy
161 -- to get this wrong (and, for instance, call graph traversal functions from
162 -- Cabal rather than from cabal-install). Instead, see 'PackageInstalled'.
163 data GenericPlanPackage ipkg srcpkg
167 deriving (Eq
, Show, Generic
)
169 displayGenericPlanPackage
:: (IsUnit ipkg
, IsUnit srcpkg
) => GenericPlanPackage ipkg srcpkg
-> String
170 displayGenericPlanPackage
(PreExisting pkg
) = "PreExisting " ++ prettyShow
(nodeKey pkg
)
171 displayGenericPlanPackage
(Configured pkg
) = "Configured " ++ prettyShow
(nodeKey pkg
)
172 displayGenericPlanPackage
(Installed pkg
) = "Installed " ++ prettyShow
(nodeKey pkg
)
174 -- | Convenience combinator for destructing 'GenericPlanPackage'.
175 -- This is handy because if you case manually, you have to handle
176 -- 'Configured' and 'Installed' separately (where often you want
177 -- them to be the same.)
178 foldPlanPackage
:: (ipkg
-> a
)
180 -> GenericPlanPackage ipkg srcpkg
182 foldPlanPackage f _
(PreExisting ipkg
) = f ipkg
183 foldPlanPackage _ g
(Configured srcpkg
) = g srcpkg
184 foldPlanPackage _ g
(Installed srcpkg
) = g srcpkg
186 type IsUnit a
= (IsNode a
, Key a ~ UnitId
)
188 depends
:: IsUnit a
=> a
-> [UnitId
]
189 depends
= nodeNeighbors
191 -- NB: Expanded constraint synonym here to avoid undecidable
192 -- instance errors in GHC 7.8 and earlier.
193 instance (IsNode ipkg
, IsNode srcpkg
, Key ipkg ~ UnitId
, Key srcpkg ~ UnitId
)
194 => IsNode
(GenericPlanPackage ipkg srcpkg
) where
195 type Key
(GenericPlanPackage ipkg srcpkg
) = UnitId
196 nodeKey
(PreExisting ipkg
) = nodeKey ipkg
197 nodeKey
(Configured spkg
) = nodeKey spkg
198 nodeKey
(Installed spkg
) = nodeKey spkg
199 nodeNeighbors
(PreExisting ipkg
) = nodeNeighbors ipkg
200 nodeNeighbors
(Configured spkg
) = nodeNeighbors spkg
201 nodeNeighbors
(Installed spkg
) = nodeNeighbors spkg
203 instance (Binary ipkg
, Binary srcpkg
) => Binary
(GenericPlanPackage ipkg srcpkg
)
204 instance (Structured ipkg
, Structured srcpkg
) => Structured
(GenericPlanPackage ipkg srcpkg
)
206 type PlanPackage
= GenericPlanPackage
207 InstalledPackageInfo
(ConfiguredPackage UnresolvedPkgLoc
)
209 instance (Package ipkg
, Package srcpkg
) =>
210 Package
(GenericPlanPackage ipkg srcpkg
) where
211 packageId
(PreExisting ipkg
) = packageId ipkg
212 packageId
(Configured spkg
) = packageId spkg
213 packageId
(Installed spkg
) = packageId spkg
215 instance (HasMungedPackageId ipkg
, HasMungedPackageId srcpkg
) =>
216 HasMungedPackageId
(GenericPlanPackage ipkg srcpkg
) where
217 mungedId
(PreExisting ipkg
) = mungedId ipkg
218 mungedId
(Configured spkg
) = mungedId spkg
219 mungedId
(Installed spkg
) = mungedId spkg
221 instance (HasUnitId ipkg
, HasUnitId srcpkg
) =>
223 (GenericPlanPackage ipkg srcpkg
) where
224 installedUnitId
(PreExisting ipkg
) = installedUnitId ipkg
225 installedUnitId
(Configured spkg
) = installedUnitId spkg
226 installedUnitId
(Installed spkg
) = installedUnitId spkg
228 instance (HasConfiguredId ipkg
, HasConfiguredId srcpkg
) =>
229 HasConfiguredId
(GenericPlanPackage ipkg srcpkg
) where
230 configuredId
(PreExisting ipkg
) = configuredId ipkg
231 configuredId
(Configured spkg
) = configuredId spkg
232 configuredId
(Installed spkg
) = configuredId spkg
234 data GenericInstallPlan ipkg srcpkg
= GenericInstallPlan
{
235 planGraph
:: !(Graph
(GenericPlanPackage ipkg srcpkg
)),
236 planIndepGoals
:: !IndependentGoals
240 -- | 'GenericInstallPlan' specialised to most commonly used types.
241 type InstallPlan
= GenericInstallPlan
242 InstalledPackageInfo
(ConfiguredPackage UnresolvedPkgLoc
)
244 -- | Smart constructor that deals with caching the 'Graph' representation.
246 mkInstallPlan
:: (IsUnit ipkg
, IsUnit srcpkg
)
248 -> Graph
(GenericPlanPackage ipkg srcpkg
)
250 -> GenericInstallPlan ipkg srcpkg
251 mkInstallPlan loc graph indepGoals
=
252 assert
(valid loc graph
)
255 planIndepGoals
= indepGoals
258 internalError
:: WithCallStack
(String -> String -> a
)
259 internalError loc msg
= error $ "internal error in InstallPlan." ++ loc
260 ++ if null msg
then "" else ": " ++ msg
262 instance (Structured ipkg
, Structured srcpkg
) => Structured
(GenericInstallPlan ipkg srcpkg
) where
263 structure p
= Nominal
(typeRep p
) 0 "GenericInstallPlan"
264 [ structure
(Proxy
:: Proxy ipkg
)
265 , structure
(Proxy
:: Proxy srcpkg
)
268 instance (IsNode ipkg
, Key ipkg ~ UnitId
, IsNode srcpkg
, Key srcpkg ~ UnitId
,
269 Binary ipkg
, Binary srcpkg
)
270 => Binary
(GenericInstallPlan ipkg srcpkg
) where
271 put GenericInstallPlan
{
273 planIndepGoals
= indepGoals
274 } = put graph
>> put indepGoals
279 return $! mkInstallPlan
"(instance Binary)" graph indepGoals
281 showPlanGraph
:: (Package ipkg
, Package srcpkg
,
282 IsUnit ipkg
, IsUnit srcpkg
)
283 => Graph
(GenericPlanPackage ipkg srcpkg
) -> String
284 showPlanGraph graph
= renderStyle defaultStyle
$
285 vcat
(map dispPlanPackage
(Foldable
.toList graph
))
286 where dispPlanPackage p
=
287 hang
(hsep
[ text
(showPlanPackageTag p
)
288 , pretty
(packageId p
)
289 , parens
(pretty
(nodeKey p
))]) 2
290 (vcat
(map pretty
(nodeNeighbors p
)))
292 showInstallPlan
:: (Package ipkg
, Package srcpkg
,
293 IsUnit ipkg
, IsUnit srcpkg
)
294 => GenericInstallPlan ipkg srcpkg
-> String
295 showInstallPlan
= showPlanGraph
. planGraph
297 showPlanPackageTag
:: GenericPlanPackage ipkg srcpkg
-> String
298 showPlanPackageTag
(PreExisting _
) = "PreExisting"
299 showPlanPackageTag
(Configured _
) = "Configured"
300 showPlanPackageTag
(Installed _
) = "Installed"
302 -- | Build an installation plan from a valid set of resolved packages.
304 new
:: (IsUnit ipkg
, IsUnit srcpkg
)
306 -> Graph
(GenericPlanPackage ipkg srcpkg
)
307 -> GenericInstallPlan ipkg srcpkg
308 new indepGoals graph
= mkInstallPlan
"new" graph indepGoals
310 toGraph
:: GenericInstallPlan ipkg srcpkg
311 -> Graph
(GenericPlanPackage ipkg srcpkg
)
314 toList
:: GenericInstallPlan ipkg srcpkg
315 -> [GenericPlanPackage ipkg srcpkg
]
316 toList
= Foldable
.toList
. planGraph
318 toMap
:: GenericInstallPlan ipkg srcpkg
319 -> Map UnitId
(GenericPlanPackage ipkg srcpkg
)
320 toMap
= Graph
.toMap
. planGraph
322 keys
:: GenericInstallPlan ipkg srcpkg
-> [UnitId
]
323 keys
= Graph
.keys
. planGraph
325 keysSet
:: GenericInstallPlan ipkg srcpkg
-> Set UnitId
326 keysSet
= Graph
.keysSet
. planGraph
328 -- | Remove packages from the install plan. This will result in an
329 -- error if there are remaining packages that depend on any matching
330 -- package. This is primarily useful for obtaining an install plan for
331 -- the dependencies of a package or set of packages without actually
332 -- installing the package itself, as when doing development.
334 remove
:: (IsUnit ipkg
, IsUnit srcpkg
)
335 => (GenericPlanPackage ipkg srcpkg
-> Bool)
336 -> GenericInstallPlan ipkg srcpkg
337 -> GenericInstallPlan ipkg srcpkg
338 remove shouldRemove plan
=
339 mkInstallPlan
"remove" newGraph
(planIndepGoals plan
)
341 newGraph
= Graph
.fromDistinctList
$
342 filter (not . shouldRemove
) (toList plan
)
344 -- | Change a number of packages in the 'Configured' state to the 'Installed'
347 -- To preserve invariants, the package must have all of its dependencies
348 -- already installed too (that is 'PreExisting' or 'Installed').
350 installed
:: (IsUnit ipkg
, IsUnit srcpkg
)
352 -> GenericInstallPlan ipkg srcpkg
353 -> GenericInstallPlan ipkg srcpkg
354 installed shouldBeInstalled installPlan
=
355 foldl' markInstalled installPlan
357 | Configured pkg
<- reverseTopologicalOrder installPlan
358 , shouldBeInstalled pkg
]
360 markInstalled plan pkg
=
361 assert
(all isInstalled
(directDeps plan
(nodeKey pkg
))) $
363 planGraph
= Graph
.insert (Installed pkg
) (planGraph plan
)
366 -- | Lookup a package in the plan.
368 lookup :: (IsUnit ipkg
, IsUnit srcpkg
)
369 => GenericInstallPlan ipkg srcpkg
371 -> Maybe (GenericPlanPackage ipkg srcpkg
)
372 lookup plan pkgid
= Graph
.lookup pkgid
(planGraph plan
)
374 -- | Find all the direct dependencies of the given package.
376 -- Note that the package must exist in the plan or it is an error.
378 directDeps
:: GenericInstallPlan ipkg srcpkg
380 -> [GenericPlanPackage ipkg srcpkg
]
381 directDeps plan pkgid
=
382 case Graph
.neighbors
(planGraph plan
) pkgid
of
384 Nothing
-> internalError
"directDeps" "package not in graph"
386 -- | Find all the direct reverse dependencies of the given package.
388 -- Note that the package must exist in the plan or it is an error.
390 revDirectDeps
:: GenericInstallPlan ipkg srcpkg
392 -> [GenericPlanPackage ipkg srcpkg
]
393 revDirectDeps plan pkgid
=
394 case Graph
.revNeighbors
(planGraph plan
) pkgid
of
396 Nothing
-> internalError
"revDirectDeps" "package not in graph"
398 -- | Return all the packages in the 'InstallPlan' in reverse topological order.
399 -- That is, for each package, all dependencies of the package appear first.
401 -- Compared to 'executionOrder', this function returns all the installed and
402 -- source packages rather than just the source ones. Also, while both this
403 -- and 'executionOrder' produce reverse topological orderings of the package
404 -- dependency graph, it is not necessarily exactly the same order.
406 reverseTopologicalOrder
:: GenericInstallPlan ipkg srcpkg
407 -> [GenericPlanPackage ipkg srcpkg
]
408 reverseTopologicalOrder plan
= Graph
.revTopSort
(planGraph plan
)
411 -- | Return the packages in the plan that are direct or indirect dependencies of
412 -- the given packages.
414 dependencyClosure
:: GenericInstallPlan ipkg srcpkg
416 -> [GenericPlanPackage ipkg srcpkg
]
417 dependencyClosure plan
= fromMaybe []
418 . Graph
.closure
(planGraph plan
)
420 -- | Return the packages in the plan that depend directly or indirectly on the
423 reverseDependencyClosure
:: GenericInstallPlan ipkg srcpkg
425 -> [GenericPlanPackage ipkg srcpkg
]
426 reverseDependencyClosure plan
= fromMaybe []
427 . Graph
.revClosure
(planGraph plan
)
430 -- Alert alert! Why does SolverId map to a LIST of plan packages?
431 -- The sordid story has to do with 'build-depends' on a package
432 -- with libraries and executables. In an ideal world, we would
433 -- ONLY depend on the library in this situation. But c.f. #3661
434 -- some people rely on the build-depends to ALSO implicitly
435 -- depend on an executable.
437 -- I don't want to commit to a strategy yet, so the only possible
438 -- thing you can do in this case is return EVERYTHING and let
439 -- the client filter out what they want (executables? libraries?
440 -- etc). This similarly implies we can't return a 'ConfiguredId'
441 -- because that's not enough information.
443 fromSolverInstallPlan
::
444 (IsUnit ipkg
, IsUnit srcpkg
)
445 => ( (SolverId
-> [GenericPlanPackage ipkg srcpkg
])
446 -> SolverInstallPlan
.SolverPlanPackage
447 -> [GenericPlanPackage ipkg srcpkg
] )
449 -> GenericInstallPlan ipkg srcpkg
450 fromSolverInstallPlan f plan
=
451 mkInstallPlan
"fromSolverInstallPlan"
452 (Graph
.fromDistinctList pkgs
'')
453 (SolverInstallPlan
.planIndepGoals plan
)
455 (_
, _
, pkgs
'') = foldl' f
' (Map
.empty, Map
.empty, [])
456 (SolverInstallPlan
.reverseTopologicalOrder plan
)
458 f
' (pidMap
, ipiMap
, pkgs
) pkg
= (pidMap
', ipiMap
', pkgs
' ++ pkgs
)
460 pkgs
' = f
(mapDep pidMap ipiMap
) pkg
463 = case nodeKey pkg
of
464 PreExistingId _ uid
-> (pidMap
, Map
.insert uid pkgs
' ipiMap
)
465 PlannedId pid
-> (Map
.insert pid pkgs
' pidMap
, ipiMap
)
467 mapDep _ ipiMap
(PreExistingId _pid uid
)
468 | Just pkgs
<- Map
.lookup uid ipiMap
= pkgs
469 |
otherwise = error ("fromSolverInstallPlan: PreExistingId " ++ prettyShow uid
)
470 mapDep pidMap _
(PlannedId pid
)
471 | Just pkgs
<- Map
.lookup pid pidMap
= pkgs
472 |
otherwise = error ("fromSolverInstallPlan: PlannedId " ++ prettyShow pid
)
473 -- This shouldn't happen, since mapDep should only be called
474 -- on neighbor SolverId, which must have all been done already
475 -- by the reverse top-sort (we assume the graph is not broken).
478 fromSolverInstallPlanWithProgress
::
479 (IsUnit ipkg
, IsUnit srcpkg
)
480 => ( (SolverId
-> [GenericPlanPackage ipkg srcpkg
])
481 -> SolverInstallPlan
.SolverPlanPackage
482 -> LogProgress
[GenericPlanPackage ipkg srcpkg
] )
484 -> LogProgress
(GenericInstallPlan ipkg srcpkg
)
485 fromSolverInstallPlanWithProgress f plan
= do
486 (_
, _
, pkgs
'') <- foldM f
' (Map
.empty, Map
.empty, [])
487 (SolverInstallPlan
.reverseTopologicalOrder plan
)
488 return $ mkInstallPlan
"fromSolverInstallPlanWithProgress"
489 (Graph
.fromDistinctList pkgs
'')
490 (SolverInstallPlan
.planIndepGoals plan
)
492 f
' (pidMap
, ipiMap
, pkgs
) pkg
= do
493 pkgs
' <- f
(mapDep pidMap ipiMap
) pkg
494 let (pidMap
', ipiMap
')
495 = case nodeKey pkg
of
496 PreExistingId _ uid
-> (pidMap
, Map
.insert uid pkgs
' ipiMap
)
497 PlannedId pid
-> (Map
.insert pid pkgs
' pidMap
, ipiMap
)
498 return (pidMap
', ipiMap
', pkgs
' ++ pkgs
)
500 mapDep _ ipiMap
(PreExistingId _pid uid
)
501 | Just pkgs
<- Map
.lookup uid ipiMap
= pkgs
502 |
otherwise = error ("fromSolverInstallPlan: PreExistingId " ++ prettyShow uid
)
503 mapDep pidMap _
(PlannedId pid
)
504 | Just pkgs
<- Map
.lookup pid pidMap
= pkgs
505 |
otherwise = error ("fromSolverInstallPlan: PlannedId " ++ prettyShow pid
)
506 -- This shouldn't happen, since mapDep should only be called
507 -- on neighbor SolverId, which must have all been done already
508 -- by the reverse top-sort (we assume the graph is not broken).
510 -- | Conversion of 'SolverInstallPlan' to 'InstallPlan'.
511 -- Similar to 'elaboratedInstallPlan'
512 configureInstallPlan
:: Cabal
.ConfigFlags
-> SolverInstallPlan
-> InstallPlan
513 configureInstallPlan configFlags solverPlan
=
514 flip fromSolverInstallPlan solverPlan
$ \mapDep planpkg
->
516 SolverInstallPlan
.PreExisting pkg
->
517 PreExisting
(instSolverPkgIPI pkg
)
519 SolverInstallPlan
.Configured pkg
->
520 Configured
(configureSolverPackage mapDep pkg
)
523 configureSolverPackage
:: (SolverId
-> [PlanPackage
])
524 -> SolverPackage UnresolvedPkgLoc
525 -> ConfiguredPackage UnresolvedPkgLoc
526 configureSolverPackage mapDep spkg
=
528 confPkgId
= Configure
.computeComponentId
529 (Cabal
.fromFlagOrDefault
False
530 (Cabal
.configDeterministic configFlags
))
534 (PD
.CLibName PD
.LMainLibName
)
535 (Just
(map confInstId
(CD
.libraryDeps deps
),
536 solverPkgFlags spkg
)),
537 confPkgSource
= solverPkgSource spkg
,
538 confPkgFlags
= solverPkgFlags spkg
,
539 confPkgStanzas
= solverPkgStanzas spkg
,
541 -- NB: no support for executable dependencies
544 deps
= fmap (concatMap (map configuredId
. mapDep
)) (solverPkgLibDeps spkg
)
547 -- ------------------------------------------------------------
548 -- * Primitives for traversing plans
549 -- ------------------------------------------------------------
553 -- Algorithms to traverse or execute an 'InstallPlan', especially in parallel,
554 -- may make use of the 'Processing' type and the associated operations
555 -- 'ready', 'completed' and 'failed'.
557 -- The 'Processing' type is used to keep track of the state of a traversal and
558 -- includes the set of packages that are in the processing state, e.g. in the
559 -- process of being installed, plus those that have been completed and those
560 -- where processing failed.
562 -- Traversal algorithms start with an 'InstallPlan':
564 -- * Initially there will be certain packages that can be processed immediately
565 -- (since they are configured source packages and have all their dependencies
566 -- installed already). The function 'ready' returns these packages plus a
567 -- 'Processing' state that marks these same packages as being in the
570 -- * The algorithm must now arrange for these packages to be processed
571 -- (possibly in parallel). When a package has completed processing, the
572 -- algorithm needs to know which other packages (if any) are now ready to
573 -- process as a result. The 'completed' function marks a package as completed
574 -- and returns any packages that are newly in the processing state (ie ready
575 -- to process), along with the updated 'Processing' state.
577 -- * If failure is possible then when processing a package fails, the algorithm
578 -- needs to know which other packages have also failed as a result. The
579 -- 'failed' function marks the given package as failed as well as all the
580 -- other packages that depend on the failed package. In addition it returns
581 -- the other failed packages.
584 -- | The 'Processing' type is used to keep track of the state of a traversal
585 -- and includes the set of packages that are in the processing state, e.g. in
586 -- the process of being installed, plus those that have been completed and
587 -- those where processing failed.
589 data Processing
= Processing
!(Set UnitId
) !(Set UnitId
) !(Set UnitId
)
590 -- processing, completed, failed
592 -- | The packages in the plan that are initially ready to be installed.
593 -- That is they are in the configured state and have all their dependencies
594 -- installed already.
596 -- The result is both the packages that are now ready to be installed and also
597 -- a 'Processing' state containing those same packages. The assumption is that
598 -- all the packages that are ready will now be processed and so we can consider
599 -- them to be in the processing state.
601 ready
:: (IsUnit ipkg
, IsUnit srcpkg
)
602 => GenericInstallPlan ipkg srcpkg
603 -> ([GenericReadyPackage srcpkg
], Processing
)
605 assert
(processingInvariant plan processing
) $
606 (readyPackages
, processing
)
610 (Set
.fromList
[ nodeKey pkg | pkg
<- readyPackages
])
611 (Set
.fromList
[ nodeKey pkg | pkg
<- toList plan
, isInstalled pkg
])
615 | Configured pkg
<- toList plan
616 , all isInstalled
(directDeps plan
(nodeKey pkg
))
619 isInstalled
:: GenericPlanPackage a b
-> Bool
620 isInstalled
(PreExisting
{}) = True
621 isInstalled
(Installed
{}) = True
622 isInstalled _
= False
624 -- | Given a package in the processing state, mark the package as completed
625 -- and return any packages that are newly in the processing state (ie ready to
626 -- process), along with the updated 'Processing' state.
628 completed
:: forall ipkg srcpkg
. (IsUnit ipkg
, IsUnit srcpkg
)
629 => GenericInstallPlan ipkg srcpkg
630 -> Processing
-> UnitId
631 -> ([GenericReadyPackage srcpkg
], Processing
)
632 completed plan
(Processing processingSet completedSet failedSet
) pkgid
=
633 assert
(pkgid `Set
.member` processingSet
) $
634 assert
(processingInvariant plan processing
') $
636 ( map asReadyPackage newlyReady
639 completedSet
' = Set
.insert pkgid completedSet
641 -- each direct reverse dep where all direct deps are completed
643 | dep
<- revDirectDeps plan pkgid
644 , all ((`Set
.member` completedSet
') . nodeKey
)
645 (directDeps plan
(nodeKey dep
))
648 processingSet
' = foldl' (flip Set
.insert)
649 (Set
.delete pkgid processingSet
)
650 (map nodeKey newlyReady
)
651 processing
' = Processing processingSet
' completedSet
' failedSet
653 asReadyPackage
:: GenericPlanPackage ipkg srcpkg
-> GenericReadyPackage srcpkg
654 asReadyPackage
(Configured pkg
) = ReadyPackage pkg
655 asReadyPackage pkg
= internalError
"completed" $ "not in configured state: " ++ displayGenericPlanPackage pkg
657 failed
:: (IsUnit ipkg
, IsUnit srcpkg
)
658 => GenericInstallPlan ipkg srcpkg
659 -> Processing
-> UnitId
660 -> ([srcpkg
], Processing
)
661 failed plan
(Processing processingSet completedSet failedSet
) pkgid
=
662 assert
(pkgid `Set
.member` processingSet
) $
663 assert
(all (`Set
.notMember` processingSet
) (tail newlyFailedIds
)) $
664 assert
(all (`Set
.notMember` completedSet
) (tail newlyFailedIds
)) $
665 -- but note that some newlyFailed may already be in the failed set
666 -- since one package can depend on two packages that both fail and
667 -- so would be in the rev-dep closure for both.
668 assert
(processingInvariant plan processing
') $
670 ( map asConfiguredPackage
(tail newlyFailed
)
673 processingSet
' = Set
.delete pkgid processingSet
674 failedSet
' = failedSet `Set
.union` Set
.fromList newlyFailedIds
675 newlyFailedIds
= map nodeKey newlyFailed
676 newlyFailed
= fromMaybe (internalError
"failed" "package not in graph")
677 $ Graph
.revClosure
(planGraph plan
) [pkgid
]
678 processing
' = Processing processingSet
' completedSet failedSet
'
680 asConfiguredPackage
(Configured pkg
) = pkg
681 asConfiguredPackage pkg
= internalError
"failed" $ "not in configured state: " ++ displayGenericPlanPackage pkg
683 processingInvariant
:: (IsUnit ipkg
, IsUnit srcpkg
)
684 => GenericInstallPlan ipkg srcpkg
685 -> Processing
-> Bool
686 processingInvariant plan
(Processing processingSet completedSet failedSet
) =
688 -- All the packages in the three sets are actually in the graph
689 assert
(Foldable
.all (flip Graph
.member
(planGraph plan
)) processingSet
) $
690 assert
(Foldable
.all (flip Graph
.member
(planGraph plan
)) completedSet
) $
691 assert
(Foldable
.all (flip Graph
.member
(planGraph plan
)) failedSet
) $
693 -- The processing, completed and failed sets are disjoint from each other
694 assert
(noIntersection processingSet completedSet
) $
695 assert
(noIntersection processingSet failedSet
) $
696 assert
(noIntersection failedSet completedSet
) $
698 -- Packages that depend on a package that's still processing cannot be
700 assert
(noIntersection
(reverseClosure processingSet
) completedSet
) $
702 -- On the other hand, packages that depend on a package that's still
703 -- processing /can/ have failed (since they may have depended on multiple
704 -- packages that were processing, but it only takes one to fail to cause
705 -- knock-on failures) so it is quite possible to have an
706 -- intersection (reverseClosure processingSet) failedSet
708 -- The failed set is upwards closed, i.e. equal to its own rev dep closure
709 assert
(failedSet
== reverseClosure failedSet
) $
711 -- All immediate reverse deps of packages that are currently processing
712 -- are not currently being processed (ie not in the processing set).
713 assert
(and [ rdeppkgid `Set
.notMember` processingSet
714 | pkgid
<- Set
.toList processingSet
715 , rdeppkgid
<- maybe (internalError
"processingInvariant" "")
717 (Graph
.revNeighbors
(planGraph plan
) pkgid
)
720 -- Packages from the processing or failed sets are only ever in the
722 assert
(and [ case Graph
.lookup pkgid
(planGraph plan
) of
723 Just
(Configured _
) -> True
724 Just
(PreExisting _
) -> False
725 Just
(Installed _
) -> False
727 | pkgid
<- Set
.toList processingSet
++ Set
.toList failedSet
])
729 -- We use asserts rather than returning False so that on failure we get
730 -- better details on which bit of the invariant was violated.
733 reverseClosure
= Set
.fromList
735 . fromMaybe (internalError
"processingInvariant" "")
736 . Graph
.revClosure
(planGraph plan
)
738 noIntersection a b
= Set
.null (Set
.intersection a b
)
741 -- ------------------------------------------------------------
742 -- * Traversing plans
743 -- ------------------------------------------------------------
745 -- | Flatten an 'InstallPlan', producing the sequence of source packages in
746 -- the order in which they would be processed when the plan is executed. This
747 -- can be used for simulations or presenting execution dry-runs.
749 -- It is guaranteed to give the same order as using 'execute' (with a serial
750 -- in-order 'JobControl'), which is a reverse topological orderings of the
751 -- source packages in the dependency graph, albeit not necessarily exactly the
752 -- same ordering as that produced by 'reverseTopologicalOrder'.
754 executionOrder
:: (IsUnit ipkg
, IsUnit srcpkg
)
755 => GenericInstallPlan ipkg srcpkg
756 -> [GenericReadyPackage srcpkg
]
757 executionOrder plan
=
758 let (newpkgs
, processing
) = ready plan
759 in tryNewTasks processing newpkgs
761 tryNewTasks _processing
[] = []
762 tryNewTasks processing
(p
:todo
) = waitForTasks processing p todo
764 waitForTasks processing p todo
=
765 p
: tryNewTasks processing
' (todo
++nextpkgs
)
767 (nextpkgs
, processing
') = completed plan processing
(nodeKey p
)
770 -- ------------------------------------------------------------
772 -- ------------------------------------------------------------
774 -- | The set of results we get from executing an install plan.
776 type BuildOutcomes failure result
= Map UnitId
(Either failure result
)
778 -- | Lookup the build result for a single package.
780 lookupBuildOutcome
:: HasUnitId pkg
781 => pkg
-> BuildOutcomes failure result
782 -> Maybe (Either failure result
)
783 lookupBuildOutcome
= Map
.lookup . installedUnitId
785 -- | Execute an install plan. This traverses the plan in dependency order.
787 -- Executing each individual package can fail and if so all dependents fail
788 -- too. The result for each package is collected as a 'BuildOutcomes' map.
790 -- Visiting each package happens with optional parallelism, as determined by
791 -- the 'JobControl'. By default, after any failure we stop as soon as possible
792 -- (using the 'JobControl' to try to cancel in-progress tasks). This behaviour
793 -- can be reversed to keep going and build as many packages as possible.
795 -- Note that the 'BuildOutcomes' is /not/ guaranteed to cover all the packages
796 -- in the plan. In particular in the default mode where we stop as soon as
797 -- possible after a failure then there may be packages which are skipped and
798 -- these will have no 'BuildOutcome'.
800 execute
:: forall m ipkg srcpkg result failure
.
801 (IsUnit ipkg
, IsUnit srcpkg
,
803 => JobControl m
(UnitId
, Either failure result
)
804 -> Bool -- ^ Keep going after failure
805 -> (srcpkg
-> failure
) -- ^ Value for dependents of failed packages
806 -> GenericInstallPlan ipkg srcpkg
807 -> (GenericReadyPackage srcpkg
-> m
(Either failure result
))
808 -> m
(BuildOutcomes failure result
)
809 execute jobCtl keepGoing depFailure plan installPkg
=
810 let (newpkgs
, processing
) = ready plan
811 in tryNewTasks Map
.empty False False processing newpkgs
813 tryNewTasks
:: BuildOutcomes failure result
814 -> Bool -> Bool -> Processing
815 -> [GenericReadyPackage srcpkg
]
816 -> m
(BuildOutcomes failure result
)
818 tryNewTasks
!results tasksFailed tasksRemaining
!processing newpkgs
819 -- we were in the process of cancelling and now we're finished
820 | tasksFailed
&& not keepGoing
&& not tasksRemaining
823 -- we are still in the process of cancelling, wait for remaining tasks
824 | tasksFailed
&& not keepGoing
&& tasksRemaining
825 = waitForTasks results tasksFailed processing
827 -- no new tasks to do and all tasks are done so we're finished
828 |
null newpkgs
&& not tasksRemaining
831 -- no new tasks to do, remaining tasks to wait for
833 = waitForTasks results tasksFailed processing
835 -- new tasks to do, spawn them, then wait for tasks to complete
837 = do sequence_ [ spawnJob jobCtl
$ do
838 result
<- installPkg pkg
839 return (nodeKey pkg
, result
)
841 waitForTasks results tasksFailed processing
843 waitForTasks
:: BuildOutcomes failure result
844 -> Bool -> Processing
845 -> m
(BuildOutcomes failure result
)
846 waitForTasks
!results tasksFailed
!processing
= do
847 (pkgid
, result
) <- collectJob jobCtl
852 tasksRemaining
<- remainingJobs jobCtl
853 tryNewTasks results
' tasksFailed tasksRemaining
856 results
' = Map
.insert pkgid result results
857 (nextpkgs
, processing
') = completed plan processing pkgid
860 -- if this is the first failure and we're not trying to keep going
861 -- then try to cancel as many of the remaining jobs as possible
862 when (not tasksFailed
&& not keepGoing
) $
865 tasksRemaining
<- remainingJobs jobCtl
866 tryNewTasks results
' True tasksRemaining processing
' []
868 (depsfailed
, processing
') = failed plan processing pkgid
869 results
' = Map
.insert pkgid result results `Map
.union` depResults
870 depResults
= Map
.fromList
871 [ (nodeKey deppkg
, Left
(depFailure deppkg
))
872 | deppkg
<- depsfailed
]
874 -- ------------------------------------------------------------
875 -- * Checking validity of plans
876 -- ------------------------------------------------------------
878 -- | A valid installation plan is a set of packages that is closed, acyclic
879 -- and respects the package state relation.
881 -- * if the result is @False@ use 'problems' to get a detailed list.
883 valid
:: (IsUnit ipkg
, IsUnit srcpkg
)
884 => String -> Graph
(GenericPlanPackage ipkg srcpkg
) -> Bool
886 case problems graph
of
888 ps
-> internalError loc
('\n' : unlines (map showPlanProblem ps
))
890 data PlanProblem ipkg srcpkg
=
891 PackageMissingDeps
(GenericPlanPackage ipkg srcpkg
) [UnitId
]
892 | PackageCycle
[GenericPlanPackage ipkg srcpkg
]
893 | PackageStateInvalid
(GenericPlanPackage ipkg srcpkg
)
894 (GenericPlanPackage ipkg srcpkg
)
896 showPlanProblem
:: (IsUnit ipkg
, IsUnit srcpkg
)
897 => PlanProblem ipkg srcpkg
-> String
898 showPlanProblem
(PackageMissingDeps pkg missingDeps
) =
899 "Package " ++ prettyShow
(nodeKey pkg
)
900 ++ " depends on the following packages which are missing from the plan: "
901 ++ intercalate
", " (map prettyShow missingDeps
)
903 showPlanProblem
(PackageCycle cycleGroup
) =
904 "The following packages are involved in a dependency cycle "
905 ++ intercalate
", " (map (prettyShow
. nodeKey
) cycleGroup
)
906 showPlanProblem
(PackageStateInvalid pkg pkg
') =
907 "Package " ++ prettyShow
(nodeKey pkg
)
908 ++ " is in the " ++ showPlanPackageTag pkg
909 ++ " state but it depends on package " ++ prettyShow
(nodeKey pkg
')
910 ++ " which is in the " ++ showPlanPackageTag pkg
'
913 -- | For an invalid plan, produce a detailed list of problems as human readable
914 -- error messages. This is mainly intended for debugging purposes.
915 -- Use 'showPlanProblem' for a human readable explanation.
917 problems
:: (IsUnit ipkg
, IsUnit srcpkg
)
918 => Graph
(GenericPlanPackage ipkg srcpkg
)
919 -> [PlanProblem ipkg srcpkg
]
922 [ PackageMissingDeps pkg
924 (fmap nodeKey
. flip Graph
.lookup graph
)
926 |
(pkg
, missingDeps
) <- Graph
.broken graph
]
928 ++ [ PackageCycle cycleGroup
929 | cycleGroup
<- Graph
.cycles graph
]
931 ++ [ PackageInconsistency name inconsistencies
932 | (name, inconsistencies) <-
933 dependencyInconsistencies indepGoals graph ]
934 --TODO: consider re-enabling this one, see SolverInstallPlan
936 ++ [ PackageStateInvalid pkg pkg
'
937 | pkg
<- Foldable
.toList graph
938 , Just pkg
' <- map (flip Graph
.lookup graph
)
940 , not (stateDependencyRelation pkg pkg
') ]
942 -- | The states of packages have that depend on each other must respect
943 -- this relation. That is for very case where package @a@ depends on
944 -- package @b@ we require that @stateDependencyRelation a b = True@.
946 stateDependencyRelation
:: GenericPlanPackage ipkg srcpkg
947 -> GenericPlanPackage ipkg srcpkg
-> Bool
948 stateDependencyRelation PreExisting
{} PreExisting
{} = True
950 stateDependencyRelation Installed
{} PreExisting
{} = True
951 stateDependencyRelation Installed
{} Installed
{} = True
953 stateDependencyRelation Configured
{} PreExisting
{} = True
954 stateDependencyRelation Configured
{} Installed
{} = True
955 stateDependencyRelation Configured
{} Configured
{} = True
957 stateDependencyRelation _ _
= False