All, If you want to play with the new dependency resolver stuff then here is a bunch of patches.
Dependency resolvers are now expected to produce a valid InstallPlan. There is new error checking code to tell you all the ways in which the InstallPlan your dep resolver produced, is invalid. The current dep resolver can sometimes produce valid install plans but can easily be persuaded into making invalid ones, especially if you have many broken packages installed. So there's still some work to be done to make the dep resolvers more pluggable and to try to patch up the current dep resolver a bit. Duncan Sun May 4 20:45:30 BST 2008 Duncan Coutts <[EMAIL PROTECTED]> * Add new utils, duplicates, duplicatesBy and mergeBy Sun May 4 21:17:42 BST 2008 Duncan Coutts <[EMAIL PROTECTED]> * Use the mergeBy from the Utils module Sun May 4 21:18:57 BST 2008 Duncan Coutts <[EMAIL PROTECTED]> * Improve InstallPlan error checking and reporting Instead of just reporting that a plan is invalid, produce a detailed list of reasons why it is invalid. This should be useful for people debugging dependency resolvers. Also rename the complete property to closed, since the property is about the set being closed under the dependency relation. Also re-use the PackageIndex functions for checking the validity conditions rather than re-implementing the checks locally. Mon May 5 10:05:13 BST 2008 Duncan Coutts <[EMAIL PROTECTED]> * Packages are only invalid if they have more than zero problems. Mon May 5 10:17:43 BST 2008 Duncan Coutts <[EMAIL PROTECTED]> * Switch from DepGraph to InstallPlan The dependency resolver has had to be extended in a slightly hacky way to gather the extra information needed by an install plan. In particular it requires the flags to use to configure each package, the actual versions of dependencies to use and all of the installed packages and their closure of dependencies. However the current resolver is fairly naive and so can be easily persuaded into producing an invalid install plan, in which case you'll get a detailed list of reasons as to why it is invalid.
New patches: [Add new utils, duplicates, duplicatesBy and mergeBy Duncan Coutts <[EMAIL PROTECTED]>**20080504194530] { hunk ./Hackage/Utils.hs 9 +import Data.List + ( sortBy, groupBy ) hunk ./Hackage/Utils.hs 28 +-- | Generic merging utility. For sorted input lists this is a full outer join. +-- +-- * The result list never contains @(Nothing, Nothing)@. +-- +mergeBy :: (a -> b -> Ordering) -> [a] -> [b] -> [MergeResult a b] +mergeBy cmp = merge + where + merge [] ys = [ OnlyInRight y | y <- ys] + merge xs [] = [ OnlyInLeft x | x <- xs] + merge (x:xs) (y:ys) = + case x `cmp` y of + GT -> OnlyInRight y : merge (x:xs) ys + EQ -> InBoth x y : merge xs ys + LT -> OnlyInLeft x : merge xs (y:ys) + +data MergeResult a b = OnlyInLeft a | InBoth a b | OnlyInRight b + +duplicates :: Ord a => [a] -> [[a]] +duplicates = duplicatesBy compare + +duplicatesBy :: (a -> a -> Ordering) -> [a] -> [[a]] +duplicatesBy cmp = filter moreThanOne . groupBy eq . sortBy cmp + where + eq a b = case cmp a b of + EQ -> True + _ -> False + moreThanOne (_:_:_) = True + moreThanOne _ = False + } [Use the mergeBy from the Utils module Duncan Coutts <[EMAIL PROTECTED]>**20080504201742] { hunk ./Hackage/List.hs 45 +import Hackage.Utils (mergeBy, MergeResult(..)) + hunk ./Hackage/List.hs 170 - map (\(is, as) -> (maybe [] snd is - ,maybe [] snd as)) + map collect hunk ./Hackage/List.hs 174 + where + collect (OnlyInLeft (_,is) ) = (is, []) + collect ( InBoth (_,is) (_,as)) = (is, as) + collect (OnlyInRight (_,as)) = ([], as) hunk ./Hackage/List.hs 184 --- | Generic merging utility. For sorted input lists this is a full outer join. --- --- * The result list never contains @(Nothing, Nothing)@. --- -mergeBy :: (a -> b -> Ordering) -> [a] -> [b] -> [(Maybe a, Maybe b)] -mergeBy cmp = merge - where - merge [] ys = [ (Nothing, Just y) | y <- ys] - merge xs [] = [ (Just x, Nothing) | x <- xs] - merge (x:xs) (y:ys) = - case x `cmp` y of - GT -> (Nothing, Just y) : merge (x:xs) ys - EQ -> (Just x, Just y) : merge xs ys - LT -> (Just x, Nothing) : merge xs (y:ys) - } [Improve InstallPlan error checking and reporting Duncan Coutts <[EMAIL PROTECTED]>**20080504201857 Instead of just reporting that a plan is invalid, produce a detailed list of reasons why it is invalid. This should be useful for people debugging dependency resolvers. Also rename the complete property to closed, since the property is about the set being closed under the dependency relation. Also re-use the PackageIndex functions for checking the validity conditions rather than re-implementing the checks locally. ] { hunk ./Hackage/InstallPlan.hs 29 - complete, + closed, hunk ./Hackage/InstallPlan.hs 32 - validConfiguredPackage, + configuredPackageValid, + + -- ** Details on invalid plans + PlanProblem(..), + showPlanProblem, + PackageProblem(..), + showPackageProblem, + problems, + configuredPackageProblems hunk ./Hackage/InstallPlan.hs 44 - ( AvailablePackage(packageDescription), UnresolvedDependency ) + ( AvailablePackage(packageDescription) ) hunk ./Hackage/InstallPlan.hs 46 - ( PackageIdentifier(..), Package(..), PackageFixedDeps(..) ) + ( PackageIdentifier(..), Package(..), PackageFixedDeps(..) + , packageName, Dependency(..) ) +import Distribution.Version + ( Version, withinRange ) hunk ./Hackage/InstallPlan.hs 55 - , Flag(MkFlag, flagName), FlagAssignment ) + , Flag(flagName), FlagName(..), FlagAssignment ) hunk ./Hackage/InstallPlan.hs 61 -import Distribution.Simple.Utils - ( comparing, equating ) hunk ./Hackage/InstallPlan.hs 67 +import Hackage.Utils + ( duplicates, duplicatesBy, mergeBy, MergeResult(..) ) +import Distribution.Simple.Utils + ( comparing, intercalate ) hunk ./Hackage/InstallPlan.hs 73 - ( sort, sortBy, groupBy ) -import Data.Maybe - ( isJust ) -import qualified Data.Graph as Graph - ( SCC(..), stronglyConnCompR ) + ( sort, sortBy ) hunk ./Hackage/InstallPlan.hs 103 --- The goal is to calculate an installation plan that is acyclic, consistent --- and complete. +-- The goal is to calculate an installation plan that is closed, acyclic and +-- consistent and where every configured package is valid. hunk ./Hackage/InstallPlan.hs 109 --- is complete if for every package in the set, all of its dependencies are +-- is closed if for every package in the set, all of its dependencies are hunk ./Hackage/InstallPlan.hs 116 --- final configure process will be independent of the environment. +-- final configure process will be independent of the environment. hunk ./Hackage/InstallPlan.hs 157 -toList :: InstallPlan buildResult -> [PlanPackage buildResult] -toList = PackageIndex.allPackages . planIndex - hunk ./Hackage/InstallPlan.hs 161 --- A valid installation plan is a set of packages that is 'acyclic', 'complete' --- and 'consistent'. --- -valid :: OS -> Arch -> CompilerId -> PackageIndex (PlanPackage a) -> Bool -valid os arch comp index = - acyclic index - && complete index - && consistent index - && all (validConfiguredPackage os arch comp) - [ pkg | Configured pkg <- PackageIndex.allPackages index ] - --- | The graph of packages (nodes) and dependencies (edges) must be acyclic. --- -acyclic :: PackageIndex (PlanPackage a) -> Bool -acyclic index = - null [ vs - | Graph.CyclicSCC vs <- Graph.stronglyConnCompR - [ (pkg, packageId pkg, depends pkg) - | pkg <- PackageIndex.allPackages index ] ] - --- | An installation plan is complete if for every package in the set, all of --- its dependencies are also in the set. --- -complete :: PackageIndex (PlanPackage a) -> Bool -complete index = - all (isJust . PackageIndex.lookupPackageId index) - (concatMap depends (PackageIndex.allPackages index)) - --- An installation plan is consistent if for every package in the set, all --- dependencies which target that package have the same version. -consistent :: PackageIndex (PlanPackage a) -> Bool -consistent index = - all same - . map (map snd) - . groupBy (equating fst) - . sortBy (comparing fst) - $ [ (name, [version]) - | pkg <- PackageIndex.allPackages index - , PackageIdentifier name version <- depends pkg ] - where - same :: Eq a => [a] -> Bool - same xs = and (zipWith (==) xs (tail xs)) - -validConfiguredPackage :: OS -> Arch -> CompilerId -> ConfiguredPackage -> Bool -validConfiguredPackage os arch comp (ConfiguredPackage pkginfo flags deps) = - flagsTotal (packageDescription pkginfo) - && depsValid (packageDescription pkginfo) - - where - flagsTotal :: GenericPackageDescription -> Bool - flagsTotal pkg = - sort [ name | (name,_) <- flags ] - == sort [ name | MkFlag { flagName = name } <- genPackageFlags pkg ] - - depsValid :: GenericPackageDescription -> Bool - depsValid pkg = - --TODO: use something lower level than finalizePackageDescription - case finalizePackageDescription flags (Nothing :: Maybe (PackageIndex PackageIdentifier)) os arch comp [] pkg of - Right (pkg', _) -> flip all (buildDepends pkg') $ \dep -> - case PackageIndex.lookupDependency index' dep of - [_] -> True - _ -> False - _ -> False - where index' = PackageIndex.fromList deps - hunk ./Hackage/InstallPlan.hs 163 -new :: OS -> Arch -> CompilerId -> PackageIndex (PlanPackage a) -> InstallPlan a -new os arch compiler pkgs - | not (valid os arch compiler pkgs) = error "InstallPlan.new: invalid plan" - | otherwise = InstallPlan pkgs os arch compiler +new :: OS -> Arch -> CompilerId -> PackageIndex (PlanPackage a) + -> Either (InstallPlan a) [PlanProblem a] +new os arch compiler index = case problems os arch compiler index of + [] -> Left (InstallPlan index os arch compiler) + ps -> Right ps + +toList :: InstallPlan buildResult -> [PlanPackage buildResult] +toList = PackageIndex.allPackages . planIndex hunk ./Hackage/InstallPlan.hs 214 --- packages that depended on it. --- +-- packages that depended on it as having failed. hunk ./Hackage/InstallPlan.hs 216 --- * The package must exist in the graph. +-- * The package must exist in the graph and be in the configured state. hunk ./Hackage/InstallPlan.hs 218 -failed :: PackageIdentifier -> buildResult -> buildResult - -> InstallPlan buildResult -> InstallPlan buildResult -failed pkgid0 buildResult dependentBuildResult plan = - case PackageIndex.lookupPackageId index0 pkgid0 of +failed :: PackageIdentifier -- ^ The id of the package that failed to install + -> buildResult -- ^ The build result to use for the failed package + -> buildResult -- ^ The build result to use for its dependencies + -> InstallPlan buildResult + -> InstallPlan buildResult +failed pkgid buildResult dependentBuildResult + plan@(InstallPlan { planIndex = index }) = + case PackageIndex.lookupPackageId index pkgid of hunk ./Hackage/InstallPlan.hs 227 - let index = PackageIndex.insert index0 (Failed cp buildResult) - in plan { planIndex = markDepsAsFailed pkgid0 index } - _ -> error "" + plan { + planIndex = markDepsAsFailed pkgid + . PackageIndex.insert (Failed cp buildResult) + $ index + } + Just _ -> error $ "InstallPlan.failed: not configured " ++ display pkgid + Nothing -> error $ "InstallPlan.failed: no such package " ++ display pkgid hunk ./Hackage/InstallPlan.hs 235 - index0 = planIndex plan hunk ./Hackage/InstallPlan.hs 236 - markDepsAsFailed pkgid index = - case PackageIndex.lookupPackageId index pkgid of + markDepsAsFailed pkgid' index' = + case PackageIndex.lookupPackageId index' pkgid' of hunk ./Hackage/InstallPlan.hs 239 - let index1 = PackageIndex.insert index (Failed cp dependentBuildResult) - deps = depends cp - in foldr markDepsAsFailed index1 deps - _ -> index + let index'' = PackageIndex.insert (Failed cp dependentBuildResult) index' + deps = depends cp + in foldr markDepsAsFailed index'' deps + _ -> index' + +-- ------------------------------------------------------------ +-- * Checking valididy of plans +-- ------------------------------------------------------------ + +-- | A valid installation plan is a set of packages that is 'acyclic', +-- 'closed' and 'consistent'. Also, every 'ConfiguredPackage' in the +-- plan has to have a valid configuration (see 'configuredPackageValid'). +-- +-- * if the result is @False@ use 'problems' to get a detailed list. +-- +valid :: OS -> Arch -> CompilerId -> PackageIndex (PlanPackage a) -> Bool +valid os arch comp index = null (problems os arch comp index) + +data PlanProblem a = + PackageInvalid ConfiguredPackage [PackageProblem] + | PackageMissingDeps (PlanPackage a) [PackageIdentifier] + | PackageCycle [PlanPackage a] + | PackageInconsistency String [(PackageIdentifier, Version)] + +showPlanProblem :: PlanProblem a -> String +showPlanProblem (PackageInvalid pkg packageProblems) = + "Package " ++ display (packageId pkg) + ++ " has an invalid configuration, in particular:\n" + ++ unlines [ " " ++ showPackageProblem problem + | problem <- packageProblems ] + +showPlanProblem (PackageMissingDeps pkg missingDeps) = + "Package " ++ display (packageId pkg) + ++ " depends on the following packages which are missing from the plan " + ++ intercalate ", " (map display missingDeps) + +showPlanProblem (PackageCycle cycleGroup) = + "The following packages are involved in a dependency cycle " + ++ intercalate ", " (map (display.packageId) cycleGroup) + +showPlanProblem (PackageInconsistency name inconsistencies) = + "Package " ++ name + ++ " is required by several packages," + ++ " but they require inconsistent versions:\n" + ++ unlines [ " package " ++ display pkg ++ " requires " + ++ display (PackageIdentifier name ver) + | (pkg, ver) <- inconsistencies ] + +-- | For an invalid plan, produce a detailed list of problems as human readable +-- error messages. This is mainly intended for debugging purposes. +-- Use 'showPlanProblem' for a human readable explanation. +-- +problems :: OS -> Arch -> CompilerId + -> PackageIndex (PlanPackage a) -> [PlanProblem a] +problems os arch comp index = + [ PackageInvalid pkg (configuredPackageProblems os arch comp pkg) + | Configured pkg <- PackageIndex.allPackages index ] + + ++ [ PackageMissingDeps pkg missingDeps + | (pkg, missingDeps) <- PackageIndex.brokenPackages index ] + + ++ [ PackageCycle cycleGroup + | cycleGroup <- PackageIndex.dependencyCycles index ] + + ++ [ PackageInconsistency name inconsistencies + | (name, inconsistencies) <- PackageIndex.dependencyInconsistencies index ] + +-- | The graph of packages (nodes) and dependencies (edges) must be acyclic. +-- +-- * if the result is @False@ use 'PackageIndex.dependencyCycles' to find out +-- which packages are involved in dependency cycles. +-- +acyclic :: PackageIndex (PlanPackage a) -> Bool +acyclic = null . PackageIndex.dependencyCycles + +-- | An installation plan is closed if for every package in the set, all of +-- its dependencies are also in the set. That is, the set is closed under the +-- dependency relation. +-- +-- * if the result is @False@ use 'PackageIndex.brokenPackages' to find out +-- which packages depend on packages not in the index. +-- +closed :: PackageIndex (PlanPackage a) -> Bool +closed = null . PackageIndex.brokenPackages + +-- | An installation plan is consistent if all dependencies that target a +-- single package name, target the same version. +-- +-- This is slightly subtle. It is not the same as requiring that there be at +-- most one version of any package in the set. It only requires that of +-- packages which have more than one other package depending on them. We could +-- actually make the condition even more precise and say that different +-- versions are ok so long as they are not both in the transative closure of +-- any other package (or equivalently that their inverse closures do not +-- intersect). The point is we do not want to have any packages depending +-- directly or indirectly on two different versions of the same package. The +-- current definition is just a safe aproximation of that. +-- +-- * if the result is @False@ use 'PackageIndex.dependencyInconsistencies' to +-- find out which packages are. +-- +consistent :: PackageIndex (PlanPackage a) -> Bool +consistent = null . PackageIndex.dependencyInconsistencies + +configuredPackageValid :: OS -> Arch -> CompilerId -> ConfiguredPackage -> Bool +configuredPackageValid os arch comp pkg = + null (configuredPackageProblems os arch comp pkg) + +data PackageProblem = DuplicateFlag FlagName + | MissingFlag FlagName + | ExtraFlag FlagName + | DuplicateDeps [PackageIdentifier] + | MissingDep Dependency + | ExtraDep PackageIdentifier + | InvalidDep Dependency PackageIdentifier + +showPackageProblem :: PackageProblem -> String +showPackageProblem (DuplicateFlag (FlagName flag)) = + "duplicate flag in the flag assignment: " ++ flag + +showPackageProblem (MissingFlag (FlagName flag)) = + "missing an assignment for the flag: " ++ flag + +showPackageProblem (ExtraFlag (FlagName flag)) = + "extra flag given that is not used by the package: " ++ flag + +showPackageProblem (DuplicateDeps pkgids) = + "duplicate packages specified as selected dependencies: " + ++ intercalate ", " (map display pkgids) + +showPackageProblem (MissingDep dep) = + "the package has a dependency " ++ display dep + ++ " but no package has been selected to satisfy it." + +showPackageProblem (ExtraDep pkgid) = + "the package configuration specifies " ++ display pkgid + ++ " but (with the given flag assignment) the package does not actually" + ++ " depend on any version of that package." + +showPackageProblem (InvalidDep dep pkgid) = + "the package depends on " ++ display dep + ++ " but the configuration specifies " ++ display pkgid + ++ " which does not satisfy the dependency." + +configuredPackageProblems :: OS -> Arch -> CompilerId + -> ConfiguredPackage -> [PackageProblem] +configuredPackageProblems os arch comp + (ConfiguredPackage pkg specifiedFlags specifiedDeps) = + [ DuplicateFlag flag | ((flag,_):_) <- duplicates specifiedFlags ] + ++ [ MissingFlag flag | OnlyInLeft flag <- mergedFlags ] + ++ [ ExtraFlag flag | OnlyInRight flag <- mergedFlags ] + ++ [ DuplicateDeps pkgs + | pkgs <- duplicatesBy (comparing packageName) specifiedDeps ] + ++ [ MissingDep dep | OnlyInLeft dep <- mergedDeps ] + ++ [ ExtraDep pkgid | OnlyInRight pkgid <- mergedDeps ] + ++ [ InvalidDep dep pkgid | InBoth dep pkgid <- mergedDeps + , not (packageSatisfiesDependency pkgid dep) ] + where + mergedFlags = mergeBy compare + (sort $ map flagName (genPackageFlags (packageDescription pkg))) + (sort $ map fst specifiedFlags) + + mergedDeps = mergeBy + (\dep pkgid -> dependencyName dep `compare` packageName pkgid) + (sortBy (comparing dependencyName) requiredDeps) + (sortBy (comparing packageName) specifiedDeps) + + packageSatisfiesDependency + (PackageIdentifier name version) + (Dependency name' versionRange) = assert (name == name') $ + version `withinRange` versionRange + + dependencyName (Dependency name _) = name + + requiredDeps :: [Dependency] + requiredDeps = + --TODO: use something lower level than finalizePackageDescription + case finalizePackageDescription specifiedFlags + (Nothing :: Maybe (PackageIndex PackageIdentifier)) os arch comp [] + (packageDescription pkg) of + Right (resolvedPkg, _) -> buildDepends resolvedPkg + Left _ -> error "configuredPackageInvalidDeps internal error" } [Packages are only invalid if they have more than zero problems. Duncan Coutts <[EMAIL PROTECTED]>**20080505090513] { hunk ./Hackage/InstallPlan.hs 294 - [ PackageInvalid pkg (configuredPackageProblems os arch comp pkg) - | Configured pkg <- PackageIndex.allPackages index ] + [ PackageInvalid pkg packageProblems + | Configured pkg <- PackageIndex.allPackages index + , let packageProblems = configuredPackageProblems os arch comp pkg + , not (null packageProblems) ] } [Switch from DepGraph to InstallPlan Duncan Coutts <[EMAIL PROTECTED]>**20080505091743 The dependency resolver has had to be extended in a slightly hacky way to gather the extra information needed by an install plan. In particular it requires the flags to use to configure each package, the actual versions of dependencies to use and all of the installed packages and their closure of dependencies. However the current resolver is fairly naive and so can be easily persuaded into producing an invalid install plan, in which case you'll get a detailed list of reasons as to why it is invalid. ] { hunk ./Hackage/DepGraph.hs 1 ------------------------------------------------------------------------------ --- | --- Module : Hackage.DepGraph --- Copyright : (c) Duncan Coutts 2008 --- License : BSD-like --- --- Maintainer : [EMAIL PROTECTED] --- Stability : provisional --- Portability : portable --- --- Package dependency graph --- ------------------------------------------------------------------------------ -module Hackage.DepGraph ( - DepGraph, ResolvedPackage(..), - fromList, toList, - empty, - ready, - removeCompleted, - removeFailed - ) where - -import Hackage.Types -import Distribution.Package - ( PackageIdentifier, Package(..), PackageFixedDeps(..) ) -import Distribution.PackageDescription - ( FlagAssignment ) -import Distribution.Text - ( display ) -import Distribution.Simple.Utils - ( intercalate, equating ) - -import Data.List - ( partition, intersect, nubBy ) -import Control.Exception - ( assert ) - -data ResolvedPackage = ResolvedPackage AvailablePackage FlagAssignment [PackageIdentifier] - deriving Show - -instance Package ResolvedPackage where - packageId (ResolvedPackage p _ _) = packageId p - -instance PackageFixedDeps ResolvedPackage where - depends (ResolvedPackage _ _ d) = d - --- | A package dependency graph --- --- * Invariant: the graph is acyclic --- -newtype DepGraph = DepGraph [ResolvedPackage] - --- | Build a dependency graph from a set of resolved packages. --- --- * The dependencies must not by cyclic. --- -fromList :: [ResolvedPackage] -> DepGraph -fromList = DepGraph . nubBy (equating packageId) - -toList :: DepGraph -> [ResolvedPackage] -toList (DepGraph g) = g - --- | Is the graph empty? -empty :: DepGraph -> Bool -empty (DepGraph g) = null g - - --- | The next package, meaning a package which has no dependencies. --- --- * The graph must be non-empty. --- -ready :: DepGraph -> ResolvedPackage -ready (DepGraph pkgs) = - case [ pkg | pkg@(ResolvedPackage _ _ []) <- pkgs ] of - [] -> error $ "DepGraph.head: internal error: no nodes with 0-outdegree\n" - ++ unlines (map show pkgs) - (pkg:_) -> pkg - - --- | Remove a package from the graph, getting back an updated graph. --- --- * The package must exist in the graph. --- * The package must have had no dependent packages. --- -removeCompleted :: PackageIdentifier -> DepGraph -> DepGraph -removeCompleted pkgid (DepGraph pkgs) = - case partition isCompleted pkgs of - ([_pkg], pkgs') -> DepGraph [ ResolvedPackage pkg fs (filter (/=pkgid) deps) - | ResolvedPackage pkg fs deps <- pkgs' ] - _ -> error $ "DepGraph.removeCompleted: no such package " - ++ display pkgid - ++ "\nin DepGraph: " - ++ intercalate ", " - (map (display . packageId) pkgs) - - where isCompleted = (==pkgid) . packageId - --- | Remove a package and all the packages that depend on it from the graph. --- --- You get back an updated graph and a list of packages that were removed --- (the given package will be first in that list). --- --- * The package must exist in the graph. --- -removeFailed :: PackageIdentifier -> DepGraph -> (DepGraph, [ResolvedPackage]) -removeFailed pkgid (DepGraph pkgs0) = - case partition ((==pkgid) . packageId) pkgs0 of - ([pkg], pkgs') -> case remove [pkg] [pkgid] pkgs' of - result -> assert (packageId p == pkgid) result - where (_,p:_) = result - ((_:_),_) -> error $ "DepGraph.removeFailed: internal error multiple instances of " - ++ display pkgid - _ -> error $ "DepGraph.removeFailed: no such package " - ++ display pkgid - - where - remove rmpkgs pkgids pkgs = - case partition (not . null . intersect pkgids . depends) pkgs of - ([], _) -> (DepGraph pkgs, rmpkgs) - (rmpkgs', pkgs') -> remove (rmpkgs ++ rmpkgs') pkgids' pkgs' - where pkgids' = map packageId rmpkgs' rmfile ./Hackage/DepGraph.hs hunk ./Hackage/Dependency.hs 23 -import qualified Hackage.DepGraph as DepGraph +import qualified Hackage.InstallPlan as InstallPlan +import Hackage.InstallPlan (InstallPlan) hunk ./Hackage/Dependency.hs 37 -import Distribution.Simple.Utils (comparing) +import Distribution.Simple.Utils (comparing, intercalate) +import Distribution.Text + ( display ) hunk ./Hackage/Dependency.hs 52 - -> Either [Dependency] DepGraph.DepGraph + -> Either [Dependency] (InstallPlan a) hunk ./Hackage/Dependency.hs 54 - packagesToInstall + packagesToInstall os arch comp installed hunk ./Hackage/Dependency.hs 57 -resolveDependencies _ _ _ Nothing available deps = - packagesToInstall (resolveDependenciesBogusly available deps) +resolveDependencies os arch comp Nothing available deps = + packagesToInstall os arch comp undefined + (resolveDependenciesBogusly available deps) hunk ./Hackage/Dependency.hs 72 +{- +type DependencyResolver a = OS + -> Arch + -> CompilerId + -> PackageIndex InstalledPackageInfo + -> PackageIndex AvailablePackage + -> [UnresolvedDependency] + -> InstallPlan.InstallPlan a + +-- | This is an example resolver that produces valid plans but plans where we +-- say that every package failed. +-- +failingResolver :: DependencyResolver a +failingResolver os arch compiler _ _ deps = + InstallPlan.new os arch compiler $ + PackageIndex.fromList (map InstallPlan.Unresolved deps) +-} + hunk ./Hackage/Dependency.hs 104 - let deps = getDependencies os arch comp installed available (packageDescription pkg) flags - resolved = map (\d -> resolveDependency os arch comp installed available d []) deps - return $ AvailableDependency dep pkg flags resolved + (deps, flags') <- getDependencies os arch comp installed available + (packageDescription pkg) flags + return $ AvailableDependency dep pkg flags' + [ resolveDependency os arch comp installed available dep' [] + | dep' <- deps ] hunk ./Hackage/Dependency.hs 134 - -> [Dependency] + -> Maybe ([Dependency], FlagAssignment) hunk ./Hackage/Dependency.hs 140 - Left missing -> missing - Right (desc,_) -> buildDepends desc + Left _missing -> Nothing + Right (desc,flags') -> Just (buildDepends desc, flags') hunk ./Hackage/Dependency.hs 153 -packagesToInstall :: [ResolvedDependency] - -> Either [Dependency] DepGraph.DepGraph +packagesToInstall :: OS -> Arch -> CompilerId + -> PackageIndex InstalledPackageInfo + -> [ResolvedDependency] + -> Either [Dependency] (InstallPlan a) hunk ./Hackage/Dependency.hs 159 -packagesToInstall deps0 = case unzipEithers (map getDeps deps0) of - ([], ok) -> Right (DepGraph.fromList (concatMap snd ok)) - (missing, _) -> Left (concat missing) +packagesToInstall os arch comp allInstalled deps0 = + case unzipEithers (map getAvailable deps0) of + ([], ok) -> + let selectedAvailable :: [InstallPlan.ConfiguredPackage] + selectedAvailable = concatMap snd ok + + selectedInstalled :: [InstalledPackageInfo] + selectedInstalled = either PackageIndex.allPackages + (\borked -> error $ unlines + [ "Package " ++ display (packageId pkg) + ++ " depends on the following packages which are missing from the plan " + ++ intercalate ", " (map display missingDeps) + | (pkg, missingDeps) <- borked ]) + $ PackageIndex.dependencyClosure + allInstalled + (getInstalled deps0) + index = PackageIndex.fromList + $ map InstallPlan.Configured selectedAvailable + ++ map InstallPlan.PreExisting selectedInstalled + in case InstallPlan.new os arch comp index of + Left plan -> Right plan + Right problems -> error $ unlines $ + "internal error: could not construct a valid install plan." + : "The proposed (invalid) plan contained the following problems:" + : map InstallPlan.showPlanProblem problems + (missing, _) -> Left $ concat missing hunk ./Hackage/Dependency.hs 187 - getDeps :: ResolvedDependency - -> Either [Dependency] - (Maybe PackageIdentifier, [DepGraph.ResolvedPackage]) - getDeps (InstalledDependency _ _ ) = Right (Nothing, []) - getDeps (AvailableDependency _ pkg flags deps) = - case unzipEithers (map getDeps deps) of - ([], ok) -> let resolved :: [DepGraph.ResolvedPackage] - resolved = DepGraph.ResolvedPackage pkg flags - [ pkgid | (Just pkgid, _) <- ok ] + getAvailable :: ResolvedDependency + -> Either [Dependency] + (PackageIdentifier, [InstallPlan.ConfiguredPackage]) + getAvailable (InstalledDependency _ pkgid ) + = Right (pkgid, []) + getAvailable (AvailableDependency _ pkg flags deps) = + case unzipEithers (map getAvailable deps) of + ([], ok) -> let resolved = InstallPlan.ConfiguredPackage pkg flags + [ pkgid | (pkgid, _) <- ok ] hunk ./Hackage/Dependency.hs 197 - in Right (Just $ packageId pkg, resolved) + in Right (packageId pkg, resolved) hunk ./Hackage/Dependency.hs 199 - getDeps (UnavailableDependency dep) = Left [dep] + getAvailable (UnavailableDependency dep) = Left [dep] + + getInstalled :: [ResolvedDependency] -> [PackageIdentifier] + getInstalled [] = [] + getInstalled (dep:deps) = case dep of + InstalledDependency _ pkgid -> pkgid : getInstalled deps + AvailableDependency _ _ _ deps' -> getInstalled (deps'++deps) + UnavailableDependency _ -> getInstalled deps hunk ./Hackage/Fetch.hs 31 -import qualified Hackage.DepGraph as DepGraph +import qualified Hackage.InstallPlan as InstallPlan hunk ./Hackage/Fetch.hs 139 - Right pkgs -> do ps <- filterM (fmap not . isFetched) - [ pkg | (DepGraph.ResolvedPackage pkg _ _) <- DepGraph.toList pkgs ] - mapM_ (fetchPackage verbosity) ps + Right pkgs -> do + ps <- filterM (fmap not . isFetched) + [ pkg | (InstallPlan.Configured + (InstallPlan.ConfiguredPackage pkg _ _)) + <- InstallPlan.toList pkgs ] + mapM_ (fetchPackage verbosity) ps hunk ./Hackage/Install.hs 29 -import qualified Hackage.DepGraph as DepGraph +import qualified Hackage.InstallPlan as InstallPlan +import Hackage.InstallPlan (InstallPlan) hunk ./Hackage/Install.hs 161 - Right pkgs -> do + Right installPlan -> do hunk ./Hackage/Install.hs 163 - then printDryRun verbosity pkgs >> return [] - else installPackages verbosity scriptOptions miscOptions configFlags pkgs + then printDryRun verbosity installPlan >> return [] + else executeInstallPlan verbosity scriptOptions miscOptions + configFlags installPlan >> return [] hunk ./Hackage/Install.hs 200 - Right pkgs - | DepGraph.empty pkgs -> notice verbosity + Right installPlan + | InstallPlan.done installPlan -> notice verbosity hunk ./Hackage/Install.hs 204 - | dryRun miscOptions -> do - printDryRun verbosity pkgs - return [] - | otherwise -> installPackages verbosity scriptOptions miscOptions configFlags pkgs + | dryRun miscOptions -> printDryRun verbosity installPlan >> return [] + | otherwise -> executeInstallPlan verbosity scriptOptions miscOptions + configFlags installPlan >> return [] hunk ./Hackage/Install.hs 208 -printDryRun :: Verbosity -> DepGraph.DepGraph -> IO () +printDryRun :: Verbosity -> InstallPlan BuildResult -> IO () hunk ./Hackage/Install.hs 210 - | DepGraph.empty pkgs = notice verbosity "No packages to be installed." + | InstallPlan.done pkgs = notice verbosity "No packages to be installed." hunk ./Hackage/Install.hs 216 - | DepGraph.empty ps = [] + | InstallPlan.done ps = [] hunk ./Hackage/Install.hs 218 - let (DepGraph.ResolvedPackage pkgInfo _ _) = DepGraph.ready ps + let (InstallPlan.ConfiguredPackage pkgInfo _ _) = InstallPlan.next ps hunk ./Hackage/Install.hs 220 - in (pkgId : order (DepGraph.removeCompleted pkgId ps)) + in (pkgId : order (InstallPlan.completed pkgId ps)) hunk ./Hackage/Install.hs 222 -installPackages :: Verbosity - -> SetupScriptOptions - -> InstallMisc - -> Cabal.ConfigFlags -- ^Options which will be passed to every package. - -> DepGraph.DepGraph - -> IO [(PackageIdentifier, BuildResult)] -installPackages verbosity scriptOptions miscOptions configFlags = installPackagesErrs [] +executeInstallPlan :: Verbosity + -> SetupScriptOptions + -> InstallMisc + -> Cabal.ConfigFlags -- ^Options which will be passed to every package. + -> InstallPlan BuildResult + -> IO (InstallPlan BuildResult) +executeInstallPlan verbosity scriptOptions miscOptions configFlags = execute hunk ./Hackage/Install.hs 230 - installPackagesErrs :: [(PackageIdentifier, BuildResult)] - -> DepGraph.DepGraph - -> IO [(PackageIdentifier, BuildResult)] - installPackagesErrs done remaining - | DepGraph.empty remaining = return (reverse done) - | otherwise = case DepGraph.ready remaining of - DepGraph.ResolvedPackage pkg flags _depids -> do--TODO build against exactly these deps + execute :: InstallPlan BuildResult -> IO (InstallPlan BuildResult) + execute plan + | InstallPlan.done plan = return plan + | otherwise = case InstallPlan.next plan of + InstallPlan.ConfiguredPackage pkg flags _depids -> do--TODO build against exactly these deps hunk ./Hackage/Install.hs 238 - BuildOk -> - let remaining' = DepGraph.removeCompleted pkgid remaining - in installPackagesErrs ((pkgid, buildResult):done) remaining' - _ -> - let (remaining', _:failed) = DepGraph.removeFailed pkgid remaining - -- So this first pkgid failed for whatever reason (buildResult) - -- all the other packages that depended on this pkgid which we - -- now cannot build (failed :: [ResolvedPackage]) we mark as - -- failing due to DependentFailed which kind of means it was - -- not their fault. - done' = (pkgid, buildResult) - : [ (packageId pkg', DependentFailed pkgid) - | pkg' <- failed ] - in installPackagesErrs (done'++done) remaining' + BuildOk -> execute $ InstallPlan.completed pkgid plan + _ -> execute $ InstallPlan.failed pkgid buildResult depResult plan + where depResult = DependentFailed pkgid + -- So this first pkgid failed for whatever reason (buildResult) all + -- the other packages that depended on this pkgid which we now + -- cannot build we mark as failing due to DependentFailed which + -- kind of means it was not their fault. hunk ./cabal-install.cabal 42 - Hackage.DepGraph } Context: [Update for flipped order of args for PackageIndex.insert Duncan Coutts <[EMAIL PROTECTED]>**20080504193722] [Don't echo when prompting for the hackage upload password. Duncan Coutts <[EMAIL PROTECTED]>**20080501214456 Fixes ticket #268. And use newtypes for the username and password, just to be more sure we're not mixing them up with other strings. ] [Remove the resolveDependenciesLocal, implement it via resolveDependencies Duncan Coutts <[EMAIL PROTECTED]>**20080430193351 The local variant was for the case that we were starting from a package description rather than a dependency to a named package. In the local case we not only need to resolve the dependencies of the package but also to find a flag assignment for the local package. This case crops up in the resolver normally when we try to satisfy a dependency, we have to pick a flag assignment for the dependency and resolve its dependencies. It is annoying to have both entry points, especially as we want the resolver to be plugable. So instead we define the local package as an available package, then by resolving a single dependency on exactly the name and version of the local package then we can get an install plan for the local package. It also requires generalising installPkg to deal with the local case. ] [Generalise and rename PkgInfo to include local packages Duncan Coutts <[EMAIL PROTECTED]>**20080430180922 Renamed to AvailablePackage since that what it is really. Now instead of just representing packages from a remote hackage repo it includes an alternative for a local unpacked package. In future we should add more alternatives, eg for other local packages (ie not just the one that's unpacked in the current dir) and for remote packages in source control like darcs, git etc. ] [Convert the Install module to use the new SetupWrapper Duncan Coutts <[EMAIL PROTECTED]>**20080430115021 And refactor slightly to batch some of the misc parameters together in a record rather than passing them all separately. ] [Add a new --cabal-lib-version flag to the install command Duncan Coutts <[EMAIL PROTECTED]>**20080430114709 It's used to select which version of the Cabal lib to use when configuring, building and installing packages. It's mainly so that we can use cabal-install to help us test that packages build ok with both old and new versions of the Cabal library. In particular we'd like to check every package on hackage to make sure that new Cabal versions are not breaking packages that worked with older versions. ] [Convert Main to use the new SetupWrapper module Duncan Coutts <[EMAIL PROTECTED]>**20080430114604] [Fix setupWrapper bug, when no compiler was configured Duncan Coutts <[EMAIL PROTECTED]>**20080430114448] [Add replacement SetupWrapper module Duncan Coutts <[EMAIL PROTECTED]>**20080430103412 Clearer code and a wider interface to give more control. ] [Simplify the way we derive configure, install and upgrade commands Duncan Coutts <[EMAIL PROTECTED]>**20080430085201 so we do not accidentally inherit the default flags for the underlying configure command when we in fact want the empty flags. I am beginning to suspect that having any defaults at all was a mistake. ] [Make the default SavedConfig contain empty rather than default ConfigFlags Duncan Coutts <[EMAIL PROTECTED]>**20080430084938 We do not want to pass a load of default command line flags. We only want to pass flags where we're overriding the defaults. Also it's because old Cabal versions do not understand new flags. ] [Rearange install plan checking code Duncan Coutts <[EMAIL PROTECTED]>**20080424224159 Make it usable for callers that want to check properties before constructing an InstallPlan. ] [Update for checkPackage change Duncan Coutts <[EMAIL PROTECTED]>**20080423153817] [Update for FlagAssignment changes Duncan Coutts <[EMAIL PROTECTED]>**20080420191543] [Add new InstallPlan type Duncan Coutts <[EMAIL PROTECTED]>**20080414171914 This is joint work with Kolmodin. An InstallPlan records what we want to install and records the outcome of installing packages. It has a fairly strict internal invariant which should guarantee that each package can be configured sucessfully. ] [Simplify install --only implementation Duncan Coutts <[EMAIL PROTECTED]>**20080414143457 The install --only doesn't do any planning or build reporting so make it completely separate so we do not have to make up fake build plans or results. ] [Change resolveDependencies to take OS and Arch as parameters Duncan Coutts <[EMAIL PROTECTED]>**20080414000419 Rather than using global 'constants' for the current os and arch. ] [Make ResolvedDependency an internal detail of dependency calculation Duncan Coutts <[EMAIL PROTECTED]>**20080413234119 Externally we currently use a DepGraph, though we'll move to a InstallPlan. This is a step towards making it easier to swap in different package dependency resolution algorithms. Because we're hiding ResolvedDependency we have to disable the extended Hackage.Info stuff for the moment. ] [Relax version constraint on HTTP, seems to work with 3001.1 Duncan Coutts <[EMAIL PROTECTED]>**20080413223940] [Update for change to finalizePackageDescription Duncan Coutts <[EMAIL PROTECTED]>**20080413223910] [Half-done build reporting stuff **20080412234057] [Add missing modules in other-modules, fixes sdist Spencer Janssen <[EMAIL PROTECTED]>**20080330193710] [TAG 0.4.6 Duncan Coutts <[EMAIL PROTECTED]>**20080329195601] Patch bundle hash: a8279940ebb1e7255551943edc04a57a907d1719
_______________________________________________ cabal-devel mailing list cabal-devel@haskell.org http://www.haskell.org/mailman/listinfo/cabal-devel