module GHC.SysTools.Process where
#include "HsVersions.h"
import GHC.Prelude
import GHC.Driver.Session
import GHC.Utils.Exception
import GHC.Utils.Error
import GHC.Utils.Outputable
import GHC.Utils.Panic
import GHC.Utils.Misc
import GHC.Utils.Logger
import GHC.Types.SrcLoc ( SrcLoc, mkSrcLoc, mkSrcSpan )
import GHC.Data.FastString
import Control.Concurrent
import Data.Char
import System.Exit
import System.Environment
import System.FilePath
import System.IO
import System.IO.Error as IO
import System.Process
import GHC.Utils.TmpFs
enableProcessJobs :: CreateProcess -> CreateProcess
#if defined(MIN_VERSION_process)
enableProcessJobs opts = opts { use_process_jobs = True }
#else
enableProcessJobs opts = opts
#endif
#if !MIN_VERSION_base(4,15,0)
hGetContents' :: Handle -> IO String
hGetContents' hdl = do
output <- hGetContents hdl
_ <- evaluate $ length output
return output
#endif
readCreateProcessWithExitCode'
:: CreateProcess
-> IO (ExitCode, String)
readCreateProcessWithExitCode' proc = do
(_, Just outh, _, pid) <-
createProcess $ enableProcessJobs $ proc{ std_out = CreatePipe }
outMVar <- newEmptyMVar
let onError :: SomeException -> IO ()
onError exc = putMVar outMVar (Left exc)
_ <- forkIO $ handle onError $ do
output <- hGetContents' outh
putMVar outMVar $ Right output
result <- takeMVar outMVar
hClose outh
output <- case result of
Left exc -> throwIO exc
Right output -> return output
ex <- waitForProcess pid
return (ex, output)
replaceVar :: (String, String) -> [(String, String)] -> [(String, String)]
replaceVar (var, value) env =
(var, value) : filter (\(var',_) -> var /= var') env
readProcessEnvWithExitCode
:: String
-> [String]
-> (String, String)
-> IO (ExitCode, String, String)
readProcessEnvWithExitCode prog args env_update = do
current_env <- getEnvironment
readCreateProcessWithExitCode (proc prog args) {
env = Just (replaceVar env_update current_env) } ""
c_locale_env :: (String, String)
c_locale_env = ("LANGUAGE", "C")
getGccEnv :: [Option] -> IO (Maybe [(String,String)])
getGccEnv opts =
if null b_dirs
then return Nothing
else do env <- getEnvironment
return (Just (mangle_paths env))
where
(b_dirs, _) = partitionWith get_b_opt opts
get_b_opt (Option ('-':'B':dir)) = Left dir
get_b_opt other = Right other
#if defined(mingw32_HOST_OS)
mangle_paths = map mangle_path
mangle_path (path,paths) | map toUpper path == "PATH"
= (path, '\"' : head b_dirs ++ "\";" ++ paths)
mangle_path other = other
#else
mangle_paths = id
#endif
runSomething :: Logger
-> DynFlags
-> String
-> String
-> [Option]
-> IO ()
runSomething logger dflags phase_name pgm args =
runSomethingFiltered logger dflags id phase_name pgm args Nothing Nothing
runSomethingResponseFile
:: Logger
-> TmpFs
-> DynFlags
-> (String->String)
-> String
-> String
-> [Option]
-> Maybe [(String,String)]
-> IO ()
runSomethingResponseFile logger tmpfs dflags filter_fn phase_name pgm args mb_env =
runSomethingWith logger dflags phase_name pgm args $ \real_args -> do
fp <- getResponseFile real_args
let args = ['@':fp]
r <- builderMainLoop logger dflags filter_fn pgm args Nothing mb_env
return (r,())
where
getResponseFile args = do
fp <- newTempName logger tmpfs dflags TFL_CurrentModule "rsp"
withFile fp WriteMode $ \h -> do
#if defined(mingw32_HOST_OS)
hSetEncoding h latin1
#else
hSetEncoding h utf8
#endif
hPutStr h $ unlines $ map escape args
return fp
escape x = concat
[ "\""
, concatMap
(\c ->
case c of
'\\' -> "\\\\"
'\n' -> "\\n"
'\"' -> "\\\""
_ -> [c])
x
, "\""
]
runSomethingFiltered
:: Logger -> DynFlags -> (String->String) -> String -> String -> [Option]
-> Maybe FilePath -> Maybe [(String,String)] -> IO ()
runSomethingFiltered logger dflags filter_fn phase_name pgm args mb_cwd mb_env =
runSomethingWith logger dflags phase_name pgm args $ \real_args -> do
r <- builderMainLoop logger dflags filter_fn pgm real_args mb_cwd mb_env
return (r,())
runSomethingWith
:: Logger -> DynFlags -> String -> String -> [Option]
-> ([String] -> IO (ExitCode, a))
-> IO a
runSomethingWith logger dflags phase_name pgm args io = do
let real_args = filter notNull (map showOpt args)
cmdLine = showCommandForUser pgm real_args
traceCmd logger dflags phase_name cmdLine $ handleProc pgm phase_name $ io real_args
handleProc :: String -> String -> IO (ExitCode, r) -> IO r
handleProc pgm phase_name proc = do
(rc, r) <- proc `catchIO` handler
case rc of
ExitSuccess{} -> return r
ExitFailure n -> throwGhcExceptionIO (
ProgramError ("`" ++ takeFileName pgm ++ "'" ++
" failed in phase `" ++ phase_name ++ "'." ++
" (Exit code: " ++ show n ++ ")"))
where
handler err =
if IO.isDoesNotExistError err
then does_not_exist
else throwGhcExceptionIO (ProgramError $ show err)
does_not_exist = throwGhcExceptionIO (InstallationError ("could not execute: " ++ pgm))
builderMainLoop :: Logger -> DynFlags -> (String -> String) -> FilePath
-> [String] -> Maybe FilePath -> Maybe [(String, String)]
-> IO ExitCode
builderMainLoop logger dflags filter_fn pgm real_args mb_cwd mb_env = do
chan <- newChan
let safely inner = mask $ \restore -> do
let procdata =
enableProcessJobs
$ (proc pgm real_args) { cwd = mb_cwd
, env = mb_env
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
(Just hStdIn, Just hStdOut, Just hStdErr, hProcess) <- restore $
createProcess_ "builderMainLoop" procdata
let cleanup_handles = do
hClose hStdIn
hClose hStdOut
hClose hStdErr
r <- try $ restore $ do
hSetBuffering hStdOut LineBuffering
hSetBuffering hStdErr LineBuffering
let make_reader_proc h = forkIO $ readerProc chan h filter_fn
bracketOnError (make_reader_proc hStdOut) killThread $ \_ ->
bracketOnError (make_reader_proc hStdErr) killThread $ \_ ->
inner hProcess
case r of
Left (SomeException e) -> do
terminateProcess hProcess
cleanup_handles
throw e
Right s -> do
cleanup_handles
return s
safely $ \h -> do
log_loop chan (2 :: Integer)
waitForProcess h
where
log_loop _ 0 = return ()
log_loop chan t = do
msg <- readChan chan
case msg of
BuildMsg msg -> do
logInfo logger dflags $ withPprStyle defaultUserStyle msg
log_loop chan t
BuildError loc msg -> do
putLogMsg logger dflags NoReason SevError (mkSrcSpan loc loc)
$ withPprStyle defaultUserStyle msg
log_loop chan t
EOF ->
log_loop chan (t1)
readerProc :: Chan BuildMessage -> Handle -> (String -> String) -> IO ()
readerProc chan hdl filter_fn =
(do str <- hGetContents hdl
loop (linesPlatform (filter_fn str)) Nothing)
`finally`
writeChan chan EOF
where
loop [] Nothing = return ()
loop [] (Just err) = writeChan chan err
loop (l:ls) in_err =
case in_err of
Just err@(BuildError srcLoc msg)
| leading_whitespace l ->
loop ls (Just (BuildError srcLoc (msg $$ text l)))
| otherwise -> do
writeChan chan err
checkError l ls
Nothing ->
checkError l ls
_ -> panic "readerProc/loop"
checkError l ls
= case parseError l of
Nothing -> do
writeChan chan (BuildMsg (text l))
loop ls Nothing
Just (file, lineNum, colNum, msg) -> do
let srcLoc = mkSrcLoc (mkFastString file) lineNum colNum
loop ls (Just (BuildError srcLoc (text msg)))
leading_whitespace [] = False
leading_whitespace (x:_) = isSpace x
parseError :: String -> Maybe (String, Int, Int, String)
parseError s0 = case breakColon s0 of
Just (filename, s1) ->
case breakIntColon s1 of
Just (lineNum, s2) ->
case breakIntColon s2 of
Just (columnNum, s3) ->
Just (filename, lineNum, columnNum, s3)
Nothing ->
Just (filename, lineNum, 0, s2)
Nothing -> Nothing
Nothing -> Nothing
breakColon :: String -> Maybe (String, String)
breakColon = go []
where
go accum (':':'\\':rest) = go ('\\':':':accum) rest
go accum (':':'/':rest) = go ('/':':':accum) rest
go accum (':':rest) = Just (reverse accum, rest)
go accum (c:rest) = go (c:accum) rest
go _accum [] = Nothing
breakIntColon :: String -> Maybe (Int, String)
breakIntColon xs = case break (':' ==) xs of
(ys, _:zs)
| not (null ys) && all isAscii ys && all isDigit ys ->
Just (read ys, zs)
_ -> Nothing
data BuildMessage
= BuildMsg !SDoc
| BuildError !SrcLoc !SDoc
| EOF
linesPlatform :: String -> [String]
#if !defined(mingw32_HOST_OS)
linesPlatform ls = lines ls
#else
linesPlatform "" = []
linesPlatform xs =
case lineBreak xs of
(as,xs1) -> as : linesPlatform xs1
where
lineBreak "" = ("","")
lineBreak ('\r':'\n':xs) = ([],xs)
lineBreak ('\n':xs) = ([],xs)
lineBreak (x:xs) = let (as,bs) = lineBreak xs in (x:as,bs)
#endif