baserock-schema-0.0.1.0: src/Baserock/Schema/V9.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
module Baserock.Schema.V9 where
import Algebra.Graph
import Baserock.Schema.Utils
import Control.Arrow
import Control.Error.Safe
import Control.Error.Util
import Control.Monad
import Control.Monad.Except
import qualified Data.ByteString as BS
import Data.List
import Data.Maybe
import Data.Monoid
import Data.Profunctor
import Data.Text (Text, unpack)
import Data.Yaml
import Data.Yaml.Pretty
import GHC.Generics
--- ChunkInstructions ("chunk.morph")
data ChunkInstructions = ChunkInstructions {
chunkInstructionsName :: Text,
buildSystem :: Text,
preConfigureCommands :: [Text],
configureCommands :: [Text],
postConfigureCommands :: [Text],
preBuildCommands :: [Text],
buildCommands :: [Text],
postBuildCommands :: [Text],
preInstallCommands :: [Text],
installCommands :: [Text],
postInstallCommands :: [Text]
} deriving (Eq, Generic, Show)
instance FromJSON ChunkInstructions where
parseJSON = withObject "ChunkInstructions" $ \v -> ChunkInstructions
<$> v .: "name"
<*> v .:? "build-system" .!= "manual"
<*> v .:? "pre-configure-commands" .!= []
<*> v .:? "configure-commands" .!= []
<*> v .:? "post-configure-commands" .!= []
<*> v .:? "pre-build-commands" .!= []
<*> v .:? "build-commands" .!= []
<*> v .:? "post-build-commands" .!= []
<*> v .:? "pre-install-commands" .!= []
<*> v .:? "install-commands" .!= []
<*> v .:? "post-install-commands" .!= []
chunkInstructionsFieldOrder :: [Text] =
["name", "kind", "build-system"]
<> fmap (<> "-commands") ["configure", "build", "install"] >>= \x -> ["pre-" <> x, x, "post-" <> x]
<> ["rpm-metadata"]
chunkInstructionsFieldCmp = listElemCmp chunkInstructionsFieldOrder
instance ToJSON ChunkInstructions where
toJSON x =
object $ ["name" .= chunkInstructionsName x, "kind" .= ("chunk" :: Text), "build-system" .= buildSystem x]
<> possibly "pre-configure-commands" (preConfigureCommands x)
<> possibly "configure-commands" (configureCommands x)
<> possibly "post-configure-commands" (postConfigureCommands x)
<> possibly "pre-build-commands" (preBuildCommands x)
<> possibly "build-commands" (buildCommands x)
<> possibly "post-build-commands" (postBuildCommands x)
<> possibly "pre-install-commands" (preInstallCommands x)
<> possibly "install-commands" (installCommands x)
<> possibly "post-install-commands" (postInstallCommands x)
encodePrettyChunkInstructions = encodePretty $ setConfCompare chunkInstructionsFieldCmp defConfig
-- Stratum ("stratum.morph")
data Chunk = Chunk {
chunkName :: Text,
chunkMorph :: Maybe Text,
repo :: Text,
ref :: Text,
sha :: Maybe Text,
buildMode :: Text,
chunkBuildSystem :: Text,
chunkBDs :: [Text]
} deriving (Eq, Generic, Show)
instance FromJSON Chunk where
parseJSON = withObject "Chunk" $ \v -> Chunk
<$> v .: "name"
<*> v .:? "morph"
<*> v .: "repo"
<*> v .: "ref"
<*> v .:? "sha"
<*> v .:? "build-mode" .!= "staging"
<*> v .:? "build-system" .!= "manual"
<*> v .:? "build-depends" .!= []
chunkFieldOrder :: [Text] = ["name", "morph", "repo", "ref", "sha", "build-mode", "build-system", "build-depends"]
chunkFieldCmp = listElemCmp chunkFieldOrder
instance ToJSON Chunk where
toJSON x = object $ ["name" .= chunkName x, "repo" .= repo x, "ref" .= ref x, "sha" .= sha x]
<> possibly "morph" (chunkMorph x)
<> possibly "build-mode" (buildMode x)
<> possibly "build-system" (chunkBuildSystem x)
<> possibly "build-depends" (chunkBDs x)
encodePrettyChunk = encodePretty $ setConfCompare chunkFieldCmp defConfig
data StratumBD = StratumBD {
stratumBDMorph :: Text
} deriving (Eq, Generic, Show)
instance FromJSON StratumBD where
parseJSON = withObject "StratumBD" $ \v -> StratumBD
<$> v .: "morph"
instance ToJSON StratumBD where
toJSON x = object ["morph" .= stratumBDMorph x]
data Stratum = Stratum {
stratumName :: Text,
stratumDescription :: Maybe Text,
stratumBDs :: [StratumBD],
chunks :: [Chunk]
} deriving (Eq, Generic, Show)
instance FromJSON Stratum where
parseJSON = withObject "Stratum" $ \v -> Stratum
<$> v .: "name"
<*> v .:? "description"
<*> v .:? "build-depends" .!= []
<*> v .: "chunks"
stratumFieldOrder :: [Text] = chunkFieldOrder ++ ["kind", "chunks"]
stratumFieldCmp = listElemCmp stratumFieldOrder
instance ToJSON Stratum where
toJSON x = object $ ["name" .= stratumName x, "kind" .= ("stratum" :: Text), "chunks" .= chunks x]
<> possibly "description" (stratumDescription x)
<> possibly "build-depends" (stratumBDs x)
encodePrettyStratum = encodePretty $ setConfCompare stratumFieldCmp defConfig
-- System ("system.morph")
data StratumInclude = StratumInclude {
stratumIncludeName :: Text,
stratumIncludeMorph :: Text
} deriving (Eq, Generic, Show)
instance FromJSON StratumInclude where
parseJSON = withObject "StratumInclude" $ \v -> StratumInclude
<$> v .: "name"
<*> v .: "morph"
stratumIncludeFieldOrder :: [Text] = ["name", "morph"]
stratumIncludeFieldCmp = listElemCmp stratumIncludeFieldOrder
instance ToJSON StratumInclude where
toJSON x = object ["name" .= stratumIncludeName x, "morph" .= stratumIncludeMorph x]
encodePrettyStratumInclude = encodePretty $ setConfCompare stratumIncludeFieldCmp defConfig
data System = System {
systemName :: Text,
systemDescription :: Maybe Text,
arch :: Text,
strata :: [StratumInclude],
configurationExtensions :: [Text]
} deriving (Eq, Generic, Show)
instance FromJSON System where
parseJSON = withObject "System" $ \v -> System
<$> v .: "name"
<*> v .:? "description"
<*> v .: "arch"
<*> v .: "strata"
<*> v .: "configuration-extensions"
systemFieldOrder :: [Text] = stratumIncludeFieldOrder ++ ["kind", "description", "arch", "strata", "configuration-extensions"]
systemFieldCmp = listElemCmp systemFieldOrder
instance ToJSON System where
toJSON x = object $ ["name" .= systemName x, "kind" .= ("system" :: Text), "arch" .= arch x, "strata" .= strata x, "configuration-extensions" .= configurationExtensions x]
<> possibly "description" (systemDescription x)
encodePrettySystem = encodePretty $ setConfCompare systemFieldCmp defConfig
--- Decoders
type StratumAST = (Stratum, [(FilePath, ChunkInstructions)])
type SystemAST = (System, [(FilePath, StratumAST)])
splitK a b = runKleisli $ Kleisli a &&& Kleisli b
decodeASTWith selector decoder x = runExceptT $ withExceptT (\e -> AesonException ("Error in " ++ x ++ ": " ++ show e)) $ do
(ExceptT . decodeFileEither $ x) >>= splitK return (mapM (splitK return decoder) . selector)
decodeStratumAST :: FilePath -> IO (Either ParseException StratumAST)
decodeStratumAST = decodeASTWith (fmap unpack . mapMaybe chunkMorph . chunks) (ExceptT . decodeFileEither)
decodeSystemAST :: FilePath -> IO (Either ParseException SystemAST)
decodeSystemAST = decodeASTWith (fmap (unpack . stratumIncludeMorph) . strata) (ExceptT . decodeStratumAST)
-- Encoders
encodeStratumAST f (x, as) = do
BS.writeFile f (encodePrettyStratum x)
forM_ as $ \(i, j) -> BS.writeFile i (encodePrettyChunkInstructions j)
encodeSystemAST f (x, as) = do
BS.writeFile f (encodePrettySystem x)
forM_ as $ \(i, j) -> encodeStratumAST i j
-- Utility
data BaserockGraphException = NoExistChunkBD Chunk Text
| NoExistStratumBD Stratum Text deriving Show
stratumGraph :: Stratum -> Either BaserockGraphException (Graph Chunk)
stratumGraph s = right edges $ sequence $ runExceptT $ do
x <- ExceptT (fmap Right $ chunks s)
y <- ExceptT (fmap Right $ chunkBDs x)
z <- failWith (NoExistChunkBD x y) $ find ((== y) . chunkName) (chunks s)
return (z, x)
-- V9 is non-regular in that graphs of Strata can be derived purely from the Stratum itself
-- where as graphs of Systems require a SystemAST. We aim to correct this.