git.iko.soy/typeable/cabal2nix
on master help sign in
Support all VCS that have a nix-prefetch script
f6e1b43Benno Fünfstück
cabal2nix.cabal+4−2
@@ -49,7 +49,7 @@ Executable cabal2nix
49 main-is: cabal2nix.hs
49 main-is: cabal2nix.hs
50 hs-source-dirs: src
50 hs-source-dirs: src
51 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
51 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
52− filepath, directory, process, HTTP, hackage-db, uri
52+ filepath, directory, process, hackage-db, transformers
53 Extensions: PatternGuards, RecordWildCards, CPP
53 Extensions: PatternGuards, RecordWildCards, CPP
54 Ghc-Options: -Wall
54 Ghc-Options: -Wall
55 other-modules: Cabal2Nix.CorePackages
55 other-modules: Cabal2Nix.CorePackages
@@ -60,6 +60,7 @@ Executable cabal2nix
60 Cabal2Nix.Name
60 Cabal2Nix.Name
61 Cabal2Nix.Normalize
61 Cabal2Nix.Normalize
62 Cabal2Nix.PostProcess
62 Cabal2Nix.PostProcess
63+ Distribution.NixOS.Fetch
63 Distribution.NixOS.Derivation.Cabal
64 Distribution.NixOS.Derivation.Cabal
64 Distribution.NixOS.Derivation.License
65 Distribution.NixOS.Derivation.License
65 Distribution.NixOS.Derivation.Meta
66 Distribution.NixOS.Derivation.Meta
@@ -69,7 +70,7 @@ Executable hackage4nix
69 main-is: hackage4nix.hs
70 main-is: hackage4nix.hs
70 hs-source-dirs: src
71 hs-source-dirs: src
71 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
72 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
72− mtl, containers, directory, filepath, hackage-db
73+ mtl, containers, directory, filepath, hackage-db, transformers, process
73 Extensions: PatternGuards, RecordWildCards, CPP
74 Extensions: PatternGuards, RecordWildCards, CPP
74 Ghc-Options: -Wall
75 Ghc-Options: -Wall
75 other-modules: Cabal2Nix.CorePackages
76 other-modules: Cabal2Nix.CorePackages
@@ -79,6 +80,7 @@ Executable hackage4nix
79 Cabal2Nix.Name
80 Cabal2Nix.Name
80 Cabal2Nix.Normalize
81 Cabal2Nix.Normalize
81 Cabal2Nix.PostProcess
82 Cabal2Nix.PostProcess
83+ Distribution.NixOS.Fetch
82 Distribution.NixOS.Derivation.Cabal
84 Distribution.NixOS.Derivation.Cabal
83 Distribution.NixOS.Derivation.License
85 Distribution.NixOS.Derivation.License
84 Distribution.NixOS.Derivation.Meta
86 Distribution.NixOS.Derivation.Meta
src/Cabal2Nix/Package.hs+96−109
@@ -1,20 +1,18 @@
1module Cabal2Nix.Package where
1module Cabal2Nix.Package where
2
2
3−import Control.Exception ( handle, SomeException(..))
4import Control.Monad
3import Control.Monad
4+import Control.Monad.IO.Class
5+import Control.Monad.Trans.Maybe
5−import Data.Functor ( (<$>) )
6+import Data.Functor ( (<$>), (<$) )
6−import Data.List ( isSuffixOf )
7+import Data.List ( isSuffixOf, isPrefixOf )
7−import Data.Maybe ( fromMaybe, fromJust )
8+import Data.Maybe ( listToMaybe )
8import Distribution.NixOS.Derivation.Cabal
9import Distribution.NixOS.Derivation.Cabal
10+import Distribution.NixOS.Fetch
9import Distribution.Text ( simpleParse )
11import Distribution.Text ( simpleParse )
10−import Network.Browser ( browse, request, setCheckForProxy, setDebugLog, setOutHandler )
11−import Network.HTTP ( getRequest, rspBody )
12import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
12import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
13import System.Exit ( exitFailure )
13import System.Exit ( exitFailure )
14−import System.FilePath ( dropFileName, (</>), (<.>) )
14+import System.FilePath ( (</>), (<.>) )
15−import System.IO ( hPutStrLn, stderr)
15+import System.IO ( hPutStrLn, stderr, hPutStr )
16−import System.Process ( readProcess )
17−import Text.URI
18
16
19import qualified Distribution.Hackage.DB as DB
17import qualified Distribution.Hackage.DB as DB
20import qualified Distribution.Package as Cabal
18import qualified Distribution.Package as Cabal
@@ -26,102 +24,91 @@ data Package = Package
26 , cabal :: Cabal.GenericPackageDescription
24 , cabal :: Cabal.GenericPackageDescription
27 }
25 }
28
26
29−readPackage :: Maybe String -> URI -> IO Package
27+getPackage :: Source -> IO Package
30−readPackage optHash uri = case uriScheme uri of
28+getPackage source = do
31− Nothing -> local
29+ (derivSource, pkgDesc) <- fetchOrFromDB source
32− Just "file" -> local
30+ flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) $ showPackageIdentifier pkgDesc) return derivSource
33− Just "cabal" -> cabalPackage optHash $ fromMaybe "" (uriRegName uri) ++ uriPath uri
31+
34− Just scheme -> hPutStrLn stderr ("*** unsupported URI scheme: " ++ scheme) >> exitFailure
32+
35− where local = localPackage optHash $ fromMaybe "" (uriRegName uri) ++ uriPath uri
33+fetchOrFromDB :: Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
36−
34+fetchOrFromDB src
37−localPackage :: Maybe String -> FilePath -> IO Package
35+ | "cabal://" `isPrefixOf` sourceUrl src = fmap ((,) Nothing) . fromDB . drop (length "cabal://") $ sourceUrl src
38−localPackage optHash path = do
36+ | otherwise = do
39− isDir <- doesDirectoryExist path
37+ r <- fetch cabalFromPath src
40− cabalFile <- if isDir
41− then do
42− cabals <- filter (".cabal" `isSuffixOf`) <$> getDirectoryContents path
43− case cabals of
38+ case r of
44− [] -> hPutStrLn stderr "*** No cabal file found in given directory" >> exitFailure
39+ Nothing -> hPutStrLn stderr "*** failed to fetch source. Does the URL exist?" >> exitFailure
45− (f:_) -> return $ path </> f
40+ Just (derivSource, (externalSource, pkgDesc)) ->
46− else return path
41+ return (derivSource <$ guard externalSource, pkgDesc)
47− pkgDesc <- readFile cabalFile >>= readPackageDescription
42+
48− if isDir
43+fromDB :: String -> IO Cabal.GenericPackageDescription
49− then return $ Package (Directory path) pkgDesc
44+fromDB pkg = do
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
45+ pkgDesc <- (lookupVersion <=< DB.lookup name) <$> DB.readHackage
59− case packageDescription of
46+ case pkgDesc of
60− Just d -> return $ snd $ last $ DB.toList d
47+ Just r -> return r
61− Nothing -> error "No such package"
48+ Nothing -> hPutStrLn stderr "*** no such package in the cabal database (did you run cabal update?). " >> exitFailure
62− _ -> fetchUrl (hackagePath (pid packageName) Cabal) >>= readPackageDescription
49+ where
63− hash <- getHash optHash pkgDesc
64− return $ Package (Hackage hash) pkgDesc
65−
66− where pid p = fromJust $ simpleParse p
50+ Just pkgId = simpleParse pkg
67−
51+ Cabal.PackageName name = Cabal.pkgName pkgId
68−fetchUrl :: String -> IO String
52+ version = Cabal.pkgVersion pkgId
69−fetchUrl url = do
53+
70− (_,rsp) <- browse $ do
54+ lookupVersion :: DB.Map DB.Version Cabal.GenericPackageDescription -> Maybe Cabal.GenericPackageDescription
71− setCheckForProxy True
55+ lookupVersion
72− setDebugLog Nothing
56+ | null (versionBranch version) = fmap snd . listToMaybe . reverse . DB.toAscList
73− setOutHandler (\_ -> return ())
57+ | otherwise = DB.lookup version
74− request (getRequest url)
58+
75− return (rspBody rsp)
59+
76−
60+readFileMay :: String -> IO (Maybe String)
77−readPackageDescription :: String -> IO Cabal.GenericPackageDescription
61+readFileMay file = do
78−readPackageDescription source = case Cabal.parsePackageDescription source of
62+ e <- doesFileExist file
79− Cabal.ParseOk _ a -> return a
63+ if e
80− Cabal.ParseFailed err -> do
64+ then Just <$> readFile file
81− hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
65+ else return Nothing
82− exitFailure
66+
83−
67+hashCachePath :: String -> IO String
84−getHash :: Maybe String -> Cabal.GenericPackageDescription -> IO String
68+hashCachePath pid = do
85−getHash optHash pkgDesc = maybe (hashPackage $ Cabal.package $ Cabal.packageDescription pkgDesc) return optHash
69+ home <- getHomeDirectory
86−
70+ let cacheDir = home </> ".cache/cabal2nix"
87−data Ext = TarGz | Cabal deriving Eq
71+ createDirectoryIfMissing True cacheDir
88−
72+ return $ cacheDir </> pid <.> "sha256"
89−showExt :: Ext -> String
73+
90−showExt TarGz = ".tar.gz"
74+sourceFromHackage :: Maybe String -> String -> IO DerivationSource
91−showExt Cabal = ".cabal"
75+sourceFromHackage optHash pkgId = do
92−
76+ cacheFile <- hashCachePath pkgId
93−hackagePath :: Cabal.PackageIdentifier -> Ext -> String
77+ hash <- maybe (readFileMay cacheFile) (return . Just) optHash
94−hackagePath (Cabal.PackageIdentifier (Cabal.PackageName name) version') ext =
78+ res <- runMaybeT $ fst <$> fetchWith (False, "url") (Source ("mirror://hackage/" ++ pkgId ++ ".tar.gz") "" hash)
95− "http://hackage.haskell.org/packages/archive/" ++
79+ case res of
96− name ++ "/" ++ version ++ "/" ++ name ++
80+ Just r -> writeFile cacheFile (derivHash r) >> return r
97− (if ext == TarGz then '-' : version else "") ++
81+ Nothing -> do
98− showExt ext
82+ hPutStr stderr $ unlines
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" ++
83+ [ "*** cannot compute hash. (Not a hackage project?)"
119− "Specify hash explicitly via --sha256 and add appropriate \"src\" attribute " ++
84+ , " Specify hash explicitly via --sha256 and add appropriate \"src\" attribute"
120− "to resulting nix expression."
85+ , " to resulting nix expression."
121−
86+ ]
122−hashCachePath :: Cabal.PackageIdentifier -> IO FilePath
87+ exitFailure
123−hashCachePath (Cabal.PackageIdentifier (Cabal.PackageName name) version') = do
88+
124− home <- getHomeDirectory
89+showPackageIdentifier :: Cabal.GenericPackageDescription -> String
125− return $ home ++ "/.cache/cabal2nix" </> name ++ "-" ++ version <.> "sha256"
90+showPackageIdentifier pkgDesc = name ++ "-" ++ showVersion version where
126− where
91+ pkgId = Cabal.package . Cabal.packageDescription $ pkgDesc
127− version = showVersion version'
92+ Cabal.PackageName name = Cabal.packageName pkgId
93+ version = Cabal.packageVersion pkgId
94+
95+cabalFromPath :: FilePath -> MaybeT IO (Bool, Cabal.GenericPackageDescription)
96+cabalFromPath path = do
97+ d <- liftIO $ doesDirectoryExist path
98+ (,) d <$> if d
99+ then cabalFromDirectory path
100+ else cabalFromFile path
101+
102+cabalFromDirectory :: FilePath -> MaybeT IO Cabal.GenericPackageDescription
103+cabalFromDirectory dir = do
104+ cabals <- liftIO $ getDirectoryContents dir >>= filterM doesFileExist . map (dir </>) . filter (".cabal" `isSuffixOf`)
105+ case cabals of
106+ [cabalFile] -> cabalFromFile cabalFile
107+ _ -> liftIO $ hPutStrLn stderr "*** found zero or more than one cabal file. Exiting." >> exitFailure
108+
109+cabalFromFile :: FilePath -> MaybeT IO Cabal.GenericPackageDescription
110+cabalFromFile file = do
111+ content <- liftIO $ readFile file
112+ case Cabal.parsePackageDescription content of
113+ Cabal.ParseFailed _ -> mzero
114+ Cabal.ParseOk _ a -> return a
src/Distribution/NixOS/Derivation/Cabal.hs+21−16
@@ -21,6 +21,7 @@ module Distribution.NixOS.Derivation.Cabal
21 where
21 where
22
22
23import Distribution.NixOS.Derivation.Meta
23import Distribution.NixOS.Derivation.Meta
24+import Distribution.NixOS.Fetch
24import Distribution.NixOS.PrettyPrinting
25import Distribution.NixOS.PrettyPrinting
25import Distribution.Package
26import Distribution.Package
26import Distribution.Text
27import Distribution.Text
@@ -34,8 +35,6 @@ import Data.Char
34import Data.Function
35import Data.Function
35import Text.Regex.Posix hiding ( empty )
36import Text.Regex.Posix hiding ( empty )
36
37
37−data DerivationSource = Hackage String | Directory String | FetchUrl String String deriving (Eq, Show, Ord)
38−
39-- | A represtation of Nix expressions for building Haskell packages.
38-- | A represtation of Nix expressions for building Haskell packages.
40-- The data type correspond closely to the definition of
39-- The data type correspond closely to the definition of
41-- 'PackageDescription' from Cabal.
40-- 'PackageDescription' from Cabal.
@@ -79,8 +78,8 @@ renderDerivation deriv = funargs (map text ("cabal" : inputs)) $$ vcat
79 , nest 2 $ vcat $
78 , nest 2 $ vcat $
80 [ attr "pname" $ string (pname deriv)
79 [ attr "pname" $ string (pname deriv)
81 , attr "version" $ doubleQuotes (disp (version deriv))
80 , attr "version" $ doubleQuotes (disp (version deriv))
82− ] ++ sourceAttrs (src deriv) ++
81+ , sourceAttr (src deriv)
83− [ boolattr "isLibrary" (not (isLibrary deriv) || isExecutable deriv) (isLibrary deriv)
82+ , boolattr "isLibrary" (not (isLibrary deriv) || isExecutable deriv) (isLibrary deriv)
84 , boolattr "isExecutable" (not (isLibrary deriv) || isExecutable deriv) (isExecutable deriv)
83 , boolattr "isExecutable" (not (isLibrary deriv) || isExecutable deriv) (isExecutable deriv)
85 , listattr "buildDepends" (buildDepends deriv)
84 , listattr "buildDepends" (buildDepends deriv)
86 , listattr "testDepends" (testDepends deriv)
85 , listattr "testDepends" (testDepends deriv)
@@ -101,20 +100,24 @@ renderDerivation deriv = funargs (map text ("cabal" : inputs)) $$ vcat
101 where
100 where
102 inputs = nub $ sortBy (compare `on` map toLower) $ filter (/="cabal") $ filter (not . isPrefixOf "self.") $
101 inputs = nub $ sortBy (compare `on` map toLower) $ filter (/="cabal") $ filter (not . isPrefixOf "self.") $
103 buildDepends deriv ++ testDepends deriv ++ buildTools deriv ++ extraLibs deriv ++ pkgConfDeps deriv ++ extraFunctionArgs deriv
102 buildDepends deriv ++ testDepends deriv ++ buildTools deriv ++ extraLibs deriv ++ pkgConfDeps deriv ++ extraFunctionArgs deriv
104− ++ ["fetchurl" | FetchUrl _ _ <- [src deriv]]
103+ ++ ["fetch" ++ derivKind (src deriv) | derivKind (src deriv) /= ""]
105 renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text f | (FlagName f, enable) <- cabalFlags deriv ]
104 renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text f | (FlagName f, enable) <- cabalFlags deriv ]
106 ++ map text (configureFlags deriv)
105 ++ map text (configureFlags deriv)
107− sourceAttrs (Hackage hash) = [attr "sha256" $ string hash]
106+ sourceAttr (DerivationSource{..})
108− sourceAttrs (Directory dir) = [attr "src" $ text dir]
107+ | derivKind /= "" = vcat
109− sourceAttrs (FetchUrl url hash) = return $ attr "src" $ text "fetchurl" <+> lbrace $$ nest 2 (vcat
108+ [ text "src" <+> equals <+> text ("fetch" ++ derivKind) <+> lbrace
109+ , nest 2 $ vcat
110− [ attr "url" $ string url
110+ [ attr "url" $ string derivUrl
111− , attr "sha256" $ string hash
111+ , attr "sha256" $ string derivHash
112− ])
112+ , if derivRevision /= "" then attr "rev" (string derivRevision) else empty
113+ ]
114+ , rbrace <> semi
115+ ]
116+ | otherwise = attr "src" $ text derivUrl
113
117
114-- | A very incomplete parser that extracts 'pname', 'version',
118-- | A very incomplete parser that extracts 'pname', 'version',
115-- 'sha256', 'platforms', 'hydraPlatforms', 'maintainers', 'doCheck',
119-- 'sha256', 'platforms', 'hydraPlatforms', 'maintainers', 'doCheck',
116-- 'jailbreak', and 'runHaddock' from the given Nix expression.
120-- 'jailbreak', and 'runHaddock' from the given Nix expression.
117−
118parseDerivation :: String -> Maybe Derivation
121parseDerivation :: String -> Maybe Derivation
119parseDerivation buf
122parseDerivation buf
120 | buf =~ "cabal.mkDerivation"
123 | buf =~ "cabal.mkDerivation"
@@ -123,7 +126,8 @@ parseDerivation buf
123 , Just vers <- simpleParse vers'
126 , Just vers <- simpleParse vers'
124 , sha <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
127 , sha <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
125 , sr <- buf `regsubmatch` "src *= *([^;]+);"
128 , sr <- buf `regsubmatch` "src *= *([^;]+);"
126− , url <- buf `regsubmatch` "url *= *([^;]+);"
129+ , url <- buf `regsubmatch` "url *= *\"?([^;\"]+)\"? *;"
130+ , rev <- buf `regsubmatch` "rev *= *\"([^\"]+)\""
127 , hplats <- buf `regsubmatch` "hydraPlatforms *= *([^;]+);"
131 , hplats <- buf `regsubmatch` "hydraPlatforms *= *([^;]+);"
128 , plats <- buf `regsubmatch` "platforms *= *([^;]+);"
132 , plats <- buf `regsubmatch` "platforms *= *([^;]+);"
129 , maint <- buf `regsubmatch` "maintainers *= *\\[([^\"]+)]"
133 , maint <- buf `regsubmatch` "maintainers *= *\\[([^\"]+)]"
@@ -134,9 +138,10 @@ parseDerivation buf
134 { pname = name
138 { pname = name
135 , version = vers
139 , version = vers
136 , src = case (sr,url) of
140 , src = case (sr,url) of
137− ([] , _ ) -> Hackage $ head sha
141+ ([] , _ ) -> DerivationSource "url" ("mirror://hackage/" ++ name ++ "-" ++ vers' ++ ".tar.gz") "" $ head sha
138− (_ , url':_ ) -> FetchUrl url' $ head sha
142+ (sr':_, [] ) -> DerivationSource "" sr' "" ""
139− (sr':_, [] ) -> Directory sr'
143+ (sr':_, url':_ ) -> DerivationSource (drop (length "fetch") . head . words $ sr')
144+ url' (head $ rev ++ [""]) $ head sha
140
145
141 , isLibrary = False
146 , isLibrary = False
142 , isExecutable = False
147 , isExecutable = False
src/Distribution/NixOS/Fetch.hsnew file+126
@@ -0,0 +1,126 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE RecordWildCards #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+module Distribution.NixOS.Fetch
5+ ( Source(..)
6+ , DerivationSource(..), fromDerivationSource
7+ , fetch
8+ , fetchWith
9+ ) where
10+
11+import Control.Applicative
12+import Control.Monad
13+import Control.Monad.IO.Class
14+import Control.Monad.Trans.Maybe
15+import Data.Maybe
16+import System.Directory
17+import System.Environment
18+import System.Exit
19+import System.IO
20+import System.Process
21+
22+-- | A source is a location from which we can fetch, such as a HTTP URL, a GIT URL, ....
23+data Source = Source
24+ { sourceUrl :: String -- ^ URL to fetch from.
25+ , sourceRevision :: String -- ^ Revision to use. For protocols where this doesn't make sense (such as HTTP), this
26+ -- should be the empty string.
27+ , sourceHash :: Maybe String -- ^ The expected hash of the source, if available.
28+ } deriving (Show, Eq, Ord)
29+
30+-- | A source for a derivation. It always needs a hash and also has a protocol attached to it (url, git, svn, ...).
31+-- A @DerivationSource@ also always has it's revision fully resolved (not relative revisions like @master@, @HEAD@, etc).
32+data DerivationSource = DerivationSource
33+ { derivKind :: String -- ^ The kind of the source. The name of the build-support fetch derivation should be fetch<kind>.
34+ , derivUrl :: String -- ^ URL to fetch from.
35+ , derivRevision :: String -- ^ Revision to use. Leave empty if the fetcher doesn't support revisions.
36+ , derivHash :: String -- ^ The hash of the source.
37+ } deriving (Show, Eq, Ord)
38+
39+fromDerivationSource :: DerivationSource -> Source
40+fromDerivationSource DerivationSource{..} = Source derivUrl derivRevision $ Just derivHash
41+
42+-- | Fetch a source, trying any of the various nix-prefetch-* scripts.
43+fetch :: forall a. (String -> MaybeT IO a) -- ^ This function is passed the output path name as an argument.
44+ -- It should return 'Nothing' if the file doesn't match the expected format.
45+ -- This is required, because we cannot always check if a download succeeded otherwise.
46+ -> Source -- ^ The source to fetch from.
47+ -> IO (Maybe (DerivationSource, a)) -- ^ The derivation source and the result of the processing function. Returns Nothing if the download failed.
48+fetch f = runMaybeT . fetchers where
49+ fetchers :: Source -> MaybeT IO (DerivationSource, a)
50+ fetchers source = msum . (fetchLocal source :) $ map (\fetcher -> fetchWith fetcher source >>= process)
51+ [ (False, "url")
52+ , (True, "git")
53+ , (True, "hg")
54+ , (True, "svn")
55+ , (True, "bzr")
56+ ]
57+
58+ fetchLocal :: Source -> MaybeT IO (DerivationSource, a)
59+ fetchLocal src = do
60+ let path = stripPrefix "file://" $ sourceUrl src
61+ existsFile <- liftIO $ doesFileExist path
62+ existsDir <- liftIO $ doesDirectoryExist path
63+ guard $ existsDir || existsFile
64+ process (DerivationSource "" path "" "", path)
65+
66+ process :: (DerivationSource, FilePath) -> MaybeT IO (DerivationSource, a)
67+ process (derivSource, file) = (,) derivSource <$> f file
68+
69+-- | Like 'fetch', but allows to specify which script to use.
70+fetchWith :: (Bool, String) -> Source -> MaybeT IO (DerivationSource, FilePath)
71+fetchWith (supportsRev, kind) source = do
72+ liftIO $ hPutStrLn stderr $ "* trying fetcher: " ++ kind
73+
74+ unless ((sourceRevision source /= "") || isNothing (sourceHash source) || not supportsRev) $
75+ liftIO (hPutStrLn stderr "** need a revision for VCS when the hash is given. skipping.") >> mzero
76+
77+ MaybeT $ liftIO $ do
78+ envs <- getEnvironment
79+ (Nothing, Just stdoutH, _, processH) <- createProcess (proc script args)
80+ { env = Just $ ("PRINT_PATH", "1") : envs
81+ , std_in = Inherit
82+ , std_err = CreatePipe
83+ , std_out = CreatePipe
84+ }
85+
86+ exitCode <- waitForProcess processH
87+ case exitCode of
88+ ExitSuccess -> fmap Just . processOutputSwitch . lines =<< hGetContents stdoutH
89+ ExitFailure _ -> return Nothing
90+
91+ where
92+
93+ script :: String
94+ script = "nix-prefetch-" ++ kind
95+
96+ args :: [String]
97+ args = sourceUrl source : [ sourceRevision source | supportsRev ] ++ maybeToList (sourceHash source)
98+
99+ processOutputWithRev :: [String] -> IO (DerivationSource, FilePath)
100+ processOutputWithRev [rev,hash,path] = return (DerivationSource kind (sourceUrl source) (extractRevision rev) hash, path)
101+ processOutputWithRev out = unexpectedOutput out
102+
103+ extractRevision :: String -> String
104+ extractRevision = unwords . drop 3 . words -- drop the "<vcs> revision is" prefix
105+
106+ processOutput :: [String] -> IO (DerivationSource, FilePath)
107+ processOutput [hash,path] = return (DerivationSource kind (sourceUrl source) (sourceRevision source) hash, path)
108+ processOutput out = unexpectedOutput out
109+
110+ unexpectedOutput :: [String] -> IO a
111+ unexpectedOutput out = do
112+ hPutStrLn stderr $ "*** unexpected output from " ++ "nix-prefetch-" ++ kind ++ " script: "
113+ hPutStr stderr $ unlines out
114+ exitFailure
115+
116+ processOutputSwitch :: [String] -> IO (DerivationSource, FilePath)
117+ processOutputSwitch
118+ | supportsRev && isNothing (sourceHash source) = processOutputWithRev
119+ | otherwise = processOutput
120+
121+stripPrefix :: Eq a => [a] -> [a] -> [a]
122+stripPrefix prefix as
123+ | prefix' == prefix = stripped
124+ | otherwise = as
125+ where
126+ (prefix', stripped) = splitAt (length prefix) as
src/cabal2nix.hs+14−11
@@ -4,6 +4,7 @@ import Cabal2Nix.Generate ( cabal2nix )
4import Cabal2Nix.Normalize ( normalize )
4import Cabal2Nix.Normalize ( normalize )
5import Cabal2Nix.Package
5import Cabal2Nix.Package
6import Distribution.NixOS.Derivation.Cabal
6import Distribution.NixOS.Derivation.Cabal
7+import Distribution.NixOS.Fetch
7
8
8import Control.Exception ( bracket )
9import Control.Exception ( bracket )
9import Control.Monad ( when )
10import Control.Monad ( when )
@@ -12,7 +13,6 @@ import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageIn
12import System.Environment ( getArgs )
13import System.Environment ( getArgs )
13import System.Exit ( exitFailure, exitSuccess )
14import System.Exit ( exitFailure, exitSuccess )
14import System.IO ( hPutStrLn, hFlush, stdout, stderr )
15import System.IO ( hPutStrLn, hFlush, stdout, stderr )
15−import Text.URI ( parseURI )
16
16
17data Configuration = Configuration
17data Configuration = Configuration
18 { optPrintHelp :: Bool
18 { optPrintHelp :: Bool
@@ -23,6 +23,7 @@ data Configuration = Configuration
23 , optHaddock :: Bool
23 , optHaddock :: Bool
24 , optDoCheck :: Bool
24 , optDoCheck :: Bool
25 , optJailbreak :: Bool
25 , optJailbreak :: Bool
26+ , optRevision :: String
26 }
27 }
27 deriving (Show)
28 deriving (Show)
28
29
@@ -36,6 +37,7 @@ defaultConfiguration = Configuration
36 , optHaddock = True
37 , optHaddock = True
37 , optDoCheck = True
38 , optDoCheck = True
38 , optJailbreak = False
39 , optJailbreak = False
40+ , optRevision = ""
39 }
41 }
40
42
41options :: [OptDescr (Configuration -> Configuration)]
43options :: [OptDescr (Configuration -> Configuration)]
@@ -47,6 +49,7 @@ options =
47 , Option "" ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package"
49 , Option "" ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package"
48 , Option "" ["no-check"] (NoArg (\o -> o { optDoCheck = False })) "don't run regression test suites of this package"
50 , Option "" ["no-check"] (NoArg (\o -> o { optDoCheck = False })) "don't run regression test suites of this package"
49 , Option "" ["jailbreak"] (NoArg (\o -> o { optJailbreak = True })) "don't honor version restrictions on build inputs"
51 , Option "" ["jailbreak"] (NoArg (\o -> o { optJailbreak = True })) "don't honor version restrictions on build inputs"
52+ , Option "" ["rev"] (ReqArg (\x o -> o { optRevision = x }) "REVISION") "revision, only used when fetching from VCS"
50 ]
53 ]
51
54
52usage :: String
55usage :: String
@@ -54,13 +57,16 @@ usage = usageInfo "Usage: cabal2nix [options] URI" options ++ unlines
54 [ ""
57 [ ""
55 , "Recognized URI schemes:"
58 , "Recognized URI schemes:"
56 , ""
59 , ""
57− , " cabal://pkgname-pkgversion download the specified package from Hackage"
60+ , " cabal://pkgname-pkgversion download the specified package from Hackage"
58− , " cabal://pkgname download latest version of the specified package from Hackage"
61+ , " cabal://pkgname download latest version of the specified package from Hackage"
59− , " file:///local/path load the Cabal file from the local disk"
62+ , " file:///local/path load the Cabal file from the local disk"
60− , " /local/path abbreviated version of file URI"
63+ , " /local/path abbreviated version of file URI"
64+ , " <git/svn/bzr/hg URL> download the source from the specified repository"
61 , ""
65 , ""
62− , "If URI is a local path and is not a file, then it will be used as the source of the package. Otherwise, the source"
66+ , "If the URI refers to a cabal file, information for building the package will be retrieved from that file, but "
63− , "will be downloaded from hackage."
67+ , "hackage will be used as a source for the derivation."
68+ , "Otherwise, the supplied URI will be used to as the source for the derivation and the information is taken"
69+ , "from the cabal file at the root of the downloaded source."
64 ]
70 ]
65
71
66cmdlineError :: String -> IO a
72cmdlineError :: String -> IO a
@@ -77,10 +83,7 @@ main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
77 when (optPrintHelp cfg) (putStr usage >> exitSuccess)
83 when (optPrintHelp cfg) (putStr usage >> exitSuccess)
78 when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
84 when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
79
85
80− uri <- case parseURI (head args) of
86+ pkg <- getPackage $ Source (head args) (optRevision cfg) (optSha256 cfg)
81− Nothing -> hPutStrLn stderr "*** invalid uri" >> exitFailure
82− Just uri' -> return uri'
83− pkg <- readPackage (optSha256 cfg) uri
84
87
85 let deriv = (cabal2nix $ cabal pkg) { src = source pkg, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
88 let deriv = (cabal2nix $ cabal pkg) { src = source pkg, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
86 deriv' = deriv { metaSection = (metaSection deriv)
89 deriv' = deriv { metaSection = (metaSection deriv)