2017-12-31 20:08:31 +00:00
|
|
|
{- Process transcript
|
|
|
|
-
|
2020-06-05 17:58:04 +00:00
|
|
|
- Copyright 2012-2020 Joey Hess <id@joeyh.name>
|
2017-12-31 20:08:31 +00:00
|
|
|
-
|
|
|
|
- License: BSD-2-clause
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
{-# OPTIONS_GHC -fno-warn-tabs #-}
|
|
|
|
|
2019-11-21 19:38:06 +00:00
|
|
|
module Utility.Process.Transcript (
|
|
|
|
processTranscript,
|
|
|
|
processTranscript',
|
|
|
|
processTranscript'',
|
|
|
|
) where
|
2017-12-31 20:08:31 +00:00
|
|
|
|
|
|
|
import Utility.Process
|
|
|
|
|
|
|
|
import System.IO
|
2018-03-07 21:25:42 +00:00
|
|
|
import System.Exit
|
2018-03-15 19:34:25 +00:00
|
|
|
import Control.Concurrent.Async
|
2017-12-31 20:08:31 +00:00
|
|
|
import Control.Monad
|
|
|
|
#ifndef mingw32_HOST_OS
|
2020-11-23 18:00:17 +00:00
|
|
|
import Control.Exception
|
2017-12-31 20:08:31 +00:00
|
|
|
import qualified System.Posix.IO
|
|
|
|
#else
|
|
|
|
import Control.Applicative
|
|
|
|
#endif
|
|
|
|
import Data.Maybe
|
|
|
|
import Prelude
|
|
|
|
|
2018-03-07 21:25:42 +00:00
|
|
|
-- | Runs a process and returns a transcript combining its stdout and
|
|
|
|
-- stderr, and whether it succeeded or failed.
|
2017-12-31 20:08:31 +00:00
|
|
|
processTranscript :: String -> [String] -> (Maybe String) -> IO (String, Bool)
|
|
|
|
processTranscript cmd opts = processTranscript' (proc cmd opts)
|
|
|
|
|
2018-03-07 21:25:42 +00:00
|
|
|
-- | Also feeds the process some input.
|
2017-12-31 20:08:31 +00:00
|
|
|
processTranscript' :: CreateProcess -> Maybe String -> IO (String, Bool)
|
|
|
|
processTranscript' cp input = do
|
2018-03-07 21:25:42 +00:00
|
|
|
(t, c) <- processTranscript'' cp input
|
|
|
|
return (t, c == ExitSuccess)
|
|
|
|
|
|
|
|
processTranscript'' :: CreateProcess -> Maybe String -> IO (String, ExitCode)
|
|
|
|
processTranscript'' cp input = do
|
2017-12-31 20:08:31 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
{- This implementation interleves stdout and stderr in exactly the order
|
|
|
|
- the process writes them. -}
|
2020-06-05 17:58:04 +00:00
|
|
|
let setup = do
|
|
|
|
(readf, writef) <- System.Posix.IO.createPipe
|
|
|
|
System.Posix.IO.setFdOption readf System.Posix.IO.CloseOnExec True
|
|
|
|
System.Posix.IO.setFdOption writef System.Posix.IO.CloseOnExec True
|
|
|
|
readh <- System.Posix.IO.fdToHandle readf
|
|
|
|
writeh <- System.Posix.IO.fdToHandle writef
|
|
|
|
return (readh, writeh)
|
|
|
|
let cleanup (readh, writeh) = do
|
|
|
|
hClose readh
|
2020-06-03 19:48:09 +00:00
|
|
|
hClose writeh
|
2020-06-05 17:58:04 +00:00
|
|
|
bracket setup cleanup $ \(readh, writeh) -> do
|
|
|
|
let cp' = cp
|
|
|
|
{ std_in = if isJust input then CreatePipe else Inherit
|
|
|
|
, std_out = UseHandle writeh
|
|
|
|
, std_err = UseHandle writeh
|
|
|
|
}
|
|
|
|
withCreateProcess cp' $ \hin hout herr pid -> do
|
2020-11-19 20:36:37 +00:00
|
|
|
get <- asyncreader pid readh
|
2020-06-05 17:58:04 +00:00
|
|
|
writeinput input (hin, hout, herr, pid)
|
|
|
|
code <- waitForProcess pid
|
2020-11-19 20:36:37 +00:00
|
|
|
transcript <- wait get
|
2020-06-05 17:58:04 +00:00
|
|
|
return (transcript, code)
|
2017-12-31 20:08:31 +00:00
|
|
|
#else
|
|
|
|
{- This implementation for Windows puts stderr after stdout. -}
|
2020-06-03 19:48:09 +00:00
|
|
|
let cp' = cp
|
2017-12-31 20:08:31 +00:00
|
|
|
{ std_in = if isJust input then CreatePipe else Inherit
|
|
|
|
, std_out = CreatePipe
|
|
|
|
, std_err = CreatePipe
|
|
|
|
}
|
2020-07-01 20:53:50 +00:00
|
|
|
withCreateProcess cp' $ \hin hout herr pid -> do
|
2020-06-03 19:48:09 +00:00
|
|
|
let p = (hin, hout, herr, pid)
|
2020-11-19 20:36:37 +00:00
|
|
|
getout <- asyncreader pid (stdoutHandle p)
|
|
|
|
geterr <- asyncreader pid (stderrHandle p)
|
2020-06-03 19:48:09 +00:00
|
|
|
writeinput input p
|
|
|
|
code <- waitForProcess pid
|
2020-11-19 20:36:37 +00:00
|
|
|
transcript <- (++) <$> wait getout <*> wait geterr
|
2020-06-03 19:48:09 +00:00
|
|
|
return (transcript, code)
|
2020-06-05 17:58:04 +00:00
|
|
|
#endif
|
2017-12-31 20:08:31 +00:00
|
|
|
where
|
2020-11-19 20:36:37 +00:00
|
|
|
asyncreader pid h = async $ reader pid h []
|
|
|
|
reader pid h c = hGetLineUntilExitOrEOF pid h >>= \case
|
2021-08-02 17:42:27 +00:00
|
|
|
Nothing -> return (unlines (reverse c))
|
2020-11-19 20:36:37 +00:00
|
|
|
Just l -> reader pid h (l:c)
|
2017-12-31 20:08:31 +00:00
|
|
|
writeinput (Just s) p = do
|
|
|
|
let inh = stdinHandle p
|
|
|
|
unless (null s) $ do
|
|
|
|
hPutStr inh s
|
|
|
|
hFlush inh
|
|
|
|
hClose inh
|
|
|
|
writeinput Nothing _ = return ()
|