{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} module Pier.Build.Config ( configRules , askConfig , Config(..) , Resolved(..) , resolvePackage , resolvedPackageId ) where import Control.Exception (throw) import Control.Monad (void) import Data.Maybe (fromMaybe) import Data.Yaml import Development.Shake import Development.Shake.Classes import Distribution.Package import Distribution.Text (display) import Distribution.Version import GHC.Generics hiding (packageName) import qualified Data.HashMap.Strict as HM import Pier.Build.Package import Pier.Build.Stackage import Pier.Core.Artifact import Pier.Core.Persistent data PierYamlPath = PierYamlPath deriving (Show, Eq, Typeable, Generic) instance Hashable PierYamlPath instance Binary PierYamlPath instance NFData PierYamlPath type instance RuleResult PierYamlPath = FilePath configRules :: FilePath -> Rules () configRules f = do void $ addOracle $ \PierYamlPath -> return f void $ addPersistent $ \PierYamlQ -> do path <- askOracle PierYamlPath need [path] yamlE <- liftIO $ decodeFileEither path either (liftIO . throw) return yamlE -- TODO: rename; maybe ConfigSpec and ConfigEnv? Or Config and Env? data PierYaml = PierYaml { resolver :: PlanName , packages :: [FilePath] , extraDeps :: [PackageIdentifier] , systemGhc :: Bool } deriving (Show, Eq, Typeable, Generic) instance Hashable PierYaml instance Binary PierYaml instance NFData PierYaml instance FromJSON PierYaml where parseJSON = withObject "PierYaml" $ \o -> do r <- o .: "resolver" pkgs <- o .:? "packages" ed <- o .:? "extra-deps" sysGhc <- o .:? "system-ghc" return PierYaml { resolver = r , packages = fromMaybe [] pkgs , extraDeps = fromMaybe [] ed , systemGhc = fromMaybe False sysGhc } data PierYamlQ = PierYamlQ deriving (Eq, Typeable, Generic) instance Hashable PierYamlQ instance Binary PierYamlQ instance NFData PierYamlQ type instance RuleResult PierYamlQ = PierYaml instance Show PierYamlQ where show _ = "Pier YAML configuration" data Config = Config { plan :: BuildPlan , configExtraDeps :: HM.HashMap PackageName Version , localPackages :: HM.HashMap PackageName (Artifact, Version) , configGhc :: InstalledGhc } deriving Show -- TODO: cache? askConfig :: Action Config askConfig = do yaml <- askPersistent PierYamlQ p <- askBuildPlan (resolver yaml) ghc <- askInstalledGhc p (if systemGhc yaml then SystemGhc else StackageGhc) -- TODO: don't parse local package defs twice. -- We do it again later so the full PackageDescription -- doesn't need to get saved in the cache. pkgDescs <- mapM (\f -> do let a = externalFile f pkg <- parseCabalFileInDir a return (packageName pkg, (a, packageVersion pkg))) $ packages yaml return Config { plan = p , configGhc = ghc , localPackages = HM.fromList pkgDescs , configExtraDeps = HM.fromList [ (packageName pkg, packageVersion pkg) | pkg <- extraDeps yaml ] } data Resolved = Builtin PackageId | Hackage PackageId Flags -- TODO: flags for local packages as well | Local Artifact PackageId deriving (Show,Typeable,Eq,Generic) instance Hashable Resolved instance Binary Resolved instance NFData Resolved resolvePackage :: Config -> PackageName -> Resolved resolvePackage conf n -- TODO: nicer syntax -- core packages can't be overridden. (TODO: is this right?) | Just v <- HM.lookup n (corePackageVersions $ plan conf) = Builtin $ PackageIdentifier n v | Just (a, v) <- HM.lookup n (localPackages conf) = Local a $ PackageIdentifier n v -- Extra-deps override packages in the build plan: | Just v <- HM.lookup n (configExtraDeps conf) = Hackage (PackageIdentifier n v) HM.empty | Just p <- HM.lookup n (planPackages $ plan conf) = Hackage (PackageIdentifier n $ planPackageVersion p) (planPackageFlags p) | otherwise = error $ "Couldn't find package " ++ show (display n) resolvedPackageId :: Resolved -> PackageIdentifier resolvedPackageId (Builtin p) = p resolvedPackageId (Hackage p _) = p resolvedPackageId (Local _ p) = p