git.iko.soy/typeable/cabal2nix
on master help sign in
Allow specifying the location of the source code
commit2c45ef149c0f782726a9c5dbbe43be5cd5b44d06 authorBenno Fünfstück <benno.fuenfstueck@gmail.com> dateSat 12 Apr 2014, 16:44 (13 years ago) parent1f68b44
8 files changed+159−121
cabal2nix.cabal+2−2 src/Cabal2Nix/Generate.hs+1−2 src/Cabal2Nix/Hackage.hs−84 src/Cabal2Nix/Package.hs+127 src/Distribution/NixOS/Derivation/Cabal.hs+19−10 src/cabal2nix.hs+7−20 src/hackage4nix.hs+2−2 test/doc-test.hs+1−1
cabal2nix.cabal+2−2
@@ -49,13 +49,13 @@ Executable cabal2nix
4949 main-is: cabal2nix.hs
5050 hs-source-dirs: src
5151 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
52− filepath, directory, process, HTTP, hackage-db
52+ filepath, directory, process, HTTP, hackage-db, uri
5353 Extensions: PatternGuards, RecordWildCards, CPP
5454 Ghc-Options: -Wall
5555 other-modules: Cabal2Nix.CorePackages
5656 Cabal2Nix.Flags
5757 Cabal2Nix.Generate
58− Cabal2Nix.Hackage
58+ Cabal2Nix.Package
5959 Cabal2Nix.License
6060 Cabal2Nix.Name
6161 Cabal2Nix.Normalize
src/Cabal2Nix/Generate.hs+1−2
@@ -16,8 +16,7 @@ cabal2nix :: Cabal.GenericPackageDescription -> Derivation
1616cabal2nix cabal = normalize $ postProcess MkDerivation
1717 { pname = let Cabal.PackageName x = Cabal.pkgName pkg in x
1818 , version = Cabal.pkgVersion pkg
19− , sha256 = error "cabal2nix left the sha256 field undefined"
20− , src = Nothing
19+ , src = error "cabal2nix left the src field undefined"
2120 , isLibrary = isJust (Cabal.library tpkg)
2221 , isExecutable = not (null (Cabal.executables tpkg))
2322 , extraFunctionArgs = []
src/Cabal2Nix/Hackage.hsdeleted−84
@@ -1,84 +0,0 @@
1−module Cabal2Nix.Hackage ( hashPackage, readCabalFile ) where
2−
3−import Control.Monad ( unless )
4−import Data.List ( isPrefixOf )
5−import Data.Maybe ( fromJust )
6−import Data.Version ( showVersion, versionBranch )
7−import Distribution.Package ( PackageIdentifier(..), PackageName(..) )
8−import Distribution.Text
9−import Network.HTTP ( getRequest, rspBody )
10−import Network.Browser ( browse, request, setCheckForProxy, setDebugLog, setOutHandler )
11−import System.Directory ( doesFileExist, getHomeDirectory, createDirectoryIfMissing )
12−import System.FilePath ( dropFileName, (</>), (<.>) )
13−import System.Process ( readProcess )
14−import Control.Exception (handle, SomeException(..))
15−
16−import qualified Distribution.Hackage.DB as DB
17−
18−data Ext = TarGz | Cabal deriving Eq
19−
20−showExt :: Ext -> String
21−showExt TarGz = ".tar.gz"
22−showExt Cabal = ".cabal"
23−
24−hackagePath :: PackageIdentifier -> Ext -> String
25−hackagePath (PackageIdentifier (PackageName name) version') ext =
26− "http://hackage.haskell.org/packages/archive/" ++
27− name ++ "/" ++ version ++ "/" ++ name ++
28− (if ext == TarGz then '-' : version else "") ++
29− showExt ext
30− where
31− version = showVersion version'
32−
33−hashPackage :: PackageIdentifier -> IO String
34−hashPackage pkg = do
35− cachePath <- hashCachePath pkg
36− exists <- doesFileExist cachePath
37− hash' <- if exists
38− then readFile cachePath
39− else getHackageHash
40− let hash = reverse (dropWhile (=='\n') (reverse hash'))
41− unless exists $ do
42− createDirectoryIfMissing True (dropFileName cachePath)
43− writeFile cachePath hash
44− return hash
45− where getHackageHash = do
46− let command = "exec nix-prefetch-url 2>/dev/tty " ++ hackagePath pkg TarGz
47− handle handlePrefetchError (readProcess "bash" ["-c", command] "")
48− handlePrefetchError (SomeException _) =
49− error $ "\nError: Cannot compute hash. (Not a hackage project?)\n" ++
50− "Specify hash explicitly via --sha256 and add appropriate \"src\" attribute " ++
51− "to resulting nix expression."
52−
53−hashCachePath :: PackageIdentifier -> IO FilePath
54−hashCachePath (PackageIdentifier (PackageName name) version') = do
55− home <- getHomeDirectory
56− return $ home ++ "/.cache/cabal2nix" </> name ++ "-" ++ version <.> "sha256"
57− where
58− version = showVersion version'
59−
60−readCabalFile :: FilePath -> IO String
61−readCabalFile path
62− | "cabal://" `isPrefixOf` path = do let pid p = fromJust $ simpleParse p
63− packageName = drop 8 path
64− case versionBranch $ pkgVersion $ pid packageName of
65− [] -> do
66− packageDescription <- DB.lookup packageName `fmap` DB.readHackage
67− case packageDescription of
68− Just d -> do
69− let version = showVersion $ last $ DB.keys d
70− readCabalFile $ hackagePath (pid $ packageName ++ "-" ++ version) Cabal
71− Nothing -> error "No such package"
72− _ -> readCabalFile $ hackagePath (pid packageName) Cabal
73− | "http://" `isPrefixOf` path = fetchUrl path
74− | "file://" `isPrefixOf` path = readCabalFile (drop 7 path)
75− | otherwise = readFile path
76−
77−fetchUrl :: String -> IO String
78−fetchUrl url = do
79− (_,rsp) <- browse $ do
80− setCheckForProxy True
81− setDebugLog Nothing
82− setOutHandler (\_ -> return ())
83− request (getRequest url)
84− return (rspBody rsp)
src/Cabal2Nix/Package.hsnew file+127
@@ -0,0 +1,127 @@
1+module Cabal2Nix.Package where
2+
3+import Control.Exception ( handle, SomeException(..))
4+import Control.Monad
5+import Data.Functor ( (<$>) )
6+import Data.List ( isSuffixOf )
7+import Data.Maybe ( fromMaybe, fromJust )
8+import Distribution.NixOS.Derivation.Cabal
9+import Distribution.Text ( simpleParse )
10+import Network.Browser ( browse, request, setCheckForProxy, setDebugLog, setOutHandler )
11+import Network.HTTP ( getRequest, rspBody )
12+import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
13+import System.Exit ( exitFailure )
14+import System.FilePath ( dropFileName, (</>), (<.>) )
15+import System.IO ( hPutStrLn, stderr)
16+import System.Process ( readProcess )
17+import Text.URI
18+
19+import qualified Distribution.Hackage.DB as DB
20+import qualified Distribution.Package as Cabal
21+import qualified Distribution.PackageDescription as Cabal
22+import qualified Distribution.PackageDescription.Parse as Cabal
23+
24+data Package = Package
25+ { source :: DerivationSource
26+ , cabal :: Cabal.GenericPackageDescription
27+ }
28+
29+readPackage :: Maybe String -> URI -> IO Package
30+readPackage optHash uri = case uriScheme uri of
31+ Nothing -> local
32+ Just "file" -> local
33+ Just "cabal" -> cabalPackage optHash $ fromMaybe "" (uriRegName uri) ++ uriPath uri
34+ Just scheme -> hPutStrLn stderr ("*** unsupported URI scheme: " ++ scheme) >> exitFailure
35+ where local = localPackage optHash $ fromMaybe "" (uriRegName uri) ++ uriPath uri
36+
37+localPackage :: Maybe String -> FilePath -> IO Package
38+localPackage optHash path = do
39+ isDir <- doesDirectoryExist path
40+ cabalFile <- if isDir
41+ then do
42+ cabals <- filter (".cabal" `isSuffixOf`) <$> getDirectoryContents path
43+ case cabals of
44+ [] -> hPutStrLn stderr "*** No cabal file found in given directory" >> exitFailure
45+ (f:_) -> return $ path </> f
46+ else return path
47+ pkgDesc <- readFile cabalFile >>= readPackageDescription
48+ if isDir
49+ then return $ Package (Directory path) pkgDesc
50+ else do
51+ hash <- getHash optHash pkgDesc
52+ return $ Package (Hackage hash) pkgDesc
53+
54+cabalPackage :: Maybe String -> String -> IO Package
55+cabalPackage optHash packageName = do
56+ pkgDesc <- case versionBranch $ Cabal.pkgVersion $ pid packageName of
57+ [] -> do
58+ packageDescription <- DB.lookup packageName `fmap` DB.readHackage
59+ case packageDescription of
60+ Just d -> return $ snd $ last $ DB.toList d
61+ Nothing -> error "No such package"
62+ _ -> fetchUrl (hackagePath (pid packageName) Cabal) >>= readPackageDescription
63+ hash <- getHash optHash pkgDesc
64+ return $ Package (Hackage hash) pkgDesc
65+
66+ where pid p = fromJust $ simpleParse p
67+
68+fetchUrl :: String -> IO String
69+fetchUrl url = do
70+ (_,rsp) <- browse $ do
71+ setCheckForProxy True
72+ setDebugLog Nothing
73+ setOutHandler (\_ -> return ())
74+ request (getRequest url)
75+ return (rspBody rsp)
76+
77+readPackageDescription :: String -> IO Cabal.GenericPackageDescription
78+readPackageDescription source = case Cabal.parsePackageDescription source of
79+ Cabal.ParseOk _ a -> return a
80+ Cabal.ParseFailed err -> do
81+ hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
82+ exitFailure
83+
84+getHash :: Maybe String -> Cabal.GenericPackageDescription -> IO String
85+getHash optHash pkgDesc = maybe (hashPackage $ Cabal.package $ Cabal.packageDescription pkgDesc) return optHash
86+
87+data Ext = TarGz | Cabal deriving Eq
88+
89+showExt :: Ext -> String
90+showExt TarGz = ".tar.gz"
91+showExt Cabal = ".cabal"
92+
93+hackagePath :: Cabal.PackageIdentifier -> Ext -> String
94+hackagePath (Cabal.PackageIdentifier (Cabal.PackageName name) version') ext =
95+ "http://hackage.haskell.org/packages/archive/" ++
96+ name ++ "/" ++ version ++ "/" ++ name ++
97+ (if ext == TarGz then '-' : version else "") ++
98+ showExt ext
99+ where
100+ version = showVersion version'
101+
102+hashPackage :: Cabal.PackageIdentifier -> IO String
103+hashPackage pkg = do
104+ cachePath <- hashCachePath pkg
105+ exists <- doesFileExist cachePath
106+ hash' <- if exists
107+ then readFile cachePath
108+ else getHackageHash
109+ let hash = reverse (dropWhile (=='\n') (reverse hash'))
110+ unless exists $ do
111+ createDirectoryIfMissing True (dropFileName cachePath)
112+ writeFile cachePath hash
113+ return hash
114+ where getHackageHash = do
115+ let command = "exec nix-prefetch-url 2>/dev/tty " ++ hackagePath pkg TarGz
116+ handle handlePrefetchError (readProcess "bash" ["-c", command] "")
117+ handlePrefetchError (SomeException _) =
118+ error $ "\nError: Cannot compute hash. (Not a hackage project?)\n" ++
119+ "Specify hash explicitly via --sha256 and add appropriate \"src\" attribute " ++
120+ "to resulting nix expression."
121+
122+hashCachePath :: Cabal.PackageIdentifier -> IO FilePath
123+hashCachePath (Cabal.PackageIdentifier (Cabal.PackageName name) version') = do
124+ home <- getHomeDirectory
125+ return $ home ++ "/.cache/cabal2nix" </> name ++ "-" ++ version <.> "sha256"
126+ where
127+ version = showVersion version'
src/Distribution/NixOS/Derivation/Cabal.hs+19−10
@@ -14,12 +14,12 @@
1414module Distribution.NixOS.Derivation.Cabal
1515 ( Derivation(..)
1616 , parseDerivation
17+ , DerivationSource(..)
1718 , module Distribution.NixOS.Derivation.Meta
1819 , module Data.Version
1920 )
2021 where
2122
22−import Data.Maybe
2323import Distribution.NixOS.Derivation.Meta
2424import Distribution.NixOS.PrettyPrinting
2525import Distribution.Package
@@ -34,18 +34,18 @@ import Data.Char
3434import Data.Function
3535import Text.Regex.Posix hiding ( empty )
3636
37+data DerivationSource = Hackage String | Directory String | FetchUrl String String deriving (Eq, Show, Ord)
38+
3739-- | A represtation of Nix expressions for building Haskell packages.
3840-- The data type correspond closely to the definition of
3941-- 'PackageDescription' from Cabal.
4042--
4143-- Note that the "Text" instance definition provides pretty-printing,
4244-- but no parsing as of now!
43−
4445data Derivation = MkDerivation
4546 { pname :: String
4647 , version :: Version
47− , sha256 :: Maybe String
48− , src :: Maybe FilePath
48+ , src :: DerivationSource
4949 , isLibrary :: Bool
5050 , isExecutable :: Bool
5151 , extraFunctionArgs :: [String]
@@ -79,9 +79,7 @@ renderDerivation deriv = funargs (map text ("cabal" : inputs)) $$ vcat
7979 , nest 2 $ vcat $
8080 [ attr "pname" $ string (pname deriv)
8181 , attr "version" $ doubleQuotes (disp (version deriv))
82− ] ++ maybeToList (fmap (attr "sha256" . string) $ sha256 deriv)
83− ++ maybeToList (fmap (attr "src" . text) $ src deriv)
84− ++
82+ ] ++ sourceAttrs (src deriv) ++
8583 [ boolattr "isLibrary" (not (isLibrary deriv) || isExecutable deriv) (isLibrary deriv)
8684 , boolattr "isExecutable" (not (isLibrary deriv) || isExecutable deriv) (isExecutable deriv)
8785 , listattr "buildDepends" (buildDepends deriv)
@@ -103,8 +101,15 @@ renderDerivation deriv = funargs (map text ("cabal" : inputs)) $$ vcat
103101 where
104102 inputs = nub $ sortBy (compare `on` map toLower) $ filter (/="cabal") $ filter (not . isPrefixOf "self.") $
105103 buildDepends deriv ++ testDepends deriv ++ buildTools deriv ++ extraLibs deriv ++ pkgConfDeps deriv ++ extraFunctionArgs deriv
104+ ++ ["fetchurl" | FetchUrl _ _ <- [src deriv]]
106105 renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text f | (FlagName f, enable) <- cabalFlags deriv ]
107106 ++ map text (configureFlags deriv)
107+ sourceAttrs (Hackage hash) = [attr "sha256" $ string hash]
108+ sourceAttrs (Directory dir) = [attr "src" $ text dir]
109+ sourceAttrs (FetchUrl url hash) = return $ attr "src" $ text "fetchurl" <+> lbrace $$ nest 2 (vcat
110+ [ attr "url" $ string url
111+ , attr "sha256" $ string hash
112+ ])
108113
109114-- | A very incomplete parser that extracts 'pname', 'version',
110115-- 'sha256', 'platforms', 'hydraPlatforms', 'maintainers', 'doCheck',
@@ -116,8 +121,9 @@ parseDerivation buf
116121 , [name] <- buf `regsubmatch` "pname *= *\"([^\"]+)\""
117122 , [vers'] <- buf `regsubmatch` "version *= *\"([^\"]+)\""
118123 , Just vers <- simpleParse vers'
119− , [sha] <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
124+ , sha <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
120125 , sr <- buf `regsubmatch` "src *= *([^;]+);"
126+ , url <- buf `regsubmatch` "url *= *([^;]+);"
121127 , hplats <- buf `regsubmatch` "hydraPlatforms *= *([^;]+);"
122128 , plats <- buf `regsubmatch` "platforms *= *([^;]+);"
123129 , maint <- buf `regsubmatch` "maintainers *= *\\[([^\"]+)]"
@@ -127,8 +133,11 @@ parseDerivation buf
127133 = Just MkDerivation
128134 { pname = name
129135 , version = vers
130− , sha256 = listToMaybe sha
131− , src = listToMaybe sr
136+ , src = case (sr,url) of
137+ ([] , _ ) -> Hackage $ head sha
138+ (_ , url':_ ) -> FetchUrl url' $ head sha
139+ (sr':_, [] ) -> Directory sr'
140+
132141 , isLibrary = False
133142 , isExecutable = False
134143 , extraFunctionArgs = []
src/cabal2nix.hs+7−20
@@ -1,19 +1,18 @@
11module Main ( main ) where
22
33import Cabal2Nix.Generate ( cabal2nix )
4−import Cabal2Nix.Hackage ( hashPackage, readCabalFile )
54import Cabal2Nix.Normalize ( normalize )
5+import Cabal2Nix.Package
66import Distribution.NixOS.Derivation.Cabal
77
88import Control.Exception ( bracket )
99import Control.Monad ( when )
10−import Distribution.PackageDescription ( package, packageDescription )
11−import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
1210import Distribution.Text ( disp )
1311import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
1412import System.Environment ( getArgs )
1513import System.Exit ( exitFailure, exitSuccess )
1614import System.IO ( hPutStrLn, hFlush, stdout, stderr )
15+import Text.URI ( parseURI )
1716
1817data Configuration = Configuration
1918 { optPrintHelp :: Bool
@@ -24,7 +23,6 @@ data Configuration = Configuration
2423 , optHaddock :: Bool
2524 , optDoCheck :: Bool
2625 , optJailbreak :: Bool
27− , optLocal :: Bool
2826 }
2927 deriving (Show)
3028
@@ -38,7 +36,6 @@ defaultConfiguration = Configuration
3836 , optHaddock = True
3937 , optDoCheck = True
4038 , optJailbreak = False
41− , optLocal = False
4239 }
4340
4441options :: [OptDescr (Configuration -> Configuration)]
@@ -50,7 +47,6 @@ options =
5047 , Option "" ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package"
5148 , Option "" ["no-check"] (NoArg (\o -> o { optDoCheck = False })) "don't run regression test suites of this package"
5249 , Option "" ["jailbreak"] (NoArg (\o -> o { optJailbreak = True })) "don't honor version restrictions on build inputs"
53− , Option "" ["local"] (NoArg (\o -> o { optLocal = True })) "assume a local package that is not on hackage yet. "
5450 ]
5551
5652usage :: String
@@ -79,26 +75,17 @@ main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
7975 when (optPrintHelp cfg) (putStr usage >> exitSuccess)
8076 when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
8177
82− cabal' <- fmap parsePackageDescription (readCabalFile (head args))
83− cabal <- case cabal' of
84− ParseOk _ a -> return a
85− ParseFailed err -> do
86− hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
87− exitFailure
78+ uri <- case parseURI (head args) of
79+ Nothing -> hPutStrLn stderr "*** invalid uri" >> exitFailure
80+ Just uri' -> return uri'
81+ pkg <- readPackage (optSha256 cfg) uri
8882
89− let packageId = package (packageDescription cabal)
90− sha <- case optSha256 cfg of
91− Just hash -> return $ Just hash
92− Nothing | optLocal cfg -> return Nothing
93− Nothing -> fmap Just $ hashPackage packageId
94−
95− let deriv = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
83+ let deriv = (cabal2nix $ cabal pkg) { src = source pkg, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
9684 deriv' = deriv { metaSection = (metaSection deriv)
9785 { maintainers = optMaintainer cfg
9886 , platforms = optPlatform cfg
9987 }
10088 , doCheck = doCheck deriv && optDoCheck cfg
101− , src = if optLocal cfg then Just "./." else Nothing
10289 }
10390
10491 putStr (show (disp (normalize deriv')))
src/hackage4nix.hs+2−2
@@ -1,7 +1,7 @@
11module Main ( main ) where
22
3−import Cabal2Nix.Normalize
43import Cabal2Nix.Generate
4+import Cabal2Nix.Normalize
55import Control.Exception ( bracket )
66import Control.Monad.RWS
77import Data.List
@@ -116,7 +116,7 @@ updateNixPkgs paths = do
116116 when regenerate $ do
117117 msgDebug ("re-generate " ++ path)
118118 pkg <- getCabalPackage (pname deriv) (version deriv)
119− let deriv' = (cabal2nix pkg) { sha256 = sha256 deriv
119+ let deriv' = (cabal2nix pkg) { src = src deriv
120120 , runHaddock = runHaddock deriv
121121 , doCheck = doCheck deriv
122122 , jailbreak = jailbreak deriv
test/doc-test.hs+1−1
@@ -20,7 +20,7 @@ main = do
2020 , "src/Cabal2Nix/CorePackages.hs"
2121 , "src/Cabal2Nix/Flags.hs"
2222 , "src/Cabal2Nix/Generate.hs"
23− , "src/Cabal2Nix/Hackage.hs"
23+ , "src/Cabal2Nix/Package.hs"
2424 , "src/Cabal2Nix/Normalize.hs"
2525 , "src/Cabal2Nix/Name.hs"
2626 , "src/Cabal2Nix/PostProcess.hs"