git.iko.soy/typeable/cabal2nix
on d3bb1da help sign in
typeable/cabal2nix/src/hackage2nix.hs
189 lines 8.7 kB history raw
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
4module Main ( main ) where
6import Distribution.Nixpkgs.Haskell.FromCabal ( fromGenericPackageDescription )
7import Distribution.Nixpkgs.Haskell.FromCabal.Flags
8import Distribution.Nixpkgs.Haskell.FromCabal.HackageGit ( readHackage, Hackage )
9import Distribution.Nixpkgs.Haskell.Constraint
10import Distribution.Nixpkgs.Haskell.FromCabal.Configuration.GHC7102
11import Internal.HaskellPackage
12import Control.Monad
13import Control.Monad.Par.Combinator
14import Control.Monad.Par.IO
15import Control.Monad.Trans ( liftIO )
16import Data.Function
17import Data.List
18import Data.Map.Strict ( Map )
19import qualified Data.Map.Strict as Map
20import Data.Maybe
21import Data.Monoid
22import Data.Set ( Set )
23import qualified Data.Set as Set
24import Distribution.Nixpkgs.Fetch
25import Distribution.Nixpkgs.Haskell
26import Distribution.Nixpkgs.Meta
27import Distribution.Nixpkgs.PackageMap
28import Distribution.PackageDescription hiding ( options, buildDepends, extraLibs, buildTools )
29import Distribution.Text
30import Distribution.Package
31import Distribution.Version
32import Internal.Lens
33import Internal.PrettyPrinting hiding ( attr, (<>) )
34import Internal.Version
35import Language.Nix
36import Options.Applicative
37import System.FilePath
39defaultConfiguration :: Configuration
40defaultConfiguration = ghc7102
42type Nixpkgs = PackageMap -- Map String (Set [String])
43type PackageSet = Map String Version
44type PackageMultiSet = Map String (Set Version)
46resolveConstraint :: Constraint -> Hackage -> Version
47resolveConstraint c = fromMaybe (error ("constraint " ++ display c ++ " cannot be resolved in Hackage")) .
48 resolveConstraint' c
50resolveConstraint' :: Constraint -> Hackage -> Maybe Version
51resolveConstraint' (Dependency (PackageName name) vrange) hackage | Set.null vs = Nothing
52 | otherwise = Just (Set.findMax vs)
53 where vs = Set.filter (`withinRange` vrange) (Map.keysSet (hackage Map.! name))
55data Options = Options
56 { hackageRepository :: FilePath
57 , nixpkgsRepository :: FilePath
58 }
59 deriving (Show)
61options :: Parser Options
62options = Options
63 <$> strOption (long "hackage" <> help "path to Hackage git repository" <> value "hackage" <> showDefault <> metavar "PATH")
64 <*> strOption (long "nixpkgs" <> help "path to Nixpkgs repository" <> value "<nixpkgs>" <> showDefault <> metavar "PATH")
66pinfo :: ParserInfo Options
67pinfo = info
68 ( helper
69 <*> infoOption ("hackage2nix " ++ version) (long "version" <> help "Show version number")
70 <*> options
71 )
72 ( fullDesc
73 <> header "hackage2nix converts a Hackage database into a haskell-packages.nix file."
74 )
76main :: IO ()
77main = do
78 Options {..} <- execParser pinfo
80 hackage <- readHackage hackageRepository
81 nixpkgs <- readNixpkgPackageMap nixpkgsRepository Nothing
82 preferredVersions <- readPreferredVersions (hackageRepository </> "preferred-versions")
83 let fixup = Map.delete "acme-everything" -- TODO: https://github.com/NixOS/cabal2nix/issues/164
84 . Map.delete "som" -- TODO: https://github.com/NixOS/cabal2nix/issues/164
85 . Map.delete "type" -- TODO: https://github.com/NixOS/cabal2nix/issues/163
86 . Map.delete "dictionary-sharing" -- TODO: https://github.com/NixOS/cabal2nix/issues/175
87 . Map.filter (/= Map.empty)
88 . Map.mapWithKey (enforcePreferredVersions preferredVersions)
89 runParIO $ generatePackageSet defaultConfiguration (fixup hackage) nixpkgs
91enforcePreferredVersions :: [Constraint] -> String -> Map Version GenericPackageDescription
92 -> Map Version GenericPackageDescription
93enforcePreferredVersions cs pkg = Map.filterWithKey (\v _ -> PackageIdentifier (PackageName pkg) v `satisfiesConstraints` cs)
95generatePackageSet :: Configuration -> Hackage -> Nixpkgs -> ParIO ()
96generatePackageSet config hackage nixpkgs = do
97 let
98 corePackageSet :: PackageSet
99 corePackageSet = Map.fromList [ (name, v) | PackageIdentifier (PackageName name) v <- corePackages config ++ hardCorePackages config ]
101 latestVersionSet :: PackageSet
102 latestVersionSet = Map.map (Set.findMax . Map.keysSet) hackage
104 defaultPackageOverridesSet :: PackageSet
105 defaultPackageOverridesSet = Map.fromList [ (name, resolveConstraint c hackage) | c@(Dependency (PackageName name) _) <- defaultPackageOverrides config ]
107 generatedDefaultPackageSet :: PackageSet
108 generatedDefaultPackageSet = (defaultPackageOverridesSet `Map.union` latestVersionSet) `Map.difference` corePackageSet
110 latestCorePackageSet :: PackageSet
111 latestCorePackageSet = latestVersionSet `Map.intersection` corePackageSet
113 latestOverridePackageSet :: PackageSet
114 latestOverridePackageSet = latestVersionSet `Map.intersection` defaultPackageOverridesSet
116 extraPackageSet :: PackageMultiSet
117 extraPackageSet = Map.unionsWith Set.union
118 [ Map.singleton name (Set.singleton (resolveConstraint c hackage)) | c@(Dependency (PackageName name) _) <- extraPackages config ]
120 db :: PackageMultiSet
121 db = Map.unionsWith Set.union [ Map.map Set.singleton generatedDefaultPackageSet
122 , Map.map Set.singleton latestCorePackageSet
123 , Map.map Set.singleton latestOverridePackageSet
124 , extraPackageSet
125 ]
127 haskellResolver :: Dependency -> Bool
128 haskellResolver (Dependency (PackageName name) vrange)
129 | Just v <- Map.lookup name corePackageSet = v `withinRange` vrange
130 | Just v <- Map.lookup name generatedDefaultPackageSet = v `withinRange` vrange
131 | otherwise = False
133 nixpkgsResolver :: Identifier -> Maybe Binding
134 nixpkgsResolver = resolve (Map.map (Set.map (over path ("pkgs":))) nixpkgs)
136 liftIO $ do putStrLn "/* hackage-packages.nix is an auto-generated file -- DO NOT EDIT! */"
137 putStrLn ""
138 putStrLn "{ pkgs, stdenv, callPackage }:"
139 putStrLn ""
140 putStrLn "self: {"
141 putStrLn ""
142 pkgs <- flip parMapM (Map.toAscList db) $ \(name, vs) -> do
143 defs <- forM (Set.toAscList vs) $ \pkgversion -> do
144 let -- TODO: Include list of broken dependencies in the generated output.
145 descr = hackage Map.! name Map.! pkgversion
147 flagAssignment :: FlagAssignment
148 flagAssignment = configureCabalFlags (packageId descr)
150 drv' :: Derivation
151 drv' = fromGenericPackageDescription haskellResolver nixpkgsResolver (platform ghc7102) (compilerInfo ghc7102) flagAssignment [] descr
153 attr :: String
154 attr | Just v <- Map.lookup name generatedDefaultPackageSet, v == pkgversion = name
155 | otherwise = name ++ '_' : [ if c == '.' then '_' else c | c <- display pkgversion ]
157 sha256 :: String
158 sha256 | Just x <- lookup "X-Package-SHA256" (customFieldsPD (packageDescription descr)) = x
159 | otherwise = error $ display (packageId descr) ++ " has no hash"
161 srcSpec <- liftIO $ sourceFromHackage (Certain sha256) (name ++ "-" ++ display pkgversion)
163 let drv = drv' & src .~ srcSpec
164 & metaSection.hydraPlatforms .~ (if PackageName name `Set.member` brokenPackages config
165 then Set.singleton "stdenv.lib.platforms.none"
166 else drv'^.metaSection.hydraPlatforms)
168 isFromHackage :: Binding -> Bool
169 isFromHackage b = ["self"] `isPrefixOf` view (reference . path) b
171 overrides :: Doc
172 overrides = fcat $ punctuate space
173 [ pPrint b | b <- Set.toList (view (dependencies . each) drv), not (isFromHackage b) ]
176 return $ nest 2 $ hang (string attr <+> equals <+> text "callPackage") 2 (parens (pPrint drv)) <+> (braces overrides <> semi)
177 return (intercalate "\n\n" (map render defs))
179 liftIO $ mapM_ (\pkg -> putStrLn pkg >> putStrLn "") pkgs
180 liftIO $ putStrLn "}"
182readPreferredVersions :: FilePath -> IO [Constraint]
183readPreferredVersions p = mapMaybe parsePreferredVersionsLine . lines <$> readFile p
185parsePreferredVersionsLine :: String -> Maybe Constraint
186parsePreferredVersionsLine ('-':'-':_) = Nothing
187parsePreferredVersionsLine l = case simpleParse l of
188 Just c -> Just c
189 Nothing -> error ("invalid preferred-versions line: " ++ show l)