Commit a0891eba authored by Edward Z. Yang's avatar Edward Z. Yang

Make a copy of InstallPlan named SolverInstallPlan.

Now we copy-paste the contents of InstallPlan into SolverInstallPlan,
thus giving it a separate set of types.  For now, we don't do anything
else, e.g., remove unnecessary functions or specialize.

We need a new function 'fromSolverInstallPlan' akin to
'mapPreservingGraph' which can take an InstallPlan from the
old solver install plan to the new one.
Signed-off-by: default avatarEdward Z. Yang <ezyang@cs.stanford.edu>
parent 49d69a90
......@@ -20,7 +20,6 @@ module Distribution.Client.Configure (
import Distribution.Client.Dependency
import qualified Distribution.Client.InstallPlan as InstallPlan
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.SolverInstallPlan (SolverInstallPlan)
import Distribution.Client.IndexUtils as IndexUtils
( getSourcePackages, getInstalledPackages )
......@@ -136,7 +135,7 @@ configure verbosity packageDBs repoCtxt comp platform conf
Nothing configureCommand (const configFlags) extraArgs
Right installPlan0 ->
let installPlan = SolverInstallPlan.configureInstallPlan installPlan0
let installPlan = InstallPlan.configureInstallPlan installPlan0
in case InstallPlan.ready installPlan of
[pkg@(ReadyPackage
(ConfiguredPackage _ (SourcePackage _ _ (LocalUnpackedPackage _) _)
......
......@@ -70,7 +70,6 @@ import Distribution.Solver.Modular
( modularResolver, SolverConfig(..) )
import Distribution.Simple.PackageIndex (InstalledPackageIndex)
import qualified Distribution.Simple.PackageIndex as InstalledPackageIndex
import qualified Distribution.Client.InstallPlan as InstallPlan
import Distribution.Client.SolverInstallPlan (SolverInstallPlan)
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.Types
......@@ -727,11 +726,11 @@ validateSolverResult platform comp indepGoals pkgs =
where
index = InstalledPackageIndex.fromList (map toPlanPackage pkgs)
toPlanPackage (PreExisting pkg) = InstallPlan.PreExisting pkg
toPlanPackage (Configured pkg) = InstallPlan.Configured pkg
toPlanPackage (PreExisting pkg) = SolverInstallPlan.PreExisting pkg
toPlanPackage (Configured pkg) = SolverInstallPlan.Configured pkg
formatPkgProblems = formatProblemMessage . map showPlanPackageProblem
formatPlanProblems = formatProblemMessage . map InstallPlan.showPlanProblem
formatPlanProblems = formatProblemMessage . map SolverInstallPlan.showPlanProblem
formatProblemMessage problems =
unlines $
......@@ -739,7 +738,7 @@ validateSolverResult platform comp indepGoals pkgs =
: "The proposed (invalid) plan contained the following problems:"
: problems
++ "Proposed plan:"
: [InstallPlan.showPlanIndex index]
: [SolverInstallPlan.showPlanIndex index]
data PlanPackageProblem =
......
......@@ -21,7 +21,7 @@ import Distribution.Client.FetchUtils hiding (fetchPackage)
import Distribution.Client.Dependency
import Distribution.Client.IndexUtils as IndexUtils
( getSourcePackages, getInstalledPackages )
import qualified Distribution.Client.InstallPlan as InstallPlan
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.Setup
( GlobalFlags(..), FetchFlags(..), RepoContext(..) )
......@@ -141,8 +141,8 @@ planPackages verbosity comp platform fetchFlags
-- that are in the 'InstallPlan.Configured' state.
return
[ solverPkgSource cpkg
| (InstallPlan.Configured cpkg)
<- InstallPlan.toList installPlan ]
| (SolverInstallPlan.Configured cpkg)
<- SolverInstallPlan.toList installPlan ]
| otherwise =
either (die . unlines . map show) return $
......
......@@ -24,7 +24,7 @@ import Distribution.Client.IndexUtils as IndexUtils
( getSourcePackages, getInstalledPackages )
import Distribution.Client.SolverInstallPlan
( SolverInstallPlan, SolverPlanPackage )
import qualified Distribution.Client.InstallPlan as InstallPlan
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.Setup
( GlobalFlags(..), FreezeFlags(..), ConfigExFlags(..)
, RepoContext(..) )
......@@ -227,7 +227,7 @@ pruneInstallPlan :: SolverInstallPlan
-> [SolverPlanPackage]
pruneInstallPlan installPlan pkgSpecifiers =
removeSelf pkgIds $
InstallPlan.dependencyClosure installPlan (map installedUnitId pkgIds)
SolverInstallPlan.dependencyClosure installPlan (map installedUnitId pkgIds)
where
pkgIds = [ PlannedId (packageId pkg)
| SpecificSourcePackage pkg <- pkgSpecifiers ]
......
......@@ -340,7 +340,7 @@ processInstallPlan verbosity
args installedPkgIndex installPlan
postInstallActions verbosity args userTargets installPlan'
where
installPlan = SolverInstallPlan.configureInstallPlan installPlan0
installPlan = InstallPlan.configureInstallPlan installPlan0
dryRun = fromFlag (installDryRun installFlags)
nothingToInstall = null (InstallPlan.ready installPlan)
......@@ -455,9 +455,9 @@ pruneInstallPlan pkgSpecifiers =
-- Also, the InstallPlan.remove should return info more precise to the
-- problem, rather than the very general PlanProblem type.
either (Fail . explain) Done
. InstallPlan.remove (\pkg -> packageName pkg `elem` targetnames)
. SolverInstallPlan.remove (\pkg -> packageName pkg `elem` targetnames)
where
explain :: [InstallPlan.PlanProblem ipkg srcpkg iresult ifailure] -> String
explain :: [SolverInstallPlan.PlanProblem ipkg srcpkg iresult ifailure] -> String
explain problems =
"Cannot select only the dependencies (as requested by the "
++ "'--only-dependencies' flag), "
......@@ -469,7 +469,7 @@ pruneInstallPlan pkgSpecifiers =
where
pkgids =
nub [ depid
| InstallPlan.PackageMissingDeps _ depids <- problems
| SolverInstallPlan.PackageMissingDeps _ depids <- problems
, depid <- depids
, packageName depid `elem` targetnames ]
......
......@@ -24,6 +24,9 @@ module Distribution.Client.InstallPlan (
toList,
mapPreservingGraph,
fromSolverInstallPlan,
configureInstallPlan,
ready,
processing,
completed,
......@@ -53,16 +56,17 @@ module Distribution.Client.InstallPlan (
reverseTopologicalOrder,
) where
import Distribution.Client.Types
import qualified Distribution.PackageDescription as PD
import qualified Distribution.Simple.Configure as Configure
import qualified Distribution.Simple.Setup as Cabal
import Distribution.InstalledPackageInfo
( InstalledPackageInfo )
import Distribution.Package
( PackageIdentifier(..), PackageName(..), Package(..)
, HasUnitId(..), UnitId(..) )
import Distribution.Client.Types
( BuildSuccess, BuildFailure
, ConfiguredPackage(..)
, UnresolvedPkgLoc
, GenericReadyPackage(..) )
import Distribution.Solver.Types.SolverPackage
import Distribution.Version
( Version )
import Distribution.Simple.PackageIndex
......@@ -71,6 +75,8 @@ import qualified Distribution.Simple.PackageIndex as PackageIndex
import qualified Distribution.Client.PlanIndex as PlanIndex
import Distribution.Text
( display )
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.SolverInstallPlan (SolverInstallPlan)
import Distribution.Solver.Types.ComponentDeps (ComponentDeps)
import qualified Distribution.Solver.Types.ComponentDeps as CD
......@@ -752,3 +758,71 @@ reverseTopologicalOrder plan =
map (planPkgOf plan)
. Graph.topSort
$ planGraphRev plan
fromSolverInstallPlan ::
(HasUnitId ipkg, PackageFixedDeps ipkg,
HasUnitId srcpkg, PackageFixedDeps srcpkg)
=> ((UnitId -> UnitId) -> SolverInstallPlan.SolverPlanPackage -> GenericPlanPackage ipkg srcpkg iresult ifailure)
-> SolverInstallPlan
-> GenericInstallPlan ipkg srcpkg iresult ifailure
fromSolverInstallPlan f plan =
mkInstallPlan (PackageIndex.fromList pkgs')
(SolverInstallPlan.planIndepGoals plan)
where
(_, pkgs') = foldl' f' (Map.empty, []) (SolverInstallPlan.reverseTopologicalOrder plan)
f' (ipkgidMap, pkgs) pkg = (ipkgidMap', pkg' : pkgs)
where
pkg' = f (mapDep ipkgidMap) pkg
ipkgidMap'
| ipkgid /= ipkgid' = Map.insert ipkgid ipkgid' ipkgidMap
| otherwise = ipkgidMap
where
ipkgid = installedUnitId pkg
ipkgid' = installedUnitId pkg'
mapDep ipkgidMap ipkgid = Map.findWithDefault ipkgid ipkgid ipkgidMap
-- | Conversion of 'SolverInstallPlan' to 'InstallPlan'.
-- Similar to 'elaboratedInstallPlan'
configureInstallPlan :: SolverInstallPlan -> InstallPlan
configureInstallPlan solverPlan =
flip fromSolverInstallPlan solverPlan $ \mapDep planpkg ->
case planpkg of
SolverInstallPlan.PreExisting pkg ->
PreExisting pkg
SolverInstallPlan.Configured pkg ->
Configured (configureSolverPackage mapDep pkg)
_ -> error "configureInstallPlan: unexpected package state"
where
configureSolverPackage :: (UnitId -> UnitId)
-> SolverPackage UnresolvedPkgLoc
-> ConfiguredPackage UnresolvedPkgLoc
configureSolverPackage mapDep spkg =
ConfiguredPackage {
confPkgId = SimpleUnitId
$ Configure.computeComponentId
Cabal.NoFlag
(packageId spkg)
(PD.CLibName (display (pkgName (packageId spkg))))
-- TODO: this is a hack that won't work for Backpack.
(map ((\(SimpleUnitId cid0) -> cid0) . confInstId)
(CD.libraryDeps deps))
(solverPkgFlags spkg),
confPkgSource = solverPkgSource spkg,
confPkgFlags = solverPkgFlags spkg,
confPkgStanzas = solverPkgStanzas spkg,
confPkgDeps = deps
}
where
deps = fmap (map (configureSolverId mapDep)) (solverPkgDeps spkg)
configureSolverId mapDep sid =
ConfiguredId {
confSrcId = packageId sid, -- accurate!
confInstId = mapDep (installedUnitId sid)
}
......@@ -64,6 +64,7 @@ import Distribution.Client.Types
hiding ( BuildResult, BuildSuccess(..), BuildFailure(..)
, DocsResult(..), TestsResult(..) )
import qualified Distribution.Client.InstallPlan as InstallPlan
import qualified Distribution.Client.SolverInstallPlan as SolverInstallPlan
import Distribution.Client.Dependency
import Distribution.Client.Dependency.Types
import qualified Distribution.Client.IndexUtils as IndexUtils
......@@ -697,8 +698,8 @@ packageLocationsSignature :: SolverInstallPlan
-> [(PackageId, PackageLocation (Maybe FilePath))]
packageLocationsSignature solverPlan =
[ (packageId pkg, packageSource pkg)
| InstallPlan.Configured (SolverPackage { solverPkgSource = pkg})
<- InstallPlan.toList solverPlan
| SolverInstallPlan.Configured (SolverPackage { solverPkgSource = pkg})
<- SolverInstallPlan.toList solverPlan
]
......@@ -718,8 +719,8 @@ getPackageSourceHashes verbosity withRepoCtx solverPlan = do
let allPkgLocations :: [(PackageId, PackageLocation (Maybe FilePath))]
allPkgLocations =
[ (packageId pkg, packageSource pkg)
| InstallPlan.Configured (SolverPackage { solverPkgSource = pkg})
<- InstallPlan.toList solverPlan ]
| SolverInstallPlan.Configured (SolverPackage { solverPkgSource = pkg})
<- SolverInstallPlan.toList solverPlan ]
-- Tarballs that were local in the first place.
-- We'll hash these tarball files directly.
......@@ -1015,12 +1016,12 @@ elaborateInstallPlan platform compiler compilerprogdb
}
elaboratedInstallPlan =
flip InstallPlan.mapPreservingGraph solverPlan $ \mapDep planpkg ->
flip InstallPlan.fromSolverInstallPlan solverPlan $ \mapDep planpkg ->
case planpkg of
InstallPlan.PreExisting pkg ->
SolverInstallPlan.PreExisting pkg ->
InstallPlan.PreExisting pkg
InstallPlan.Configured pkg ->
SolverInstallPlan.Configured pkg ->
InstallPlan.Configured
(elaborateSolverPackage mapDep pkg)
......@@ -1255,7 +1256,7 @@ elaborateInstallPlan platform compiler compilerprogdb
pkgsToBuildInplaceOnly =
Set.fromList
$ map installedPackageId
$ InstallPlan.reverseDependencyClosure
$ SolverInstallPlan.reverseDependencyClosure
solverPlan
[ installedPackageId (PlannedId (packageId pkg))
| pkg <- localPackages ]
......@@ -1303,10 +1304,10 @@ elaborateInstallPlan platform compiler compilerprogdb
packagesWithDownwardClosedProperty property =
Set.fromList
$ map packageId
$ InstallPlan.dependencyClosure
$ SolverInstallPlan.dependencyClosure
solverPlan
[ installedPackageId pkg
| pkg <- InstallPlan.toList solverPlan
| pkg <- SolverInstallPlan.toList solverPlan
, property pkg ] -- just the packages that satisfy the propety
--TODO: [nice to have] this does not check the config consistency,
-- e.g. a package explicitly turning off profiling, but something
......
-- | These graph traversal functions mirror the ones in Cabal, but work with
-- the more complete (and fine-grained) set of dependencies provided by
-- PackageFixedDeps rather than only the library dependencies provided by
-- PackageInstalled.
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
module Distribution.Client.SolverPlanIndex (
-- * Graph traversal functions
brokenPackages
, dependencyCycles
, dependencyGraph
, dependencyInconsistencies
) where
import Prelude hiding (lookup)
import qualified Data.Map as Map
import qualified Data.Graph as Graph
import Data.Array ((!))
import Data.Map (Map)
import Data.Maybe (isNothing)
import Data.Either (rights)
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid(..))
#endif
import Distribution.Package
( PackageName(..), PackageIdentifier(..), UnitId(..)
, Package(..), packageName, packageVersion
)
import Distribution.Version
( Version )
import qualified Distribution.Solver.Types.ComponentDeps as CD
import Distribution.Solver.Types.PackageFixedDeps
import Distribution.Solver.Types.Settings
import Distribution.Simple.PackageIndex
( PackageIndex, allPackages, insert, lookupUnitId )
import Distribution.Package
( HasUnitId(..), PackageId )
-- | All packages that have dependencies that are not in the index.
--
-- Returns such packages along with the dependencies that they're missing.
--
brokenPackages :: (PackageFixedDeps pkg)
=> PackageIndex pkg
-> [(pkg, [UnitId])]
brokenPackages index =
[ (pkg, missing)
| pkg <- allPackages index
, let missing =
[ pkg' | pkg' <- CD.flatDeps (depends pkg)
, isNothing (lookupUnitId index pkg') ]
, not (null missing) ]
-- | Compute all roots of the install plan, and verify that the transitive
-- plans from those roots are all consistent.
--
-- NOTE: This does not check for dependency cycles. Moreover, dependency cycles
-- may be absent from the subplans even if the larger plan contains a dependency
-- cycle. Such cycles may or may not be an issue; either way, we don't check
-- for them here.
dependencyInconsistencies :: forall pkg. (PackageFixedDeps pkg, HasUnitId pkg)
=> IndependentGoals
-> PackageIndex pkg
-> [(PackageName, [(PackageIdentifier, Version)])]
dependencyInconsistencies indepGoals index =
concatMap dependencyInconsistencies' subplans
where
subplans :: [PackageIndex pkg]
subplans = rights $
map (dependencyClosure index)
(rootSets indepGoals index)
-- | Compute the root sets of a plan
--
-- A root set is a set of packages whose dependency closure must be consistent.
-- This is the set of all top-level library roots (taken together normally, or
-- as singletons sets if we are considering them as independent goals), along
-- with all setup dependencies of all packages.
rootSets :: (PackageFixedDeps pkg, HasUnitId pkg)
=> IndependentGoals -> PackageIndex pkg -> [[UnitId]]
rootSets (IndependentGoals indepGoals) index =
if indepGoals then map (:[]) libRoots else [libRoots]
++ setupRoots index
where
libRoots = libraryRoots index
-- | Compute the library roots of a plan
--
-- The library roots are the set of packages with no reverse dependencies
-- (no reverse library dependencies but also no reverse setup dependencies).
libraryRoots :: (PackageFixedDeps pkg, HasUnitId pkg)
=> PackageIndex pkg -> [UnitId]
libraryRoots index =
map toPkgId roots
where
(graph, toPkgId, _) = dependencyGraph index
indegree = Graph.indegree graph
roots = filter isRoot (Graph.vertices graph)
isRoot v = indegree ! v == 0
-- | The setup dependencies of each package in the plan
setupRoots :: PackageFixedDeps pkg => PackageIndex pkg -> [[UnitId]]
setupRoots = filter (not . null)
. map (CD.setupDeps . depends)
. allPackages
-- | Given a package index where we assume we want to use all the packages
-- (use 'dependencyClosure' if you need to get such a index subset) find out
-- if the dependencies within it use consistent versions of each package.
-- Return all cases where multiple packages depend on different versions of
-- some other package.
--
-- Each element in the result is a package name along with the packages that
-- depend on it and the versions they require. These are guaranteed to be
-- distinct.
--
dependencyInconsistencies' :: forall pkg.
(PackageFixedDeps pkg, HasUnitId pkg)
=> PackageIndex pkg
-> [(PackageName, [(PackageIdentifier, Version)])]
dependencyInconsistencies' index =
[ (name, [ (pid,packageVersion dep) | (dep,pids) <- uses, pid <- pids])
| (name, ipid_map) <- Map.toList inverseIndex
, let uses = Map.elems ipid_map
, reallyIsInconsistent (map fst uses)
]
where
-- For each package name (of a dependency, somewhere)
-- and each installed ID of that that package
-- the associated package instance
-- and a list of reverse dependencies (as source IDs)
inverseIndex :: Map PackageName (Map UnitId (pkg, [PackageId]))
inverseIndex = Map.fromListWith (Map.unionWith (\(a,b) (_,b') -> (a,b++b')))
[ (packageName dep, Map.fromList [(ipid,(dep,[packageId pkg]))])
| -- For each package @pkg@
pkg <- allPackages index
-- Find out which @ipid@ @pkg@ depends on
, ipid <- CD.nonSetupDeps (depends pkg)
-- And look up those @ipid@ (i.e., @ipid@ is the ID of @dep@)
, Just dep <- [lookupUnitId index ipid]
]
-- If, in a single install plan, we depend on more than one version of a
-- package, then this is ONLY okay in the (rather special) case that we
-- depend on precisely two versions of that package, and one of them
-- depends on the other. This is necessary for example for the base where
-- we have base-3 depending on base-4.
reallyIsInconsistent :: [pkg] -> Bool
reallyIsInconsistent [] = False
reallyIsInconsistent [_p] = False
reallyIsInconsistent [p1, p2] =
let pid1 = installedUnitId p1
pid2 = installedUnitId p2
in pid1 `notElem` CD.nonSetupDeps (depends p2)
&& pid2 `notElem` CD.nonSetupDeps (depends p1)
reallyIsInconsistent _ = True
-- | Find if there are any cycles in the dependency graph. If there are no
-- cycles the result is @[]@.
--
-- This actually computes the strongly connected components. So it gives us a
-- list of groups of packages where within each group they all depend on each
-- other, directly or indirectly.
--
dependencyCycles :: (PackageFixedDeps pkg, HasUnitId pkg)
=> PackageIndex pkg
-> [[pkg]]
dependencyCycles index =
[ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ]
where
adjacencyList = [ (pkg, installedUnitId pkg,
CD.flatDeps (depends pkg))
| pkg <- allPackages index ]
-- | Tries to take the transitive closure of the package dependencies.
--
-- If the transitive closure is complete then it returns that subset of the
-- index. Otherwise it returns the broken packages as in 'brokenPackages'.
--
-- * Note that if the result is @Right []@ it is because at least one of
-- the original given 'PackageIdentifier's do not occur in the index.
dependencyClosure :: (PackageFixedDeps pkg, HasUnitId pkg)
=> PackageIndex pkg
-> [UnitId]
-> Either [(pkg, [UnitId])]
(PackageIndex pkg)
dependencyClosure index pkgids0 = case closure mempty [] pkgids0 of
(completed, []) -> Right completed
(completed, _) -> Left (brokenPackages completed)
where
closure completed failed [] = (completed, failed)
closure completed failed (pkgid:pkgids) =
case lookupUnitId index pkgid of
Nothing -> closure completed (pkgid:failed) pkgids
Just pkg ->
case lookupUnitId completed
(installedUnitId pkg) of
Just _ -> closure completed failed pkgids
Nothing -> closure completed' failed pkgids'
where completed' = insert pkg completed
pkgids' = CD.nonSetupDeps (depends pkg) ++ pkgids
-- | Builds a graph of the package dependencies.
--
-- Dependencies on other packages that are not in the index are discarded.
-- You can check if there are any such dependencies with 'brokenPackages'.
--
dependencyGraph :: (PackageFixedDeps pkg, HasUnitId pkg)
=> PackageIndex pkg
-> (Graph.Graph,
Graph.Vertex -> UnitId,
UnitId -> Maybe Graph.Vertex)
dependencyGraph index = (graph, vertexToPkg, idToVertex)
where
(graph, vertexToPkg', idToVertex) = Graph.graphFromEdges edges
vertexToPkg v = case vertexToPkg' v of
((), pkgid, _targets) -> pkgid
pkgs = allPackages index
edges = map edgesFrom pkgs
resolve pid = pid
edgesFrom pkg = ( ()
, resolve (installedUnitId pkg)
, CD.flatDeps (depends pkg)
)
......@@ -250,6 +250,7 @@ executable cabal
Distribution.Client.SetupWrapper
Distribution.Client.SrcDist
Distribution.Client.SolverInstallPlan
Distribution.Client.SolverPlanIndex
Distribution.Client.Tar
Distribution.Client.Targets
Distribution.Client.Types
......
......@@ -49,6 +49,7 @@ import Distribution.Client.Dependency
import Distribution.Client.Dependency.Types
import Distribution.Client.Types
import qualified Distribution.Client.InstallPlan as CI.InstallPlan
import qualified Distribution.Client.SolverInstallPlan as CI.SolverInstallPlan
import Distribution.Solver.Types.ComponentDeps (ComponentDeps)
import qualified Distribution.Solver.Types.ComponentDeps as CD
......@@ -417,7 +418,7 @@ exResolve :: ExampleDb
-> EnableBackjumping
-> Maybe [ExampleVar]
-> [ExPreference]
-> Progress String String CI.InstallPlan.SolverInstallPlan
-> Progress String String CI.SolverInstallPlan.SolverInstallPlan
exResolve db exts langs pkgConfigDb targets solver mbj indepGoals reorder
enableBj vars prefs
= resolveDependencies C.buildPlatform compiler pkgConfigDb solver params
......@@ -470,11 +471,11 @@ exResolve db exts langs pkgConfigDb targets solver mbj indepGoals reorder
Setup p -> P.PackagePath P.DefaultNamespace (P.Setup (C.PackageName p))
IndepSetup x p -> P.PackagePath (P.Independent x) (P.Setup (C.PackageName p))
extractInstallPlan :: CI.InstallPlan.SolverInstallPlan
extractInstallPlan :: CI.SolverInstallPlan.SolverInstallPlan
-> [(ExamplePkgName, ExamplePkgVersion)]
extractInstallPlan = catMaybes . map confPkg . CI.InstallPlan.toList
where
confPkg :: CI.InstallPlan.SolverPlanPackage -> Maybe (String, Int)
confPkg :: CI.SolverInstallPlan.SolverPlanPackage -> Maybe (String, Int)
confPkg (CI.InstallPlan.Configured pkg) = Just $ srcPkg pkg
confPkg _ = Nothing
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment