{-# LANGUAGE PatternGuards, BangPatterns #-}
module BuildBox.Command.System
( module System.Exit
, system
, systemq
, ssystem
, ssystemq
, sesystem
, sesystemq
, systemTee
, systemTeeLog
, ssystemTee
, systemTeeIO
, systemTeeLogIO)
where
import BuildBox.Command.System.Internals
import BuildBox.Build
import Control.Concurrent
import Control.Concurrent.STM.TChan
import Control.Monad
import Control.Monad.STM
import System.Exit
import System.IO
import Data.ByteString (ByteString)
import BuildBox.Data.Log (Log)
import System.Process hiding (system)
import qualified BuildBox.Data.Log as Log
import qualified Data.Text.Encoding as Text
debug :: Bool
debug = False
trace :: String -> IO ()
trace s = when debug $ putStrLn s
system :: String -> Build (ExitCode, String, String)
system cmd
= do (code, logOut, logErr) <- systemTeeLog True cmd Log.empty
return (code, Log.toString logOut, Log.toString logErr)
systemq :: String -> Build (ExitCode, String, String)
systemq cmd
= do (code, logOut, logErr) <- systemTeeLog False cmd Log.empty
return (code, Log.toString logOut, Log.toString logErr)
ssystem :: String -> Build (String, String)
ssystem cmd
= do (code, logOut, logErr) <- systemTeeLog True cmd Log.empty
when (code /= ExitSuccess)
$ throw $ ErrorSystemCmdFailed cmd code logOut logErr
return (Log.toString logOut, Log.toString logErr)
ssystemq :: String -> Build (String, String)
ssystemq cmd
= do (code, logOut, logErr) <- systemTeeLog False cmd Log.empty
when (code /= ExitSuccess)
$ throw $ ErrorSystemCmdFailed cmd code logOut logErr
return (Log.toString logOut, Log.toString logErr)
sesystem :: String -> Build String
sesystem cmd
= do (code, logOut, logErr) <- systemTeeLog True cmd Log.empty
when (code /= ExitSuccess || (not $ Log.null logErr))
$ throw $ ErrorSystemCmdFailed cmd code logOut logErr
return $ Log.toString logOut
sesystemq :: String -> Build String
sesystemq cmd
= do (code, logOut, logErr) <- systemTeeLog False cmd Log.empty
when (code /= ExitSuccess || (not $ Log.null logErr))
$ throw $ ErrorSystemCmdFailed cmd code logOut logErr
return $ Log.toString logOut
systemTee :: Bool -> String -> String -> Build (ExitCode, String, String)
systemTee tee cmd strIn
= do logSystem cmd
io $ systemTeeIO tee cmd strIn
systemTeeLog :: Bool -> String -> Log -> Build (ExitCode, Log, Log)
systemTeeLog tee cmd logIn
= do logSystem cmd
io $ systemTeeLogIO tee cmd logIn
ssystemTee :: Bool -> String -> String -> Build ()
ssystemTee tee cmd strIn
= do (code, logOut, logErr) <- systemTeeLog tee cmd (Log.fromString strIn)
when (code /= ExitSuccess)
$ throw $ ErrorSystemCmdFailed cmd code logOut logErr
systemTeeIO :: Bool -> String -> String -> IO (ExitCode, String, String)
systemTeeIO tee cmd strIn
= do (code, logOut, logErr) <- systemTeeLogIO tee cmd $ Log.fromString strIn
return (code, Log.toString logOut, Log.toString logErr)
systemTeeLogIO
:: Bool
-> String
-> Log
-> IO (ExitCode, Log, Log)
systemTeeLogIO tee cmd logIn
= do trace $ "systemTeeIO " ++ show tee ++ ": " ++ cmd
trace $ "systemTeeIO: Creating process"
(Just hInWrite, Just hOutRead, Just hErrRead, phProc)
<- createProcess
$ CreateProcess
{ cmdspec = ShellCommand cmd
, cwd = Nothing
, env = Nothing
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
, close_fds = False
, create_group = False
, delegate_ctlc = False
, detach_console = False
, create_new_console = False
, new_session = False
, child_group = Nothing
, child_user = Nothing
, use_process_jobs = True }
hPutStr hInWrite $ Log.toString logIn
hClose hInWrite
chanOut <- newTChanIO
chanErr <- newTChanIO
semOut <- newQSem 0
semErr <- newQSem 0
chanOutAcc <- atomically $ dupTChan chanOut
chanErrAcc <- atomically $ dupTChan chanErr
_tidOut <- forkIO $ streamIn hOutRead chanOut
_tidErr <- forkIO $ streamIn hErrRead chanErr
_tidStream <- forkIO $ streamOuts
[ (chanOut, if tee then Just stdout else Nothing, semOut)
, (chanErr, if tee then Just stderr else Nothing, semErr) ]
code <- waitForProcess phProc
trace $ "systemTeeIO: Process done, code = " ++ show code
trace $ "systemTeeIO: Waiting for sems"
mapM_ waitQSem [semOut, semErr]
trace $ "systemTeeIO: Getting output"
logOut <- slurpChan chanOutAcc Log.empty
logErr <- slurpChan chanErrAcc Log.empty
trace $ "systemTeeIO stdout: " ++ Log.toString logOut
trace $ "systemTeeIO stderr: " ++ Log.toString logErr
trace $ "systemTeeIO: All done"
hClose hOutRead
hClose hErrRead
code `seq` logOut `seq` logErr `seq`
return (code, logOut, logErr)
slurpChan :: TChan (Maybe ByteString) -> Log -> IO Log
slurpChan !chan !ll
= do mBS <- atomically $ readTChan chan
case mBS of
Nothing -> return ll
Just bs -> slurpChan chan (ll Log.|> Text.decodeUtf8 bs)