@@ -17,6 +17,7 @@ module Obelisk.CliApp.Process
17 , callProcessAndLogOutput
17 , callProcessAndLogOutput
18 , createProcess
18 , createProcess
19 , createProcess_
19 , createProcess_
20+ , exitCodeToException
20 , overCreateProcess
21 , overCreateProcess
22 , readCreateProcessWithExitCode
23 , readCreateProcessWithExitCode
@@ -24,6 +25,7 @@ module Obelisk.CliApp.Process
24 , readProcessAndLogStderr
25 , readProcessAndLogStderr
25 , readProcessJSONAndLogStderr
26 , readProcessJSONAndLogStderr
26 , reconstructCommand
27 , reconstructCommand
28 , setDelegateCtlc
30 , setDelegateCtlc
29 , setEnvOverride
31 , setEnvOverride
@@ -32,6 +34,7 @@ module Obelisk.CliApp.Process
34import Control.Monad ((<=<), join, void)
36import Control.Monad ((<=<), join, void)
37+import Control.Monad.Catch (MonadMask, bracketOnError)
35import Control.Monad.Except (throwError)
38import Control.Monad.Except (throwError)
36import Control.Monad.Fail
39import Control.Monad.Fail
37import Control.Monad.IO.Class (MonadIO, liftIO)
40import Control.Monad.IO.Class (MonadIO, liftIO)
@@ -99,7 +102,7 @@ instance AsProcessFailure ProcessFailure where
99 asProcessFailure = id
102 asProcessFailure = id
101readProcessAndLogStderr
104readProcessAndLogStderr
102− :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m)
105+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m, MonadMask m)
103 => Severity -> ProcessSpec -> m Text
106 => Severity -> ProcessSpec -> m Text
104readProcessAndLogStderr sev process = do
107readProcessAndLogStderr sev process = do
105 (out, _err) <- withProcess process $ \_out err -> do
108 (out, _err) <- withProcess process $ \_out err -> do
@@ -107,7 +110,7 @@ readProcessAndLogStderr sev process = do
107 liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
110 liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
109readProcessJSONAndLogStderr
112readProcessJSONAndLogStderr
110− :: (Aeson.FromJSON a, MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m)
113+ :: (Aeson.FromJSON a, MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m, MonadMask m)
111 => Severity -> ProcessSpec -> m a
114 => Severity -> ProcessSpec -> m a
112readProcessJSONAndLogStderr sev process = do
115readProcessJSONAndLogStderr sev process = do
113 (out, _err) <- withProcess process $ \_out err -> do
116 (out, _err) <- withProcess process $ \_out err -> do
@@ -146,9 +149,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
146 outText <- liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
149 outText <- liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
147 putLogRaw sev_out outText
150 putLogRaw sev_out outText
149− waitForProcess p >>= \case
152+ outText <$ (exitCodeToException process =<< waitForProcess p)
150− ExitSuccess -> pure outText
151− ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code
153-- | Like 'System.Process.callProcess' but logs the combined output (stdout and stderr)
154-- | Like 'System.Process.callProcess' but logs the combined output (stdout and stderr)
154-- with the corresponding severity.
155-- with the corresponding severity.
@@ -158,7 +159,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
158-- which case it is advisable to call it with a non-Error severity for stderr, like
159-- which case it is advisable to call it with a non-Error severity for stderr, like
159-- `callProcessAndLogOutput (Debug, Debug)`.
160-- `callProcessAndLogOutput (Debug, Debug)`.
160callProcessAndLogOutput
161callProcessAndLogOutput
161− :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m)
162+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m, MonadMask m)
162 => (Severity, Severity) -> ProcessSpec -> m ()
163 => (Severity, Severity) -> ProcessSpec -> m ()
163callProcessAndLogOutput (sev_out, sev_err) process =
164callProcessAndLogOutput (sev_out, sev_err) process =
164 void $ withProcess process $ \out err -> do
165 void $ withProcess process $ \out err -> do
@@ -188,12 +189,11 @@ createProcess_ name procSpec = do
188 liftIO $ Process.createProcess_ name p
189 liftIO $ Process.createProcess_ name p
190mkCreateProcess :: MonadIO m => ProcessSpec -> m Process.CreateProcess
191mkCreateProcess :: MonadIO m => ProcessSpec -> m Process.CreateProcess
191−mkCreateProcess (ProcessSpec p override') = do
192+mkCreateProcess (ProcessSpec p override') = case override' of
193− Nothing -> pure p
193+ Nothing -> pure p
194− Just override -> do
194+ Just override -> do
195− procEnv <- Map.fromList <$> maybe (liftIO getEnvironment) pure (Process.env p)
195+ procEnv <- Map.fromList <$> maybe (liftIO getEnvironment) pure (Process.env p)
196− pure $ p { Process.env = Just $ Map.toAscList (override procEnv) }
196+ pure $ p { Process.env = Just $ Map.toAscList (override procEnv) }
198-- | Like `System.Process.callProcess` but also logs (debug) the process being run
198-- | Like `System.Process.callProcess` but also logs (debug) the process being run
199callProcess
199callProcess
@@ -212,18 +212,28 @@ callCommand cmd = do
212 liftIO $ Process.callCommand cmd
212 liftIO $ Process.callCommand cmd
214withProcess
214withProcess
215− :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m)
215+ :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e, MonadFail m, MonadMask m)
216 => ProcessSpec -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
216 => ProcessSpec -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
217−withProcess process f = do -- TODO: Use bracket.
217+withProcess process f =
218− -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
218+ bracketOnError
219− (_, Just out, Just err, p) <- createProcess $ overCreateProcess
219+ (createProcess $ overCreateProcess
220− (\x -> x { std_out = CreatePipe , std_err = CreatePipe }) process
220+ (\x -> x { std_out = CreatePipe , std_err = CreatePipe }) process
222+ (liftIO . Process.cleanupProcess)
223+ (\(_, Just out, Just err, p) -> do
222− f out err -- Pass the handles to the passed function
224+ f out err -- Pass the handles to the passed function
224− waitForProcess p >>= \case
225+ (out, err) <$ (exitCodeToException process =<< waitForProcess p)
225− ExitSuccess -> return (out, err)
226+ )
226− ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code
227+
228+-- | Runs a process to completion failing if it does not exit cleanly.
230+ :: (MonadIO m, CliLog m, CliThrow e m, MonadMask m, AsProcessFailure e, MonadFail m)
231+ => ProcessSpec -> m ()
232+runProcess_ process =
233+ bracketOnError -- TODO: Wrap exceptions up in ProcessFailure
234+ (createProcess process)
235+ (liftIO . Process.cleanupProcess)
236+ (\(_, _, _, ph) -> exitCodeToException process =<< waitForProcess ph)
228-- Create an input stream from the file handle, associating each item with the given severity.
238-- Create an input stream from the file handle, associating each item with the given severity.
229streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
239streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
@@ -242,6 +252,12 @@ streamToLog stream = fix $ \loop -> do
242waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode
252waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode
243waitForProcess = liftIO . Process.waitForProcess
253waitForProcess = liftIO . Process.waitForProcess
255+-- | Converts an 'ExitCode' to an exception when it's non-zero.
256+exitCodeToException :: (CliThrow e m, AsProcessFailure e) => ProcessSpec -> ExitCode -> m ()
257+exitCodeToException spec = \case
258+ ExitSuccess -> pure ()
259+ ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess spec) code
245-- | Pretty print a 'CmdSpec'
261-- | Pretty print a 'CmdSpec'
246reconstructCommand :: Process.CmdSpec -> Text
262reconstructCommand :: Process.CmdSpec -> Text
247reconstructCommand p = case p of
263reconstructCommand p = case p of