It wasn’t why I started playing with Cabal, but after extracting dependencies from a single package I thought struck me that I could extract dependencies from many packages, e.g. hackage, and draw a dependency graph of the result.
The basic idea is to use the code from my earlier post, accumulate dependency information by mapping it over several cabal files. Then convert that information into nodes and edges suitable for building a graph (
Data.Graph). That graph is then “graphviz’ed” using
Data.Graph.Inductive.Graphviz. Not that since this is performed on Debian Sid I’m using some rather old versions of packagesi.
First off a shortcut for reading cabal files:
readCabalFile = readPackageDescription silent
Once I have a
GenericPackageDescription I want to collapse it into a regular
PackageDescription (see the comments in my previous post for some details regarding this). Then I extract the package name and its dependencies and package them into a tuple. by mapping this function over a list of
GenericPackageDescription I end up with an association list where the key is the package and the value is a list of all its dependencies.
processFile gpd = let finPkgDesc = finalizePackageDescription  Nothing "Linux" "X86_64" ("GHC", Version [6, 8, 2] ) (Right (pd, _)) = finPkgDesc gpd getPackageName (Dependency name _) = name nameNDeps = (pkgName . package) &&& (nub . map getPackageName . buildDepends) in nameNDeps pd
In order to create the graph later on I need a complete list of all nodes. To do this I take all the keys and all the values in the association list, collapse them into a single list, and remove duplicates. To turn this resulting list of packages into a list of
LNode I then
zip it with a list of integers.
getNodes = let assocKeys = map fst assocVals = concat . map snd in zip [1..] . nub . uncurry (++) . (assocKeys &&& assocVals)
Building the edges is straight forward, but a bit more involved. An edge is a tuple of two integers and something else, I don’t need to label the edges so in my case it is
(Int, Int, ()). The list of nodes is basically an association list (an integer for key and a string for value), but I need to flip keys and values since I know the package name and need its node number.
getEdges deps = let nodes = getNodes deps nodesAssoc = map (\ (a, b) -> (b, a)) nodes buildEdges (name, dep) = let getNode n = fromJust $ lookup n nodesAssoc fromNode = getNode name in map (\ t -> (fromNode, getNode t, ())) dep in concat $ map buildEdges deps
Now that that’s done I can put it all together in a
main function. The trickiest bit of that was to find the size of A4 in incehs
main = do files <- getArgs gpds <- mapM readCabalFile files let deps = map processFile gpds let (nodes, edges) = (getNodes &&& getEdges) deps let depGraph = mkGraph nodes edges :: Gr String () putStrLn $ graphviz depGraph "Dependency_Graph" (11.7, 16.5) (1,1) Landscape
I downloaded all cabal files from Hackage and ran this code over them. I bumped into a few that use Cabal features not supported in the ancient version I’m using. I was a bit disappointed that Cabal wouldn’t let me handle that kind of errors myself (as I already expressed in an earlier post) so I was forced to delete them manually.
Here’s the output, completely unreadable, I know, but still sort of cool.
- Yes, I’d be really happy if the transition to GHC 6.10 would finish soon.[back]