diff --git a/cabal2nix.cabal b/cabal2nix.cabal
--- a/cabal2nix.cabal
+++ b/cabal2nix.cabal
@@ -1,5 +1,5 @@
 Name:                   cabal2nix
-Version:                1.52
+Version:                1.53
 Copyright:              Peter Simons, Andres Loeh
 License:                BSD3
 License-File:           LICENSE
@@ -34,8 +34,8 @@
   .
   The only required argument is the path to the cabal file. For example:
   .
-  > cabal2nix http://hackage.haskell.org/packages/archive/cabal2nix/1.52/cabal2nix.cabal
-  > cabal2nix cabal://cabal2nix-1.52
+  > cabal2nix http://hackage.haskell.org/packages/archive/cabal2nix/1.53/cabal2nix.cabal
+  > cabal2nix cabal://cabal2nix-1.53
   .
   If the @--sha256@ option has not been specified, cabal2nix calls
   @nix-prefetch-url@ to determine the hash automatically. This causes
@@ -46,7 +46,7 @@
   Location:             git://github.com/NixOS/cabal2nix.git
 
 Executable cabal2nix
-  main-is:              Cabal2Nix.hs
+  main-is:              cabal2nix.hs
   hs-source-dirs:       src
   Build-Depends:        base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.10,
                         filepath, directory, process, HTTP, hackage-db
@@ -66,7 +66,7 @@
                         Distribution.NixOS.PrettyPrinting
 
 Executable hackage4nix
-  main-is:              Hackage4Nix.hs
+  main-is:              hackage4nix.hs
   hs-source-dirs:       src
   Build-Depends:        base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.10,
                         mtl, containers, directory, filepath, hackage-db
diff --git a/src/Cabal2Nix.hs b/src/Cabal2Nix.hs
deleted file mode 100644
--- a/src/Cabal2Nix.hs
+++ /dev/null
@@ -1,97 +0,0 @@
-module Main ( main ) where
-
-import Cabal2Nix.Hackage ( hashPackage, readCabalFile )
-import Cabal2Nix.Generate ( cabal2nix )
-import Cabal2Nix.Normalize ( normalize )
-import Distribution.NixOS.Derivation.Cabal
-
-import Control.Exception ( bracket )
-import Control.Monad ( when )
-import Distribution.PackageDescription ( package, packageDescription )
-import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
-import Distribution.Text ( disp )
-import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
-import System.Environment ( getArgs )
-import System.Exit ( exitFailure, exitSuccess )
-import System.IO ( hPutStrLn, hFlush, stdout, stderr )
-
-data Configuration = Configuration
-  { optPrintHelp :: Bool
-  , optPrintVersion :: Bool
-  , optSha256 :: String
-  , optMaintainer :: [String]
-  , optPlatform :: [String]
-  , optHaddock :: Bool
-  , optDoCheck :: Bool
-  , optJailbreak :: Bool
-  }
-  deriving (Show)
-
-defaultConfiguration :: Configuration
-defaultConfiguration = Configuration
-  { optPrintHelp = False
-  , optPrintVersion = False
-  , optSha256 = ""
-  , optMaintainer = []
-  , optPlatform = []
-  , optHaddock = True
-  , optDoCheck = True
-  , optJailbreak = False
-  }
-
-options :: [OptDescr (Configuration -> Configuration)]
-options =
-  [ Option "h" ["help"]       (NoArg (\o -> o { optPrintHelp = True }))                                  "show this help text"
-  , Option ""  ["sha256"]     (ReqArg (\x o -> o { optSha256 = x }) "HASH")                              "sha256 hash of source tarball"
-  , Option "m" ["maintainer"] (ReqArg (\x o -> o { optMaintainer = x : optMaintainer o }) "MAINTAINER")  "maintainer of this package (may be specified multiple times)"
-  , Option "p" ["platform"]   (ReqArg (\x o -> o { optPlatform = x : optPlatform o }) "PLATFORM")        "supported build platforms (may be specified multiple times)"
-  , Option ""  ["no-haddock"] (NoArg (\o -> o { optHaddock = False }))                                   "don't run Haddock when building this package"
-  , Option ""  ["no-check"]   (NoArg (\o -> o { optDoCheck = False }))                                   "don't run regression test suites of this package"
-  , Option ""  ["jailbreak"]  (NoArg (\o -> o { optJailbreak = True }))                                  "don't honor version restrictions on build inputs"
-  ]
-
-usage :: String
-usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines
-        [ ""
-        , "Recognized URI schemes:"
-        , ""
-        , "  cabal://pkgname-pkgversion       download the specified package from Hackage"
-        , "  cabal://pkgname                  download latest version of the specified package from Hackage"
-        , "  http://host/path                 fetch the Cabal file via HTTP"
-        , "  file:///local/path               load the Cabal file from the local disk"
-        , "  /local/path                      abbreviated version of file URI"
-        ]
-
-cmdlineError :: String -> IO a
-cmdlineError ""     = hPutStrLn stderr usage >> exitFailure
-cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
-
-main :: IO ()
-main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
-  args' <- getArgs
-  (cfg,args) <- case getOpt Permute options args' of
-                  (o,n,[]  ) -> return (foldl (flip ($)) defaultConfiguration o,n)
-                  (_,_,errs) -> cmdlineError (concatMap ("*** "++) errs)
-
-  when (optPrintHelp cfg) (putStr usage >> exitSuccess)
-  when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
-
-  cabal' <- fmap parsePackageDescription (readCabalFile (head args))
-  cabal <- case cabal' of
-             ParseOk _ a -> return a
-             ParseFailed err -> do
-               hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
-               exitFailure
-
-  let packageId = package (packageDescription cabal)
-  sha <- if null (optSha256 cfg) then hashPackage packageId else return (optSha256 cfg)
-
-  let deriv  = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
-      deriv' = deriv { metaSection = (metaSection deriv)
-                                     { maintainers = optMaintainer cfg
-                                     , platforms   = optPlatform cfg
-                                     }
-                     , doCheck = doCheck deriv && optDoCheck cfg
-                     }
-
-  putStr (show (disp (normalize deriv')))
diff --git a/src/Cabal2Nix/Flags.hs b/src/Cabal2Nix/Flags.hs
--- a/src/Cabal2Nix/Flags.hs
+++ b/src/Cabal2Nix/Flags.hs
@@ -8,7 +8,7 @@
  | name == "accelerate-examples"= [disable "opencl"]
  | name == "pandoc"             = [enable "blaze_html_0_5"]
  | name == "git-annex"          = [ enable "S3", enable "WebDAV", enable "Inotify"
-                                  , enable "Dbus", disable "Assistant", disable "Webapp"
+                                  , enable "Dbus", enable "Assistant", enable "Webapp"
                                   , enable "Pairing", enable "XMPP", enable "DNS"
                                   , enable "Production", enable "TDFA"]
  | name == "haskeline"          = [enable "terminfo"]
diff --git a/src/Cabal2Nix/Name.hs b/src/Cabal2Nix/Name.hs
--- a/src/Cabal2Nix/Name.hs
+++ b/src/Cabal2Nix/Name.hs
@@ -24,6 +24,7 @@
 libNixName "cairo"              = return "cairo"
 libNixName "cairo-svg"          = return "cairo"
 libNixName "crypto"             = return "openssl"
+libNixName "gconf-2.0"          = return "gconf"
 libNixName "gio-2.0"            = return "glib"
 libNixName "glib-2.0"           = return "glib"
 libNixName "GL"                 = return "mesa"
@@ -32,8 +33,16 @@
 libNixName "gnome-keyring-1"    = return "gnome_keyring"
 libNixName "gnome-keyring"      = return "gnome_keyring"
 libNixName "gobject-2.0"        = return "glib"
+libNixName "gstreamer-0.10"     = return "gstreamer"
+libNixName "gstreamer-audio-0.10"               = return "gstreamer-audio"
+libNixName "gstreamer-base-0.10"                = return "gstreamer-base"
+libNixName "gstreamer-controller-0.10"          = return "gstreamer-controller"
+libNixName "gstreamer-dataprotocol-0.10"        = return "gstreamer-dataprotocol"
+libNixName "gstreamer-net-0.10"                 = return "gstreamer-net"
+libNixName "gstreamer-plugins-base-0.10"        = return "gstreamer-plugins-base"
 libNixName "gthread-2.0"        = return "glib"
 libNixName "gtk+-2.0"           = return "gtk"
+libNixName "gtkglext-1.0"       = return "gtkglext"
 libNixName "gtksourceview-2.0"  = return "gtksourceview"
 libNixName "icudata"            = return "icu"
 libNixName "icui18n"            = return "icu"
@@ -51,10 +60,12 @@
 libNixName "pcre"               = return "pcre"
 libNixName "png"                = return "libpng"
 libNixName "pq"                 = return "postgresql"
+libNixName "pthread"            = []
 libNixName "sndfile"            = return "libsndfile"
 libNixName "sqlite3"            = return "sqlite"
 libNixName "ssl"                = return "openssl"
 libNixName "stdc++"             = []  -- in stdenv
+libNixName "webkit-1.0"         = return "webkit"
 libNixName "X11"                = return "libX11"
 libNixName "Xext"               = return "libXext"
 libNixName "xft"                = return "libXft"
diff --git a/src/Cabal2Nix/Normalize.hs b/src/Cabal2Nix/Normalize.hs
--- a/src/Cabal2Nix/Normalize.hs
+++ b/src/Cabal2Nix/Normalize.hs
@@ -1,6 +1,6 @@
 {-# LANGUAGE RecordWildCards #-}
 
-module Cabal2Nix.Normalize ( normalize ) where
+module Cabal2Nix.Normalize ( normalize, normalizeList ) where
 
 import Distribution.NixOS.Derivation.Cabal
 import Cabal2Nix.Name
diff --git a/src/Cabal2Nix/PostProcess.hs b/src/Cabal2Nix/PostProcess.hs
--- a/src/Cabal2Nix/PostProcess.hs
+++ b/src/Cabal2Nix/PostProcess.hs
@@ -15,8 +15,10 @@
   | pname == "darcs"            = deriv { phaseOverrides = darcsInstallPostInstall }
   | pname == "editline"         = deriv { extraLibs = "libedit":extraLibs }
   | pname == "epic"             = deriv { extraLibs = "gmp":"boehmgc":extraLibs, buildTools = "happy":buildTools }
+  | pname == "ghc-heap-view"    = deriv { phaseOverrides = ghciPostInstall }
   | pname == "ghc-mod"          = deriv { phaseOverrides = ghcModPostInstall, buildTools = "emacs":buildTools }
-  | pname == "git-annex"        = deriv { phaseOverrides = gitAnnexOverrides, buildTools = "git":"rsync":"gnupg1":"curl":"lsof":"openssh":"bup":buildTools }
+  | pname == "ghc-vis"          = deriv { phaseOverrides = ghciPostInstall }
+  | pname == "git-annex"        = deriv { phaseOverrides = gitAnnexOverrides, buildTools = "git":"rsync":"gnupg1":"curl":"lsof":"openssh":"which":"bup":buildTools }
   | pname == "glade"            = deriv { extraLibs = "pkgconfig":"libc":extraLibs, pkgConfDeps = "gtkC":delete "gtk" pkgConfDeps }
   | pname == "glib"             = deriv { extraLibs = "pkgconfig":"libc":extraLibs }
   | pname == "GLUT"             = deriv { extraLibs = "glut":"libSM":"libICE":"libXmu":"libXi":"mesa":extraLibs }
@@ -31,7 +33,9 @@
   | pname == "highlighting-kate"= highlightingKatePostProcessing deriv
   | pname == "hmatrix"          = deriv { extraLibs = "gsl":"liblapack":"blas":extraLibs }
   | pname == "hspec"            = deriv { doCheck = False }
-  | pname == "idris"            = deriv { buildTools = "happy":buildTools, extraLibs = "gmp":extraLibs }
+  | pname == "idris"            = if version < Version [0,9,9] []
+                                    then deriv { buildTools = "happy":buildTools, extraLibs = "gmp":extraLibs }
+                                    else deriv { buildTools = "happy":buildTools, extraLibs = "gmp":"boehmgc":extraLibs }
   | pname == "language-c-quote" = deriv { buildTools = "alex":"happy":buildTools }
   | pname == "language-java"    = deriv { buildDepends = "syb":buildDepends }
   | pname == "leksah-server"    = deriv { buildDepends = "process-leksah":buildDepends }
@@ -141,10 +145,19 @@
 gitAnnexOverrides :: String
 gitAnnexOverrides = unlines
   [ "preConfigure = \"patchShebangs .\";"
+  , "installPhase = \"make PREFIX=$out CABAL=./Setup docs install\";"
   , "checkPhase = ''"
   , "  export HOME=\"$NIX_BUILD_TOP/tmp\""
   , "  mkdir \"$HOME\""
   , "  cp dist/build/git-annex/git-annex git-annex"
   , "  ./git-annex test"
   , "'';"
+  ]
+
+ghciPostInstall :: String
+ghciPostInstall = unlines
+  [ "postInstall = ''"
+  , "    ensureDir \"$out/share/ghci\""
+  , "    ln -s \"$out/share/$pname-$version/ghci\" \"$out/share/ghci/$pname\""
+  , "  '';"
   ]
diff --git a/src/Hackage4Nix.hs b/src/Hackage4Nix.hs
deleted file mode 100644
--- a/src/Hackage4Nix.hs
+++ /dev/null
@@ -1,220 +0,0 @@
-module Main ( main ) where
-
-import Cabal2Nix.Normalize
-import Cabal2Nix.Generate
-import Control.Exception ( bracket )
-import Control.Monad.RWS
-import Data.List
-import qualified Data.Set as Set
-import Data.Version
-import qualified Distribution.Hackage.DB as DB
-import Distribution.NixOS.Derivation.Cabal
-import Distribution.Package
-import Distribution.PackageDescription ( GenericPackageDescription() )
-import Distribution.Text
-import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
-import System.Directory
-import System.Environment
-import System.Exit
-import System.FilePath
-import System.IO
-import Text.Regex.Posix
-
-data Pkg = Pkg Derivation FilePath Bool
-  deriving (Show, Eq, Ord)
-
-type PkgSet = Set.Set Pkg
-
-data Configuration = Configuration
-  { _msgDebug  :: String -> IO ()
-  , _msgInfo   :: String -> IO ()
-  , _hackageDb :: DB.Hackage
-  }
-
-defaultConfiguration :: Configuration
-defaultConfiguration = Configuration
-  { _msgDebug  = hPutStrLn stderr
-  , _msgInfo   = hPutStrLn stderr
-  , _hackageDb = DB.empty
-  }
-
-type Hackage4Nix a = RWST Configuration () PkgSet IO a
-
-io :: (MonadIO m) => IO a -> m a
-io = liftIO
-
-readDirectory :: FilePath -> IO [FilePath]
-readDirectory dirpath = do
-  entries <- getDirectoryContents dirpath
-  return [ x | x <- entries, x /= ".", x /= ".." ]
-
-msgDebug, msgInfo :: String -> Hackage4Nix ()
-msgDebug msg = ask >>= \s -> io (_msgDebug s msg)
-msgInfo msg = ask >>= \s -> io (_msgInfo s msg)
-
-getCabalPackage :: String -> Version -> Hackage4Nix GenericPackageDescription
-getCabalPackage name vers = do
-  db <- asks _hackageDb
-  case DB.lookup name db of
-    Just db' -> case DB.lookup vers db' of
-                  Just pkg -> return pkg
-                  Nothing  -> fail ("hackage doesn't know about " ++ name ++ " version " ++ display vers)
-    Nothing  -> fail ("hackage doesn't know about " ++ show name)
-
-discoverNixFiles :: (FilePath -> Hackage4Nix ()) -> FilePath -> Hackage4Nix ()
-discoverNixFiles yield dirOrFile = do
-  isFile <- io (doesFileExist dirOrFile)
-  case (isFile, takeExtension dirOrFile) of
-    (True,".nix") -> yield dirOrFile
-    (True,_)     -> return ()
-    (False,_)    -> io (readDirectory dirOrFile) >>= mapM_ (discoverNixFiles yield . (dirOrFile </>))
-
-regenerateDerivation :: Derivation -> String -> Bool
-regenerateDerivation _ buf = not (buf =~ "(pre|post)Configure|(pre|post)Install|patchPhase|patches")
-
-parseNixFile :: FilePath -> String -> Hackage4Nix (Maybe Pkg)
-parseNixFile path buf
-  | not (buf =~ "cabal.mkDerivation")
-               = msgDebug ("ignore non-cabal package " ++ path) >> return Nothing
-  | any (`isSuffixOf`path) badPackagePaths
-               = msgDebug ("ignore known bad package " ++ path) >> return Nothing
-  | Just deriv <- parseDerivation buf
-               = return (Just (Pkg deriv path (regenerateDerivation deriv buf)))
-  | otherwise = msgInfo ("failed to parse file " ++ path) >> return Nothing
-
-selectLatestVersions :: PkgSet -> PkgSet
-selectLatestVersions = Set.fromList . nubBy f2 . sortBy f1 . Set.toList
-  where
-    f1 (Pkg deriv1 _ _) (Pkg deriv2 _ _)
-      | pname deriv1 == pname deriv2     = compare (version deriv2) (version deriv1)
-      | otherwise                        = compare (pname deriv1) (pname deriv2)
-    f2 (Pkg deriv1 _ _) (Pkg deriv2 _ _) = pname deriv1 == pname deriv2
-
-discoverUpdates :: String -> Version -> Hackage4Nix [Version]
-discoverUpdates name vers = do
-  db <- asks _hackageDb
-  case DB.lookup name db of
-    Just pkgs -> return (filter (>vers) (DB.keys pkgs))
-    Nothing   -> fail ("discoverUpdates cannot find package " ++ show name ++ " on Hackage")
-
-updateNixPkgs :: [FilePath] -> Hackage4Nix ()
-updateNixPkgs paths = do
-  msgDebug $ "updating = " ++ show paths
-  forM_ paths $ \fileOrDir ->
-    flip discoverNixFiles fileOrDir $ \file -> do
-      nix' <- io (readFile file) >>= parseNixFile file
-      flip (maybe (return ())) nix' $ \nix -> do
-        let Pkg deriv path regenerate = nix
-            maints = maintainers (metaSection deriv)
-            plats  = platforms (metaSection deriv)
-        modify (Set.insert nix)
-        when regenerate $ do
-          msgDebug ("re-generate " ++ path)
-          pkg <- getCabalPackage (pname deriv) (version deriv)
-          let deriv'  = (cabal2nix pkg) { sha256 = sha256 deriv
-                                        , runHaddock = runHaddock deriv
-                                        , doCheck = doCheck deriv
-                                        , jailbreak = jailbreak deriv
-                                        }
-              meta    = metaSection deriv'
-              plats'  = if null plats then platforms meta else plats
-              deriv'' = deriv' { metaSection = meta
-                                               { maintainers = maints -- ++ ["andres","simons"]
-                                               , platforms   = plats'
-                                               }
-                               }
-          io $ writeFile path (show (disp (normalize deriv'')))
-  pkgset <- gets selectLatestVersions
-  updates' <- forM (Set.elems pkgset) $ \pkg -> do
-    let Pkg deriv _ _ = pkg
-    updates <- discoverUpdates (pname deriv) (version deriv)
-    return (pkg,updates)
-  let updates = [ u | u@(_,_:_) <- updates' ]
-  unless (null updates) $ do
-    msgInfo "The following updates are available:"
-    forM_ updates $ \(pkg,versions) -> do
-      let Pkg deriv path regenerate = pkg
-      msgInfo ""
-      msgInfo $ display (packageId deriv) ++ ":"
-      forM_ versions $ \newVersion -> do
-        let deriv' = deriv { version = newVersion }
-        msgInfo $ "  " ++ genCabal2NixCmdline (Pkg deriv' path regenerate)
-  return ()
-
-genCabal2NixCmdline :: Pkg -> String
-genCabal2NixCmdline (Pkg deriv path _) = unwords $ ["cabal2nix"] ++ opts ++ ['>':path']
-  where
-    meta = metaSection deriv
-    opts = [cabal] ++ maints' ++ plats'
-                   ++ (if jailbreak deriv then ["--jailbreak"] else [])
-                   ++ (if runHaddock deriv then [] else ["--no-haddock"])
-                   ++ (if doCheck deriv then [] else ["--no-check"])
-    cabal = "cabal://" ++ display (packageId deriv)
-    maints' = [ "--maintainer=" ++ normalizeMaintainer m | m <- maintainers meta ]
-    plats'
-      | ["self.ghc.meta.platforms"] == platforms meta = []
-      | otherwise                                     = [ "--platform=" ++ p | p <- platforms meta ]
-    path'
-      | path =~ "/[0-9\\.]+\\.nix$" = replaceFileName path (display (version deriv) <.> "nix")
-      | otherwise                   = path
-
-normalizeMaintainer :: String -> String
-normalizeMaintainer x
-  | "self.stdenv.lib.maintainers." `isPrefixOf` x = drop 28 x
-  | otherwise                                     = x
-
-data CliOption = PrintHelp | Verbose
-  deriving (Eq)
-
-main :: IO ()
-main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
-  let options :: [OptDescr CliOption]
-      options =
-        [ Option "h" ["help"]     (NoArg PrintHelp)                 "show this help text"
-        , Option "v" ["verbose"]  (NoArg Verbose)                   "enable noisy debug output"
-        ]
-
-      usage :: String
-      usage = usageInfo "Usage: hackage4nix [options] [dir-or-file ...]" options ++ unlines
-              [ ""
-              , "The purpose of 'hackage4nix' is to keep all Haskell packages in our"
-              , "repository packages up-to-date. It scans a checked-out copy of"
-              , "Nixpkgs for expressions that use 'cabal.mkDerivation', and"
-              , "re-generates them in-place with cabal2nix."
-              ]
-
-      cmdlineError :: String -> IO a
-      cmdlineError ""     = hPutStrLn stderr usage >> exitFailure
-      cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
-
-  args' <- getArgs
-  (opts,args) <- case getOpt Permute options args' of
-     (o,n,[]  ) -> return (o,n)
-     (_,_,errs) -> cmdlineError (concatMap (\e -> '*':'*':'*':' ':e) errs)
-
-  when (PrintHelp `elem` opts) (cmdlineError "")
-
-  hackage <- DB.readHackage
-  let cfg = defaultConfiguration
-            { _msgDebug  = if Verbose `elem` opts then _msgDebug defaultConfiguration else const (return ())
-            , _hackageDb = hackage
-            }
-  ((),_,()) <- runRWST (updateNixPkgs args) cfg Set.empty
-  return ()
-
-
--- Packages that cabal2nix cannot generate build expressions for.
-
-badPackagePaths :: [FilePath]
-badPackagePaths = [ -- These expression are not found on Hackage:
-                    "haskell-platform/2009.2.0.2.nix", "haskell-platform/2010.1.0.0.nix"
-                  , "haskell-platform/2010.2.0.0.nix", "haskell-platform/2011.2.0.0.nix"
-                  , "haskell-platform/2011.2.0.1.nix", "haskell-platform/2011.4.0.0.nix"
-                  , "haskell-platform/2012.2.0.0.nix", "haskell-platform/2012.4.0.0.nix"
-                  , "haskell-platform/2013.2.0.0.nix", "compilers/flapjax/default.nix"
-                  , "pkgs/games/uqm/3dovideo.nix"
-                    -- Our primitive parser cannot handle these files.
-                  , "top-level/all-packages.nix", "top-level/haskell-packages.nix"
-                    -- This build is way too complicated to maintain it automatically.
-                  , "pkgs/development/compilers/pakcs/default.nix"
-                  ]
diff --git a/src/cabal2nix.hs b/src/cabal2nix.hs
new file mode 100644
--- /dev/null
+++ b/src/cabal2nix.hs
@@ -0,0 +1,97 @@
+module Main ( main ) where
+
+import Cabal2Nix.Hackage ( hashPackage, readCabalFile )
+import Cabal2Nix.Generate ( cabal2nix )
+import Cabal2Nix.Normalize ( normalize )
+import Distribution.NixOS.Derivation.Cabal
+
+import Control.Exception ( bracket )
+import Control.Monad ( when )
+import Distribution.PackageDescription ( package, packageDescription )
+import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
+import Distribution.Text ( disp )
+import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
+import System.Environment ( getArgs )
+import System.Exit ( exitFailure, exitSuccess )
+import System.IO ( hPutStrLn, hFlush, stdout, stderr )
+
+data Configuration = Configuration
+  { optPrintHelp :: Bool
+  , optPrintVersion :: Bool
+  , optSha256 :: String
+  , optMaintainer :: [String]
+  , optPlatform :: [String]
+  , optHaddock :: Bool
+  , optDoCheck :: Bool
+  , optJailbreak :: Bool
+  }
+  deriving (Show)
+
+defaultConfiguration :: Configuration
+defaultConfiguration = Configuration
+  { optPrintHelp = False
+  , optPrintVersion = False
+  , optSha256 = ""
+  , optMaintainer = []
+  , optPlatform = []
+  , optHaddock = True
+  , optDoCheck = True
+  , optJailbreak = False
+  }
+
+options :: [OptDescr (Configuration -> Configuration)]
+options =
+  [ Option "h" ["help"]       (NoArg (\o -> o { optPrintHelp = True }))                                  "show this help text"
+  , Option ""  ["sha256"]     (ReqArg (\x o -> o { optSha256 = x }) "HASH")                              "sha256 hash of source tarball"
+  , Option "m" ["maintainer"] (ReqArg (\x o -> o { optMaintainer = x : optMaintainer o }) "MAINTAINER")  "maintainer of this package (may be specified multiple times)"
+  , Option "p" ["platform"]   (ReqArg (\x o -> o { optPlatform = x : optPlatform o }) "PLATFORM")        "supported build platforms (may be specified multiple times)"
+  , Option ""  ["no-haddock"] (NoArg (\o -> o { optHaddock = False }))                                   "don't run Haddock when building this package"
+  , Option ""  ["no-check"]   (NoArg (\o -> o { optDoCheck = False }))                                   "don't run regression test suites of this package"
+  , Option ""  ["jailbreak"]  (NoArg (\o -> o { optJailbreak = True }))                                  "don't honor version restrictions on build inputs"
+  ]
+
+usage :: String
+usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines
+        [ ""
+        , "Recognized URI schemes:"
+        , ""
+        , "  cabal://pkgname-pkgversion       download the specified package from Hackage"
+        , "  cabal://pkgname                  download latest version of the specified package from Hackage"
+        , "  http://host/path                 fetch the Cabal file via HTTP"
+        , "  file:///local/path               load the Cabal file from the local disk"
+        , "  /local/path                      abbreviated version of file URI"
+        ]
+
+cmdlineError :: String -> IO a
+cmdlineError ""     = hPutStrLn stderr usage >> exitFailure
+cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
+
+main :: IO ()
+main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
+  args' <- getArgs
+  (cfg,args) <- case getOpt Permute options args' of
+                  (o,n,[]  ) -> return (foldl (flip ($)) defaultConfiguration o,n)
+                  (_,_,errs) -> cmdlineError (concatMap ("*** "++) errs)
+
+  when (optPrintHelp cfg) (putStr usage >> exitSuccess)
+  when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
+
+  cabal' <- fmap parsePackageDescription (readCabalFile (head args))
+  cabal <- case cabal' of
+             ParseOk _ a -> return a
+             ParseFailed err -> do
+               hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
+               exitFailure
+
+  let packageId = package (packageDescription cabal)
+  sha <- if null (optSha256 cfg) then hashPackage packageId else return (optSha256 cfg)
+
+  let deriv  = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
+      deriv' = deriv { metaSection = (metaSection deriv)
+                                     { maintainers = optMaintainer cfg
+                                     , platforms   = optPlatform cfg
+                                     }
+                     , doCheck = doCheck deriv && optDoCheck cfg
+                     }
+
+  putStr (show (disp (normalize deriv')))
diff --git a/src/hackage4nix.hs b/src/hackage4nix.hs
new file mode 100644
--- /dev/null
+++ b/src/hackage4nix.hs
@@ -0,0 +1,220 @@
+module Main ( main ) where
+
+import Cabal2Nix.Normalize
+import Cabal2Nix.Generate
+import Control.Exception ( bracket )
+import Control.Monad.RWS
+import Data.List
+import qualified Data.Set as Set
+import Data.Version
+import qualified Distribution.Hackage.DB as DB
+import Distribution.NixOS.Derivation.Cabal
+import Distribution.Package
+import Distribution.PackageDescription ( GenericPackageDescription() )
+import Distribution.Text
+import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
+import System.Directory
+import System.Environment
+import System.Exit
+import System.FilePath
+import System.IO
+import Text.Regex.Posix
+
+data Pkg = Pkg Derivation FilePath Bool
+  deriving (Show, Eq, Ord)
+
+type PkgSet = Set.Set Pkg
+
+data Configuration = Configuration
+  { _msgDebug  :: String -> IO ()
+  , _msgInfo   :: String -> IO ()
+  , _hackageDb :: DB.Hackage
+  }
+
+defaultConfiguration :: Configuration
+defaultConfiguration = Configuration
+  { _msgDebug  = hPutStrLn stderr
+  , _msgInfo   = hPutStrLn stderr
+  , _hackageDb = DB.empty
+  }
+
+type Hackage4Nix a = RWST Configuration () PkgSet IO a
+
+io :: (MonadIO m) => IO a -> m a
+io = liftIO
+
+readDirectory :: FilePath -> IO [FilePath]
+readDirectory dirpath = do
+  entries <- getDirectoryContents dirpath
+  return [ x | x <- entries, x /= ".", x /= ".." ]
+
+msgDebug, msgInfo :: String -> Hackage4Nix ()
+msgDebug msg = ask >>= \s -> io (_msgDebug s msg)
+msgInfo msg = ask >>= \s -> io (_msgInfo s msg)
+
+getCabalPackage :: String -> Version -> Hackage4Nix GenericPackageDescription
+getCabalPackage name vers = do
+  db <- asks _hackageDb
+  case DB.lookup name db of
+    Just db' -> case DB.lookup vers db' of
+                  Just pkg -> return pkg
+                  Nothing  -> fail ("hackage doesn't know about " ++ name ++ " version " ++ display vers)
+    Nothing  -> fail ("hackage doesn't know about " ++ show name)
+
+discoverNixFiles :: (FilePath -> Hackage4Nix ()) -> FilePath -> Hackage4Nix ()
+discoverNixFiles yield dirOrFile = do
+  isFile <- io (doesFileExist dirOrFile)
+  case (isFile, takeExtension dirOrFile) of
+    (True,".nix") -> yield dirOrFile
+    (True,_)     -> return ()
+    (False,_)    -> io (readDirectory dirOrFile) >>= mapM_ (discoverNixFiles yield . (dirOrFile </>))
+
+regenerateDerivation :: Derivation -> String -> Bool
+regenerateDerivation _ buf = not (buf =~ "(pre|post)Configure|(pre|post)Install|patchPhase|patches")
+
+parseNixFile :: FilePath -> String -> Hackage4Nix (Maybe Pkg)
+parseNixFile path buf
+  | not (buf =~ "cabal.mkDerivation")
+               = msgDebug ("ignore non-cabal package " ++ path) >> return Nothing
+  | any (`isSuffixOf`path) badPackagePaths
+               = msgDebug ("ignore known bad package " ++ path) >> return Nothing
+  | Just deriv <- parseDerivation buf
+               = return (Just (Pkg deriv path (regenerateDerivation deriv buf)))
+  | otherwise = msgInfo ("failed to parse file " ++ path) >> return Nothing
+
+selectLatestVersions :: PkgSet -> PkgSet
+selectLatestVersions = Set.fromList . nubBy f2 . sortBy f1 . Set.toList
+  where
+    f1 (Pkg deriv1 _ _) (Pkg deriv2 _ _)
+      | pname deriv1 == pname deriv2     = compare (version deriv2) (version deriv1)
+      | otherwise                        = compare (pname deriv1) (pname deriv2)
+    f2 (Pkg deriv1 _ _) (Pkg deriv2 _ _) = pname deriv1 == pname deriv2
+
+discoverUpdates :: String -> Version -> Hackage4Nix [Version]
+discoverUpdates name vers = do
+  db <- asks _hackageDb
+  case DB.lookup name db of
+    Just pkgs -> return (filter (>vers) (DB.keys pkgs))
+    Nothing   -> fail ("discoverUpdates cannot find package " ++ show name ++ " on Hackage")
+
+updateNixPkgs :: [FilePath] -> Hackage4Nix ()
+updateNixPkgs paths = do
+  msgDebug $ "updating = " ++ show paths
+  forM_ paths $ \fileOrDir ->
+    flip discoverNixFiles fileOrDir $ \file -> do
+      nix' <- io (readFile file) >>= parseNixFile file
+      flip (maybe (return ())) nix' $ \nix -> do
+        let Pkg deriv path regenerate = nix
+            maints = maintainers (metaSection deriv)
+            plats  = platforms (metaSection deriv)
+        modify (Set.insert nix)
+        when regenerate $ do
+          msgDebug ("re-generate " ++ path)
+          pkg <- getCabalPackage (pname deriv) (version deriv)
+          let deriv'  = (cabal2nix pkg) { sha256 = sha256 deriv
+                                        , runHaddock = runHaddock deriv
+                                        , doCheck = doCheck deriv
+                                        , jailbreak = jailbreak deriv
+                                        }
+              meta    = metaSection deriv'
+              plats'  = if null plats then platforms meta else plats
+              deriv'' = deriv' { metaSection = meta
+                                               { maintainers = maints -- ++ ["andres","simons"]
+                                               , platforms   = plats'
+                                               }
+                               }
+          io $ writeFile path (show (disp (normalize deriv'')))
+  pkgset <- gets selectLatestVersions
+  updates' <- forM (Set.elems pkgset) $ \pkg -> do
+    let Pkg deriv _ _ = pkg
+    updates <- discoverUpdates (pname deriv) (version deriv)
+    return (pkg,updates)
+  let updates = [ u | u@(_,_:_) <- updates' ]
+  unless (null updates) $ do
+    msgInfo "The following updates are available:"
+    forM_ updates $ \(pkg,versions) -> do
+      let Pkg deriv path regenerate = pkg
+      msgInfo ""
+      msgInfo $ display (packageId deriv) ++ ":"
+      forM_ versions $ \newVersion -> do
+        let deriv' = deriv { version = newVersion }
+        msgInfo $ "  " ++ genCabal2NixCmdline (Pkg deriv' path regenerate)
+  return ()
+
+genCabal2NixCmdline :: Pkg -> String
+genCabal2NixCmdline (Pkg deriv path _) = unwords $ ["cabal2nix"] ++ opts ++ ['>':path']
+  where
+    meta = metaSection deriv
+    opts = [cabal] ++ maints' ++ plats'
+                   ++ (if jailbreak deriv then ["--jailbreak"] else [])
+                   ++ (if runHaddock deriv then [] else ["--no-haddock"])
+                   ++ (if doCheck deriv then [] else ["--no-check"])
+    cabal = "cabal://" ++ display (packageId deriv)
+    maints' = [ "--maintainer=" ++ normalizeMaintainer m | m <- maintainers meta ]
+    plats'
+      | ["self.ghc.meta.platforms"] == platforms meta = []
+      | otherwise                                     = [ "--platform=" ++ p | p <- platforms meta ]
+    path'
+      | path =~ "/[0-9\\.]+\\.nix$" = replaceFileName path (display (version deriv) <.> "nix")
+      | otherwise                   = path
+
+normalizeMaintainer :: String -> String
+normalizeMaintainer x
+  | "self.stdenv.lib.maintainers." `isPrefixOf` x = drop 28 x
+  | otherwise                                     = x
+
+data CliOption = PrintHelp | Verbose
+  deriving (Eq)
+
+main :: IO ()
+main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
+  let options :: [OptDescr CliOption]
+      options =
+        [ Option "h" ["help"]     (NoArg PrintHelp)                 "show this help text"
+        , Option "v" ["verbose"]  (NoArg Verbose)                   "enable noisy debug output"
+        ]
+
+      usage :: String
+      usage = usageInfo "Usage: hackage4nix [options] [dir-or-file ...]" options ++ unlines
+              [ ""
+              , "The purpose of 'hackage4nix' is to keep all Haskell packages in our"
+              , "repository packages up-to-date. It scans a checked-out copy of"
+              , "Nixpkgs for expressions that use 'cabal.mkDerivation', and"
+              , "re-generates them in-place with cabal2nix."
+              ]
+
+      cmdlineError :: String -> IO a
+      cmdlineError ""     = hPutStrLn stderr usage >> exitFailure
+      cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
+
+  args' <- getArgs
+  (opts,args) <- case getOpt Permute options args' of
+     (o,n,[]  ) -> return (o,n)
+     (_,_,errs) -> cmdlineError (concatMap (\e -> '*':'*':'*':' ':e) errs)
+
+  when (PrintHelp `elem` opts) (cmdlineError "")
+
+  hackage <- DB.readHackage
+  let cfg = defaultConfiguration
+            { _msgDebug  = if Verbose `elem` opts then _msgDebug defaultConfiguration else const (return ())
+            , _hackageDb = hackage
+            }
+  ((),_,()) <- runRWST (updateNixPkgs args) cfg Set.empty
+  return ()
+
+
+-- Packages that cabal2nix cannot generate build expressions for.
+
+badPackagePaths :: [FilePath]
+badPackagePaths = [ -- These expression are not found on Hackage:
+                    "haskell-platform/2009.2.0.2.nix", "haskell-platform/2010.1.0.0.nix"
+                  , "haskell-platform/2010.2.0.0.nix", "haskell-platform/2011.2.0.0.nix"
+                  , "haskell-platform/2011.2.0.1.nix", "haskell-platform/2011.4.0.0.nix"
+                  , "haskell-platform/2012.2.0.0.nix", "haskell-platform/2012.4.0.0.nix"
+                  , "haskell-platform/2013.2.0.0.nix", "compilers/flapjax/default.nix"
+                  , "pkgs/games/uqm/3dovideo.nix"
+                    -- Our primitive parser cannot handle these files.
+                  , "top-level/all-packages.nix", "top-level/haskell-packages.nix"
+                    -- This build is way too complicated to maintain it automatically.
+                  , "pkgs/development/compilers/pakcs/default.nix"
+                  ]
diff --git a/test/doc-test.hs b/test/doc-test.hs
--- a/test/doc-test.hs
+++ b/test/doc-test.hs
@@ -29,5 +29,5 @@
              , "src/Distribution/NixOS/Derivation/Meta.hs"
              , "src/Distribution/NixOS/PrettyPrinting.hs"
              ]
-  doctest $ "src/Cabal2Nix.hs" : libs
-  doctest $ "src/Hackage4Nix.hs" : libs
+  doctest $ "src/cabal2nix.hs" : libs
+  doctest $ "src/hackage4nix.hs" : libs
