1module Main ( main ) where
3import Cabal2Nix.Normalize
4import Cabal2Nix.Generate
5import Control.Exception ( bracket )
6import Control.Monad.RWS
8import qualified Data.Set as Set
10import qualified Distribution.Hackage.DB as DB
11import Distribution.NixOS.Derivation.Cabal
12import Distribution.Package
13import Distribution.PackageDescription ( GenericPackageDescription() )
14import Distribution.Text
15import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
16import System.Directory
17import System.Environment
21import Text.Regex.Posix
23data Pkg = Pkg Derivation FilePath Bool
24 deriving (Show, Eq, Ord)
26type PkgSet = Set.Set Pkg
28data Configuration = Configuration
29 { _msgDebug :: String -> IO ()
30 , _msgInfo :: String -> IO ()
31 , _hackageDb :: DB.Hackage
35defaultConfiguration :: Configuration
36defaultConfiguration = Configuration
37 { _msgDebug = hPutStrLn stderr
38 , _msgInfo = hPutStrLn stderr
39 , _hackageDb = DB.empty
43type Hackage4Nix a = RWST Configuration () PkgSet IO a
45io :: (MonadIO m) => IO a -> m a
48readDirectory :: FilePath -> IO [FilePath]
49readDirectory dirpath = do
50 entries <- getDirectoryContents dirpath
51 return [ x | x <- entries, x /= ".", x /= ".." ]
53msgDebug, msgInfo :: String -> Hackage4Nix ()
54msgDebug msg = ask >>= \s -> io (_msgDebug s msg)
55msgInfo msg = ask >>= \s -> io (_msgInfo s msg)
57getCabalPackage :: String -> Version -> Hackage4Nix GenericPackageDescription
58getCabalPackage name vers = do
60 case DB.lookup name db of
61 Just db' -> case DB.lookup vers db' of
62 Just pkg -> return pkg
63 Nothing -> fail ("hackage doesn't know about " ++ name ++ " version " ++ display vers)
64 Nothing -> fail ("hackage doesn't know about " ++ show name)
66discoverNixFiles :: (FilePath -> Hackage4Nix ()) -> FilePath -> Hackage4Nix ()
67discoverNixFiles yield dirOrFile = do
68 isFile <- io (doesFileExist dirOrFile)
69 case (isFile, takeExtension dirOrFile) of
70 (True,".nix") -> yield dirOrFile
72 (False,_) -> io (readDirectory dirOrFile) >>= mapM_ (discoverNixFiles yield . (dirOrFile </>))
74regenerateDerivation :: Derivation -> String -> Bool
75regenerateDerivation _ buf = not (buf =~ "(pre|post)Configure|(pre|post)Install|patchPhase|patches")
77parseNixFile :: FilePath -> String -> Hackage4Nix (Maybe Pkg)
79 | not (buf =~ "cabal.mkDerivation")
80 = msgDebug ("ignore non-cabal package " ++ path) >> return Nothing
81 | any (`isSuffixOf`path) badPackagePaths
82 = msgDebug ("ignore known bad package " ++ path) >> return Nothing
83 | Just deriv <- parseDerivation buf
84 = do forceReGen <- asks _force
85 let reGen = regenerateDerivation deriv buf
86 return (Just (Pkg deriv path (forceReGen || reGen)))
87 | otherwise = msgInfo ("failed to parse file " ++ path) >> return Nothing
89selectLatestVersions :: PkgSet -> PkgSet
90selectLatestVersions = Set.fromList . nubBy f2 . sortBy f1 . Set.toList
92 f1 (Pkg deriv1 _ _) (Pkg deriv2 _ _)
93 | pname deriv1 == pname deriv2 = compare (version deriv2) (version deriv1)
94 | otherwise = compare (pname deriv1) (pname deriv2)
95 f2 (Pkg deriv1 _ _) (Pkg deriv2 _ _) = pname deriv1 == pname deriv2
97discoverUpdates :: String -> Version -> Hackage4Nix [Version]
98discoverUpdates name vers = do
100 case DB.lookup name db of
101 Just pkgs -> return (filter (>vers) (DB.keys pkgs))
102 Nothing -> fail ("discoverUpdates cannot find package " ++ show name ++ " on Hackage")
104updateNixPkgs :: [FilePath] -> Hackage4Nix ()
105updateNixPkgs paths = do
106 msgDebug $ "updating = " ++ show paths
107 forM_ paths $ \fileOrDir ->
108 flip discoverNixFiles fileOrDir $ \file -> do
109 nix' <- io (readFile file) >>= parseNixFile file
110 flip (maybe (return ())) nix' $ \nix -> do
111 let Pkg deriv path regenerate = nix
112 maints = maintainers (metaSection deriv)
113 plats = platforms (metaSection deriv)
114 hplats = hydraPlatforms (metaSection deriv)
115 modify (Set.insert nix)
117 msgDebug ("re-generate " ++ path)
118 pkg <- getCabalPackage (pname deriv) (version deriv)
119 let deriv' = (cabal2nix pkg) { sha256 = sha256 deriv
120 , runHaddock = runHaddock deriv
121 , doCheck = doCheck deriv
122 , jailbreak = jailbreak deriv
124 meta = metaSection deriv'
125 plats' = if null plats then platforms meta else plats
126 deriv'' = deriv' { metaSection = meta
127 { maintainers = maints -- ++ ["andres","simons"]
129 , hydraPlatforms = hplats
132 io $ writeFile path (show (disp (normalize deriv'')))
133 pkgset <- gets selectLatestVersions
134 updates' <- forM (Set.elems pkgset) $ \pkg -> do
135 let Pkg deriv _ _ = pkg
136 updates <- discoverUpdates (pname deriv) (version deriv)
138 let updates = [ u | u@(_,_:_) <- updates' ]
139 unless (null updates) $ do
140 msgInfo "The following updates are available:"
141 forM_ updates $ \(pkg,versions) -> do
142 let Pkg deriv path regenerate = pkg
144 msgInfo $ display (packageId deriv) ++ ":"
145 forM_ versions $ \newVersion -> do
146 let deriv' = deriv { version = newVersion }
147 msgInfo $ " " ++ genCabal2NixCmdline (Pkg deriv' path regenerate)
150genCabal2NixCmdline :: Pkg -> String
151genCabal2NixCmdline (Pkg deriv path _) = unwords $ ["cabal2nix"] ++ opts ++ ['>':path']
153 meta = metaSection deriv
154 opts = [cabal] ++ maints' ++ plats' ++ hplats'
155 ++ (if jailbreak deriv then ["--jailbreak"] else [])
156 ++ (if runHaddock deriv then [] else ["--no-haddock"])
157 ++ (if doCheck deriv then [] else ["--no-check"])
158 cabal = "cabal://" ++ display (packageId deriv)
159 maints' = [ "--maintainer=" ++ normalizeMaintainer m | m <- maintainers meta ]
161 | ["self.ghc.meta.platforms"] == platforms meta = []
162 | otherwise = [ "--platform=" ++ p | p <- platforms meta ]
164 | ["self.ghc.meta.platforms"] == platforms meta = []
165 | otherwise = [ "--hydra-platform=" ++ p | p <- platforms meta ]
167 | path =~ "/[0-9\\.]+\\.nix$" = replaceFileName path (display (version deriv) <.> "nix")
170normalizeMaintainer :: String -> String
172 | "self.stdenv.lib.maintainers." `isPrefixOf` x = drop 28 x
175data CliOption = PrintHelp | Verbose | ForceRegen
179main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
180 let options :: [OptDescr CliOption]
182 [ Option "h" ["help"] (NoArg PrintHelp) "show this help text"
183 , Option "v" ["verbose"] (NoArg Verbose) "enable noisy debug output"
184 , Option "" ["force"] (NoArg ForceRegen) "re-generate all files in place"
188 usage = usageInfo "Usage: hackage4nix [options] [dir-or-file ...]" options ++ unlines
190 , "The purpose of 'hackage4nix' is to keep all Haskell packages in our"
191 , "repository packages up-to-date. It scans a checked-out copy of"
192 , "Nixpkgs for expressions that use 'cabal.mkDerivation', and"
193 , "re-generates them in-place with cabal2nix."
196 cmdlineError :: String -> IO a
197 cmdlineError "" = hPutStrLn stderr usage >> exitFailure
198 cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
201 (opts,args) <- case getOpt Permute options args' of
202 (o,n,[] ) -> return (o,n)
203 (_,_,errs) -> cmdlineError (concatMap (\e -> '*':'*':'*':' ':e) errs)
205 when (PrintHelp `elem` opts) (cmdlineError "")
207 hackage <- DB.readHackage
208 let cfg = defaultConfiguration
209 { _msgDebug = if Verbose `elem` opts then _msgDebug defaultConfiguration else const (return ())
210 , _hackageDb = hackage
211 , _force = ForceRegen `elem` opts
213 ((),_,()) <- runRWST (updateNixPkgs args) cfg Set.empty
217-- Packages that cabal2nix cannot generate build expressions for.
219badPackagePaths :: [FilePath]
220badPackagePaths = [ -- These expression are not found on Hackage:
221 "haskell-platform/2009.2.0.2.nix", "haskell-platform/2010.1.0.0.nix"
222 , "haskell-platform/2010.2.0.0.nix", "haskell-platform/2011.2.0.0.nix"
223 , "haskell-platform/2011.2.0.1.nix", "haskell-platform/2011.4.0.0.nix"
224 , "haskell-platform/2012.2.0.0.nix", "haskell-platform/2012.4.0.0.nix"
225 , "haskell-platform/2013.2.0.0.nix", "compilers/flapjax/default.nix"
226 , "pkgs/games/uqm/3dovideo.nix"
227 -- Our primitive parser cannot handle these files.
228 , "top-level/all-packages.nix", "top-level/haskell-packages.nix"
229 -- This build is way too complicated to maintain it automatically.
230 , "pkgs/development/compilers/pakcs/default.nix"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list