Development/Shake/Command.hs

{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, TypeOperators #-}

-- | /Deprecated:/ This module should no longer be imported as all the functions are available directly
-- from "Development.Shake". In future versions this module will be removed.
module Development.Shake.Command(
 command, command_, cmd,
 Stdout(..), Stderr(..), Exit(..),
 CmdResult, CmdOption(..),
 ) where

import Control.Arrow
import Control.Concurrent
import Control.DeepSeq
import Control.Exception as C
import Control.Monad
import Control.Monad.IO.Class
import Data.Either
import Foreign.C.Error
import System.Exit
import System.IO
import System.Process

import Development.Shake.Core
import Development.Shake.FilePath
import Development.Shake.Types

import GHC.IO.Exception (IOErrorType(..), IOException(..))


---------------------------------------------------------------------
-- ACTUAL EXECUTION

-- | Options passed to 'command' or 'cmd' to control how processes are executed.
data CmdOption
 = Cwd FilePath -- ^ Change the current directory in the spawned process. By default uses this processes current directory.
 | Env [(String,String)] -- ^ Change the environment variables in the spawned process. By default uses this processes environment.
 | Stdin String -- ^ Given as the @stdin@ of the spawned process. By default no @stdin@ is given.
 | Shell -- ^ Pass the command to the shell without escaping - any arguments will be joined with spaces. By default arguments are escaped properly.
 | BinaryPipes -- ^ Treat the @stdin@\/@stdout@\/@stderr@ messages as binary. By default streams use text encoding.
 | Traced String -- ^ Name to use with 'traced', or @\"\"@ for no tracing. By default traces using the name of the executable.
 | WithStderr Bool -- ^ Should I include the @stderr@ in the exception if the command fails? Defaults to 'True'.
 | EchoStdout Bool -- ^ Should I echo the @stdout@? Defaults to 'True' unless a 'Stdout' result is required.
 | EchoStderr Bool -- ^ Should I echo the @stderr@? Defaults to 'True' unless a 'Stderr' result is required.
 deriving (Eq,Ord,Show)

data Result
 = ResultStdout String
 | ResultStderr String
 | ResultCode ExitCode
 deriving Eq


commandExplicit :: String -> [CmdOption] -> [Result] -> String -> [String] -> Action [Result]
commandExplicit funcName opts results exe args = verboser $ tracer $
-- BEGIN COPIED
-- Originally from readProcessWithExitCode with as few changes as possible
 mask $ \restore -> do
 ans <- try $ createProcess cp
 (inh, outh, errh, pid) <- case ans of
 Right a -> return a
 Left err -> do
 let msg = "Development.Shake." ++ funcName ++ ", system command failed\n" ++
 "Command: " ++ saneCommandForUser exe args ++ "\n" ++
 show (err :: SomeException)
 error msg

 let close = maybe (return ()) hClose
 flip onException
 (do close inh; close outh; close errh
 terminateProcess pid; waitForProcess pid) $ restore $ do

 -- set pipes to binary if appropriate
 when (BinaryPipes `elem` opts) $ do
 let bin = maybe (return ()) (`hSetBinaryMode` True)
 bin inh; bin outh; bin errh

 -- fork off a thread to start consuming stdout
 (out,waitOut,waitOutEcho) <- case outh of
 Nothing -> return ("", return (), return ())
 Just outh -> do
 out <- hGetContents outh
 waitOut <- forkWait $ C.evaluate $ rnf out
 waitOutEcho <- if stdoutEcho
 then forkWait (hPutStr stdout out)
 else return (return ())
 return (out,waitOut,waitOutEcho)

 -- fork off a thread to start consuming stderr
 (err,waitErr,waitErrEcho) <- case errh of
 Nothing -> return ("", return (), return ())
 Just errh -> do
 err <- hGetContents errh
 waitErr <- forkWait $ C.evaluate $ rnf err
 waitErrEcho <- if stderrEcho
 then forkWait (hPutStr stderr err)
 else return (return ())
 return (err,waitErr,waitErrEcho)

 -- now write and flush any input
 let writeInput = do
 case inh of
 Nothing -> return ()
 Just inh -> do
 hPutStr inh input
 hFlush inh
 hClose inh

 C.catch writeInput $ \e -> case e of
 IOError { ioe_type = ResourceVanished
 , ioe_errno = Just ioe }
 | Errno ioe == ePIPE -> return ()
 _ -> throwIO e

 -- wait on the output
 waitOut
 waitErr

 waitOutEcho
 waitErrEcho

 close outh
 close errh

 -- wait on the process
 ex <- waitForProcess pid
-- END COPIED

 when (ResultCode ExitSuccess `notElem` results && ex /= ExitSuccess) $ do
 let msg = "Development.Shake." ++ funcName ++ ", system command failed\n" ++
 "Command: " ++ saneCommandForUser exe args ++ "\n" ++
 "Exit code: " ++ show (case ex of ExitFailure i -> i; _ -> 0) ++ "\n" ++
 (if not stderrThrow then "Stderr not captured because ErrorsWithoutStderr was used"
 else if null err then "Stderr was empty"
 else "Stderr:\n" ++ unlines (dropWhile null $ lines err))
 error msg

 return $ flip map results $ \x -> case x of
 ResultStdout _ -> ResultStdout out
 ResultStderr _ -> ResultStderr err
 ResultCode _ -> ResultCode ex
 where
 input = last $ "" : [x | Stdin x <- opts]
 verboser act = do
 v <- getVerbosity
 putLoud $ saneCommandForUser exe args
 (if v >= Loud then quietly else id) act
 tracer = case reverse [x | Traced x <- opts] of
 "":_ -> liftIO
 msg:_ -> traced msg
 [] -> traced (takeFileName exe)

 -- what should I do with these handles
 binary = BinaryPipes `elem` opts
 stdoutEcho = last $ (ResultStdout "" `notElem` results) : [b | EchoStdout b <- opts]
 stdoutCapture = ResultStdout "" `elem` results
 stderrEcho = last $ (ResultStderr "" `notElem` results) : [b | EchoStderr b <- opts]
 stderrThrow = last $ True : [b | WithStderr b <- opts]
 stderrCapture = ResultStderr "" `elem` results || (stderrThrow && ResultCode ExitSuccess `notElem` results)

 cp0 = (if Shell `elem` opts then shell $ unwords $ exe:args else proc exe args)
 {std_out = if binary || stdoutCapture || not stdoutEcho then CreatePipe else Inherit
 ,std_err = if binary || stderrCapture || not stderrEcho then CreatePipe else Inherit
 ,std_in = if null input then Inherit else CreatePipe
 }
 cp = foldl applyOpt cp0{std_out = CreatePipe, std_err = CreatePipe} opts
 applyOpt :: CreateProcess -> CmdOption -> CreateProcess
 applyOpt o (Cwd x) = o{cwd = if x == "" then Nothing else Just x}
 applyOpt o (Env x) = o{env = Just x}
 applyOpt o _ = o


-- Copied from System.Process
forkWait :: IO a -> IO (IO a)
forkWait a = do
 res <- newEmptyMVar
 _ <- mask $ \restore -> forkIO $ try (restore a) >>= putMVar res
 return (takeMVar res >>= either (\ex -> throwIO (ex :: SomeException)) return)


-- Like System.Process, but tweaked to show less escaping,
-- Relies on relatively detailed internals of showCommandForUser.
saneCommandForUser :: FilePath -> [String] -> String
saneCommandForUser cmd args = unwords $ map f $ cmd:args
 where
 f x = if take (length y - 2) (drop 1 y) == x then x else y
 where y = showCommandForUser x []


---------------------------------------------------------------------
-- FIXED ARGUMENT WRAPPER

-- | Collect the @stdout@ of the process.
-- If you are collecting the @stdout@, it will not be echoed to the terminal, unless you include 'EchoStdout'.
newtype Stdout = Stdout {fromStdout :: String}

-- | Collect the @stderr@ of the process.
-- If you are collecting the @stderr@, it will not be echoed to the terminal, unless you include 'EchoStderr'.
newtype Stderr = Stderr {fromStderr :: String}

-- | Collect the 'ExitCode' of the process.
-- If you do not collect the exit code, any 'ExitFailure' will cause an exception.
newtype Exit = Exit {fromExit :: ExitCode}

-- | A class for specifying what results you want to collect from a process.
-- Values are formed of 'Stdout', 'Stderr', 'Exit' and tuples of those.
class CmdResult a where
 -- Return a list of results (with the right type but dummy data)
 -- and a function to transform a populated set of results into a value
 cmdResult :: ([Result], [Result] -> a)

instance CmdResult Exit where
 cmdResult = ([ResultCode $ ExitSuccess], \[ResultCode x] -> Exit x)

instance CmdResult ExitCode where
 cmdResult = ([ResultCode $ ExitSuccess], \[ResultCode x] -> x)

instance CmdResult Stdout where
 cmdResult = ([ResultStdout ""], \[ResultStdout x] -> Stdout x)

instance CmdResult Stderr where
 cmdResult = ([ResultStderr ""], \[ResultStderr x] -> Stderr x)

instance CmdResult () where
 cmdResult = ([], \[] -> ())

instance (CmdResult x1, CmdResult x2) => CmdResult (x1,x2) where
 cmdResult = (a1++a2, \rs -> let (r1,r2) = splitAt (length a1) rs in (b1 r1, b2 r2))
 where (a1,b1) = cmdResult
 (a2,b2) = cmdResult

cmdResultWith f = second (f .) cmdResult

instance (CmdResult x1, CmdResult x2, CmdResult x3) => CmdResult (x1,x2,x3) where
 cmdResult = cmdResultWith $ \(a,(b,c)) -> (a,b,c)


-- | Execute a system command. Before running 'command' make sure you 'Development.Shake.need' any files
-- that are required by the command.
--
-- This function takes a list of options (often just @[]@, see 'CmdOption' for the available
-- options), the name of the executable (either a full name, or a program on the @$PATH@) and
-- a list of arguments. The result is often @()@, but can be a tuple containg any of 'Stdout',
-- 'Stderr' and 'Exit'. Some examples:
--
-- @
-- 'command_' [] \"gcc\" [\"-c\",\"myfile.c\"] -- compile a file, throwing an exception on failure
-- 'Exit' c <- 'command' [] \"gcc\" [\"-c\",myfile] -- run a command, recording the exit code
-- ('Exit' c, 'Stderr' err) <- 'command' [] \"gcc\" [\"-c\",\"myfile.c\"] -- run a command, recording the exit code and error output
-- 'Stdout' out <- 'command' [] \"gcc\" [\"-MM\",\"myfile.c\"] -- run a command, recording the output
-- 'command_' ['Cwd' \"generated\"] \"gcc\" [\"-c\",myfile] -- run a command in a directory
-- @
--
-- Unless you retrieve the 'ExitCode' using 'Exit', any 'ExitFailure' will throw an error, including
-- the 'Stderr' in the exception message. If you capture the 'Stdout' or 'Stderr', that stream will not be echoed to the console,
-- unless you use the option 'EchoStdout' or 'EchoStderr'.
--
-- If you use 'command' inside a @do@ block and do not use the result, you may get a compile-time error about being
-- unable to deduce 'CmdResult'. To avoid this error, use 'command_'.
command :: CmdResult r => [CmdOption] -> String -> [String] -> Action r
command opts x xs = fmap b $ commandExplicit "command" opts a x xs
 where (a,b) = cmdResult

-- | A version of 'command' where you do not require any results, used to avoid errors about being unable
-- to deduce 'CmdResult'.
command_ :: [CmdOption] -> String -> [String] -> Action ()
command_ opts x xs = commandExplicit "command_" opts [] x xs >> return ()


---------------------------------------------------------------------
-- VARIABLE ARGUMENT WRAPPER

type a :-> t = a


-- | A variable arity version of 'command'.
--
-- * @String@ arguments are treated as whitespace separated arguments.
--
-- * @[String]@ arguments are treated as literal arguments.
--
-- * 'CmdOption' arguments are used as options.
--
-- To take the examples from 'command':
--
-- @
-- () <- 'cmd' \"gcc -c myfile.c\" -- compile a file, throwing an exception on failure
-- 'Exit' c <- 'cmd' \"gcc -c\" [myfile] -- run a command, recording the exit code
-- ('Exit' c, 'Stderr' err) <- 'cmd' \"gcc -c myfile.c\" -- run a command, recording the exit code and error output
-- 'Stdout' out <- 'cmd' \"gcc -MM myfile.c\" -- run a command, recording the output
-- 'cmd' ('Cwd' \"generated\") \"gcc -c\" [myfile] :: 'Action' () -- run a command in a directory
-- @
--
-- When passing file arguments we use @[myfile]@ so that if the @myfile@ variable contains spaces they are properly escaped.
--
-- If you use 'cmd' inside a @do@ block and do not use the result, you may get a compile-time error about being
-- unable to deduce 'CmdResult'. To avoid this error, bind the result to @()@, or include a type signature.
cmd :: CmdArguments args => args :-> Action r
cmd = cmdArguments []

class CmdArguments t where cmdArguments :: [Either CmdOption String] -> t
instance (Arg a, CmdArguments r) => CmdArguments (a -> r) where
 cmdArguments xs x = cmdArguments $ xs ++ arg x
instance CmdResult r => CmdArguments (Action r) where
 cmdArguments x = case partitionEithers x of
 (opts, x:xs) -> let (a,b) = cmdResult in fmap b $ commandExplicit "cmd" opts a x xs
 _ -> error "Error, no executable or arguments given to Development.Shake.cmd"

class Arg a where arg :: a -> [Either CmdOption String]
instance Arg String where arg = map Right . words
instance Arg [String] where arg = map Right
instance Arg CmdOption where arg = return . Left
instance Arg [CmdOption] where arg = map Left

AltStyle によって変換されたページ (->オリジナル) /