1module Main ( main ) where
3import Cabal2Nix.Generate
4import Cabal2Nix.Normalize
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|broken")
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 -- Traverse the given directorcy structure and discover all available
108 forM_ paths $ \fileOrDir -> do
109 msgDebug $ "scanning " ++ show fileOrDir
110 flip discoverNixFiles fileOrDir $ \file -> do
111 nix' <- io (readFile file) >>= parseNixFile file
112 flip (maybe (return ())) nix' $ \nix ->
113 modify (Set.insert nix)
115 -- Re-generate all Haskell packages in-place (unless
116 -- 'regenerateDerivation' decided that this particular package
117 -- shouldn't be touched.
118 latestVersions <- gets selectLatestVersions
119 let latestVersionMap :: [(String,Version)]
120 latestVersionMap = [ (pname deriv, version deriv) | Pkg deriv _ _ <- Set.toList latestVersions ]
121 let isLatest :: Derivation -> Bool
122 isLatest deriv = maybe False (version deriv ==) (lookup (pname deriv) latestVersionMap)
123 get >>= \pkgset -> forM_ (Set.toList pkgset) $ \nix -> do
124 let Pkg deriv path regenerate = nix
125 maints = maintainers (metaSection deriv)
126 plats = platforms (metaSection deriv)
127 hplats = if isLatest deriv then hydraPlatforms (metaSection deriv) else ["self.stdenv.lib.platforms.none"]
130 msgDebug ("re-generate " ++ path)
131 pkg <- getCabalPackage (pname deriv) (version deriv)
132 let deriv' = (cabal2nix pkg) { src = src deriv
133 , runHaddock = runHaddock deriv
134 , doCheck = doCheck deriv
135 , jailbreak = jailbreak deriv
136 , hyperlinkSource = hyperlinkSource deriv
137 , enableSplitObjs = enableSplitObjs deriv
139 meta = metaSection deriv'
140 plats' = if null plats then platforms meta else plats
141 deriv'' = deriv' { metaSection = meta
142 { maintainers = maints
144 , hydraPlatforms = hplats
147 io $ writeFile path (show (disp (normalize deriv'')))
149 -- Discover available updates and print the appropriate cabal2nix
150 -- command line to the console for the user to execute at his/her
152 updates' <- forM (Set.elems latestVersions) $ \pkg -> do
153 let Pkg deriv _ _ = pkg
154 updates <- discoverUpdates (pname deriv) (version deriv)
156 let updates = [ u | u@(_,_:_) <- updates' ]
157 unless (null updates) $ do
158 msgInfo "The following updates are available:"
159 forM_ updates $ \(pkg,versions) -> do
160 let Pkg deriv path regenerate = pkg
162 msgInfo $ display (packageId deriv) ++ ":"
163 forM_ versions $ \newVersion -> do
164 let deriv' = deriv { version = newVersion }
165 msgInfo $ " " ++ genCabal2NixCmdline (Pkg deriv' path regenerate)
168genCabal2NixCmdline :: Pkg -> String
169genCabal2NixCmdline (Pkg deriv path _) = unwords $ ["cabal2nix"] ++ opts ++ ['>':path']
171 meta = metaSection deriv
172 opts = [cabal] ++ maints' ++ plats' ++ hplats'
173 ++ (if jailbreak deriv then ["--jailbreak"] else [])
174 ++ (if runHaddock deriv then [] else ["--no-haddock"])
175 ++ (if doCheck deriv then [] else ["--no-check"])
176 cabal = "cabal://" ++ display (packageId deriv)
177 maints' = [ "--maintainer=" ++ normalizeMaintainer m | m <- maintainers meta ]
179 | ["self.ghc.meta.platforms"] == platforms meta = []
180 | otherwise = [ "--platform=" ++ p | p <- platforms meta ]
182 | ["self.ghc.meta.platforms"] == platforms meta = []
183 | otherwise = [ "--hydra-platform=" ++ p | p <- hydraPlatforms meta ]
185 | path =~ "/[0-9\\.]+\\.nix$" = replaceFileName path (display (version deriv) <.> "nix")
188normalizeMaintainer :: String -> String
190 | "self.stdenv.lib.maintainers." `isPrefixOf` x = drop 28 x
193data CliOption = PrintHelp | Verbose | ForceRegen
197main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
198 let options :: [OptDescr CliOption]
200 [ Option "h" ["help"] (NoArg PrintHelp) "show this help text"
201 , Option "v" ["verbose"] (NoArg Verbose) "enable noisy debug output"
202 , Option "" ["force"] (NoArg ForceRegen) "re-generate all files in place"
206 usage = usageInfo "Usage: hackage4nix [options] [dir-or-file ...]" options ++ unlines
208 , "The purpose of 'hackage4nix' is to keep all Haskell packages in our"
209 , "repository packages up-to-date. It scans a checked-out copy of"
210 , "Nixpkgs for expressions that use 'cabal.mkDerivation', and"
211 , "re-generates them in-place with cabal2nix."
214 cmdlineError :: String -> IO a
215 cmdlineError "" = hPutStrLn stderr usage >> exitFailure
216 cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
219 (opts,args) <- case getOpt Permute options args' of
220 (o,n,[] ) -> return (o,n)
221 (_,_,errs) -> cmdlineError (concatMap (\e -> '*':'*':'*':' ':e) errs)
223 when (PrintHelp `elem` opts) (cmdlineError "")
225 hackage <- DB.readHackage
226 let cfg = defaultConfiguration
227 { _msgDebug = if Verbose `elem` opts then _msgDebug defaultConfiguration else const (return ())
228 , _hackageDb = hackage
229 , _force = ForceRegen `elem` opts
231 ((),_,()) <- runRWST (updateNixPkgs args) cfg Set.empty
235-- Packages that cabal2nix cannot generate build expressions for.
237badPackagePaths :: [FilePath]
238badPackagePaths = [ -- These expression are not found on Hackage:
239 "haskell-platform/2009.2.0.2.nix", "haskell-platform/2010.1.0.0.nix"
240 , "haskell-platform/2010.2.0.0.nix", "haskell-platform/2011.2.0.0.nix"
241 , "haskell-platform/2011.2.0.1.nix", "haskell-platform/2011.4.0.0.nix"
242 , "haskell-platform/2012.2.0.0.nix", "haskell-platform/2012.4.0.0.nix"
243 , "haskell-platform/2013.2.0.0.nix", "compilers/flapjax/default.nix"
244 , "pkgs/games/uqm/3dovideo.nix"
245 -- Our primitive parser cannot handle these files.
246 , "top-level/all-packages.nix", "top-level/haskell-packages.nix"
247 -- This build is way too complicated to maintain it automatically.
248 , "pkgs/development/compilers/pakcs/default.nix"
249 , "pkgs/development/libraries/haskell/hoogle/local.nix"
250 -- Not registered on Hackage.
251 , "pkgs/development/compilers/agda/stdlib.nix"
252 , "pkgs/development/compilers/cryptol/1.8.x.nix"
253 , "pkgs/development/compilers/cryptol/2.0.x.nix"
254 , "pkgs/development/compilers/jhc/default.nix"
255 , "pkgs/development/tools/haskell/cabal-delete/default.nix"
256 , "pkgs/tools/networking/sproxy-web/default.nix"
257 , "pkgs/tools/networking/sproxy/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