stack-2.9.3: src/Stack/Runners.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Utilities for running stack commands.
--
-- Instead of using Has-style classes below, the type signatures use
-- concrete environments to try and avoid accidentally rerunning
-- configuration parsing. For example, we want @withConfig $
-- withConfig $ ...@ to fail.
module Stack.Runners
( withBuildConfig
, withEnvConfig
, withDefaultEnvConfig
, withConfig
, withGlobalProject
, withRunnerGlobal
, ShouldReexec (..)
) where
import RIO.Process ( mkDefaultProcessContext )
import RIO.Time ( addUTCTime, getCurrentTime )
import Stack.Build.Target ( NeedTargets (..) )
import Stack.Config
import Stack.Constants
import Stack.DefaultColorWhen ( defaultColorWhen )
import qualified Stack.Docker as Docker
import qualified Stack.Nix as Nix
import Stack.Prelude
import Stack.Setup
import Stack.Storage.User ( upgradeChecksSince, logUpgradeCheck )
import Stack.Types.Config
import Stack.Types.Docker ( dockerEnable )
import Stack.Types.Nix ( nixEnable )
import Stack.Types.Version ( stackMinorVersion, minorVersion )
import System.Console.ANSI ( hSupportsANSIWithoutEmulation )
import System.Terminal ( getTerminalWidth )
-- | Type representing exceptions thrown by functions exported by the
-- "Stack.Runners" module.
data RunnersException
= CommandInvalid
| DockerAndNixInvalid
| NixWithinDockerInvalid
| DockerWithinNixInvalid
deriving (Show, Typeable)
instance Exception RunnersException where
displayException CommandInvalid =
"Error: [S-7144]\n"
++ "Cannot use this command with options which override the stack.yaml \
\location."
displayException DockerAndNixInvalid =
"Error: [S-8314]\n"
++ "Cannot use both Docker and Nix at the same time."
displayException NixWithinDockerInvalid =
"Error: [S-8641]\n"
++ "Cannot use Nix from within a Docker container."
displayException DockerWithinNixInvalid =
"Error: [S-5107]\n"
++ "Cannot use Docker from within a Nix shell."
-- | Ensure that no project settings are used when running 'withConfig'.
withGlobalProject :: RIO Runner a -> RIO Runner a
withGlobalProject inner = do
oldSYL <- view stackYamlLocL
case oldSYL of
SYLDefault -> local (set stackYamlLocL SYLGlobalProject) inner
_ -> throwIO CommandInvalid
-- | Helper for 'withEnvConfig' which passes in some default arguments:
--
-- * No targets are requested
--
-- * Default command line build options are assumed
withDefaultEnvConfig
:: RIO EnvConfig a
-> RIO Config a
withDefaultEnvConfig = withEnvConfig AllowNoTargets defaultBuildOptsCLI
-- | Upgrade a 'Config' environment to an 'EnvConfig' environment by
-- performing further parsing of project-specific configuration (like
-- 'withBuildConfig') and then setting up a build environment
-- toolchain. This is intended to be run inside a call to
-- 'withConfig'.
withEnvConfig
:: NeedTargets
-> BuildOptsCLI
-> RIO EnvConfig a
-- ^ Action that uses the build config. If Docker is enabled for builds,
-- this will be run in a Docker container.
-> RIO Config a
withEnvConfig needTargets boptsCLI inner =
withBuildConfig $ do
envConfig <- setupEnv needTargets boptsCLI Nothing
logDebug "Starting to execute command inside EnvConfig"
runRIO envConfig inner
-- | If the settings justify it, should we reexec inside Docker or Nix?
data ShouldReexec = YesReexec | NoReexec
-- | Load the configuration. Convenience function used
-- throughout this module.
withConfig
:: ShouldReexec
-> RIO Config a
-> RIO Runner a
withConfig shouldReexec inner =
loadConfig $ \config -> do
-- If we have been relaunched in a Docker container, perform in-container initialization
-- (switch UID, etc.). We do this after first loading the configuration since it must
-- happen ASAP but needs a configuration.
view (globalOptsL.to globalDockerEntrypoint) >>=
traverse_ (Docker.entrypoint config)
runRIO config $ do
-- Catching all exceptions here, since we don't want this
-- check to ever cause Stack to stop working
shouldUpgradeCheck `catchAny` \e ->
logError $
"Error: [S-7353]\n" <>
"Error when running shouldUpgradeCheck: " <>
displayShow e
case shouldReexec of
YesReexec -> reexec inner
NoReexec -> inner
-- | Perform a Docker or Nix reexec, if warranted. Otherwise run the
-- inner action.
reexec :: RIO Config a -> RIO Config a
reexec inner = do
nixEnable' <- asks $ nixEnable . configNix
dockerEnable' <- asks $ dockerEnable . configDocker
case (nixEnable', dockerEnable') of
(True, True) -> throwIO DockerAndNixInvalid
(False, False) -> inner
-- Want to use Nix
(True, False) -> do
whenM getInContainer $ throwIO NixWithinDockerInvalid
isReexec <- view reExecL
if isReexec
then inner
else Nix.runShellAndExit
-- Want to use Docker
(False, True) -> do
whenM getInNixShell $ throwIO DockerWithinNixInvalid
inContainer <- getInContainer
if inContainer
then do
isReexec <- view reExecL
if isReexec
then inner
else throwIO Docker.OnlyOnHostException
else Docker.runContainerAndExit
-- | Use the 'GlobalOpts' to create a 'Runner' and run the provided
-- action.
withRunnerGlobal :: GlobalOpts -> RIO Runner a -> IO a
withRunnerGlobal go inner = do
colorWhen <-
maybe defaultColorWhen pure $
getFirst $ configMonoidColorWhen $ globalConfigMonoid go
useColor <- case colorWhen of
ColorNever -> pure False
ColorAlways -> pure True
ColorAuto -> fromMaybe True <$>
hSupportsANSIWithoutEmulation stderr
termWidth <- clipWidth <$> maybe (fromMaybe defaultTerminalWidth
<$> getTerminalWidth)
pure (globalTermWidth go)
menv <- mkDefaultProcessContext
let update = globalStylesUpdate go
withNewLogFunc go useColor update $ \logFunc -> runRIO Runner
{ runnerGlobalOpts = go
, runnerUseColor = useColor
, runnerLogFunc = logFunc
, runnerTermWidth = termWidth
, runnerProcessContext = menv
} inner
where clipWidth w
| w < minTerminalWidth = minTerminalWidth
| w > maxTerminalWidth = maxTerminalWidth
| otherwise = w
-- | Check if we should recommend upgrading Stack and, if so, recommend it.
shouldUpgradeCheck :: RIO Config ()
shouldUpgradeCheck = do
config <- ask
when (configRecommendUpgrade config) $ do
now <- getCurrentTime
let yesterday = addUTCTime (-24 * 60 * 60) now
checks <- upgradeChecksSince yesterday
when (checks == 0) $ do
mversion <- getLatestHackageVersion NoRequireHackageIndex "stack" UsePreferredVersions
case mversion of
-- Compare the minor version so we avoid patch-level, Hackage-only releases.
-- See: https://github.com/commercialhaskell/stack/pull/4729#pullrequestreview-227176315
Just (PackageIdentifierRevision _ version _) | minorVersion version > stackMinorVersion -> do
logWarn "<<<<<<<<<<<<<<<<<<"
logWarn $
"You are currently using Stack version " <>
fromString (versionString stackVersion) <>
", but version " <>
fromString (versionString version) <>
" is available"
logWarn "You can try to upgrade by running 'stack upgrade'"
logWarn $
"Tired of seeing this? Add 'recommend-stack-upgrade: false' to " <>
fromString (toFilePath (configUserConfigPath config))
logWarn ">>>>>>>>>>>>>>>>>>"
logWarn ""
logWarn ""
_ -> pure ()
logUpgradeCheck now