-- cabal2nix.hs -- -- Copyright (c) 20011 Peter Simons -- See LICENSE file for licensing details. module Main ( main ) where import System.IO import System.Environment import Control.Exception import System.Exit import Distribution.PackageDescription.Parse import Distribution.PackageDescription import Distribution.Package import Distribution.License import Data.Version import Data.List import Control.Monad import Data.Char import Network.HTTP import System.Process type PkgName = String type PkgVersion = [Int] type PkgSHA256 = String type PkgURL = String type PkgDescription = String type PkgLicense = License type PkgDependencies = [CondTree ConfVar [Dependency] ()] type PkgExtraLibs = [String] data Pkg = Pkg PkgName PkgVersion PkgSHA256 PkgURL PkgDescription PkgLicense PkgDependencies PkgExtraLibs deriving (Show) toNixName :: String -> String toNixName [] = error "toNixName: empty string is not a valid argument" toNixName name = f ((toLower (head name)) : tail name) where f [] = [] f ('-':c:cs) | c `notElem` "-" = toUpper c : f cs f ('-':_) = error ("unexpected package name " ++ show name) f (c:cs) = c : f cs toNix :: Pkg -> String toNix (Pkg name ver sha256 url desc lic deps libs) = "{" ++ exprArgs ++"}:\n\n" ++ "cabal.mkDerivation (self : {\n" ++ " pname = " ++ show name ++ ";\n" ++ " version = \"" ++ showVer ++ "\";\n" ++ " sha256 = " ++ show sha256 ++ ";\n" ++ " propagatedBuildInputs = [" ++ depList ++ "];\n" ++ " meta = {\n" ++ " homepage = \"" ++ url ++ "\";\n" ++ " description = " ++ show desc ++ ";\n" ++ " license = " ++ showLic lic ++ ";\n" ++ " };\n" ++ "})\n" where exprArgs = concat (intersperse "," ("cabal":pkgDeps)) showVer = concat (intersperse "." (map show ver)) depList = concat (intersperse " " pkgDeps) pkgDeps :: [String] pkgDeps = filter (/="cabal") $ nub $ sort $ map toNixName $ libs ++ [ n | dep <- deps, Dependency (PackageName n) _ <- condTreeConstraints dep , n `notElem` ["base","containers"] ] showLic (GPL Nothing) = show "GPL" showLic (GPL (Just (Version [2] []))) = "self.stdenv.lib.licenses.gpl2" showLic (GPL (Just (Version [3] []))) = "self.stdenv.lib.licenses.gpl3" showLic (LGPL Nothing) = show "LGPL" showLic (LGPL (Just (Version [2,1] []))) = "self.stdenv.lib.licenses.lgpl21" showLic (LGPL (Just (Version [3] []))) = "self.stdenv.lib.licenses.lgpl3" showLic BSD3 = "self.stdenv.lib.licenses.bsd3" showLic BSD4 = "self.stdenv.lib.licenses.bsd4" showLic MIT = "self.stdenv.lib.licenses.mit" showLic PublicDomain = "self.stdenv.lib.licenses.publicDomain" showLic AllRightsReserved = "unknown" showLic OtherLicense = "unknown" showLic l = error $ "unknown license: " ++ show l cabal2nix :: GenericPackageDescription -> PkgSHA256 -> Pkg cabal2nix cabal sha256 = Pkg pkgname pkgver sha256 url desc lic (map simplify libDeps ++ map simplify exeDeps) (libs++libs') where pkg = packageDescription cabal PackageName pkgname = pkgName (package pkg) pkgver = versionBranch (pkgVersion (package pkg)) lic = license pkg url = homepage pkg desc = synopsis pkg -- globalDeps = buildDepends pkg libDeps = maybe [] (\x -> [x]) (condLibrary cabal) exeDeps = [ tree | (_,tree) <- condExecutables cabal ] libs = concat [ extraLibs (libBuildInfo (condTreeData x)) | x <- libDeps ] libs' = concat [ extraLibs (buildInfo (condTreeData x)) | x <- exeDeps ] simplify :: CondTree ConfVar [Dependency] a -> CondTree ConfVar [Dependency] () simplify (CondNode _ deps nodes) = CondNode () deps (map simp nodes) where simp (cond,tree,mtree) = (cond, simplify tree, maybe Nothing (Just . simplify) mtree) readCabalFile :: FilePath -> IO String readCabalFile path | "http://" `isPrefixOf` path = Network.HTTP.simpleHTTP (getRequest path) >>= getResponseBody | "file://" `isPrefixOf` path = readCabalFile (drop 7 path) | otherwise = readFile path hashPackage :: GenericPackageDescription -> IO String hashPackage pkg = readProcess "bash" ["-c", "exec nix-prefetch-url 2>/dev/tty " ++ url] "" where url = "http://hackage.haskell.org/packages/archive/" ++ name ++ "/" ++ version ++ "/" ++ name ++ "-" ++ version ++ ".tar.gz" PackageIdentifier (PackageName name) version' = package (packageDescription pkg) version = showVersion version' main :: IO () main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do args <- getArgs let usage = "Usage: cabal2nix url-to-cabal-file [sha256-hash]" when (length args < 1 || length args > 2) $ do mapM_ (hPutStrLn stderr) [ "*** invalid command line syntax" , usage ] exitFailure when ("--help" `elem` args || "-h" `elem` args) $ do putStrLn usage exitFailure 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 sha256 <- case args of _:hash:[] -> return hash _ -> hashPackage cabal let pkg = cabal2nix cabal sha256 putStr (toNix pkg)