on master help sign in
cliapp, command: Preserve exit code for subprocesses
commitbfa630801a8282dd4d5518d34e85080878ae2ffc authorElliot Cameron <eacameron@gmail.com> dateFri 29 May 2020, 19:21 (6 years ago) parent8868cfc
4 files changed+46−32
lib/cliapp/src/Obelisk/CliApp.hs+2 lib/cliapp/src/Obelisk/CliApp/Process.hs+39−23 lib/command/src/Obelisk/Command/Project.hs+2−4 lib/command/src/Obelisk/Command/Run.hs+3−5
lib/cliapp/src/Obelisk/CliApp.hs+2
@@ -41,6 +41,7 @@ module Obelisk.CliApp
4141 , callProcessAndLogOutput
4242 , createProcess
4343 , createProcess_
44+ , exitCodeToException
4445 , overCreateProcess
4546 , proc
4647 , readCreateProcessWithExitCode
@@ -48,6 +49,7 @@ module Obelisk.CliApp
4849 , readProcessAndLogStderr
4950 , readProcessJSONAndLogStderr
5051 , reconstructCommand
52+ , runProcess_
5153 , setCwd
5254 , setDelegateCtlc
5355 , setEnvOverride
lib/cliapp/src/Obelisk/CliApp/Process.hs+39−23
@@ -17,6 +17,7 @@ module Obelisk.CliApp.Process
1717 , callProcessAndLogOutput
1818 , createProcess
1919 , createProcess_
20+ , exitCodeToException
2021 , overCreateProcess
2122 , proc
2223 , readCreateProcessWithExitCode
@@ -24,6 +25,7 @@ module Obelisk.CliApp.Process
2425 , readProcessAndLogStderr
2526 , readProcessJSONAndLogStderr
2627 , reconstructCommand
28+ , runProcess_
2729 , setCwd
2830 , setDelegateCtlc
2931 , setEnvOverride
@@ -32,6 +34,7 @@ module Obelisk.CliApp.Process
3234 ) where
3335
3436import Control.Monad ((<=<), join, void)
37+import Control.Monad.Catch (MonadMask, bracketOnError)
3538import Control.Monad.Except (throwError)
3639import Control.Monad.Fail
3740import Control.Monad.IO.Class (MonadIO, liftIO)
@@ -99,7 +102,7 @@ instance AsProcessFailure ProcessFailure where
99102 asProcessFailure = id
100103
101104readProcessAndLogStderr
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)
103106 => Severity -> ProcessSpec -> m Text
104107readProcessAndLogStderr sev process = do
105108 (out, _err) <- withProcess process $ \_out err -> do
@@ -107,7 +110,7 @@ readProcessAndLogStderr sev process = do
107110 liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
108111
109112readProcessJSONAndLogStderr
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)
111114 => Severity -> ProcessSpec -> m a
112115readProcessJSONAndLogStderr sev process = do
113116 (out, _err) <- withProcess process $ \_out err -> do
@@ -146,9 +149,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
146149 outText <- liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
147150 putLogRaw sev_out outText
148151
149− waitForProcess p >>= \case
150− ExitSuccess -> pure outText
151− ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code
152+ outText <$ (exitCodeToException process =<< waitForProcess p)
152153
153154-- | Like 'System.Process.callProcess' but logs the combined output (stdout and stderr)
154155-- with the corresponding severity.
@@ -158,7 +159,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
158159-- which case it is advisable to call it with a non-Error severity for stderr, like
159160-- `callProcessAndLogOutput (Debug, Debug)`.
160161callProcessAndLogOutput
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)
162163 => (Severity, Severity) -> ProcessSpec -> m ()
163164callProcessAndLogOutput (sev_out, sev_err) process =
164165 void $ withProcess process $ \out err -> do
@@ -188,12 +189,11 @@ createProcess_ name procSpec = do
188189 liftIO $ Process.createProcess_ name p
189190
190191mkCreateProcess :: MonadIO m => ProcessSpec -> m Process.CreateProcess
191−mkCreateProcess (ProcessSpec p override') = do
192− case override' of
193− Nothing -> pure p
194− Just override -> do
195− procEnv <- Map.fromList <$> maybe (liftIO getEnvironment) pure (Process.env p)
196− pure $ p { Process.env = Just $ Map.toAscList (override procEnv) }
192+mkCreateProcess (ProcessSpec p override') = case override' of
193+ Nothing -> pure p
194+ Just override -> do
195+ procEnv <- Map.fromList <$> maybe (liftIO getEnvironment) pure (Process.env p)
196+ pure $ p { Process.env = Just $ Map.toAscList (override procEnv) }
197197
198198-- | Like `System.Process.callProcess` but also logs (debug) the process being run
199199callProcess
@@ -212,18 +212,28 @@ callCommand cmd = do
212212 liftIO $ Process.callCommand cmd
213213
214214withProcess
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)
216216 => ProcessSpec -> (Handle -> Handle -> m ()) -> m (Handle, Handle)
217−withProcess process f = do -- TODO: Use bracket.
218− -- FIXME: Using `withCreateProcess` here leads to something operating illegally on closed handles.
219− (_, Just out, Just err, p) <- createProcess $ overCreateProcess
220− (\x -> x { std_out = CreatePipe , std_err = CreatePipe }) process
221−
222− f out err -- Pass the handles to the passed function
223−
224− waitForProcess p >>= \case
225− ExitSuccess -> return (out, err)
226− ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code
217+withProcess process f =
218+ bracketOnError
219+ (createProcess $ overCreateProcess
220+ (\x -> x { std_out = CreatePipe , std_err = CreatePipe }) process
221+ )
222+ (liftIO . Process.cleanupProcess)
223+ (\(_, Just out, Just err, p) -> do
224+ f out err -- Pass the handles to the passed function
225+ (out, err) <$ (exitCodeToException process =<< waitForProcess p)
226+ )
227+
228+-- | Runs a process to completion failing if it does not exit cleanly.
229+runProcess_
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)
227237
228238-- Create an input stream from the file handle, associating each item with the given severity.
229239streamHandle :: Severity -> Handle -> IO (InputStream (Severity, BSC.ByteString))
@@ -242,6 +252,12 @@ streamToLog stream = fix $ \loop -> do
242252waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode
243253waitForProcess = liftIO . Process.waitForProcess
244254
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
260+
245261-- | Pretty print a 'CmdSpec'
246262reconstructCommand :: Process.CmdSpec -> Text
247263reconstructCommand p = case p of
lib/command/src/Obelisk/Command/Project.hs+2−4
@@ -307,7 +307,7 @@ nixShellWithoutPkgs root isPure chdirToRoot packageNamesAndPaths shellAttr comma
307307 packageNamesAndAbsPaths <- liftIO $ for packageNamesAndPaths makeAbsolute
308308 defShellConfig <- nixShellRunConfig root isPure command
309309 let setCwd_ = if chdirToRoot then setCwd (Just root) else id
310− (_, _, _, ph) <- createProcess_ "nixShellWithoutPkgs" $ setCwd_ $ nixShellRunProc $ defShellConfig
310+ runProcess_ $ setCwd_ $ nixShellRunProc $ defShellConfig
311311 & nixShellConfig_common . nixCmdConfig_target . target_expr ?~
312312 "{root, pkgs, shell}: ((import root {}).passthru.__unstable__.self.extend (_: _: {\
313313 \shellPackages = builtins.fromJSON pkgs;\
@@ -317,18 +317,16 @@ nixShellWithoutPkgs root isPure chdirToRoot packageNamesAndPaths shellAttr comma
317317 , strArg "pkgs" (T.unpack $ decodeUtf8 $ BSL.toStrict $ Json.encode packageNamesAndAbsPaths)
318318 , strArg "shell" shellAttr
319319 ]
320− void $ waitForProcess ph
321320
322321nixShellWithHoogle :: MonadObelisk m => FilePath -> Bool -> String -> Maybe String -> m ()
323322nixShellWithHoogle root isPure shell' command = do
324323 defShellConfig <- nixShellRunConfig root isPure command
325− (_, _, _, ph) <- createProcess_ "nixShellWithHoogle" $ setCwd (Just root) $ nixShellRunProc $ defShellConfig
324+ runProcess_ $ setCwd (Just root) $ nixShellRunProc $ defShellConfig
326325 & nixShellConfig_common . nixCmdConfig_target . target_expr ?~
327326 "{shell}: ((import ./. {}).passthru.__unstable__.self.extend (_: super: {\
328327 \userSettings = super.userSettings // { withHoogle = true; };\
329328 \})).project.shells.${shell}"
330329 & nixShellConfig_common . nixCmdConfig_args .~ [ strArg "shell" shell' ]
331− void $ waitForProcess ph
332330
333331findProjectAssets :: MonadObelisk m => FilePath -> m Text
334332findProjectAssets root = do
lib/command/src/Obelisk/Command/Run.hs+3−5
@@ -14,7 +14,7 @@ module Obelisk.Command.Run where
1414import Control.Arrow ((&&&))
1515import Control.Exception (Exception, bracket)
1616import Control.Lens (ifor, (.~), (&))
17−import Control.Monad (filterM, unless, void)
17+import Control.Monad (filterM, unless)
1818import Control.Monad.Except (runExceptT, throwError)
1919import Control.Monad.IO.Class (liftIO)
2020import Control.Monad.Reader (MonadIO)
@@ -66,15 +66,14 @@ import System.IO.Temp (withSystemTempDirectory)
6666import Obelisk.App (MonadObelisk)
6767import Obelisk.CliApp (
6868 Severity (..),
69− createProcess_,
7069 failWith,
7170 proc,
7271 putLog,
7372 readCreateProcessWithExitCode,
7473 readProcessAndLogStderr,
74+ runProcess_,
7575 setCwd,
7676 setDelegateCtlc,
77− waitForProcess,
7877 withSpinner,
7978 )
8079import Obelisk.Command.Nix
@@ -150,7 +149,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
150149 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
151150 putLog Debug $ "Storing profiled data under base name of " <> T.pack (root </> profileBaseName)
152151 freePort <- getFreePort
153− (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath </> "bin" </> "ob-run") $
152+ runProcess_ $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath </> "bin" </> "ob-run") $
154153 [ show freePort
155154 , T.unpack assets
156155 , profileBaseName
@@ -158,7 +157,6 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
158157 , "-po" <> profileBaseName
159158 ] <> rtsFlags
160159 <> [ "-RTS" ]
161− void $ waitForProcess ph
162160
163161run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
164162run root interpretPaths = do