avsm pointed me to ubigraph. So after a bit of XML-RPC hacking (haxrreally rocks, it is just so amazingly easy to use) I now have code that sends the graph data over to a ubigraph server.
Of course I had to create a video of it. The video shows the dependencies of just two packages (dataenc and datetime). I tried creating a second video of the dependencies of more packages, but xvidcap isn’t very reliable it seems. One irritating thing is that I run out of filehandles fairly soon, so I couldn’t create a 3d graph of more than about 75 packages (all packages starting with ‘a’ and ‘b’ on Hackage).
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.
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
inmap(\ t ->(fromNode, getNode t,())) dep
inconcat$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.
In my opinion the final decision of what to do in case of an error belongs in the appliation, not in libraries.
I’m sure there are exceptions to this, but I believe it’s good as a guiding principle when defining APIs.
Here’s the code in Cabal that breaks this rule. Basically, if you write an application and call readPackageDescription on a cabal file with errors then your application is terminated (the call to dieWithLocation ends up calling exitWith). I would much rather it returned an error (e.g. based on Either) so I can decide what to do with bad cabal files myself.
This is going to be a silly post, simply because it is so amazingly simple to do some basic parsing of Cabal files. It turns out to be true even for old versions of Cabal, the code below is for 1.2.3.0 since that’s the version still found in Debian Unstablei
That’ll change when GHC 6.10 makes it onto my system. It’s already in Debian, just not on my system since some Xmonad-related packages haven’t been rebuilt yet.[back]
So, after receiving several pointers to seereason’s cabal-debian tool I thought I’d take it for a spin.i
After about 30 minutes of browsing through HackageDB and seereason’s source repos, building and installing, I had finally satisfied all dependencies and the build of cabal-debian succeeded. (Oh, BTW, seereason people, it’s really confusing that you have a package called debian among your source repos, when there already is a different debian package on HackageDB. Please consider renaming it!) I decided to take the tool for a spin on my own package, dataenc.
The result was fairly good. It seems the generated files depend on some packages that either aren’t in Debian or that the people at seereason have modified. With the following changes to the generated files I was happy with the contents of ./debian and I successfully built an (almost) autogenerated Debian package:
A few days ago I thougth I’d take a look at calling C functions from haskell. I wrote up the following set of files:
foo.h:
int foo(int i);
foo.c:
int
foo(int i)
{
return i * i;
}
Foo.hs:
module Main where
import Foreign.C.Types
main = do
r <- foo 2
putStrLn $ show r
foreign import ccall safe "foo.h foo" foo :: CInt -> IO CInt
Compiling the C file was of course no problem:
% gcc -c foo.c
The haskell file offered some resistance:
% ghc -c Foo.hs
Foo.hs:9:8: parse error on input `import'
It took me a round on haskell-cafe before I found out that ghc needs to be told to use the foreign function interface, -ffi or -fffi:
% ghc -c -fffi Foo.hs
Linking is a snap after that:
% ghc -o foo foo.o Foo.o
% ./foo
4
It’s also possible to build and link it all in one go:
% ghc --make -fffi -o foo foo.c Foo.hs
Now, that’s pretty nice, however it’d be even nicer to use cabal to do the building. At the same time I decided to put c2hs to use. It seemed to be a lot easier than having to create the import statements manually. I ended up with the following:
csrc/foo.h:
#ifndef _FOO_H_
int foo(int);
#endif
csrc/foo.c:
#include "foo.h"
int
foo(int i)
{
return i * i;
}
I couldn’t get cabal to accept Foo.chs as the file containing the Main module in my project. So I ended up putting all the relevant code in Foo and then have a dummy Main.
src/Foo.chs:
module Foo where
#include "foo.h"
import Foreign.C.Types
main = do
r <- {# call foo #} 2
putStrLn $ show r
Here’s the dummy Main.
src/Main.hs:
module Main where
import qualified Foo
main = Foo.main
The cabal file is rather straight forward. It took me a round on haskell-cafe to find out how to let the compiler know that I need the foreign function interface without putting compiler directives in the source file.