Commit 5115bb2b authored by Duncan Coutts's avatar Duncan Coutts
Browse files

Detect broken and inconsistent package deps

We now check for packages that are broken due to their dependencies having
been unregistered. We fail and print a fairly sensible message in this case.
We also check for inconsistent dependencies and give a warning saying which
packages are depending on inconsistent versions of a third. This is a warning
not an error because it does not always lead to failure. Hopefully it'll help
people who are otherwise just running into random compile errors.
This fixes ticket #220.
parent 13756836
......@@ -115,7 +115,7 @@ import Control.Exception as Exception
import Data.Char
( toLower )
import Data.List
( intersperse, nub, partition, isPrefixOf )
( nub, partition, isPrefixOf )
import Data.Maybe
( fromMaybe, isNothing )
import Data.Monoid
......@@ -254,6 +254,30 @@ configure (pkg_descr0, pbi) cfg
JHC -> mapM (configDependency verbosity packageIndex) (buildDepends pkg_descr)
_ -> return $ map setDepByVersion (buildDepends pkg_descr)
packageDependsIndex <-
case InstalledPackageIndex.dependencyClosure packageIndex dep_pkgs of
Left packageDependsIndex -> return packageDependsIndex
Right broken ->
die $ "The following installed packages are broken because other"
++ " packages\nthey depend on are missing. These broken "
++ "packages must be rebuilt\nbefore they can be used.\n"
++ unlines [ "package "
++ showPackageId (InstalledPackageInfo.package pkg)
++ " is broken due to missing package "
++ intercalate ", " (map showPackageId deps)
| (pkg, deps) <- broken ]
case InstalledPackageIndex.dependencyInconsistencies
packageDependsIndex (package pkg_descr) dep_pkgs of
[] -> return ()
inconsistencies ->
warn verbosity $
"This package indirectly depends on multiple versions of the same\n"
++ "package. This is highly likely to cause a compile failure.\n"
++ unlines [ "package " ++ showPackageId pkg ++ " requires "
++ showPackageId (PackageIdentifier name ver)
| (name, uses) <- inconsistencies
, (pkg, ver) <- uses ]
removeInstalledConfig
......@@ -267,7 +291,7 @@ configure (pkg_descr0, pbi) cfg
let exts = unsupportedExtensions comp extlist
unless (null exts) $ warn verbosity $ -- Just warn, FIXME: Should this be an error?
show flavor ++ " does not support the following extensions:\n " ++
concat (intersperse ", " (map show exts))
intercalate ", " (map show exts)
let requiredBuildTools = concatMap buildTools (allBuildInfo pkg_descr)
programsConfig'' <-
......
......@@ -36,6 +36,11 @@ module Distribution.Simple.InstalledPackageIndex (
-- ** Bulk queries
allPackages,
allPackagesByName,
-- ** Special queries
brokenPackages,
dependencyClosure,
dependencyInconsistencies
) where
import Prelude hiding (lookup)
......@@ -44,11 +49,13 @@ import qualified Data.Map as Map
import Data.Map (Map)
import Data.List (nubBy, group, sort, groupBy, sortBy, find)
import Data.Monoid (Monoid(..))
import Data.Maybe (isNothing)
import Distribution.Package (PackageIdentifier(..))
import Distribution.InstalledPackageInfo
(InstalledPackageInfo, InstalledPackageInfo_(..))
import Distribution.Version (Dependency(Dependency), withinRange)
( InstalledPackageInfo, InstalledPackageInfo_(..)
, emptyInstalledPackageInfo )
import Distribution.Version (Version, Dependency(Dependency), withinRange)
import Distribution.Simple.Utils (lowercase, equating, comparing, isInfixOf)
-- | The collection of information about packages from one or more 'PackageDB's.
......@@ -206,3 +213,80 @@ lookupDependency index (Dependency name versionRange) =
[ pkg | pkg@InstalledPackageInfo { package = pkgid } <- lookup index name
, pkgName pkgid == name
, pkgVersion pkgid `withinRange` versionRange ]
-- | All packages that have depends that are not in the index.
--
-- Returns such packages along with the depends that they're missing.
--
brokenPackages :: InstalledPackageIndex
-> [(InstalledPackageInfo, [PackageIdentifier])]
brokenPackages index =
[ (pkg, missing)
| pkg <- allPackages index
, let missing = [ pkg' | pkg' <- depends pkg
, isNothing (lookupPackageId index pkg') ]
, not (null missing) ]
-- | Tries to take the transative closure of the package dependencies.
--
-- If the transative closure is complete then it returns that subset of the
-- index. Otherwise it returns the broken packages as in 'brokenPackages'.
--
-- * Note that if any of the result is @Right []@ it is because at least one of
-- the original given 'PackageIdentifier's do not occur in the index.
--
dependencyClosure :: InstalledPackageIndex
-> [PackageIdentifier]
-> Either InstalledPackageIndex
[(InstalledPackageInfo, [PackageIdentifier])]
dependencyClosure index pkgids0 = case closure [] [] pkgids0 of
(completed, []) -> Left $ fromList completed
(completed, _) -> Right $ brokenPackages (fromList completed)
where
closure :: [InstalledPackageInfo]
-> [PackageIdentifier]
-> [PackageIdentifier]
-> ([InstalledPackageInfo], [PackageIdentifier])
closure completed failed [] = (completed, failed)
closure completed failed (pkgid:pkgids) = case lookupPackageId index pkgid of
Nothing -> closure completed (pkgid:failed) pkgids
-- TODO: use more effecient test here:
Just pkg | package pkg `elem` map package completed
-> closure completed failed pkgids
| otherwise
-> closure (pkg:completed) failed (depends pkg ++ pkgids)
-- | 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 :: InstalledPackageIndex
-> PackageIdentifier -> [PackageIdentifier]
-> [(String, [(PackageIdentifier, Version)])]
dependencyInconsistencies index topPkg topDeps =
[ (name, inconsistencies)
| (name, uses) <- Map.toList inverseIndex
, let inconsistencies = duplicatesBy uses
, not (null inconsistencies) ]
where pseudoTopPackage = emptyInstalledPackageInfo {
package = topPkg,
depends = topDeps
}
inverseIndex = Map.fromListWith (++)
[ (pkgName dep, [(package pkg, pkgVersion dep)])
| pkg <- pseudoTopPackage : allPackages index
, dep <- depends pkg ]
duplicatesBy = (\groups -> if length groups == 1
then []
else concat groups)
. groupBy (equating snd)
. sortBy (comparing snd)
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