module Distribution.Simple.PackageIndex (
PackageIndex,
fromList,
merge,
insert,
deleteInstalledPackageId,
deleteSourcePackageId,
deletePackageName,
lookupInstalledPackageId,
lookupSourcePackageId,
lookupPackageName,
lookupDependency,
searchByName,
SearchResult(..),
searchByNameSubstring,
allPackages,
allPackagesByName,
brokenPackages,
dependencyClosure,
reverseDependencyClosure,
topologicalOrder,
reverseTopologicalOrder,
dependencyInconsistencies,
dependencyCycles,
dependencyGraph,
moduleNameIndex,
) where
import Prelude hiding (lookup)
import Control.Exception (assert)
import qualified Data.Map as Map
import Data.Map (Map)
import qualified Data.Tree as Tree
import qualified Data.Graph as Graph
import qualified Data.Array as Array
import Data.Array ((!))
import Data.List as List
( null, foldl', sort
, groupBy, sortBy, find, isInfixOf, nubBy, deleteBy, deleteFirstsBy )
import Data.Monoid (Monoid(..))
import Data.Maybe (isNothing, fromMaybe)
import Distribution.Package
( PackageName(..), PackageId
, Package(..), packageName, packageVersion
, Dependency(Dependency)
, InstalledPackageId(..) )
import Distribution.ModuleName
( ModuleName )
import Distribution.InstalledPackageInfo
( InstalledPackageInfo, installedPackageId )
import qualified Distribution.InstalledPackageInfo as IPI
import Distribution.Version
( Version, withinRange )
import Distribution.Simple.Utils (lowercase, comparing, equating)
data PackageIndex = PackageIndex
!(Map InstalledPackageId InstalledPackageInfo)
!(Map PackageName (Map Version [InstalledPackageInfo]))
deriving (Show, Read)
instance Monoid PackageIndex where
mempty = PackageIndex Map.empty Map.empty
mappend = merge
mconcat [] = mempty
mconcat xs = foldr1 mappend xs
invariant :: PackageIndex -> Bool
invariant (PackageIndex pids pnames) =
map installedPackageId (Map.elems pids)
== sort
[ assert pinstOk (installedPackageId pinst)
| (pname, pvers) <- Map.toList pnames
, let pversOk = not (Map.null pvers)
, (pver, pinsts) <- assert pversOk $ Map.toList pvers
, let pinsts' = sortBy (comparing installedPackageId) pinsts
pinstsOk = all (\g -> length g == 1)
(groupBy (equating installedPackageId) pinsts')
, pinst <- assert pinstsOk $ pinsts'
, let pinstOk = packageName pinst == pname
&& packageVersion pinst == pver
]
mkPackageIndex :: Map InstalledPackageId InstalledPackageInfo
-> Map PackageName (Map Version [InstalledPackageInfo])
-> PackageIndex
mkPackageIndex pids pnames = assert (invariant index) index
where index = PackageIndex pids pnames
fromList :: [InstalledPackageInfo] -> PackageIndex
fromList pkgs = mkPackageIndex pids pnames
where
pids = Map.fromList [ (installedPackageId pkg, pkg) | pkg <- pkgs ]
pnames =
Map.fromList
[ (packageName (head pkgsN), pvers)
| pkgsN <- groupBy (equating packageName)
. sortBy (comparing packageId)
$ pkgs
, let pvers =
Map.fromList
[ (packageVersion (head pkgsNV),
nubBy (equating installedPackageId) (reverse pkgsNV))
| pkgsNV <- groupBy (equating packageVersion) pkgsN
]
]
merge :: PackageIndex -> PackageIndex -> PackageIndex
merge (PackageIndex pids1 pnames1) (PackageIndex pids2 pnames2) =
mkPackageIndex (Map.union pids1 pids2)
(Map.unionWith (Map.unionWith mergeBuckets) pnames1 pnames2)
where
mergeBuckets xs ys = ys ++ (xs \\ ys)
(\\) = deleteFirstsBy (equating installedPackageId)
insert :: InstalledPackageInfo -> PackageIndex -> PackageIndex
insert pkg (PackageIndex pids pnames) =
mkPackageIndex pids' pnames'
where
pids' = Map.insert (installedPackageId pkg) pkg pids
pnames' = insertPackageName pnames
insertPackageName =
Map.insertWith' (\_ -> insertPackageVersion)
(packageName pkg)
(Map.singleton (packageVersion pkg) [pkg])
insertPackageVersion =
Map.insertWith' (\_ -> insertPackageInstance)
(packageVersion pkg) [pkg]
insertPackageInstance pkgs =
pkg : deleteBy (equating installedPackageId) pkg pkgs
deleteInstalledPackageId :: InstalledPackageId -> PackageIndex -> PackageIndex
deleteInstalledPackageId ipkgid original@(PackageIndex pids pnames) =
case Map.updateLookupWithKey (\_ _ -> Nothing) ipkgid pids of
(Nothing, _) -> original
(Just spkgid, pids') -> mkPackageIndex pids'
(deletePkgName spkgid pnames)
where
deletePkgName spkgid =
Map.update (deletePkgVersion spkgid) (packageName spkgid)
deletePkgVersion spkgid =
(\m -> if Map.null m then Nothing else Just m)
. Map.update deletePkgInstance (packageVersion spkgid)
deletePkgInstance =
(\xs -> if List.null xs then Nothing else Just xs)
. List.deleteBy (\_ pkg -> installedPackageId pkg == ipkgid) undefined
deleteSourcePackageId :: PackageId -> PackageIndex -> PackageIndex
deleteSourcePackageId pkgid original@(PackageIndex pids pnames) =
case Map.lookup (packageName pkgid) pnames of
Nothing -> original
Just pvers -> case Map.lookup (packageVersion pkgid) pvers of
Nothing -> original
Just pkgs -> mkPackageIndex
(foldl' (flip (Map.delete . installedPackageId)) pids pkgs)
(deletePkgName pnames)
where
deletePkgName =
Map.update deletePkgVersion (packageName pkgid)
deletePkgVersion =
(\m -> if Map.null m then Nothing else Just m)
. Map.delete (packageVersion pkgid)
deletePackageName :: PackageName -> PackageIndex -> PackageIndex
deletePackageName name original@(PackageIndex pids pnames) =
case Map.lookup name pnames of
Nothing -> original
Just pvers -> mkPackageIndex
(foldl' (flip (Map.delete . installedPackageId)) pids
(concat (Map.elems pvers)))
(Map.delete name pnames)
allPackages :: PackageIndex -> [InstalledPackageInfo]
allPackages (PackageIndex pids _) = Map.elems pids
allPackagesByName :: PackageIndex -> [[InstalledPackageInfo]]
allPackagesByName (PackageIndex _ pnames) =
concatMap Map.elems (Map.elems pnames)
lookupInstalledPackageId :: PackageIndex -> InstalledPackageId
-> Maybe InstalledPackageInfo
lookupInstalledPackageId (PackageIndex pids _) pid = Map.lookup pid pids
lookupSourcePackageId :: PackageIndex -> PackageId -> [InstalledPackageInfo]
lookupSourcePackageId (PackageIndex _ pnames) pkgid =
case Map.lookup (packageName pkgid) pnames of
Nothing -> []
Just pvers -> case Map.lookup (packageVersion pkgid) pvers of
Nothing -> []
Just pkgs -> pkgs
lookupPackageName :: PackageIndex -> PackageName
-> [(Version, [InstalledPackageInfo])]
lookupPackageName (PackageIndex _ pnames) name =
case Map.lookup name pnames of
Nothing -> []
Just pvers -> Map.toList pvers
lookupDependency :: PackageIndex -> Dependency
-> [(Version, [InstalledPackageInfo])]
lookupDependency (PackageIndex _ pnames) (Dependency name versionRange) =
case Map.lookup name pnames of
Nothing -> []
Just pvers -> [ entry
| entry@(ver, _) <- Map.toList pvers
, ver `withinRange` versionRange ]
searchByName :: PackageIndex -> String -> SearchResult [InstalledPackageInfo]
searchByName (PackageIndex _ pnames) name =
case [ pkgs | pkgs@(PackageName name',_) <- Map.toList pnames
, lowercase name' == lname ] of
[] -> None
[(_,pvers)] -> Unambiguous (concat (Map.elems pvers))
pkgss -> case find ((PackageName name==) . fst) pkgss of
Just (_,pvers) -> Unambiguous (concat (Map.elems pvers))
Nothing -> Ambiguous (map (concat . Map.elems . snd) pkgss)
where lname = lowercase name
data SearchResult a = None | Unambiguous a | Ambiguous [a]
searchByNameSubstring :: PackageIndex -> String -> [InstalledPackageInfo]
searchByNameSubstring (PackageIndex _ pnames) searchterm =
[ pkg
| (PackageName name, pvers) <- Map.toList pnames
, lsearchterm `isInfixOf` lowercase name
, pkgs <- Map.elems pvers
, pkg <- pkgs ]
where lsearchterm = lowercase searchterm
dependencyCycles :: PackageIndex -> [[InstalledPackageInfo]]
dependencyCycles index =
[ vs | Graph.CyclicSCC vs <- Graph.stronglyConnComp adjacencyList ]
where
adjacencyList = [ (pkg, installedPackageId pkg, IPI.depends pkg)
| pkg <- allPackages index ]
brokenPackages :: PackageIndex -> [(InstalledPackageInfo, [InstalledPackageId])]
brokenPackages index =
[ (pkg, missing)
| pkg <- allPackages index
, let missing = [ pkg' | pkg' <- IPI.depends pkg
, isNothing (lookupInstalledPackageId index pkg') ]
, not (null missing) ]
dependencyClosure :: PackageIndex
-> [InstalledPackageId]
-> Either PackageIndex
[(InstalledPackageInfo, [InstalledPackageId])]
dependencyClosure index pkgids0 = case closure mempty [] pkgids0 of
(completed, []) -> Left completed
(completed, _) -> Right (brokenPackages completed)
where
closure completed failed [] = (completed, failed)
closure completed failed (pkgid:pkgids) = case lookupInstalledPackageId index pkgid of
Nothing -> closure completed (pkgid:failed) pkgids
Just pkg -> case lookupInstalledPackageId completed (installedPackageId pkg) of
Just _ -> closure completed failed pkgids
Nothing -> closure completed' failed pkgids'
where completed' = insert pkg completed
pkgids' = IPI.depends pkg ++ pkgids
reverseDependencyClosure :: PackageIndex
-> [InstalledPackageId]
-> [InstalledPackageInfo]
reverseDependencyClosure index =
map vertexToPkg
. concatMap Tree.flatten
. Graph.dfs reverseDepGraph
. map (fromMaybe noSuchPkgId . pkgIdToVertex)
where
(depGraph, vertexToPkg, pkgIdToVertex) = dependencyGraph index
reverseDepGraph = Graph.transposeG depGraph
noSuchPkgId = error "reverseDependencyClosure: package is not in the graph"
topologicalOrder :: PackageIndex -> [InstalledPackageInfo]
topologicalOrder index = map toPkgId
. Graph.topSort
$ graph
where (graph, toPkgId, _) = dependencyGraph index
reverseTopologicalOrder :: PackageIndex -> [InstalledPackageInfo]
reverseTopologicalOrder index = map toPkgId
. Graph.topSort
. Graph.transposeG
$ graph
where (graph, toPkgId, _) = dependencyGraph index
dependencyGraph :: PackageIndex
-> (Graph.Graph,
Graph.Vertex -> InstalledPackageInfo,
InstalledPackageId -> Maybe Graph.Vertex)
dependencyGraph index = (graph, vertex_to_pkg, id_to_vertex)
where
graph = Array.listArray bounds
[ [ v | Just v <- map id_to_vertex (IPI.depends pkg) ]
| pkg <- pkgs ]
pkgs = sortBy (comparing packageId) (allPackages index)
vertices = zip (map installedPackageId pkgs) [0..]
vertex_map = Map.fromList vertices
id_to_vertex pid = Map.lookup pid vertex_map
vertex_to_pkg vertex = pkgTable ! vertex
pkgTable = Array.listArray bounds pkgs
topBound = length pkgs 1
bounds = (0, topBound)
dependencyInconsistencies :: PackageIndex
-> [(PackageName, [(PackageId, 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
inverseIndex :: Map PackageName
(Map InstalledPackageId
(InstalledPackageInfo, [PackageId]))
inverseIndex = Map.fromListWith (Map.unionWith (\(a,b) (_,b') -> (a,b++b')))
[ (packageName dep,
Map.fromList [(ipid,(dep,[packageId pkg]))])
| pkg <- allPackages index
, ipid <- IPI.depends pkg
, Just dep <- [lookupInstalledPackageId index ipid]
]
reallyIsInconsistent :: [InstalledPackageInfo] -> Bool
reallyIsInconsistent [] = False
reallyIsInconsistent [_p] = False
reallyIsInconsistent [p1, p2] =
installedPackageId p1 `notElem` IPI.depends p2
&& installedPackageId p2 `notElem` IPI.depends p1
reallyIsInconsistent _ = True
moduleNameIndex :: PackageIndex -> Map ModuleName [InstalledPackageInfo]
moduleNameIndex index =
Map.fromListWith (++)
[ (moduleName, [pkg])
| pkg <- allPackages index
, moduleName <- IPI.exposedModules pkg ]