stack-1.4.0: src/Stack/ConfigCmd.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE GADTs #-}
-- | Make changes to project or global configuration.
module Stack.ConfigCmd
(ConfigCmdSet(..)
,configCmdSetParser
,cfgCmdSet
,cfgCmdSetName
,cfgCmdName) where
import Control.Applicative
import Control.Monad
import Control.Monad.Catch (throwM)
import Control.Monad.IO.Class
import Control.Monad.Logger
import qualified Data.ByteString as S
import qualified Data.HashMap.Strict as HMap
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Yaml as Yaml
import qualified Options.Applicative as OA
import qualified Options.Applicative.Types as OA
import Path
import Path.IO
import Prelude -- Silence redundant import warnings
import Stack.BuildPlan
import Stack.Config (makeConcreteResolver, getProjectConfig, getImplicitGlobalProjectDir, LocalConfigStatus(..))
import Stack.Constants
import Stack.Types.Config
import Stack.Types.Resolver
data ConfigCmdSet
= ConfigCmdSetResolver AbstractResolver
| ConfigCmdSetSystemGhc CommandScope
Bool
| ConfigCmdSetInstallGhc CommandScope
Bool
data CommandScope
= CommandScopeGlobal
-- ^ Apply changes to the global configuration,
-- typically at @~/.stack/config.yaml@.
| CommandScopeProject
-- ^ Apply changes to the project @stack.yaml@.
configCmdSetScope :: ConfigCmdSet -> CommandScope
configCmdSetScope (ConfigCmdSetResolver _) = CommandScopeProject
configCmdSetScope (ConfigCmdSetSystemGhc scope _) = scope
configCmdSetScope (ConfigCmdSetInstallGhc scope _) = scope
cfgCmdSet
:: (StackMiniM env m, HasConfig env, HasGHCVariant env)
=> GlobalOpts -> ConfigCmdSet -> m ()
cfgCmdSet go cmd = do
conf <- view configL
configFilePath <-
liftM
toFilePath
(case configCmdSetScope cmd of
CommandScopeProject -> do
mstackYamlOption <- forM (globalStackYaml go) resolveFile'
mstackYaml <- getProjectConfig mstackYamlOption
case mstackYaml of
LCSProject stackYaml -> return stackYaml
LCSNoProject -> liftM (</> stackDotYaml) (getImplicitGlobalProjectDir conf)
LCSNoConfig -> error "config command used when no local configuration available"
CommandScopeGlobal -> return (configUserConfigPath conf))
-- We don't need to worry about checking for a valid yaml here
(config :: Yaml.Object) <-
liftIO (Yaml.decodeFileEither configFilePath) >>= either throwM return
newValue <- cfgCmdSetValue cmd
let cmdKey = cfgCmdSetOptionName cmd
config' = HMap.insert cmdKey newValue config
if config' == config
then $logInfo
(T.pack configFilePath <>
" already contained the intended configuration and remains unchanged.")
else do
liftIO (S.writeFile configFilePath (Yaml.encode config'))
$logInfo (T.pack configFilePath <> " has been updated.")
cfgCmdSetValue
:: (StackMiniM env m, HasConfig env, HasGHCVariant env)
=> ConfigCmdSet -> m Yaml.Value
cfgCmdSetValue (ConfigCmdSetResolver newResolver) = do
concreteResolver <- makeConcreteResolver newResolver
case concreteResolver of
-- Check that the snapshot actually exists
ResolverSnapshot snapName -> void $ loadMiniBuildPlan snapName
ResolverCompiler _ -> return ()
-- TODO: custom snapshot support? Would need a way to specify on CLI
ResolverCustom _ _ -> error "'stack config set resolver' does not support custom resolvers"
return (Yaml.String (resolverName concreteResolver))
cfgCmdSetValue (ConfigCmdSetSystemGhc _ bool) =
return (Yaml.Bool bool)
cfgCmdSetValue (ConfigCmdSetInstallGhc _ bool) =
return (Yaml.Bool bool)
cfgCmdSetOptionName :: ConfigCmdSet -> Text
cfgCmdSetOptionName (ConfigCmdSetResolver _) = "resolver"
cfgCmdSetOptionName (ConfigCmdSetSystemGhc _ _) = configMonoidSystemGHCName
cfgCmdSetOptionName (ConfigCmdSetInstallGhc _ _) = configMonoidInstallGHCName
cfgCmdName :: String
cfgCmdName = "config"
cfgCmdSetName :: String
cfgCmdSetName = "set"
configCmdSetParser :: OA.Parser ConfigCmdSet
configCmdSetParser =
OA.hsubparser $
mconcat
[ OA.command
"resolver"
(OA.info
(ConfigCmdSetResolver <$>
OA.argument
readAbstractResolver
(OA.metavar "RESOLVER" <>
OA.help "E.g. \"nightly\" or \"lts-7.2\""))
(OA.progDesc
"Change the resolver of the current project. See https://docs.haskellstack.org/en/stable/yaml_configuration/#resolver for more info."))
, OA.command
(T.unpack configMonoidSystemGHCName)
(OA.info
(ConfigCmdSetSystemGhc <$> scopeFlag <*> boolArgument)
(OA.progDesc
"Configure whether stack should use a system GHC installation or not."))
, OA.command
(T.unpack configMonoidInstallGHCName)
(OA.info
(ConfigCmdSetInstallGhc <$> scopeFlag <*> boolArgument)
(OA.progDesc
"Configure whether stack should automatically install GHC when necessary."))
]
scopeFlag :: OA.Parser CommandScope
scopeFlag =
OA.flag
CommandScopeProject
CommandScopeGlobal
(OA.long "global" <>
OA.help
"Modify the global configuration (typically at \"~/.stack/config.yaml\") instead of the project stack.yaml.")
readBool :: OA.ReadM Bool
readBool = do
s <- OA.readerAsk
case s of
"true" -> return True
"false" -> return False
_ -> OA.readerError ("Invalid value " ++ show s ++ ": Expected \"true\" or \"false\"")
boolArgument :: OA.Parser Bool
boolArgument = OA.argument readBool (OA.metavar "true/false")