Haskell may be difficult to start out with, but once things start rolling, they roll fast. Yesterday (real world time, these blog entries are staggered) I had started the first lines of HackPorts, but now things are getting close to done for the first version. It’s not that I’ve written much code, but that it was simple to integrate with other people’s code.
Borrowing all I can
The first thing I wanted to do was avoid dealing with any of Hackage’s data
formats, so I cribbed everything I could from the cabal-install package. I
actually imported the full source into HackPorts, ripped out its List.hs file,
renamed it to my Main.hs file, and then began changing it from a function that
prints out a list of available packages, to one that writes the data into
properly formatted Portfile entries.
The code does the following bits of work:
Talks to
cabal-installand Cabal to get a list of all known packages on Hackage.For every package, creates a directory named
haskell/$package, and then writes information about that package intohaskell/$package/Portfile.As it does this, it fetches the current version’s tarball over HTTP, and uses OpenSSL (directly, through FFI) to generate MD5, SHA1 and RIPEMD160 checksums of the tarball image.
And voilá, a directory populated with 1136 Portfile entries. What’s missing
now is the external dependency mapping. As a stub, I have them all depending
on port:ghc, but I think there’s sufficient information in the Cabal package
info to figure out what the right dependencies should be, both among the
Hackage packages themselves and against any external libraries (like OpenSSL).
What I learned
As for my Haskell education, I learned about using Haskell’s very nice FFI mechanism, and had a lot more experience using the IO Monad. An example of using FFI to call out to OpenSSL:
{-# OPTIONS -#include "openssl/md5.h" #-}
foreign import ccall "openssl/md5.h MD5" c_md5
:: Ptr CChar -> CULong -> Ptr CChar -> IO (Ptr Word8)I now have access to a c_md5 function, which go directly over to the C library
to do its work. Not too shabby!
As for the IO Monad, here is the main function for Hackports:
main :: IO ()
main = do
createDirectoryIfMissing True "haskell"
pkgs ,- allPackages verbose
mapM writePortfile pkgs
putStrLn "Hackage has been exported to MacPorts format in haskell/"The trickiest part for me was understanding how mapM differs from map. Whereas
map takes a list of values and returns a list of values, mapM takes a list of
values and returns a list of actions that get invoked in sequence in the
current Monad (in this case, IO).