git.iko.soy/typeable/cabal2nix
on 48e0804 help sign in
201 lines 9.9 kB history raw
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
4module Main ( main ) where
6import Control.Lens
7import Control.Monad
8import Control.Monad.Par.Combinator
9import Control.Monad.Par.IO
10import Control.Monad.Trans ( liftIO )
11import Data.List
12import Data.Map.Strict ( Map )
13import qualified Data.Map.Strict as Map
14import Data.Maybe
15import Data.Monoid
16import Data.Set ( Set )
17import qualified Data.Set as Set
18import Data.String
19import Distribution.Nixpkgs.Fetch
20import Distribution.Nixpkgs.Haskell
21import Distribution.Nixpkgs.Haskell.Constraint
22import Distribution.Nixpkgs.Haskell.FromCabal ( fromGenericPackageDescription )
23import Distribution.Nixpkgs.Haskell.FromCabal.Configuration
24import Distribution.Nixpkgs.Haskell.FromCabal.Flags
25import Distribution.Nixpkgs.Haskell.HackageGit ( readHackage, Hackage )
26import Distribution.Nixpkgs.Haskell.PackageSourceSpec
27import Distribution.Nixpkgs.Meta
28import Distribution.Nixpkgs.PackageMap
29import Distribution.Package
30import Distribution.PackageDescription hiding ( options, buildDepends, extraLibs, buildTools )
31import Distribution.System
32import Distribution.Text
33import Distribution.Version
34import Language.Nix
35import Options.Applicative
36import Paths_hackage2nix
37import System.FilePath
38import Text.PrettyPrint.HughesPJClass hiding ( (<>) )
40type Nixpkgs = PackageMap -- Map String (Set [String])
41type PackageSet = Map String Version
42type PackageMultiSet = Map String (Set Version)
44resolveConstraint :: Constraint -> Hackage -> Version
45resolveConstraint c = fromMaybe (error msg) . resolveConstraint' c
46 where msg = "constraint " ++ display c ++ " cannot be resolved in Hackage"
48resolveConstraint' :: Constraint -> Hackage -> Maybe Version
49resolveConstraint' (Dependency (PackageName name) vrange) hackage
50 | Just vset' <- Map.lookup name hackage
51 , vset <- Set.filter (`withinRange` vrange) (Map.keysSet vset')
52 , not (Set.null vset) = Just (Set.findMax vset)
53 | otherwise = Nothing
55data Options = Options
56 { hackageRepository :: FilePath
57 , preferredVersionsFile :: Maybe FilePath
58 , nixpkgsRepository :: FilePath
59 , configFile :: FilePath
60 , targetPlatform :: Platform
61 }
62 deriving (Show)
64options :: Parser Options
65options = Options
66 <$> strOption (long "hackage" <> help "path to Hackage git repository" <> value "hackage" <> showDefaultWith id <> metavar "PATH")
67 <*> optional (strOption (long "preferred-versions" <> help "path to Hackage preferred-versions file" <> value "hackage/preferred-versions" <> showDefault <> metavar "PATH"))
68 <*> strOption (long "nixpkgs" <> help "path to Nixpkgs repository" <> value "<nixpkgs>" <> showDefaultWith id <> metavar "PATH")
69 <*> strOption (long "config" <> help "path to configuration file" <> value "nixpkgs/pkgs/development/haskell-modules/configuration-hackage2nix.yaml" <> showDefaultWith id <> metavar "PATH")
70 <*> option (fmap fromString str) (long "platform" <> help "target platform to generate package set for" <> value "x86_64-linux" <> showDefaultWith display <> metavar "PLATFORM")
72pinfo :: ParserInfo Options
73pinfo = info
74 ( helper
75 <*> infoOption ("hackage2nix " ++ display version) (long "version" <> help "Show version number")
76 <*> options
77 )
78 ( fullDesc
79 <> header "hackage2nix converts a Hackage database into a haskell-packages.nix file."
80 )
82main :: IO ()
83main = do
84 Options {..} <- execParser pinfo
86 defaultConfiguration <- readConfiguration configFile
87 hackage <- readHackage hackageRepository
88 nixpkgs <- readNixpkgPackageMap nixpkgsRepository Nothing
89 preferredVersions <- readPreferredVersions (fromMaybe (hackageRepository </> "preferred-versions") preferredVersionsFile)
90 let fixup = Map.delete "acme-everything" -- TODO: https://github.com/NixOS/cabal2nix/issues/164
91 . Map.delete "som" -- TODO: https://github.com/NixOS/cabal2nix/issues/164
92 . Map.delete "type" -- TODO: https://github.com/NixOS/cabal2nix/issues/163
93 . Map.delete "dictionary-sharing" -- TODO: https://github.com/NixOS/cabal2nix/issues/175
94 . Map.filter (/= Map.empty)
95 . Map.mapWithKey (enforcePreferredVersions preferredVersions)
96 runParIO $ generatePackageSet targetPlatform defaultConfiguration (fixup hackage) nixpkgs
98enforcePreferredVersions :: [Constraint] -> String -> Map Version GenericPackageDescription
99 -> Map Version GenericPackageDescription
100enforcePreferredVersions cs pkg = Map.filterWithKey (\v _ -> PackageIdentifier (PackageName pkg) v `satisfiesConstraints` cs)
102generatePackageSet :: Platform -> Configuration -> Hackage -> Nixpkgs -> ParIO ()
103generatePackageSet targetPlatform config hackage nixpkgs = do
104 let
105 corePackageSet :: PackageSet
106 corePackageSet = Map.fromList [ (name, v) | PackageIdentifier (PackageName name) v <- Set.toList (corePackages config) ]
108 latestVersionSet :: PackageSet
109 latestVersionSet = Map.map (Set.findMax . Map.keysSet) hackage
111 defaultPackageOverridesSet :: PackageSet
112 defaultPackageOverridesSet = Map.fromList [ (name, resolveConstraint c hackage) | c@(Dependency (PackageName name) _) <- defaultPackageOverrides config ]
114 generatedDefaultPackageSet :: PackageSet
115 generatedDefaultPackageSet = (defaultPackageOverridesSet `Map.union` latestVersionSet) `Map.difference` corePackageSet
117 latestCorePackageSet :: PackageSet
118 latestCorePackageSet = latestVersionSet `Map.intersection` corePackageSet
120 latestOverridePackageSet :: PackageSet
121 latestOverridePackageSet = latestVersionSet `Map.intersection` defaultPackageOverridesSet
123 extraPackageSet :: PackageMultiSet
124 extraPackageSet = Map.unionsWith Set.union
125 [ Map.singleton name (Set.singleton (resolveConstraint c hackage)) | c@(Dependency (PackageName name) _) <- extraPackages config ]
127 db :: PackageMultiSet
128 db = Map.unionsWith Set.union [ Map.map Set.singleton generatedDefaultPackageSet
129 , Map.map Set.singleton latestCorePackageSet
130 , Map.map Set.singleton latestOverridePackageSet
131 , extraPackageSet
132 ]
134 haskellResolver :: Dependency -> Bool
135 haskellResolver (Dependency (PackageName name) vrange)
136 | Just v <- Map.lookup name corePackageSet = v `withinRange` vrange
137 | Just v <- Map.lookup name generatedDefaultPackageSet = v `withinRange` vrange
138 | otherwise = False
140 nixpkgsResolver :: Identifier -> Maybe Binding
141 nixpkgsResolver = resolve (Map.map (Set.map (over path ("pkgs":))) nixpkgs)
143 globalPackageMaintainers :: Map PackageName (Set Identifier)
144 globalPackageMaintainers = Map.unionsWith Set.union [ Map.singleton p (Set.singleton m) | (m,ps) <- Map.toList (packageMaintainers config), p <- Set.toList ps ]
146 liftIO $ do putStrLn "/* hackage-packages.nix is an auto-generated file -- DO NOT EDIT! */"
147 putStrLn ""
148 putStrLn "{ pkgs, stdenv, callPackage }:"
149 putStrLn ""
150 putStrLn "self: {"
151 putStrLn ""
152 pkgs <- flip parMapM (Map.toAscList db) $ \(name, vs) -> do
153 defs <- forM (Set.toAscList vs) $ \pkgversion -> do
154 let -- TODO: Include list of broken dependencies in the generated output.
155 descr = hackage Map.! name Map.! pkgversion
157 flagAssignment :: FlagAssignment
158 flagAssignment = configureCabalFlags (packageId descr)
160 drv' :: Derivation
161 drv' = fromGenericPackageDescription haskellResolver nixpkgsResolver targetPlatform (compilerInfo config) flagAssignment [] descr
163 isInDefaultPackageSet :: Bool
164 isInDefaultPackageSet = maybe False (== pkgversion) (Map.lookup name generatedDefaultPackageSet)
166 attr :: String
167 attr = name ++ if isInDefaultPackageSet then [] else '_' : [ if c == '.' then '_' else c | c <- display pkgversion ]
169 sha256 :: String
170 sha256 | Just x <- lookup "X-Package-SHA256" (customFieldsPD (packageDescription descr)) = x
171 | otherwise = error $ display (packageId descr) ++ " has no hash"
173 srcSpec <- liftIO $ sourceFromHackage (Certain sha256) (name ++ "-" ++ display pkgversion)
175 let drv = drv' & src .~ srcSpec
176 & metaSection.hydraPlatforms %~ (`Set.difference` fromMaybe Set.empty (Map.lookup (PackageName name) (dontDistributePackages config)))
177 & metaSection.hydraPlatforms %~ (if isInDefaultPackageSet then id else const Set.empty)
178 & metaSection.maintainers .~ fromMaybe Set.empty (Map.lookup (PackageName name) globalPackageMaintainers)
180 isFromHackage :: Binding -> Bool
181 isFromHackage b = case view (reference . path) b of
182 ["self",_] -> True
183 _ -> False
185 overrides :: Doc
186 overrides = fcat $ punctuate space
187 [ pPrint b | b <- Set.toList (view (dependencies . each) drv), not (isFromHackage b) ]
189 return $ nest 2 $ hang (doubleQuotes (text attr) <+> equals <+> text "callPackage") 2 (parens (pPrint drv)) <+> (braces overrides <> semi)
190 return (intercalate "\n\n" (map render defs))
192 liftIO $ mapM_ (\pkg -> putStrLn pkg >> putStrLn "") pkgs
193 liftIO $ putStrLn "}"
195readPreferredVersions :: FilePath -> IO [Constraint]
196readPreferredVersions p = mapMaybe parsePreferredVersionsLine . lines <$> readFile p
198parsePreferredVersionsLine :: String -> Maybe Constraint
199parsePreferredVersionsLine [] = Nothing
200parsePreferredVersionsLine ('-':'-':_) = Nothing
201parsePreferredVersionsLine l = simpleParse l `mplus` error ("invalid preferred-versions line: " ++ show l)