autonix-deps 0.2.0.0 → 0.3.0.0
raw patch · 12 files changed
+262/−267 lines, 12 filesdep +aesondep +aeson-prettydep +semigroups
Dependencies added: aeson, aeson-pretty, semigroups, text
Files
- autonix-deps.cabal +9/−5
- src/Autonix.hs +35/−0
- src/Autonix/Analyze.hs +29/−24
- src/Autonix/Args.hs +0/−12
- src/Autonix/CMake.hs +25/−13
- src/Autonix/Deps.hs +0/−48
- src/Autonix/Generate.hs +0/−80
- src/Autonix/Manifest.hs +58/−38
- src/Autonix/Package.hs +62/−0
- src/Autonix/PkgDeps.hs +0/−47
- src/Autonix/Renames.hs +14/−0
- src/Autonix/Src.hs +30/−0
autonix-deps.cabal view
@@ -1,5 +1,5 @@ name: autonix-deps-version: 0.2.0.0+version: 0.3.0.0 synopsis: Library for Nix expression dependency generation license: BSD3 license-file: LICENSE@@ -20,16 +20,18 @@ library exposed-modules:+ Autonix Autonix.Analyze- Autonix.Args Autonix.CMake- Autonix.Deps- Autonix.Generate Autonix.Manifest- Autonix.PkgDeps+ Autonix.Package Autonix.Regex+ Autonix.Renames+ Autonix.Src build-depends: base >=4.7 && <5+ , aeson >= 0.9+ , aeson-pretty >= 0.7 , bytestring >=0.10 , conduit >=1.2 , containers >=0.5@@ -42,6 +44,8 @@ , process >=1.2 , regex-tdfa >=1.2 , resourcet >=1.1+ , semigroups >= 0.16+ , text >= 1.2 , transformers >=0.3 , xml >=1.3 hs-source-dirs: src
+ src/Autonix.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns #-}++module Autonix (autonix) where++import Control.Monad.IO.Class+import Control.Monad.Trans.Resource+import Control.Monad.State+import Data.Aeson.Encode.Pretty (encodePretty)+import qualified Data.ByteString.Lazy as BL+import Data.Map.Strict (Map)+import Data.Text (Text)+import Options.Applicative++import Autonix.Analyze+import Autonix.Package (Package)+import Autonix.Renames++autonix :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)+ => [Analyzer (StateT (Map Text Package, Renames) m)]+ -> m ()+autonix analyzers = do+ (pkgs, renames) <- withArgs (analyzePackages (analyzeFiles analyzers))+ writePackages pkgs+ writeRenames renames++withArgs :: MonadIO m => (FilePath -> m a) -> m a+withArgs child =+ join $ liftIO $ execParser $ info (helper <*> parser) fullDesc+ where+ parser = child <$> strArgument (metavar "MANIFEST")++writePackages :: MonadIO m => Map Text Package -> m ()+writePackages = liftIO . BL.writeFile "packages.json" . encodePretty
src/Autonix/Analyze.hs view
@@ -8,39 +8,44 @@ ) where import Codec.Archive-import Control.Lens hiding (act)+import Control.Lens import Control.Monad.State import Control.Monad.Trans.Resource-import qualified Data.ByteString.Char8 as B+import Data.ByteString (ByteString) import Data.Conduit-import qualified Data.Map as M-import Data.Monoid-import qualified Data.Set as S+import Data.Map (Map)+import Data.Semigroup+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T import Prelude hiding (mapM) -import Autonix.Deps-import Autonix.Manifest+import Autonix.Manifest (Manifest, readManifests)+import qualified Autonix.Manifest as Manifest+import Autonix.Package (Package, package)+import Autonix.Renames -type Analyzer m = ByteString -> Sink (FilePath, ByteString) m ()+type Analyzer m = Text -> Manifest -> Sink (FilePath, ByteString) (ResourceT m) () -analyzePackages :: (MonadIO m, MonadState Deps m)- => (ByteString -> FilePath -> m a)- -> FilePath -> m ()-analyzePackages perPackage manifestPath = do- manifest <- readManifest manifestPath- forM_ manifest $ \(pkg, _) -> at pkg .= Just mempty- let (pkgs, _) = unzip manifest- mapM_ (uncurry perPackage) manifest- deps %= M.filterWithKey (\k _ -> S.member k $ S.fromList pkgs)+analyzePackages :: MonadIO m => (Text -> Manifest -> StateT (Map Text Package, Renames) m ())+ -> FilePath -> m (Map Text Package, Renames)+analyzePackages perPackage manifestPath = flip execStateT (mempty, mempty) $ do+ manifests <- readManifests manifestPath+ imapM_ perPackage manifests sequenceSinks_ :: (Traversable f, Monad m) => f (Sink i m ()) -> Sink i m () sequenceSinks_ = void . sequenceSinks -analyzeFiles :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)- => [Analyzer (ResourceT m)] -> ByteString -> FilePath -> m ()-analyzeFiles analyzers pkg src- | null src = error $ B.unpack $ "No store path specified for " <> pkg+analyzeFiles :: (MonadBaseControl IO m, MonadIO m, MonadState (Map Text Package, Renames) m, MonadThrow m)+ => [Analyzer m] -> Text -> Manifest -> m ()+analyzeFiles analyzers name m+ | null (m^.Manifest.store) = error (T.unpack noStore) | otherwise = do- liftIO $ B.putStrLn $ "package " <> pkg- runResourceT- $ sourceArchive src $$ sequenceSinks_ (map ($ pkg) analyzers)+ liftIO $ T.putStrLn $ "package " <> name+ let conduits = sourceArchive (m^.Manifest.store) $$ sequenceSinks_ sinks+ sinks = analyzers >>= \analyze -> return (analyze name m)+ pkg = package (m^.Manifest.name) (m^.Manifest.src)+ _1 . at name %= Just . maybe pkg (pkg <>)+ runResourceT conduits+ where+ noStore = "No store path specified for " <> (m^.Manifest.name)
− src/Autonix/Args.hs
@@ -1,12 +0,0 @@-module Autonix.Args where--import Control.Applicative-import Control.Monad (join)-import Control.Monad.IO.Class-import Options.Applicative--withArgs :: MonadIO m => (FilePath -> m a) -> m a-withArgs child =- join $ liftIO $ execParser $ info (helper <*> parser) fullDesc- where- parser = child <$> strArgument (metavar "MANIFEST")
src/Autonix/CMake.hs view
@@ -12,33 +12,45 @@ import Control.Monad.IO.Class import Control.Monad.State import Data.Conduit+import Data.Map (Map) import qualified Data.Set as S+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import System.FilePath (takeFileName) import Autonix.Analyze-import Autonix.Deps+import Autonix.Package (Package)+import qualified Autonix.Package as Package import Autonix.Regex+import Autonix.Renames -detectCMake :: MonadState Deps m => Analyzer m-detectCMake pkg = awaitForever $ \(path, _) -> do+detectCMake :: (Monad m, MonadState (Map Text Package, Renames) m) => Analyzer m+detectCMake pkg _ = awaitForever $ \(path, _) -> when (takeFileName path == "CMakeLists.txt")- $ ix pkg . nativeBuildInputs %= S.insert "cmake"+ $ _1 . ix pkg . Package.nativeBuildInputs %= S.insert "cmake" -analyzeCMakePackages :: (MonadIO m, MonadState Deps m) => Analyzer m-analyzeCMakePackages pkg = awaitForever $ \(path, contents) -> do+analyzeCMakePackages :: (MonadIO m, MonadState (Map Text Package, Renames) m) => Analyzer m+analyzeCMakePackages pkg _ = awaitForever $ \(path, contents) -> do when (takeFileName path == "CMakeLists.txt") $ do- let new = concatMap (take 1 . drop 1) $ match regex contents+ let new = S.fromList+ $ map (T.toLower . T.decodeUtf8)+ $ concatMap (take 1 . drop 1)+ $ match regex contents regex = makeRegex "find_package[[:space:]]*\\([[:space:]]*([^[:space:],$\\)]+)"- ix pkg . buildInputs %= S.union (S.fromList new)+ _1 . ix pkg . Package.buildInputs <>= new -analyzeCMakePrograms :: (MonadIO m, MonadState Deps m) => Analyzer m-analyzeCMakePrograms pkg = awaitForever $ \(path, contents) -> do+analyzeCMakePrograms :: (MonadIO m, MonadState (Map Text Package, Renames) m) => Analyzer m+analyzeCMakePrograms pkg _ = awaitForever $ \(path, contents) -> do when (takeFileName path == "CMakeLists.txt") $ do- let new = concatMap (take 1 . drop 1) $ match regex contents+ let new = S.fromList+ $ map (T.toLower . T.decodeUtf8)+ $ concatMap (take 1 . drop 1)+ $ match regex contents regex = makeRegex "find_program[[:space:]]*\\([[:space:]]*([^[:space:],$\\)]+)"- ix pkg . nativeBuildInputs %= S.union (S.fromList new)+ _1 . ix pkg . Package.nativeBuildInputs <>= new -cmakeAnalyzers :: (MonadIO m, MonadState Deps m) => [Analyzer m]+cmakeAnalyzers :: (MonadIO m, MonadState (Map Text Package, Renames) m) => [Analyzer m] cmakeAnalyzers = [detectCMake, analyzeCMakePackages, analyzeCMakePrograms]
− src/Autonix/Deps.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE ViewPatterns #-}--module Autonix.Deps- ( Deps, names, deps, rename- , module Autonix.PkgDeps- ) where--import Control.Lens-import Control.Monad.State-import qualified Data.Map as M-import Data.Monoid-import Prelude hiding (foldr)--import Autonix.PkgDeps--data Deps =- Deps { _names :: Map ByteString ByteString- , _deps :: Map ByteString PkgDeps- }- deriving (Read, Show)-makeLenses ''Deps--instance Monoid Deps where- mempty = Deps { _names = M.empty, _deps = M.empty }-- mappend a b = flip execState a $ do- let b' = execState (iforMOf_ (names.>itraversed) a rename) b- iforMOf_ (names.>itraversed) b' rename- names %= M.union (b'^.names)- deps %= M.unionWith mappend (b'^.deps)--rename :: (MonadState Deps m) => ByteString -> ByteString -> m ()-rename old new = names %= M.insert old new--type instance Index Deps = ByteString-type instance IxValue Deps = PkgDeps--instance Ixed Deps where- ix idx = deps . ix idx--instance At Deps where- at idx = deps . at idx
− src/Autonix/Generate.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-}--module Autonix.Generate (generateDeps, writeDeps, writeRenames) where--import Control.Lens-import Control.Monad.IO.Class-import Control.Monad.Trans.Resource-import Control.Monad.State-import qualified Data.ByteString.Char8 as B-import qualified Data.Map as M-import Data.Monoid-import qualified Data.Set as S--import Autonix.Analyze-import Autonix.Args-import Autonix.Deps--generateDeps :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)- => [Analyzer (ResourceT (StateT Deps m))]- -> StateT Deps m ()-generateDeps analyzers = do- withArgs $ analyzePackages (analyzeFiles analyzers)- get >>= writeDeps- get >>= writeRenames--writeDeps :: MonadIO m => Deps -> m ()-writeDeps = liftIO . B.writeFile "dependencies.nix" . depsToNix--writeRenames :: MonadIO m => Deps -> m ()-writeRenames ds = liftIO $ do- let renames = do- (old, new) <- M.toList (ds^.names)- return ("\"" <> old <> "\" = \"" <> new <> "\";")- B.writeFile- "renames.nix"- (B.unlines- ([ "# DO NOT EDIT! This file is generated automatically."- , "{ }:"- , "{"- ]- ++ renames ++- [ "}" ]))--packageDeps :: (ByteString, PkgDeps) -> ByteString-packageDeps (name, ds) =- B.unlines- [ " " <> name <> " = {"- , " buildInputs = [ "- <> listInputs buildInputs- <> " ];"- , " nativeBuildInputs = [ "- <> listInputs nativeBuildInputs- <> " ];"- , " propagatedBuildInputs = [ "- <> listInputs propagatedBuildInputs- <> " ];"- , " propagatedNativeBuildInputs = [ "- <> listInputs propagatedNativeBuildInputs- <> " ];"- , " propagatedUserEnvPkgs = [ "- <> listInputs propagatedUserEnvPkgs- <> " ];"- , " };"- ]- where- listInputs l = B.unwords $ map quoted $ S.toList $ ds^.l- quoted x = "\"" <> x <> "\""--depsToNix :: Deps -> ByteString-depsToNix (view deps -> ds) =- B.unlines- ( [ "# DO NOT EDIT! This file is generated automatically."- , "{ }:"- , "{"- ]- ++ map packageDeps (M.toList ds) ++- [ "}" ]- )
src/Autonix/Manifest.hs view
@@ -1,47 +1,67 @@-module Autonix.Manifest (readManifest, readRenames) where+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-} -import Control.Applicative-import Control.Error-import Control.Monad (liftM)+module Autonix.Manifest+ ( Manifest, name, store, src+ , readManifests+ ) where++import Control.Lens import Control.Monad.IO.Class-import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as B+import Data.Aeson+import Data.Aeson.Types+import qualified Data.ByteString.Lazy as BL+import qualified Data.Char as Char import Data.Map (Map) import qualified Data.Map as M-import Text.XML.Light+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics -type Manifest = [(ByteString, FilePath)]+import Autonix.Src (Src) -readManifest :: MonadIO m => FilePath -> m Manifest-readManifest path =- liftM (fromMaybe $ error "malformed manifest.xml")- $ liftIO $ runMaybeT $ do- xml <- MaybeT $ parseXMLDoc <$> B.readFile path- attrs <-- MaybeT $ return- $ if qName (elName xml) == "expr"- then case headMay (elChildren xml) of- Just el | qName (elName el) == "attrs" -> Just el- _ -> Nothing- else Nothing- return $ attrsToManifest attrs+data Manifest =+ Manifest { _name :: Text+ , _store :: FilePath+ , _src :: Src+ }+ deriving Generic+makeLenses ''Manifest -attrsToManifest :: Element -> Manifest-attrsToManifest = mapMaybe go . filterChildrenName isAttr where- isAttr name = qName name == "attr"- go el = do- name <- findAttr (blank_name { qName = "name" }) el- child <- headMay $ elChildren el- value <- findAttr (blank_name { qName = "value"}) child- return (B.pack name, value)+manifestOptions :: Options+manifestOptions = defaultOptions { fieldLabelModifier = tail } -readRenames :: MonadIO m => Maybe FilePath -> m (Map ByteString ByteString)-readRenames renamesPath = do- case renamesPath of- Nothing -> return M.empty- Just path ->- liftM (M.fromList . map (toPair . B.words) . B.lines)- $ liftIO $ B.readFile path+instance FromJSON Manifest where+ parseJSON = genericParseJSON manifestOptions++instance ToJSON Manifest where+ toJSON = genericToJSON manifestOptions++readManifests :: MonadIO m => FilePath -> m (Map Text Manifest)+readManifests path = do+ emanifests <- eitherDecode' <$> liftIO (BL.readFile path)+ case emanifests :: Either String [Manifest] of+ Left err -> error ("readManifests: could not read manifest.json\n" ++ err)+ Right manifests -> return (foldr assemble M.empty manifests) where- toPair [old, new] = (old, new)- toPair _ = error "readRenames: invalid line"+ assemble :: Manifest -> Map Text Manifest -> Map Text Manifest+ assemble manifest =+ M.insertWith keepLatestVersion (onlyName $ view name manifest) manifest++ onlyName :: Text -> Text+ onlyName nameAndVersion =+ T.intercalate "-"+ $ takeWhile headNotDigit+ $ T.splitOn "-" nameAndVersion++ headNotDigit :: Text -> Bool+ headNotDigit txt | T.null txt = True+ | otherwise = not $ Char.isDigit (T.head txt)++ keepLatestVersion :: Manifest -> Manifest -> Manifest+ keepLatestVersion l r =+ case compare (l^.name) (r^.name) of+ LT -> r+ EQ -> l+ GT -> l
+ src/Autonix/Package.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TemplateHaskell #-}++module Autonix.Package+ ( Package, package+ , name, src+ , buildInputs+ , nativeBuildInputs+ , propagatedBuildInputs+ , propagatedNativeBuildInputs+ , propagatedUserEnvPkgs+ ) where++import Control.Lens+import Data.Aeson+import Data.Aeson.Types+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as S+import Data.Text (Text)+import GHC.Generics++import Autonix.Src (Src)++data Package =+ Package { _name :: Text+ , _src :: Src+ , _buildInputs :: Set Text+ , _nativeBuildInputs :: Set Text+ , _propagatedBuildInputs :: Set Text+ , _propagatedNativeBuildInputs :: Set Text+ , _propagatedUserEnvPkgs :: Set Text+ }+ deriving Generic+makeLenses ''Package++packageOptions :: Options+packageOptions = defaultOptions { fieldLabelModifier = tail }++instance FromJSON Package where+ parseJSON = genericParseJSON packageOptions++instance ToJSON Package where+ toJSON = genericToJSON packageOptions++instance Semigroup Package where+ (<>) a = (buildInputs <>~ a^.buildInputs)+ . (nativeBuildInputs <>~ a^.nativeBuildInputs)+ . (propagatedBuildInputs <>~ a^.propagatedBuildInputs)+ . (propagatedNativeBuildInputs <>~ a^.propagatedNativeBuildInputs)+ . (propagatedUserEnvPkgs <>~ a^.propagatedUserEnvPkgs)++package :: Text -> Src -> Package+package n s =+ Package { _name = n+ , _src = s+ , _buildInputs = S.empty+ , _nativeBuildInputs = S.empty+ , _propagatedBuildInputs = S.empty+ , _propagatedNativeBuildInputs = S.empty+ , _propagatedUserEnvPkgs = S.empty+ }
− src/Autonix/PkgDeps.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--module Autonix.PkgDeps- ( PkgDeps- , buildInputs, nativeBuildInputs- , propagatedBuildInputs, propagatedNativeBuildInputs- , propagatedUserEnvPkgs- , module Data.ByteString- , module Data.Map- , module Data.Set- ) where--import Control.Lens-import Control.Monad.State (execState)-import Data.ByteString (ByteString)-import Data.Map (Map)-import Data.Monoid-import Data.Set (Set)-import qualified Data.Set as S--data PkgDeps =- PkgDeps- { _buildInputs :: Set ByteString- , _nativeBuildInputs :: Set ByteString- , _propagatedBuildInputs :: Set ByteString- , _propagatedNativeBuildInputs :: Set ByteString- , _propagatedUserEnvPkgs :: Set ByteString- }- deriving (Eq, Ord, Read, Show)-makeLenses ''PkgDeps--instance Monoid PkgDeps where- mempty =- PkgDeps- { _buildInputs = S.empty- , _nativeBuildInputs = S.empty- , _propagatedBuildInputs = S.empty- , _propagatedNativeBuildInputs = S.empty- , _propagatedUserEnvPkgs = S.empty- }-- mappend a = execState $ do- buildInputs %= mappend (a^.buildInputs)- nativeBuildInputs %= mappend (a^.nativeBuildInputs)- propagatedBuildInputs %= mappend (a^.propagatedBuildInputs)- propagatedNativeBuildInputs %= mappend (a^.propagatedNativeBuildInputs)- propagatedUserEnvPkgs %= mappend (a^.propagatedUserEnvPkgs)
+ src/Autonix/Renames.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE FlexibleContexts #-}++module Autonix.Renames where++import Control.Monad.IO.Class+import Data.Aeson.Encode.Pretty (encodePretty)+import qualified Data.ByteString.Lazy as BL+import Data.Map.Strict (Map)+import Data.Text (Text)++type Renames = Map Text Text++writeRenames :: MonadIO m => Renames -> m ()+writeRenames = liftIO . BL.writeFile "renames.json" . encodePretty
+ src/Autonix/Src.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TemplateHaskell #-}++module Autonix.Src (Src, mkSrc, url, sha256, name) where++import Control.Lens+import Data.Aeson+import Data.Aeson.Types+import Data.Text (Text)+import GHC.Generics++data Src =+ Src { _url :: FilePath+ , _sha256 :: Text+ , _name :: Text+ }+ deriving Generic+makeLenses ''Src++srcOptions :: Options+srcOptions = defaultOptions { fieldLabelModifier = tail }++instance FromJSON Src where+ parseJSON = genericParseJSON srcOptions++instance ToJSON Src where+ toJSON = genericToJSON srcOptions++mkSrc :: FilePath -> Text -> Text -> Src+mkSrc = Src