-- | -- Module: Staversion.Internal.BuildPlan.Pantry -- Description: Pantry YAML format of build plan -- Maintainer: Toshio Ito -- -- __This is an internal module. End-users should not use it.__ -- -- @since 0.2.4.0 module Staversion.Internal.BuildPlan.Pantry ( PantryBuildPlanMap, PantryName, pantryCompiler, pantryName, toBuildPlanMap, coresToBuildPlanMap, parseBuildPlanMapYAML, fetchBuildPlanMapYAML ) where import Control.Applicative ((<$>), (<*>), empty, (<|>)) import Control.Monad (void) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import Data.Aeson (FromJSON(..), Value(..), (.:), (.:!)) import qualified Data.Aeson.Types as Aeson import qualified Data.HashMap.Strict as HM import Data.Monoid ((<>)) import Data.Text (pack, Text) import qualified Data.Yaml as Yaml import Staversion.Internal.BuildPlan.BuildPlanMap ( BuildPlanMap, HasVersions(..), ) import qualified Staversion.Internal.BuildPlan.BuildPlanMap as BuildPlanMap import Staversion.Internal.BuildPlan.Core ( Compiler(..), CoreBuildPlanMap(..), CompilerVersion(..), CompilerCores ) import Staversion.Internal.BuildPlan.Parser (parserVersion, manyTillWithEnd) import Staversion.Internal.BuildPlan.Stackage (ExactResolver(..)) import qualified Staversion.Internal.Megaparsec as P import Staversion.Internal.HTTP (Manager, fetchURL) import Staversion.Internal.Query (ErrorMsg, PackageName) import Staversion.Internal.Version (Version) -- | Name of a pantry snapshot type PantryName = Text -- | A build plan map loaded from a Pantry YAML file. This is not a -- complete 'BuildPlanMap', because it implicitly refers to -- 'CoreBuildPlanMap'. That's why its data constructor is not -- exported. data PantryBuildPlanMap = PantryBuildPlanMap { pantryName :: Maybe PantryName, pantryCompiler :: Compiler, pantryMap :: BuildPlanMap } instance HasVersions PantryBuildPlanMap where packageVersion pbp = packageVersion (pantryMap pbp) instance FromJSON PantryBuildPlanMap where parseJSON (Object o) = PantryBuildPlanMap <$> (o .:! "name") <*> fmap unPantryCompiler parserCompiler <*> fmap fromPantryPackageList (o .: "packages") where parserCompiler = (o .: "compiler") <|> parserResolverCompiler parserResolverCompiler = do res <- o .: "resolver" res .: "compiler" parseJSON _ = empty -- | Internal type to parse a package in Pantry YAML. newtype PantryPackage = PantryPackage { unPantryPackage :: (PackageName, Version) } deriving (Show,Eq,Ord) fromPantryPackageList :: [PantryPackage] -> BuildPlanMap fromPantryPackageList = BuildPlanMap.fromList . map unPantryPackage instance FromJSON PantryPackage where parseJSON (Object o) = fmap PantryPackage $ parsePText =<< (o .: "hackage") where parsePText :: Text -> Aeson.Parser (PackageName, Version) parsePText t = either (fail . show) return $ P.runParser the_parser "" t the_parser = parserPackage (void $ P.char '@') parseJSON _ = empty -- | Internal type to parse a compiler in Pantry YAML. newtype PantryCompiler = PantryCompiler { unPantryCompiler :: Compiler } deriving (Show,Eq,Ord) instance FromJSON PantryCompiler where parseJSON (String s) = fmap toCompiler $ either (fail . show) return $ P.runParser the_parser "" s where the_parser = parserPackage (P.eof) toCompiler (name, ver) = PantryCompiler $ Compiler name $ CVNumbered ver parseJSON _ = empty -- | Combine 'PantryBuildPlanMap' and 'CoreBuildPlanMap' to make a -- complete 'BuildPlanMap'. toBuildPlanMap :: CoreBuildPlanMap -> PantryBuildPlanMap -> Either String BuildPlanMap toBuildPlanMap cbp pbp = if ccv == pcv then Right $ pantryMap pbp <> coreMap cbp else Left ("Unmatched compiler versions: Core: " <> show ccv <> ", Pantry: " <> show pcv) where ccv = coreCompiler cbp pcv = pantryCompiler pbp -- | Select a 'CoreBuildPlanMap' from the given map to make a complete -- 'BuildPlanMap' from 'PantryBuildPlanMap'. coresToBuildPlanMap :: CompilerCores -> PantryBuildPlanMap -> Either String BuildPlanMap coresToBuildPlanMap cmap pbp = do cbp <- maybe (Left ("No CoreBuildPlanMap for compiler " ++ show compiler)) Right $ HM.lookup compiler cmap toBuildPlanMap cbp pbp where compiler = pantryCompiler pbp -- | Parse a YAML document for a 'CoreBuildPlanMap'. parseBuildPlanMapYAML :: BS.ByteString -> Either ErrorMsg PantryBuildPlanMap parseBuildPlanMapYAML = either (Left . toErrorMsg) Right . Yaml.decodeEither' where toErrorMsg e = "Error while parsing PantryBuildPlanMap: " ++ show e -- | Fetch a Pantry build plan file from the Web. fetchBuildPlanMapYAML :: Manager -> ExactResolver -> IO BSL.ByteString fetchBuildPlanMapYAML man er = fetchURL man url where url = "https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/" <> resolver_part resolver_part = case er of ExactLTS major minor -> "lts/" ++ show major ++ "/" ++ show minor ++ ".yaml" ExactNightly year month day -> "nightly/" ++ show year ++ "/" ++ show month ++ "/" ++ show day ++ ".yaml" parserPackage :: P.Parser () -- ^ Parser of a symbol that follows the packageName-version string. -> P.Parser (PackageName, Version) parserPackage end = do (pstr, ver) <- manyTillWithEnd P.anyChar versionAndEnd return (pack pstr, ver) where versionAndEnd = do void $ P.char '-' v <- parserVersion end return v