{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RecordWildCards #-} -- | Run sub-processes. module System.Process.Run (runCmd ,runCmd' ,callProcess ,callProcess' ,callProcessInheritStderrStdout ,callProcessObserveStdout ,createProcess' ,ProcessExitedUnsuccessfully ,Cmd(..) ) where import Stack.Prelude import Data.Conduit.Process hiding (callProcess) import qualified Data.Text as T import System.Exit (exitWith, ExitCode (..)) import System.IO (hGetLine) import qualified System.Process import System.Process.Log import System.Process.Read -- | Cmd holds common infos needed to running a process in most cases data Cmd = Cmd { cmdDirectoryToRunIn :: Maybe (Path Abs Dir) -- ^ directory to run in , cmdCommandToRun :: FilePath -- ^ command to run , cmdEnvOverride :: EnvOverride , cmdCommandLineArguments :: [String] -- ^ command line arguments } -- | Run the given command in the given directory, inheriting stdout and stderr. -- -- If it exits with anything but success, prints an error -- and then calls 'exitWith' to exit the program. runCmd :: forall (m :: * -> *). (MonadLogger m, MonadUnliftIO m) => Cmd -> Maybe Text -- ^ optional additional error message -> m () runCmd = runCmd' id runCmd' :: forall (m :: * -> *). (MonadLogger m, MonadUnliftIO m) => (CreateProcess -> CreateProcess) -> Cmd -> Maybe Text -- ^ optional additional error message -> m () runCmd' modCP cmd@Cmd{..} mbErrMsg = do result <- try (callProcess' modCP cmd) case result of Left (ProcessExitedUnsuccessfully _ ec) -> do logError $ T.pack $ concat $ [ "Exit code " , show ec , " while running " , show (cmdCommandToRun : cmdCommandLineArguments) ] ++ (case cmdDirectoryToRunIn of Nothing -> [] Just mbDir -> [" in ", toFilePath mbDir] ) forM_ mbErrMsg logError liftIO (exitWith ec) Right () -> return () -- | Like 'System.Process.callProcess', but takes an optional working directory and -- environment override, and throws 'ProcessExitedUnsuccessfully' if the -- process exits unsuccessfully and a 'ReadProcessException' if the executable is not found. -- -- Inherits stdout and stderr. callProcess :: (MonadIO m, MonadLogger m) => Cmd -> m () callProcess = callProcess' id -- | Like 'System.Process.callProcess', but takes an optional working directory and -- environment override, and throws 'ProcessExitedUnsuccessfully' if the -- process exits unsuccessfully and a 'ReadProcessException' if the executable is not found. -- -- Inherits stdout and stderr. callProcess' :: (MonadIO m, MonadLogger m) => (CreateProcess -> CreateProcess) -> Cmd -> m () callProcess' modCP cmd = do c <- liftM modCP (cmdToCreateProcess cmd) logCreateProcess c liftIO $ do (_, _, _, p) <- System.Process.createProcess c exit_code <- waitForProcess p case exit_code of ExitSuccess -> return () ExitFailure _ -> throwM (ProcessExitedUnsuccessfully c exit_code) callProcessInheritStderrStdout :: (MonadIO m, MonadLogger m) => Cmd -> m () callProcessInheritStderrStdout cmd = do let inheritOutput cp = cp { std_in = CreatePipe, std_out = Inherit, std_err = Inherit } callProcess' inheritOutput cmd callProcessObserveStdout :: (MonadIO m, MonadLogger m) => Cmd -> m String callProcessObserveStdout cmd = do c <- liftM modCP (cmdToCreateProcess cmd) logCreateProcess c liftIO $ do (_, Just hStdout, _, p) <- System.Process.createProcess c hSetBuffering hStdout NoBuffering exit_code <- waitForProcess p case exit_code of ExitSuccess -> hGetLine hStdout ExitFailure _ -> throwM (ProcessExitedUnsuccessfully c exit_code) where modCP c = c { std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit } -- | Like 'System.Process.Internal.createProcess_', but taking a 'Cmd'. -- Note that the 'Handle's provided by 'UseHandle' are not closed -- automatically. createProcess' :: (MonadIO m, MonadLogger m) => String -> (CreateProcess -> CreateProcess) -> Cmd -> m (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) createProcess' tag modCP cmd = do c <- liftM modCP (cmdToCreateProcess cmd) logCreateProcess c liftIO $ System.Process.createProcess_ tag c -- Throws a 'ReadProcessException' if process is not found. cmdToCreateProcess :: MonadIO m => Cmd -> m CreateProcess cmdToCreateProcess (Cmd wd cmd0 menv args) = do cmd <- preProcess wd menv cmd0 return $ (proc cmd args) { delegate_ctlc = True , cwd = fmap toFilePath wd , env = envHelper menv }