module Distribution.Simple.Program.Run (
ProgramInvocation(..),
IOEncoding(..),
emptyProgramInvocation,
simpleProgramInvocation,
programInvocation,
multiStageProgramInvocation,
runProgramInvocation,
getProgramInvocationOutput,
) where
import Distribution.Simple.Program.Types
( ConfiguredProgram(..), programPath )
import Distribution.Simple.Utils
( die, rawSystemExit, rawSystemStdInOut
, toUTF8, fromUTF8, normaliseLineEndings )
import Distribution.Verbosity
( Verbosity )
import Data.List
( foldl', unfoldr )
import Control.Monad
( when )
import System.Exit
( ExitCode(..) )
data ProgramInvocation = ProgramInvocation {
progInvokePath :: FilePath,
progInvokeArgs :: [String],
progInvokeEnv :: [(String, String)],
progInvokeCwd :: Maybe FilePath,
progInvokeInput :: Maybe String,
progInvokeInputEncoding :: IOEncoding,
progInvokeOutputEncoding :: IOEncoding
}
data IOEncoding = IOEncodingText
| IOEncodingUTF8
emptyProgramInvocation :: ProgramInvocation
emptyProgramInvocation =
ProgramInvocation {
progInvokePath = "",
progInvokeArgs = [],
progInvokeEnv = [],
progInvokeCwd = Nothing,
progInvokeInput = Nothing,
progInvokeInputEncoding = IOEncodingText,
progInvokeOutputEncoding = IOEncodingText
}
simpleProgramInvocation :: FilePath -> [String] -> ProgramInvocation
simpleProgramInvocation path args =
emptyProgramInvocation {
progInvokePath = path,
progInvokeArgs = args
}
programInvocation :: ConfiguredProgram -> [String] -> ProgramInvocation
programInvocation prog extraArgs =
emptyProgramInvocation {
progInvokePath = programPath prog,
progInvokeArgs = programArgs prog ++ extraArgs
}
runProgramInvocation :: Verbosity -> ProgramInvocation -> IO ()
runProgramInvocation verbosity
ProgramInvocation {
progInvokePath = path,
progInvokeArgs = args,
progInvokeEnv = [],
progInvokeCwd = Nothing,
progInvokeInput = Nothing
} =
rawSystemExit verbosity path args
runProgramInvocation verbosity
ProgramInvocation {
progInvokePath = path,
progInvokeArgs = args,
progInvokeEnv = [],
progInvokeCwd = Nothing,
progInvokeInput = Just inputStr,
progInvokeInputEncoding = encoding
} = do
(_, errors, exitCode) <- rawSystemStdInOut verbosity
path args
(Just input) False
when (exitCode /= ExitSuccess) $
die errors
where
input = case encoding of
IOEncodingText -> (inputStr, False)
IOEncodingUTF8 -> (toUTF8 inputStr, True)
runProgramInvocation _ _ =
die "runProgramInvocation: not yet implemented for this form of invocation"
getProgramInvocationOutput :: Verbosity -> ProgramInvocation -> IO String
getProgramInvocationOutput verbosity
ProgramInvocation {
progInvokePath = path,
progInvokeArgs = args,
progInvokeEnv = [],
progInvokeCwd = Nothing,
progInvokeInput = Nothing,
progInvokeOutputEncoding = encoding
} = do
let utf8 = case encoding of IOEncodingUTF8 -> True; _ -> False
decode | utf8 = fromUTF8 . normaliseLineEndings
| otherwise = id
(output, errors, exitCode) <- rawSystemStdInOut verbosity
path args
Nothing utf8
when (exitCode /= ExitSuccess) $
die errors
return (decode output)
getProgramInvocationOutput _ _ =
die "getProgramInvocationOutput: not yet implemented for this form of invocation"
multiStageProgramInvocation
:: ProgramInvocation
-> (ProgramInvocation, ProgramInvocation, ProgramInvocation)
-> [String]
-> [ProgramInvocation]
multiStageProgramInvocation simple (initial, middle, final) args =
let argSize inv = length (progInvokePath inv)
+ foldl' (\s a -> length a + 1 + s) 1 (progInvokeArgs inv)
fixedArgSize = maximum (map argSize [simple, initial, middle, final])
chunkSize = maxCommandLineSize fixedArgSize
in case splitChunks chunkSize args of
[] -> [ simple ]
[c] -> [ simple `appendArgs` c ]
[c,c'] -> [ initial `appendArgs` c ]
++ [ final `appendArgs` c']
(c:cs) -> [ initial `appendArgs` c ]
++ [ middle `appendArgs` c'| c' <- init cs ]
++ [ final `appendArgs` c'| let c' = last cs ]
where
inv `appendArgs` as = inv { progInvokeArgs = progInvokeArgs inv ++ as }
splitChunks len = unfoldr $ \s ->
if null s then Nothing
else Just (chunk len s)
chunk len (s:_) | length s >= len = error toolong
chunk len ss = chunk' [] len ss
chunk' acc _ [] = (reverse acc,[])
chunk' acc len (s:ss)
| len' < len = chunk' (s:acc) (lenlen'1) ss
| otherwise = (reverse acc, s:ss)
where len' = length s
toolong = "multiStageProgramInvocation: a single program arg is larger "
++ "than the maximum command line length!"
maxCommandLineSize :: Int
maxCommandLineSize = 30 * 1024