git.iko.soy/typeable/cabal2nix
on master help sign in
Merge pull request #59 from bennofs/feature-local-only

Support local-only projects

commit76f685c8f25274934bf9e3ef05ed8a82e98ab890 authorPeter Simons <simons@cryp.to> dateSat 16 Aug 2014, 17:12 (12 years ago) parenta11edb55f0d5a2
10 files changed+306−125
cabal2nix.cabal+5−3 release.nix+1−1 src/Cabal2Nix/Generate.hs+4−5 src/Cabal2Nix/Hackage.hs−84 src/Cabal2Nix/Package.hs+114 src/Distribution/NixOS/Derivation/Cabal.hs+30−8 src/Distribution/NixOS/Fetch.hs+127 src/cabal2nix.hs+18−21 src/hackage4nix.hs+5−2 test/doc-test.hs+2−1
cabal2nix.cabal+5−3
@@ -52,17 +52,18 @@ Executable cabal2nix
5252 main-is: cabal2nix.hs
5353 hs-source-dirs: src
5454 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
55− filepath, directory, process, HTTP, hackage-db
55+ filepath, directory, process, hackage-db, transformers
5656 Extensions: PatternGuards, RecordWildCards, CPP
5757 Ghc-Options: -Wall
5858 other-modules: Cabal2Nix.CorePackages
5959 Cabal2Nix.Flags
6060 Cabal2Nix.Generate
61− Cabal2Nix.Hackage
61+ Cabal2Nix.Package
6262 Cabal2Nix.License
6363 Cabal2Nix.Name
6464 Cabal2Nix.Normalize
6565 Cabal2Nix.PostProcess
66+ Distribution.NixOS.Fetch
6667 Distribution.NixOS.Derivation.Cabal
6768 Distribution.NixOS.Derivation.License
6869 Distribution.NixOS.Derivation.Meta
@@ -73,7 +74,7 @@ Executable hackage4nix
7374 main-is: hackage4nix.hs
7475 hs-source-dirs: src
7576 Build-Depends: base >= 3 && < 5, regex-posix, pretty, Cabal >= 1.16,
76− mtl, containers, directory, filepath, hackage-db
77+ mtl, containers, directory, filepath, hackage-db, transformers, process
7778 Extensions: PatternGuards, RecordWildCards, CPP
7879 Ghc-Options: -Wall
7980 other-modules: Cabal2Nix.CorePackages
@@ -83,6 +84,7 @@ Executable hackage4nix
8384 Cabal2Nix.Name
8485 Cabal2Nix.Normalize
8586 Cabal2Nix.PostProcess
87+ Distribution.NixOS.Fetch
8688 Distribution.NixOS.Derivation.Cabal
8789 Distribution.NixOS.Derivation.License
8890 Distribution.NixOS.Derivation.Meta
release.nix+1−1
@@ -21,7 +21,7 @@ in
2121 version = cabal2nixSrc.gitTag;
2222 isLibrary = false;
2323 isExecutable = true;
24− buildDepends = with haskellPackages; [ Cabal filepath hackageDb HTTP mtl regexPosix ];
24+ buildDepends = with haskellPackages; [ Cabal filepath hackageDb mtl regexPosix transformers ];
2525 testDepends = with haskellPackages; [ doctest ];
2626 doCheck = self.stdenv.lib.versionOlder "7.6" self.ghc.version;
2727 meta = {
src/Cabal2Nix/Generate.hs+4−5
@@ -1,23 +1,22 @@
11module Cabal2Nix.Generate ( cabal2nix ) where
22
3+import Cabal2Nix.Flags
34import Cabal2Nix.License
4−import Cabal2Nix.PostProcess
55import Cabal2Nix.Normalize
6−import Cabal2Nix.Flags
6+import Cabal2Nix.PostProcess
77import Data.Maybe
88import Distribution.Compiler
9+import Distribution.NixOS.Derivation.Cabal
910import qualified Distribution.Package as Cabal
1011import qualified Distribution.PackageDescription as Cabal
1112import Distribution.PackageDescription.Configuration
1213import Distribution.System
13−import Distribution.Version
14−import Distribution.NixOS.Derivation.Cabal
1514
1615cabal2nix :: Cabal.GenericPackageDescription -> Derivation
1716cabal2nix cabal = normalize $ postProcess MkDerivation
1817 { pname = let Cabal.PackageName x = Cabal.pkgName pkg in x
1918 , version = Cabal.pkgVersion pkg
20− , sha256 = "cabal2nix left the sha256 field undefined"
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 :: Maybe FilePath -> FilePath -> IO String
61−readCabalFile hackageDbPath 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` maybe DB.readHackage DB.readHackage' hackageDbPath
67− case packageDescription of
68− Just d -> do
69− let version = showVersion $ last $ DB.keys d
70− readCabalFile Nothing $ hackagePath (pid $ packageName ++ "-" ++ version) Cabal
71− Nothing -> error "No such package"
72− _ -> readCabalFile Nothing $ hackagePath (pid packageName) Cabal
73− | "http://" `isPrefixOf` path = fetchUrl path
74− | "file://" `isPrefixOf` path = readCabalFile Nothing (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+114
@@ -0,0 +1,114 @@
1+module Cabal2Nix.Package where
2+
3+import Control.Monad
4+import Control.Monad.IO.Class
5+import Control.Monad.Trans.Maybe
6+import Data.Functor ( (<$>), (<$) )
7+import Data.List ( isSuffixOf, isPrefixOf )
8+import Data.Maybe ( listToMaybe )
9+import Distribution.NixOS.Derivation.Cabal
10+import Distribution.NixOS.Fetch
11+import Distribution.Text ( simpleParse )
12+import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
13+import System.Exit ( exitFailure )
14+import System.FilePath ( (</>), (<.>) )
15+import System.IO ( hPutStrLn, stderr, hPutStr )
16+
17+import qualified Distribution.Hackage.DB as DB
18+import qualified Distribution.Package as Cabal
19+import qualified Distribution.PackageDescription as Cabal
20+import qualified Distribution.PackageDescription.Parse as Cabal
21+
22+data Package = Package
23+ { source :: DerivationSource
24+ , cabal :: Cabal.GenericPackageDescription
25+ }
26+
27+getPackage :: Maybe String -> Source -> IO Package
28+getPackage optHackageDB source = do
29+ (derivSource, pkgDesc) <- fetchOrFromDB optHackageDB source
30+ flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) $ showPackageIdentifier pkgDesc) return derivSource
31+
32+
33+fetchOrFromDB :: Maybe String -> Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
34+fetchOrFromDB optHackageDB src
35+ | "cabal://" `isPrefixOf` sourceUrl src = fmap ((,) Nothing) . fromDB optHackageDB . drop (length "cabal://") $ sourceUrl src
36+ | otherwise = do
37+ r <- fetch cabalFromPath src
38+ case r of
39+ Nothing -> hPutStrLn stderr "*** failed to fetch source. Does the URL exist?" >> exitFailure
40+ Just (derivSource, (externalSource, pkgDesc)) ->
41+ return (derivSource <$ guard externalSource, pkgDesc)
42+
43+fromDB :: Maybe String -> String -> IO Cabal.GenericPackageDescription
44+fromDB optHackageDB pkg = do
45+ pkgDesc <- (lookupVersion <=< DB.lookup name) <$> maybe DB.readHackage DB.readHackage' optHackageDB
46+ case pkgDesc of
47+ Just r -> return r
48+ Nothing -> hPutStrLn stderr "*** no such package in the cabal database (did you run cabal update?). " >> exitFailure
49+ where
50+ Just pkgId = simpleParse pkg
51+ Cabal.PackageName name = Cabal.pkgName pkgId
52+ version = Cabal.pkgVersion pkgId
53+
54+ lookupVersion :: DB.Map DB.Version Cabal.GenericPackageDescription -> Maybe Cabal.GenericPackageDescription
55+ lookupVersion
56+ | null (versionBranch version) = fmap snd . listToMaybe . reverse . DB.toAscList
57+ | otherwise = DB.lookup version
58+
59+
60+readFileMay :: String -> IO (Maybe String)
61+readFileMay file = do
62+ e <- doesFileExist file
63+ if e
64+ then Just <$> readFile file
65+ else return Nothing
66+
67+hashCachePath :: String -> IO String
68+hashCachePath pid = do
69+ home <- getHomeDirectory
70+ let cacheDir = home </> ".cache/cabal2nix"
71+ createDirectoryIfMissing True cacheDir
72+ return $ cacheDir </> pid <.> "sha256"
73+
74+sourceFromHackage :: Maybe String -> String -> IO DerivationSource
75+sourceFromHackage optHash pkgId = do
76+ cacheFile <- hashCachePath pkgId
77+ hash <- maybe (readFileMay cacheFile) (return . Just) optHash
78+ res <- runMaybeT $ fst <$> fetchWith (False, "url") (Source ("mirror://hackage/" ++ pkgId ++ ".tar.gz") "" hash)
79+ case res of
80+ Just r -> writeFile cacheFile (derivHash r) >> return r
81+ Nothing -> do
82+ hPutStr stderr $ unlines
83+ [ "*** cannot compute hash. (Not a hackage project?)"
84+ , " Specify hash explicitly via --sha256 and add appropriate \"src\" attribute"
85+ , " to resulting nix expression."
86+ ]
87+ exitFailure
88+
89+showPackageIdentifier :: Cabal.GenericPackageDescription -> String
90+showPackageIdentifier pkgDesc = name ++ "-" ++ showVersion version where
91+ pkgId = Cabal.package . Cabal.packageDescription $ pkgDesc
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+30−8
@@ -1,4 +1,4 @@
1−{-# LANGUAGE PatternGuards, CPP #-}
1+{-# LANGUAGE PatternGuards, RecordWildCards, CPP #-}
22{- |
33 Module : Distribution.NixOS.Derivation.Cabal
44 License : BSD3
@@ -14,16 +14,18 @@
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
2223import Distribution.NixOS.Derivation.Meta
24+import Distribution.NixOS.Fetch
2325import Distribution.NixOS.PrettyPrinting
2426import Distribution.NixOS.Regex hiding ( empty )
25−import Distribution.Text
2627import Distribution.Package
28+import Distribution.Text
2729#ifdef __HADDOCK__
2830import Distribution.PackageDescription ( PackageDescription )
2931#endif
@@ -39,11 +41,10 @@ import Data.Function
3941--
4042-- Note that the "Text" instance definition provides pretty-printing,
4143-- but no parsing as of now!
42−
4344data Derivation = MkDerivation
4445 { pname :: String
4546 , version :: Version
46− , sha256 :: String
47+ , src :: DerivationSource
4748 , isLibrary :: Bool
4849 , isExecutable :: Bool
4950 , extraFunctionArgs :: [String]
@@ -82,7 +83,7 @@ renderDerivation deriv =
8283 , nest 2 $ vcat
8384 [ attr "pname" $ string (pname deriv)
8485 , attr "version" $ doubleQuotes (disp (version deriv))
85− , attr "sha256" $ string (sha256 deriv)
86+ , sourceAttr (src deriv)
8687 , boolattr "isLibrary" (not (isLibrary deriv) || isExecutable deriv) (isLibrary deriv)
8788 , boolattr "isExecutable" (not (isLibrary deriv) || isExecutable deriv) (isExecutable deriv)
8889 , listattr "buildDepends" empty (buildDepends deriv)
@@ -106,20 +107,36 @@ renderDerivation deriv =
106107 where
107108 inputs = nub $ sortBy (compare `on` map toLower) $ filter (/="cabal") $ filter (not . isPrefixOf "self.") $
108109 buildDepends deriv ++ testDepends deriv ++ buildTools deriv ++ extraLibs deriv ++ pkgConfDeps deriv ++ extraFunctionArgs deriv
110+ ++ ["fetch" ++ derivKind (src deriv) | derivKind (src deriv) /= "" && not isHackagePackage]
109111 renderedFlags = [ text "-f" <> (if enable then empty else char '-') <> text f | (FlagName f, enable) <- cabalFlags deriv ]
110112 ++ map text (configureFlags deriv)
113+ isHackagePackage = "mirror://hackage/" `isPrefixOf` derivUrl (src deriv)
114+ sourceAttr (DerivationSource{..})
115+ | isHackagePackage = attr "sha256" $ string derivHash
116+ | derivKind /= "" = vcat
117+ [ text "src" <+> equals <+> text ("fetch" ++ derivKind) <+> lbrace
118+ , nest 2 $ vcat
119+ [ attr "url" $ string derivUrl
120+ , attr "sha256" $ string derivHash
121+ , if derivRevision /= "" then attr "rev" (string derivRevision) else empty
122+ ]
123+ , rbrace <> semi
124+ ]
125+ | otherwise = attr "src" $ text derivUrl
111126
112127-- | A very incomplete parser that extracts 'pname', 'version',
113128-- 'sha256', 'platforms', 'hydraPlatforms', 'maintainers', 'doCheck',
114129-- 'jailbreak', and 'runHaddock' from the given Nix expression.
115−
116130parseDerivation :: String -> Maybe Derivation
117131parseDerivation buf
118132 | buf =~ "cabal.mkDerivation"
119133 , [name] <- buf `regsubmatch` "pname *= *\"([^\"]+)\""
120134 , [vers'] <- buf `regsubmatch` "version *= *\"([^\"]+)\""
121135 , Just vers <- simpleParse vers'
122− , [sha] <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
136+ , sha <- buf `regsubmatch` "sha256 *= *\"([^\"]+)\""
137+ , sr <- buf `regsubmatch` "src *= *([^;]+);"
138+ , url <- buf `regsubmatch` "url *= *\"?([^;\"]+)\"? *;"
139+ , rev <- buf `regsubmatch` "rev *= *\"([^\"]+)\""
123140 , hplats <- buf `regsubmatch` "hydraPlatforms *= *([^;]+);"
124141 , plats <- buf `regsubmatch` "platforms *= *([^;]+);"
125142 , maint <- buf `regsubmatch` "maintainers *= *(with [^;]+;)? \\[([^\"]+)]"
@@ -131,7 +148,12 @@ parseDerivation buf
131148 = Just MkDerivation
132149 { pname = name
133150 , version = vers
134− , sha256 = sha
151+ , src = case (sr,url) of
152+ ([] , _ ) -> DerivationSource "url" ("mirror://hackage/" ++ name ++ "-" ++ vers' ++ ".tar.gz") "" $ head sha
153+ (sr':_, [] ) -> DerivationSource "" sr' "" ""
154+ (sr':_, url':_ ) -> DerivationSource (drop (length "fetch") . head . words $ sr')
155+ url' (head $ rev ++ [""]) $ head sha
156+
135157 , isLibrary = False
136158 , isExecutable = False
137159 , extraFunctionArgs = []
src/Distribution/NixOS/Fetch.hsnew file+127
@@ -0,0 +1,127 @@
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, "zip")
52+ , (False, "url")
53+ , (True, "git")
54+ , (True, "hg")
55+ , (True, "svn")
56+ , (True, "bzr")
57+ ]
58+
59+ fetchLocal :: Source -> MaybeT IO (DerivationSource, a)
60+ fetchLocal src = do
61+ let path = stripPrefix "file://" $ sourceUrl src
62+ existsFile <- liftIO $ doesFileExist path
63+ existsDir <- liftIO $ doesDirectoryExist path
64+ guard $ existsDir || existsFile
65+ process (DerivationSource "" path "" "", path)
66+
67+ process :: (DerivationSource, FilePath) -> MaybeT IO (DerivationSource, a)
68+ process (derivSource, file) = (,) derivSource <$> f file
69+
70+-- | Like 'fetch', but allows to specify which script to use.
71+fetchWith :: (Bool, String) -> Source -> MaybeT IO (DerivationSource, FilePath)
72+fetchWith (supportsRev, kind) source = do
73+ liftIO $ hPutStrLn stderr $ "* trying fetcher: " ++ kind
74+
75+ unless ((sourceRevision source /= "") || isNothing (sourceHash source) || not supportsRev) $
76+ liftIO (hPutStrLn stderr "** need a revision for VCS when the hash is given. skipping.") >> mzero
77+
78+ MaybeT $ liftIO $ do
79+ envs <- getEnvironment
80+ (Nothing, Just stdoutH, _, processH) <- createProcess (proc script args)
81+ { env = Just $ ("PRINT_PATH", "1") : envs
82+ , std_in = Inherit
83+ , std_err = CreatePipe
84+ , std_out = CreatePipe
85+ }
86+
87+ exitCode <- waitForProcess processH
88+ case exitCode of
89+ ExitSuccess -> fmap Just . processOutputSwitch . lines =<< hGetContents stdoutH
90+ ExitFailure _ -> return Nothing
91+
92+ where
93+
94+ script :: String
95+ script = "nix-prefetch-" ++ kind
96+
97+ args :: [String]
98+ args = sourceUrl source : [ sourceRevision source | supportsRev ] ++ maybeToList (sourceHash source)
99+
100+ processOutputWithRev :: [String] -> IO (DerivationSource, FilePath)
101+ processOutputWithRev [rev,hash,path] = return (DerivationSource kind (sourceUrl source) (extractRevision rev) hash, path)
102+ processOutputWithRev out = unexpectedOutput out
103+
104+ extractRevision :: String -> String
105+ extractRevision = unwords . drop 3 . words -- drop the "<vcs> revision is" prefix
106+
107+ processOutput :: [String] -> IO (DerivationSource, FilePath)
108+ processOutput [hash,path] = return (DerivationSource kind (sourceUrl source) (sourceRevision source) hash, path)
109+ processOutput out = unexpectedOutput out
110+
111+ unexpectedOutput :: [String] -> IO a
112+ unexpectedOutput out = do
113+ hPutStrLn stderr $ "*** unexpected output from " ++ "nix-prefetch-" ++ kind ++ " script: "
114+ hPutStr stderr $ unlines out
115+ exitFailure
116+
117+ processOutputSwitch :: [String] -> IO (DerivationSource, FilePath)
118+ processOutputSwitch
119+ | supportsRev && isNothing (sourceHash source) = processOutputWithRev
120+ | otherwise = processOutput
121+
122+stripPrefix :: Eq a => [a] -> [a] -> [a]
123+stripPrefix prefix as
124+ | prefix' == prefix = stripped
125+ | otherwise = as
126+ where
127+ (prefix', stripped) = splitAt (length prefix) as
src/cabal2nix.hs+18−21
@@ -1,14 +1,13 @@
11module Main ( main ) where
22
3−import Cabal2Nix.Hackage ( hashPackage, readCabalFile )
43import Cabal2Nix.Generate ( cabal2nix )
54import Cabal2Nix.Normalize ( normalize )
5+import Cabal2Nix.Package
66import Distribution.NixOS.Derivation.Cabal
7+import Distribution.NixOS.Fetch
78
89import Control.Exception ( bracket )
910import Control.Monad ( when )
10−import Distribution.PackageDescription ( package, packageDescription )
11−import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
1211import Distribution.Text ( disp )
1312import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
1413import System.Environment ( getArgs )
@@ -24,6 +23,7 @@ data Configuration = Configuration
2423 , optHaddock :: Bool
2524 , optDoCheck :: Bool
2625 , optJailbreak :: Bool
26+ , optRevision :: String
2727 , optHyperlinkSource :: Bool
2828 , optHackageDb :: Maybe FilePath
2929 }
@@ -39,6 +39,7 @@ defaultConfiguration = Configuration
3939 , optHaddock = True
4040 , optDoCheck = True
4141 , optJailbreak = False
42+ , optRevision = ""
4243 , optHyperlinkSource = True
4344 , optHackageDb = Nothing
4445 }
@@ -53,19 +54,25 @@ options =
5354 , Option "" ["jailbreak"] (NoArg (\o -> o { optJailbreak = True })) "don't honor version restrictions on build inputs"
5455 , Option "" ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package"
5556 , Option "" ["no-check"] (NoArg (\o -> o { optDoCheck = False })) "don't run regression test suites of this package"
57+ , Option "" ["rev"] (ReqArg (\x o -> o { optRevision = x }) "REVISION") "revision, only used when fetching from VCS"
5658 , Option "" ["no-hyperlink-source"] (NoArg (\o -> o { optHyperlinkSource = False })) "don't add pretty-printed source code to the documentation"
5759 ]
5860
5961usage :: String
60−usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines
62+usage = usageInfo "Usage: cabal2nix [options] URI" options ++ unlines
6163 [ ""
6264 , "Recognized URI schemes:"
6365 , ""
64− , " cabal://pkgname-pkgversion download the specified package from Hackage"
65− , " cabal://pkgname download latest version of the specified package from Hackage"
66− , " http://host/path fetch the Cabal file via HTTP"
67− , " file:///local/path load the Cabal file from the local disk"
68− , " /local/path abbreviated version of file URI"
66+ , " cabal://pkgname-pkgversion download the specified package from Hackage"
67+ , " cabal://pkgname download latest version of the specified package from Hackage"
68+ , " file:///local/path load the Cabal file from the local disk"
69+ , " /local/path abbreviated version of file URI"
70+ , " <git/svn/bzr/hg URL> download the source from the specified repository"
71+ , ""
72+ , "If the URI refers to a cabal file, information for building the package will be retrieved from that file, but "
73+ , "hackage will be used as a source for the derivation."
74+ , "Otherwise, the supplied URI will be used to as the source for the derivation and the information is taken"
75+ , "from the cabal file at the root of the downloaded source."
6976 ]
7077
7178cmdlineError :: String -> IO a
@@ -82,19 +89,9 @@ main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
8289 when (optPrintHelp cfg) (putStr usage >> exitSuccess)
8390 when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
8491
85− cabal' <- fmap parsePackageDescription (readCabalFile (optHackageDb cfg) (head args))
86− cabal <- case cabal' of
87− ParseOk _ a -> return a
88− ParseFailed err -> do
89− hPutStrLn stderr ("*** cannot parse cabal file: " ++ show err)
90− exitFailure
91−
92− let packageId = package (packageDescription cabal)
93− sha <- case optSha256 cfg of
94− Just hash -> return hash
95− Nothing -> hashPackage packageId
92+ pkg <- getPackage (optHackageDb cfg) $ Source (head args) (optRevision cfg) (optSha256 cfg)
9693
97− let deriv = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
94+ let deriv = (cabal2nix $ cabal pkg) { src = source pkg, runHaddock = optHaddock cfg, jailbreak = optJailbreak cfg }
9895 deriv' = deriv { metaSection = (metaSection deriv)
9996 { maintainers = optMaintainer cfg
10097 , platforms = optPlatform cfg
src/hackage4nix.hs+5−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
@@ -111,6 +111,7 @@ updateNixPkgs paths = do
111111 nix' <- io (readFile file) >>= parseNixFile file
112112 flip (maybe (return ())) nix' $ \nix ->
113113 modify (Set.insert nix)
114+
114115 -- Re-generate all Haskell packages in-place (unless
115116 -- 'regenerateDerivation' decided that this particular package
116117 -- shouldn't be touched.
@@ -124,10 +125,11 @@ updateNixPkgs paths = do
124125 maints = maintainers (metaSection deriv)
125126 plats = platforms (metaSection deriv)
126127 hplats = if isLatest deriv then hydraPlatforms (metaSection deriv) else ["self.stdenv.lib.platforms.none"]
128+
127129 when regenerate $ do
128130 msgDebug ("re-generate " ++ path)
129131 pkg <- getCabalPackage (pname deriv) (version deriv)
130− let deriv' = (cabal2nix pkg) { sha256 = sha256 deriv
132+ let deriv' = (cabal2nix pkg) { src = src deriv
131133 , runHaddock = runHaddock deriv
132134 , doCheck = doCheck deriv
133135 , jailbreak = jailbreak deriv
@@ -143,6 +145,7 @@ updateNixPkgs paths = do
143145 }
144146 }
145147 io $ writeFile path (show (disp (normalize deriv'')))
148+
146149 -- Discover available updates and print the appropriate cabal2nix
147150 -- command line to the console for the user to execute at his/her
148151 -- discretion.
test/doc-test.hs+2−1
@@ -20,13 +20,14 @@ 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"
2727 , "src/Distribution/NixOS/Derivation/License.hs"
2828 , "src/Distribution/NixOS/Derivation/Cabal.hs"
2929 , "src/Distribution/NixOS/Derivation/Meta.hs"
30+ , "src/Distribution/NixOS/Fetch.hs"
3031 , "src/Distribution/NixOS/PrettyPrinting.hs"
3132 , "src/Distribution/NixOS/Regex.hs"
3233 ]