git.iko.soy/typeable/cabal2nix
on master help sign in
hackage2nix: implement support for generating multiple versions
c33621cPeter Simons
src/hackage2nix.hs+134−61
@@ -3,86 +3,98 @@
3module Main ( main ) where
3module Main ( main ) where
4
4
5import Cabal2Nix.Generate
5import Cabal2Nix.Generate
6+import Cabal2Nix.Hackage ( readHashedHackage, Hackage )
6-- import Cabal2Nix.Name
7-- import Cabal2Nix.Name
7import Cabal2Nix.Package
8import Cabal2Nix.Package
8−import Cabal2Nix.Hackage ( readHashedHackage, Hackage )
9import Control.Monad
9import Control.Monad
10−import Control.Monad.Par.Combinator
10+-- import Control.Monad.Par.Combinator
11−import Control.Monad.Par.IO
11+-- import Control.Monad.Par.IO
12+-- import Control.Monad.RWS
12import Control.Monad.Trans
13import Control.Monad.Trans
13−import Data.Map ( Map )
14+-- import Data.Map ( Map )
14import qualified Data.Map as Map
15import qualified Data.Map as Map
16+-- import Data.Maybe
15import Data.Monoid
17import Data.Monoid
16import Data.Set ( Set )
18import Data.Set ( Set )
17import qualified Data.Set as Set
19import qualified Data.Set as Set
20+-- import Distribution.Compiler
18import Distribution.NixOS.Derivation.Cabal
21import Distribution.NixOS.Derivation.Cabal
19−import Distribution.NixOS.PrettyPrinting hiding ( (<>) )
20import Distribution.NixOS.PackageMap ( PackageMap, readNixpkgPackageMap )
22import Distribution.NixOS.PackageMap ( PackageMap, readNixpkgPackageMap )
23+import Distribution.NixOS.PrettyPrinting hiding ( (<>) )
24+import Distribution.Package
21import Distribution.PackageDescription hiding ( buildDepends, extraLibs, buildTools )
25import Distribution.PackageDescription hiding ( buildDepends, extraLibs, buildTools )
22import Distribution.Text
26import Distribution.Text
27+import Distribution.Version
28+-- import Options.Applicative hiding ( empty )
23
29
24main :: IO ()
30main :: IO ()
25main = do
31main = do
26 hackage <- readHashedHackage
32 hackage <- readHashedHackage
27 nixpkgs <- readNixpkgPackageMap
33 nixpkgs <- readNixpkgPackageMap
28− runParIO (generatePackageSet hackage nixpkgs)
34+ generatePackageSet hackage nixpkgs
29−
35+
30−nixAttr :: String -> Version -> String
31−nixAttr name _ = name -- ++ "_" ++ [ if c == '.' then '_' else c | c <- display ver ]
32−
33−generatePackageSet :: Hackage -> PackageMap -> ParIO ()
36+generatePackageSet :: Hackage -> PackageMap -> IO ()
34−generatePackageSet db nixpkgs = do
37+generatePackageSet hackage nixpkgs = do
35− pkgs <- parMapM generatePackage (Map.toList db)
38+ let db = buildPackageSet hackage
36− liftIO $ putStrLn "/* hackage-packages.nix is an auto-generated file -- DO NOT EDIT! */"
39+ liftIO $ do putStrLn "/* hackage-packages.nix is an auto-generated file -- DO NOT EDIT! */"
37− liftIO $ putStrLn ""
40+ putStrLn ""
38− liftIO $ putStrLn "{ pkgs, stdenv, callPackage }:"
41+ putStrLn "{ pkgs, stdenv, callPackage }:"
39− liftIO $ putStrLn ""
42+ putStrLn ""
40− liftIO $ putStrLn "self: {"
43+ putStrLn "self: {"
41− liftIO $ putStrLn ""
44+ putStrLn ""
42− forM_ pkgs $ \(name, version, drv) -> do
45+ forM_ (Map.toList db) $ \(name, versions) -> do
43− let selectHackageNames :: Set String -> Set String
46+ let multiVersionPackage :: Bool
44− selectHackageNames = Set.intersection (Map.keysSet db)
47+ multiVersionPackage = Map.size versions > 1
45−
48+
46− selectMissingHackageNames :: Set String -> Set String
49+ latestVersion :: Version
47− selectMissingHackageNames = flip Set.difference (Map.keysSet db)
50+ latestVersion = fst (Map.findMax versions)
48−
51+
49− conflicts :: Set String
52+ forM_ (Map.toList versions) $ \(version, descr) -> do
50− conflicts = Set.difference (selectHackageNames $ Set.fromList (extraLibs drv ++ pkgConfDeps drv)) missing
53+ (drv, overrides) <- generatePackage db nixpkgs name version descr
51−
54+
52− conflictOverrides :: Doc
55+ let nixAttr = name ++ if multiVersionPackage then "_" ++ [ if c == '.' then '_' else c | c <- display version ] else ""
53− conflictOverrides | Set.null conflicts = empty
56+
54− | otherwise = text " inherit (pkgs) " <> hsep (map text (Set.toAscList conflicts)) <> text "; "
55−
56− missing :: Set String
57− missing = Set.union (Set.fromList (filter (not . isKnownNixpkgAttribute nixpkgs db) (extraLibs drv ++ pkgConfDeps drv ++ buildTools drv)))
58− (selectMissingHackageNames (Set.fromList (buildDepends drv ++ testDepends drv)))
59−
60− missingOverrides :: Doc
61− missingOverrides | Set.null missing = empty
62− | otherwise = fcat [ text (' ':dep++" = null;") | dep <- Set.toAscList missing ] <> space
63−
64− overrides :: Doc
65− overrides = conflictOverrides $+$ missingOverrides
66−
67− let drv' = if Set.null missing then drv else drv { metaSection = (metaSection drv) { broken = True } }
68− -- when (name /= toNixName' name) $
69− -- liftIO $ print $ nest 2 $ (string (toNixName' name) <+> equals <+> text ("self." ++ show (nixAttr name version))) <> semi
70− -- liftIO $ print $ nest 2 $ (string (toNixName' name) <+> equals <+> text ("self." ++ show (nixAttr name version))) <> semi
71− -- when (name /= toNixName' name) $
72− -- liftIO $ print $ nest 2 $ (string name <+> equals <+> text ("self." ++ show (nixAttr name version))) <> semi
73− liftIO $ print $ nest 2 $ hang (string (nixAttr name version) <+> equals <+> text "callPackage") 2 (parens (disp drv')) <+> (braces overrides <> semi)
57+ liftIO $ do print $ nest 2 $ hang ((string nixAttr) <+> equals <+> text "callPackage") 2 (parens (disp drv)) <+> (braces overrides <> semi)
74− liftIO $ putStrLn ""
58+ putStrLn ""
59+ when (multiVersionPackage && version == latestVersion) $
60+ liftIO $ print $ nest 2 $ (string name <+> equals <+> text ("self." ++ show nixAttr)) <> semi
61+
75 liftIO $ putStrLn "}"
62 liftIO $ putStrLn "}"
76
63
77−generatePackage :: (String, Map Version GenericPackageDescription) -> ParIO (String,Version,Derivation)
64+generatePackage :: Hackage -> PackageMap -> String -> Version -> GenericPackageDescription -> IO (Derivation, Doc)
78−generatePackage (name, versions) = do
65+generatePackage hackage nixpkgs name version descr = do
79− let latestVersion = Set.findMax (Map.keysSet versions)
80− pkgDescription = (Map.!) versions latestVersion
81− srcSpec <- liftIO $ sourceFromHackage Nothing (name ++ "-" ++ display latestVersion)
66+ srcSpec <- liftIO $ sourceFromHackage Nothing (name ++ "-" ++ display version)
82− let drv' = (cabal2nix pkgDescription) { src = srcSpec }
83− Just cabalFileHash = lookup "x-cabal-file-hash" (customFieldsPD (packageDescription pkgDescription))
67+ let Just cabalFileHash = lookup "x-cabal-file-hash" (customFieldsPD (packageDescription descr))
68+
69+ selectHackageNames :: Set String -> Set String
70+ selectHackageNames = Set.intersection (Map.keysSet hackage)
71+
72+ selectMissingHackageNames :: Set String -> Set String
73+ selectMissingHackageNames = flip Set.difference (Map.keysSet hackage)
74+
75+ conflicts :: Set String
76+ conflicts = Set.difference (selectHackageNames $ Set.fromList (extraLibs drv ++ pkgConfDeps drv)) missing
77+
78+ conflictOverrides :: Doc
79+ conflictOverrides | Set.null conflicts = empty
80+ | otherwise = text " inherit (pkgs) " <> hsep (map text (Set.toAscList conflicts)) <> text "; "
81+
82+ missing :: Set String
83+ missing = Set.union (Set.fromList (filter (not . isKnownNixpkgAttribute nixpkgs hackage) (extraLibs drv ++ pkgConfDeps drv ++ buildTools drv)))
84+ (selectMissingHackageNames (Set.fromList (buildDepends drv ++ testDepends drv)))
85+
86+ missingOverrides :: Doc
87+ missingOverrides | Set.null missing = empty
88+ | otherwise = fcat [ text (' ':dep++" = null;") | dep <- Set.toAscList missing ] <> space
89+
90+ overrides :: Doc
91+ overrides = conflictOverrides $+$ missingOverrides
92+
93+ drv = (cabal2nix descr) { src = srcSpec
84− drv = drv' { editedCabalFile = if revision drv' == 0 then "" else cabalFileHash }
94+ , editedCabalFile = if revision drv == 0 then "" else cabalFileHash
85− return (name, latestVersion, drv)
95+ }
96+
97+ return $ (drv { metaSection = (metaSection drv) { broken = not (Set.null missing) } }, overrides)
86
98
87isKnownNixpkgAttribute :: PackageMap -> Hackage -> String -> Bool
99isKnownNixpkgAttribute :: PackageMap -> Hackage -> String -> Bool
88isKnownNixpkgAttribute nixpkgs hackage name
100isKnownNixpkgAttribute nixpkgs hackage name
@@ -92,3 +104,64 @@ isKnownNixpkgAttribute nixpkgs hackage name
92 where
104 where
93 goodScope :: Set [String] -> Bool
105 goodScope :: Set [String] -> Bool
94 goodScope = not . Set.null . Set.intersection (Set.fromList [[], ["xlibs"], ["gnome"]])
106 goodScope = not . Set.null . Set.intersection (Set.fromList [[], ["xlibs"], ["gnome"]])
107+
108+buildPackageSet :: Hackage -> Hackage
109+buildPackageSet db = Map.unionsWith Map.union $
110+ [ selectLatestMatchingPackage (Dependency (PackageName n) anyVersion) db | n <- Map.keys db ] ++
111+ [ selectLatestMatchingPackage d db | d <- selectors ]
112+
113+selectLatestMatchingPackage :: Dependency -> Hackage -> Hackage
114+selectLatestMatchingPackage (Dependency (PackageName name) vrange) db =
115+ case Map.lookup name db of
116+ Nothing -> error (show name ++ " is not a valid Hackage package")
117+ Just vdb -> let (key,val) = Map.findMax (Map.filterWithKey (\k _ -> k `withinRange` vrange) vdb)
118+ in Map.singleton name (Map.singleton key val)
119+
120+selectors :: [Dependency]
121+selectors = map (\s -> maybe (error (show s ++ " is not a valid package selector")) id (simpleParse s))
122+ [ "mtl == 2.1.*"
123+ , "monad-control == 0.3.*"
124+ ]
125+
126+-- data Options = Options
127+-- { verbose :: Bool
128+-- , compiler :: CompilerId
129+-- }
130+-- deriving (Show)
131+--
132+-- data Config = Config
133+-- { _verbose :: Bool
134+-- , _hackage :: Hackage
135+-- , _nixpkgs :: PackageMap
136+-- }
137+-- deriving (Show)
138+--
139+-- type Compile a = RWST Config () (Set PackageId) ParIO a
140+--
141+-- run :: Compile a -> IO a
142+-- run f = do
143+-- (a, st, ws) <- runParIO (runRWST f (Config True Map.empty Map.empty) Set.empty)
144+-- return a
145+--
146+-- parseCommandLine :: IO Options
147+-- parseCommandLine = execParser mainOptions
148+-- where
149+-- parseCompilerId :: Parser CompilerId
150+-- parseCompilerId = option (eitherReader (\s -> maybe (Left (show s ++ " is no valid compiler id")) Right (simpleParse s)))
151+-- ( long "compiler"
152+-- <> help "identifier of the compiler"
153+-- <> metavar "COMPILER-ID"
154+-- <> value (fromJust (simpleParse "ghc-7.8.3"))
155+-- <> showDefaultWith display
156+-- )
157+--
158+-- parseOptions :: Parser Options
159+-- parseOptions = Options
160+-- <$> switch (long "verbose" <> help "enable detailed progress diagnostics")
161+-- <*> parseCompilerId
162+--
163+-- mainOptions :: ParserInfo Options
164+-- mainOptions = info (helper <*> parseOptions)
165+-- ( fullDesc
166+-- <> header "hackage2nix -- convert the Hackage database into Nix build instructions"
167+-- )