git.iko.soy/typeable/cabal2nix
on master help sign in
Port cabal2nix to hackage-db 2.x.
eef3b98Peter Simons
cabal2nix.cabal+8−4
@@ -99,7 +99,7 @@ library
99 , directory
99 , directory
100 , distribution-nixpkgs >= 1.1
100 , distribution-nixpkgs >= 1.1
101 , filepath
101 , filepath
102− , hackage-db
102+ , hackage-db > 2
103 , hopenssl >= 2
103 , hopenssl >= 2
104 , language-nix
104 , language-nix
105 , lens
105 , lens
@@ -108,6 +108,7 @@ library
108 , process
108 , process
109 , split
109 , split
110 , text
110 , text
111+ , time
111 , transformers
112 , transformers
112 , yaml
113 , yaml
113 exposed-modules:
114 exposed-modules:
@@ -147,7 +148,7 @@ executable cabal2nix
147 , directory
148 , directory
148 , distribution-nixpkgs >= 1.1
149 , distribution-nixpkgs >= 1.1
149 , filepath
150 , filepath
150− , hackage-db
151+ , hackage-db > 2
151 , hopenssl >= 2
152 , hopenssl >= 2
152 , language-nix
153 , language-nix
153 , lens
154 , lens
@@ -156,6 +157,7 @@ executable cabal2nix
156 , process
157 , process
157 , split
158 , split
158 , text
159 , text
160+ , time
159 , transformers
161 , transformers
160 , yaml
162 , yaml
161 , cabal2nix
163 , cabal2nix
@@ -179,7 +181,7 @@ executable hackage2nix
179 , directory
181 , directory
180 , distribution-nixpkgs >= 1.1
182 , distribution-nixpkgs >= 1.1
181 , filepath
183 , filepath
182− , hackage-db
184+ , hackage-db > 2
183 , hopenssl >= 2
185 , hopenssl >= 2
184 , language-nix
186 , language-nix
185 , lens
187 , lens
@@ -188,6 +190,7 @@ executable hackage2nix
188 , process
190 , process
189 , split
191 , split
190 , text
192 , text
193+ , time
191 , transformers
194 , transformers
192 , yaml
195 , yaml
193 , cabal2nix
196 , cabal2nix
@@ -218,7 +221,7 @@ test-suite doctests
218 , directory
221 , directory
219 , distribution-nixpkgs >= 1.1
222 , distribution-nixpkgs >= 1.1
220 , filepath
223 , filepath
221− , hackage-db
224+ , hackage-db > 2
222 , hopenssl >= 2
225 , hopenssl >= 2
223 , language-nix
226 , language-nix
224 , lens
227 , lens
@@ -227,6 +230,7 @@ test-suite doctests
227 , process
230 , process
228 , split
231 , split
229 , text
232 , text
233+ , time
230 , transformers
234 , transformers
231 , yaml
235 , yaml
232 , doctest
236 , doctest
package.yaml+2−1
@@ -90,7 +90,7 @@ dependencies:
90 - directory
90 - directory
91 - distribution-nixpkgs >= 1.1
91 - distribution-nixpkgs >= 1.1
92 - filepath
92 - filepath
93− - hackage-db
93+ - hackage-db > 2
94 - hopenssl >= 2
94 - hopenssl >= 2
95 - language-nix
95 - language-nix
96 - lens
96 - lens
@@ -99,6 +99,7 @@ dependencies:
99 - process
99 - process
100 - split
100 - split
101 - text
101 - text
102+ - time
102 - transformers
103 - transformers
103 - yaml
104 - yaml
104
105
src/Distribution/Nixpkgs/Haskell/Hackage.hs+38−27
@@ -1,33 +1,44 @@
1−module Distribution.Nixpkgs.Haskell.Hackage ( readHashedHackage, readHashedHackage', module Distribution.Hackage.DB ) where
1+module Distribution.Nixpkgs.Haskell.Hackage
2+ ( HackageDB, PackageData, VersionData(..)
3+ , hackageTarball, readTarball
4+ ) where
2
5
3−import Data.ByteString.Lazy ( ByteString )
6+import Data.Map as Map
4−import Distribution.Hackage.DB
5−import qualified Distribution.Hackage.DB.Parsed as Unparsed ( parsePackage )
7+import qualified Distribution.Hackage.DB.Parsed as P
8+import Distribution.Hackage.DB.Path
6−import qualified Distribution.Hackage.DB.Unparsed as Unparsed
9+import qualified Distribution.Hackage.DB.Unparsed as U
7import Distribution.Nixpkgs.Hashes
10import Distribution.Nixpkgs.Hashes
11+import Distribution.Package
12+import Distribution.PackageDescription
13+import Distribution.Version
8−import OpenSSL.Digest ( digest, digestByName )
14+import OpenSSL.Digest
15+import Data.Time
9
16
10−-- | A variant of 'readHackage' that adds the SHA256 digest of the
17+type HackageDB = Map PackageName PackageData
11−-- original Cabal file to the parsed 'GenericPackageDescription'. That
12−-- hash is required to build packages with an "edited" cabal file,
13−-- because Nix needs to download the edited file and patch it into the
14−-- original tarball.
15
18
16−readHashedHackage :: IO Hackage
19+type PackageData = Map Version VersionData
17−readHashedHackage = hackagePath >>= readHashedHackage'
18
20
19−readHashedHackage' :: FilePath -> IO Hackage
21+data VersionData = VersionData
20−readHashedHackage' = fmap parseUnparsedHackage . Unparsed.readHackage'
22+ { cabalFile :: !GenericPackageDescription
21− where
23+ , cabalFileSha256 :: !String
22− parseUnparsedHackage :: Unparsed.Hackage -> Hackage
24+ , tarballSha256 :: !(Maybe String)
23− parseUnparsedHackage = mapWithKey (mapWithKey . parsePackage)
25+ }
26+ deriving (Show)
27+
28+readTarball :: Maybe UTCTime -> FilePath -> IO HackageDB
29+readTarball ts p = do
30+ dbu <- U.readTarball ts p
31+ let dbp = P.parseDB dbu
32+ return (mapWithKey (parsePackageData dbu) dbp)
24
33
25− parsePackage :: String -> Version -> ByteString -> GenericPackageDescription
34+parsePackageData :: U.HackageDB -> PackageName -> P.PackageData -> PackageData
26− parsePackage name version buf =
35+parsePackageData dbu pn pd = mapWithKey (parseVersionData (dbu ! pn)) pd
27− let pkg = Unparsed.parsePackage name version buf
36+
37+parseVersionData :: U.PackageData -> Version -> P.VersionData -> VersionData
38+parseVersionData pdu v vd = VersionData
39+ { cabalFile = P.cabalFile vd
28− hash = printSHA256 (digest (digestByName "sha256") buf)
40+ , cabalFileSha256 = printSHA256 (digest (digestByName "sha256") file)
29− in
41+ , tarballSha256 = Map.lookup "sha256" (P.tarballHashes vd)
30− pkg { packageDescription = (packageDescription pkg) {
42+ }
31− customFieldsPD = ("X-Cabal-File-Hash", hash) : customFieldsPD (packageDescription pkg)
43+ where
32− }
44+ file = U.cabalFile (U.versions pdu ! v)
33− }
src/Distribution/Nixpkgs/Haskell/PackageSourceSpec.hs+42−33
@@ -6,19 +6,19 @@ import qualified Control.Exception as Exception
6import Control.Monad
6import Control.Monad
7import Control.Monad.IO.Class
7import Control.Monad.IO.Class
8import Control.Monad.Trans.Maybe
8import Control.Monad.Trans.Maybe
9−import qualified Data.ByteString.Lazy.Char8 as LBS8
10import Data.List ( isSuffixOf, isPrefixOf )
9import Data.List ( isSuffixOf, isPrefixOf )
10+import qualified Data.Map as DB
11import Data.Maybe
11import Data.Maybe
12−import Distribution.Hackage.DB.Parsed
13import Distribution.Nixpkgs.Fetch
12import Distribution.Nixpkgs.Fetch
14import Distribution.Nixpkgs.Hashes
13import Distribution.Nixpkgs.Hashes
15import qualified Distribution.Nixpkgs.Haskell.Hackage as DB
14import qualified Distribution.Nixpkgs.Haskell.Hackage as DB
16import qualified Distribution.Package as Cabal
15import qualified Distribution.Package as Cabal
17−import qualified Distribution.Version as Cabal
18import Distribution.PackageDescription
16import Distribution.PackageDescription
19import qualified Distribution.PackageDescription as Cabal
17import qualified Distribution.PackageDescription as Cabal
18+import Distribution.PackageDescription.Parse as Cabal
20import Distribution.Text ( simpleParse, display )
19import Distribution.Text ( simpleParse, display )
20+import Distribution.Version
21−import OpenSSL.Digest ( digest, digestByName )
21+import OpenSSL.Digest ( digestString, digestByName )
22import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
22import System.Directory ( doesDirectoryExist, doesFileExist, createDirectoryIfMissing, getHomeDirectory, getDirectoryContents )
23import System.Exit ( exitFailure )
23import System.Exit ( exitFailure )
24import System.FilePath ( (</>), (<.>) )
24import System.FilePath ( (</>), (<.>) )
@@ -30,42 +30,48 @@ data Package = Package
30 }
30 }
31 deriving (Show)
31 deriving (Show)
32
32
33−getPackage :: Maybe String -> Source -> IO Package
33+getPackage :: Maybe FilePath -> Source -> IO Package
34getPackage optHackageDB source = do
34getPackage optHackageDB source = do
35 (derivSource, pkgDesc) <- fetchOrFromDB optHackageDB source
35 (derivSource, pkgDesc) <- fetchOrFromDB optHackageDB source
36 flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) (showPackageIdentifier pkgDesc) $ sourceCabalDir source) return derivSource
36 flip Package pkgDesc <$> maybe (sourceFromHackage (sourceHash source) (showPackageIdentifier pkgDesc) $ sourceCabalDir source) return derivSource
37
37
38−fetchOrFromDB :: Maybe String -> Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
38+fetchOrFromDB :: Maybe FilePath -> Source -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
39fetchOrFromDB optHackageDB src
39fetchOrFromDB optHackageDB src
40− | "cabal://" `isPrefixOf` sourceUrl src = fmap ((,) Nothing) . fromDB optHackageDB . drop (length "cabal://") $ sourceUrl src
40+ | "cabal://" `isPrefixOf` sourceUrl src = fromDB optHackageDB . drop (length "cabal://") $ sourceUrl src
41 | otherwise = do
41 | otherwise = do
42 r <- fetch (\dir -> cabalFromPath (dir </> sourceCabalDir src)) src
42 r <- fetch (\dir -> cabalFromPath (dir </> sourceCabalDir src)) src
43 case r of
43 case r of
44− Nothing ->
45− hPutStrLn stderr "*** failed to fetch source. Does the URL exist?" >> exitFailure
44+ Nothing -> fail "Failed to fetch source. Does the URL exist?"
46 Just (derivSource, (externalSource, pkgDesc)) -> do
45 Just (derivSource, (externalSource, pkgDesc)) -> do
47 return (derivSource <$ guard externalSource, pkgDesc)
46 return (derivSource <$ guard externalSource, pkgDesc)
48
47
49−fromDB :: Maybe String -> String -> IO Cabal.GenericPackageDescription
48+fromDB :: Maybe FilePath -> String -> IO (Maybe DerivationSource, Cabal.GenericPackageDescription)
50fromDB optHackageDB pkg = do
49fromDB optHackageDB pkg = do
51− pkgDesc <- (lookupVersion <=< DB.lookup name) <$> maybe DB.readHashedHackage DB.readHashedHackage' optHackageDB
50+ dbPath <- maybe DB.hackageTarball return optHackageDB
52− case pkgDesc of
51+ db <- DB.readTarball Nothing dbPath
53− Just r -> return r
52+ vd <- maybe unknownPackageError return (DB.lookup name db >>= lookupVersion)
54− Nothing -> hPutStrLn stderr "*** no such package in the cabal database (did you run cabal update?). " >> exitFailure
53+ let ds = case DB.tarballSha256 vd of
54+ Nothing -> Nothing
55+ Just hash -> Just (DerivationSource "url" url "" hash)
56+ return (ds, setCabalFileHash (DB.cabalFileSha256 vd) (DB.cabalFile vd))
55 where
57 where
56 pkgId :: Cabal.PackageIdentifier
58 pkgId :: Cabal.PackageIdentifier
57 pkgId = fromMaybe (error ("invalid Haskell package id " ++ show pkg)) (simpleParse pkg)
59 pkgId = fromMaybe (error ("invalid Haskell package id " ++ show pkg)) (simpleParse pkg)
58− name = Cabal.unPackageName (Cabal.packageName pkgId)
60+ name = Cabal.packageName pkgId
61+
62+ unknownPackageError = fail "No such package in the cabal database. Did you run cabal update?"
59
63
64+ url = "mirror://hackage/" ++ display pkgId ++ ".tar.gz"
65+
60− version :: [Int]
66+ version :: Version
61− version = Cabal.versionNumbers $ Cabal.packageVersion pkgId
67+ version = Cabal.packageVersion pkgId
62
68
63− lookupVersion :: DB.Map DB.Version Cabal.GenericPackageDescription -> Maybe Cabal.GenericPackageDescription
69+ lookupVersion :: DB.Map Version DB.VersionData -> Maybe DB.VersionData
64 -- No version is specified, pick latest one
70 -- No version is specified, pick latest one
65− lookupVersion m | [] <- version = fmap snd . listToMaybe $ DB.toDescList m
71+ lookupVersion m | version == nullVersion = fmap snd (listToMaybe (DB.toDescList m))
66− lookupVersion m = DB.lookup (DB.makeVersion version) m
72+ lookupVersion m = DB.lookup version m
67
73
68−readFileMay :: String -> IO (Maybe String)
74+readFileMay :: FilePath -> IO (Maybe String)
69readFileMay file = do
75readFileMay file = do
70 e <- doesFileExist file
76 e <- doesFileExist file
71 if e
77 if e
@@ -79,7 +85,7 @@ hashCachePath pid = do
79 createDirectoryIfMissing True cacheDir
85 createDirectoryIfMissing True cacheDir
80 return $ cacheDir </> pid <.> "sha256"
86 return $ cacheDir </> pid <.> "sha256"
81
87
82−sourceFromHackage :: Hash -> String -> String -> IO DerivationSource
88+sourceFromHackage :: Hash -> String -> FilePath -> IO DerivationSource
83sourceFromHackage optHash pkgId cabalDir = do
89sourceFromHackage optHash pkgId cabalDir = do
84 cacheFile <- hashCachePath pkgId
90 cacheFile <- hashCachePath pkgId
85 cachedHash <-
91 cachedHash <-
@@ -135,7 +141,7 @@ cabalFromDirectory dir = do
135 cabals <- liftIO $ getDirectoryContents dir >>= filterM doesFileExist . map (dir </>) . filter (".cabal" `isSuffixOf`)
141 cabals <- liftIO $ getDirectoryContents dir >>= filterM doesFileExist . map (dir </>) . filter (".cabal" `isSuffixOf`)
136 case cabals of
142 case cabals of
137 [cabalFile] -> cabalFromFile True cabalFile
143 [cabalFile] -> cabalFromFile True cabalFile
138− _ -> liftIO $ hPutStrLn stderr ("*** found zero or more than one cabal file (" ++ show cabals ++ "). Exiting.") >> exitFailure
144+ _ -> liftIO $ fail ("Found zero or more than one cabal files: " ++ show cabals ++ ". Exiting.")
139
145
140handleIO :: (Exception.IOException -> IO a) -> IO a -> IO a
146handleIO :: (Exception.IOException -> IO a) -> IO a -> IO a
141handleIO = Exception.handle
147handleIO = Exception.handle
@@ -147,13 +153,16 @@ cabalFromFile failHard file =
147 -- wrap the whole block in `catchIO`, because of lazy IO. The `case` will force
153 -- wrap the whole block in `catchIO`, because of lazy IO. The `case` will force
148 -- the reading of the file, so we will always catch the expression here.
154 -- the reading of the file, so we will always catch the expression here.
149 MaybeT $ handleIO (\err -> Nothing <$ hPutStrLn stderr ("*** parsing cabal file: " ++ show err)) $ do
155 MaybeT $ handleIO (\err -> Nothing <$ hPutStrLn stderr ("*** parsing cabal file: " ++ show err)) $ do
150− buf <- LBS8.readFile file
156+ buf <- readFile file
151− let hash = printSHA256 (digest (digestByName "sha256") buf)
157+ let hash = printSHA256 (digestString (digestByName "sha256") buf)
152− case parsePackage' buf of
158+ case parseGenericPackageDescription buf of
153− Left msg -> if failHard
159+ ParseFailed perr -> if failHard
154− then fail ("*** cannot parse " ++ show file ++ ": " ++ msg)
160+ then fail ("cannot parse " ++ show file ++ ": " ++ show perr)
155− else return Nothing
161+ else return Nothing
162+ ParseOk _ pkg -> return $ Just $ setCabalFileHash hash pkg
163+
164+setCabalFileHash :: String -> GenericPackageDescription -> GenericPackageDescription
156− Right pkg -> do return $ Just $ pkg { packageDescription = (packageDescription pkg) {
165+setCabalFileHash sha256 gpd = gpd { packageDescription = (packageDescription gpd) {
157− customFieldsPD = ("X-Cabal-File-Hash", hash) : customFieldsPD (packageDescription pkg)
166+ customFieldsPD = ("X-Cabal-File-Hash", sha256) : customFieldsPD (packageDescription gpd)
158− }
167+ }
159− }
168+ }