diff --git a/Cabal/src/Distribution/Compat/Process.hs b/Cabal/src/Distribution/Compat/Process.hs index a83da2319ce9e21b1391c2fe2067c07d0c55c7bb..18a1d9f53d0f8aa7b974b2a990d2d6dfb6a85564 100644 --- a/Cabal/src/Distribution/Compat/Process.hs +++ b/Cabal/src/Distribution/Compat/Process.hs @@ -1,17 +1,12 @@ {-# LANGUAGE CPP #-} module Distribution.Compat.Process ( -- * Redefined functions - createProcess, - runInteractiveProcess, - rawSystem, + proc, -- * Additions enableProcessJobs, ) where -import System.Exit (ExitCode (..)) -import System.IO (Handle) - -import System.Process (CreateProcess, ProcessHandle, waitForProcess) +import System.Process (CreateProcess) import qualified System.Process as Process #if defined(mingw32_HOST_OS) && MIN_VERSION_process(1,6,9) @@ -60,35 +55,7 @@ enableProcessJobs cp = cp -- process redefinitions ------------------------------------------------------------------------------- --- | 'System.Process.createProcess' with process jobs enabled when appropriate. --- See 'enableProcessJobs'. -createProcess :: CreateProcess - -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -createProcess = Process.createProcess . enableProcessJobs - --- | 'System.Process.rawSystem' with process jobs enabled when appropriate. --- See 'enableProcessJobs'. -rawSystem :: String -> [String] -> IO ExitCode -rawSystem cmd args = do - (_,_,_,p) <- createProcess (Process.proc cmd args) { Process.delegate_ctlc = True } - waitForProcess p - --- | 'System.Process.runInteractiveProcess' with process jobs enabled when --- appropriate. See 'enableProcessJobs'. -runInteractiveProcess - :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details) - -> [String] -- ^ Arguments to pass to the executable - -> Maybe FilePath -- ^ Optional path to the working directory - -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit) - -> IO (Handle,Handle,Handle,ProcessHandle) -runInteractiveProcess cmd args mb_cwd mb_env = do - (mb_in, mb_out, mb_err, p) <- - createProcess (Process.proc cmd args) - { Process.std_in = Process.CreatePipe, - Process.std_out = Process.CreatePipe, - Process.std_err = Process.CreatePipe, - Process.env = mb_env, - Process.cwd = mb_cwd } - return (fromJust mb_in, fromJust mb_out, fromJust mb_err, p) - where - fromJust = maybe (error "runInteractiveProcess: fromJust") id +-- | 'System.Process.proc' with process jobs enabled when appropriate, +-- and defaulting 'delegate_ctlc' to 'True'. +proc :: FilePath -> [String] -> CreateProcess +proc path args = enableProcessJobs (Process.proc path args) { Process.delegate_ctlc = True } diff --git a/Cabal/src/Distribution/Simple/Test/LibV09.hs b/Cabal/src/Distribution/Simple/Test/LibV09.hs index 9faacefb5f8bbc2af7ca9d03a65fe56d64a820e7..1cf84a25e4f517c596130b83b0cff125ec07c9f7 100644 --- a/Cabal/src/Distribution/Simple/Test/LibV09.hs +++ b/Cabal/src/Distribution/Simple/Test/LibV09.hs @@ -41,7 +41,8 @@ import System.Directory , setCurrentDirectory ) import System.FilePath ( (</>), (<.>) ) import System.IO ( hClose, hPutStr ) -import System.Process (StdStream(..), createPipe, waitForProcess) +import Distribution.Compat.Process (proc) +import qualified System.Process as Process runTest :: PD.PackageDescription -> LBI.LocalBuildInfo @@ -78,49 +79,48 @@ runTest pkg_descr lbi clbi flags suite = do suiteLog <- CE.bracket openCabalTemp deleteIfExists $ \tempLog -> do + -- Run test executable + let opts = map (testOption pkg_descr lbi suite) $ testOptions flags + dataDirPath = pwd </> PD.dataDir pkg_descr + tixFile = pwd </> tixFilePath distPref way testName' + pkgPathEnv = (pkgPathEnvVar pkg_descr "datadir", dataDirPath) + : existingEnv + shellEnv = [("HPCTIXFILE", tixFile) | isCoverageEnabled] + ++ pkgPathEnv + -- Add (DY)LD_LIBRARY_PATH if needed + shellEnv' <- + if LBI.withDynExe lbi + then do + let (Platform _ os) = LBI.hostPlatform lbi + paths <- LBI.depLibraryPaths True False lbi clbi + cpath <- canonicalizePath $ LBI.componentBuildDir lbi clbi + return (addLibraryPath os (cpath : paths) shellEnv) + else return shellEnv + let (cmd', opts') = case testWrapper flags of + Flag path -> (path, cmd:opts) + NoFlag -> (cmd, opts) + -- TODO: this setup is broken, -- if the test output is too big, we will deadlock. - (rOut, wOut) <- createPipe - - -- Run test executable - (Just wIn, _, _, process) <- do - let opts = map (testOption pkg_descr lbi suite) $ testOptions flags - dataDirPath = pwd </> PD.dataDir pkg_descr - tixFile = pwd </> tixFilePath distPref way testName' - pkgPathEnv = (pkgPathEnvVar pkg_descr "datadir", dataDirPath) - : existingEnv - shellEnv = [("HPCTIXFILE", tixFile) | isCoverageEnabled] - ++ pkgPathEnv - -- Add (DY)LD_LIBRARY_PATH if needed - shellEnv' <- - if LBI.withDynExe lbi - then do - let (Platform _ os) = LBI.hostPlatform lbi - paths <- LBI.depLibraryPaths True False lbi clbi - cpath <- canonicalizePath $ LBI.componentBuildDir lbi clbi - return (addLibraryPath os (cpath : paths) shellEnv) - else return shellEnv - case testWrapper flags of - Flag path -> createProcessWithEnv verbosity path (cmd:opts) Nothing (Just shellEnv') - -- these handles are closed automatically - CreatePipe (UseHandle wOut) (UseHandle wOut) - - NoFlag -> createProcessWithEnv verbosity cmd opts Nothing (Just shellEnv') - -- these handles are closed automatically - CreatePipe (UseHandle wOut) (UseHandle wOut) - - hPutStr wIn $ show (tempLog, PD.testName suite) - hClose wIn - - -- Append contents of temporary log file to the final human- - -- readable log file - logText <- LBS.hGetContents rOut - -- Force the IO manager to drain the test output pipe - _ <- evaluate (force logText) - - exitcode <- waitForProcess process - unless (exitcode == ExitSuccess) $ do - debug verbosity $ cmd ++ " returned " ++ show exitcode + (rOut, wOut) <- Process.createPipe + (exitcode, logText) <- rawSystemProcAction verbosity + (proc cmd' opts') { Process.env = Just shellEnv' + , Process.std_in = Process.CreatePipe + , Process.std_out = Process.UseHandle wOut + , Process.std_err = Process.UseHandle wOut + } $ \mIn _ _ -> do + let wIn = fromCreatePipe mIn + hPutStr wIn $ show (tempLog, PD.testName suite) + hClose wIn + + -- Append contents of temporary log file to the final human- + -- readable log file + logText <- LBS.hGetContents rOut + -- Force the IO manager to drain the test output pipe + _ <- evaluate (force logText) + return logText + unless (exitcode == ExitSuccess) $ + debug verbosity $ cmd ++ " returned " ++ show exitcode -- Generate final log file name let finalLogName l = testLogDir diff --git a/Cabal/src/Distribution/Simple/Utils.hs b/Cabal/src/Distribution/Simple/Utils.hs index 7257d79116acbc332739c4fd76bcc067e2ea961c..1772bfac096c061e691be293d2f525b5a5a2113d 100644 --- a/Cabal/src/Distribution/Simple/Utils.hs +++ b/Cabal/src/Distribution/Simple/Utils.hs @@ -38,7 +38,6 @@ module Distribution.Simple.Utils ( debug, debugNoWrap, chattyTry, annotateIO, - printRawCommandAndArgs, printRawCommandAndArgsAndEnv, withOutputMarker, -- * exceptions @@ -48,12 +47,14 @@ module Distribution.Simple.Utils ( -- * running programs rawSystemExit, rawSystemExitCode, + rawSystemProc, + rawSystemProcAction, rawSystemExitWithEnv, rawSystemStdout, rawSystemStdInOut, rawSystemIOWithEnv, rawSystemIOWithEnvAndAction, - createProcessWithEnv, + fromCreatePipe, maybeExit, xargs, findProgramVersion, @@ -183,7 +184,7 @@ import qualified Distribution.Utils.IOData as IOData import Distribution.ModuleName as ModuleName import Distribution.System import Distribution.Version -import Distribution.Compat.Async +import Distribution.Compat.Async (waitCatch, withAsyncNF) import Distribution.Compat.CopyFile import Distribution.Compat.FilePath as FilePath import Distribution.Compat.Internal.TempFile @@ -234,10 +235,7 @@ import qualified Control.Exception as Exception import Foreign.C.Error (Errno (..), ePIPE) import Data.Time.Clock.POSIX (getPOSIXTime, POSIXTime) import Numeric (showFFloat) -import Distribution.Compat.Process (createProcess, rawSystem, runInteractiveProcess) -import System.Process - ( ProcessHandle - , showCommandForUser, waitForProcess) +import Distribution.Compat.Process (proc) import qualified System.Process as Process import qualified GHC.IO.Exception as GHC @@ -719,69 +717,114 @@ clearMarkers s = unlines . filter isMarker $ lines s -- ----------------------------------------------------------------------------- -- rawSystem variants +-- +-- These all use 'Distribution.Compat.Process.proc' to ensure we +-- consistently use process jobs on Windows and Ctrl-C delegation +-- on Unix. +-- +-- Additionally, they take care of logging command execution. +-- + +-- | Helper to use with one of the 'rawSystem' variants, and exit +-- unless the command completes successfully. maybeExit :: IO ExitCode -> IO () maybeExit cmd = do - res <- cmd - unless (res == ExitSuccess) $ exitWith res - - - -printRawCommandAndArgs :: Verbosity -> FilePath -> [String] -> IO () -printRawCommandAndArgs verbosity path args = withFrozenCallStack $ - printRawCommandAndArgsAndEnv verbosity path args Nothing Nothing - -printRawCommandAndArgsAndEnv :: Verbosity - -> FilePath - -> [String] - -> Maybe FilePath - -> Maybe [(String, String)] - -> IO () -printRawCommandAndArgsAndEnv verbosity path args mcwd menv = do - case menv of - Just env -> debugNoWrap verbosity ("Environment: " ++ show env) - Nothing -> return () - case mcwd of - Just cwd -> debugNoWrap verbosity ("Working directory: " ++ show cwd) - Nothing -> return () - infoNoWrap verbosity (showCommandForUser path args) - --- Exit with the same exit code if the subcommand fails -rawSystemExit :: Verbosity -> FilePath -> [String] -> IO () -rawSystemExit verbosity path args = withFrozenCallStack $ do - printRawCommandAndArgs verbosity path args + exitcode <- cmd + unless (exitcode == ExitSuccess) $ exitWith exitcode + +-- | Log a command execution (that's typically about to happen) +-- at info level, and log working directory and environment overrides +-- at debug level if specified. +-- +logCommand :: Verbosity -> Process.CreateProcess -> IO () +logCommand verbosity cp = do + infoNoWrap verbosity $ "Running: " <> case Process.cmdspec cp of + Process.ShellCommand sh -> sh + Process.RawCommand path args -> Process.showCommandForUser path args + case Process.env cp of + Just env -> debugNoWrap verbosity $ "with environment: " ++ show env + Nothing -> return () + case Process.cwd cp of + Just cwd -> debugNoWrap verbosity $ "with working directory: " ++ show cwd + Nothing -> return () hFlush stdout - exitcode <- rawSystem path args - unless (exitcode == ExitSuccess) $ do - debug verbosity $ path ++ " returned " ++ show exitcode - exitWith exitcode +-- | Execute the given command with the given arguments, exiting +-- with the same exit code if the command fails. +-- +rawSystemExit :: Verbosity -> FilePath -> [String] -> IO () +rawSystemExit verbosity path args = withFrozenCallStack $ + maybeExit $ rawSystemExitCode verbosity path args + +-- | Execute the given command with the given arguments, returning +-- the command's exit code. +-- rawSystemExitCode :: Verbosity -> FilePath -> [String] -> IO ExitCode -rawSystemExitCode verbosity path args = withFrozenCallStack $ do - printRawCommandAndArgs verbosity path args - hFlush stdout - exitcode <- rawSystem path args - unless (exitcode == ExitSuccess) $ do - debug verbosity $ path ++ " returned " ++ show exitcode +rawSystemExitCode verbosity path args = withFrozenCallStack $ + rawSystemProc verbosity $ proc path args + +-- | Execute the given command with the given arguments, returning +-- the command's exit code. +-- +-- Create the process argument with 'Distribution.Compat.Process.proc' +-- to ensure consistent options with other 'rawSystem' functions in this +-- module. +-- +rawSystemProc :: Verbosity -> Process.CreateProcess -> IO ExitCode +rawSystemProc verbosity cp = withFrozenCallStack $ do + (exitcode, _) <- rawSystemProcAction verbosity cp $ \_ _ _ -> return () return exitcode +-- | Execute the given command with the given arguments, returning +-- the command's exit code. 'action' is executed while the command +-- is running, and would typically be used to communicate with the +-- process through pipes. +-- +-- Create the process argument with 'Distribution.Compat.Process.proc' +-- to ensure consistent options with other 'rawSystem' functions in this +-- module. +-- +rawSystemProcAction :: Verbosity -> Process.CreateProcess + -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> IO a) + -> IO (ExitCode, a) +rawSystemProcAction verbosity cp action = withFrozenCallStack $ do + logCommand verbosity cp + (mStdin, mStdout, mStderr, p) <- Process.createProcess cp + a <- action mStdin mStdout mStderr + exitcode <- Process.waitForProcess p + unless (exitcode == ExitSuccess) $ do + let cmd = case Process.cmdspec cp of + Process.ShellCommand sh -> sh + Process.RawCommand path _args -> path + debug verbosity $ cmd ++ " returned " ++ show exitcode + return (exitcode, a) + +-- | fromJust for dealing with 'Maybe Handle' values as obtained via +-- 'System.Process.CreatePipe'. Creating a pipe using 'CreatePipe' guarantees +-- a 'Just' value for the corresponding handle. +-- +fromCreatePipe :: Maybe Handle -> Handle +fromCreatePipe = maybe (error "fromCreatePipe: Nothing") id + +-- | Execute the given command with the given arguments and +-- environment, exiting with the same exit code if the command fails. +-- rawSystemExitWithEnv :: Verbosity -> FilePath -> [String] -> [(String, String)] -> IO () -rawSystemExitWithEnv verbosity path args env = withFrozenCallStack $ do - printRawCommandAndArgsAndEnv verbosity path args Nothing (Just env) - hFlush stdout - (_,_,_,ph) <- createProcess $ - (Process.proc path args) { Process.env = (Just env) - , Process.delegate_ctlc = True - } - exitcode <- waitForProcess ph - unless (exitcode == ExitSuccess) $ do - debug verbosity $ path ++ " returned " ++ show exitcode - exitWith exitcode - --- Closes the passed in handles before returning. +rawSystemExitWithEnv verbosity path args env = withFrozenCallStack $ + maybeExit $ rawSystemProc verbosity $ + (proc path args) { Process.env = Just env + } + +-- | Execute the given command with the given arguments, returning +-- the command's exit code. +-- +-- Optional arguments allow setting working directory, environment +-- and input and output handles. +-- rawSystemIOWithEnv :: Verbosity -> FilePath -> [String] @@ -792,16 +835,20 @@ rawSystemIOWithEnv :: Verbosity -> Maybe Handle -- ^ stderr -> IO ExitCode rawSystemIOWithEnv verbosity path args mcwd menv inp out err = withFrozenCallStack $ do - (_,_,_,ph) <- createProcessWithEnv verbosity path args mcwd menv - (mbToStd inp) (mbToStd out) (mbToStd err) - exitcode <- waitForProcess ph - unless (exitcode == ExitSuccess) $ do - debug verbosity $ path ++ " returned " ++ show exitcode - return exitcode + (exitcode, _) <- rawSystemIOWithEnvAndAction + verbosity path args mcwd menv action inp out err + return exitcode where - mbToStd :: Maybe Handle -> Process.StdStream - mbToStd = maybe Process.Inherit Process.UseHandle + action = return () +-- | Execute the given command with the given arguments, returning +-- the command's exit code. 'action' is executed while the command +-- is running, and would typically be used to communicate with the +-- process through pipes. +-- +-- Optional arguments allow setting working directory, environment +-- and input and output handles. +-- rawSystemIOWithEnvAndAction :: Verbosity -> FilePath @@ -814,46 +861,21 @@ rawSystemIOWithEnvAndAction -> Maybe Handle -- ^ stderr -> IO (ExitCode, a) rawSystemIOWithEnvAndAction verbosity path args mcwd menv action inp out err = withFrozenCallStack $ do - (_,_,_,ph) <- createProcessWithEnv verbosity path args mcwd menv - (mbToStd inp) (mbToStd out) (mbToStd err) - a <- action - exitcode <- waitForProcess ph - unless (exitcode == ExitSuccess) $ do - debug verbosity $ path ++ " returned " ++ show exitcode - return (exitcode, a) + let cp = (proc path args) { Process.cwd = mcwd + , Process.env = menv + , Process.std_in = mbToStd inp + , Process.std_out = mbToStd out + , Process.std_err = mbToStd err + } + rawSystemProcAction verbosity cp (\_ _ _ -> action) where mbToStd :: Maybe Handle -> Process.StdStream mbToStd = maybe Process.Inherit Process.UseHandle -createProcessWithEnv :: - Verbosity - -> FilePath - -> [String] - -> Maybe FilePath -- ^ New working dir or inherit - -> Maybe [(String, String)] -- ^ New environment or inherit - -> Process.StdStream -- ^ stdin - -> Process.StdStream -- ^ stdout - -> Process.StdStream -- ^ stderr - -> IO (Maybe Handle, Maybe Handle, Maybe Handle,ProcessHandle) - -- ^ Any handles created for stdin, stdout, or stderr - -- with 'CreateProcess', and a handle to the process. -createProcessWithEnv verbosity path args mcwd menv inp out err = withFrozenCallStack $ do - printRawCommandAndArgsAndEnv verbosity path args mcwd menv - hFlush stdout - (inp', out', err', ph) <- createProcess $ - (Process.proc path args) { - Process.cwd = mcwd - , Process.env = menv - , Process.std_in = inp - , Process.std_out = out - , Process.std_err = err - , Process.delegate_ctlc = True - } - return (inp', out', err', ph) - --- | Run a command and return its output. +-- | Execute the given command with the given arguments, returning +-- the command's output. Exits if the command exits with error. -- --- The output is assumed to be text in the locale encoding. +-- Provides control over the binary/text mode of the output. -- rawSystemStdout :: forall mode. KnownIODataMode mode => Verbosity -> FilePath -> [String] -> IO mode rawSystemStdout verbosity path args = withFrozenCallStack $ do @@ -863,9 +885,13 @@ rawSystemStdout verbosity path args = withFrozenCallStack $ do die' verbosity errors return output --- | Run a command and return its output, errors and exit status. Optionally --- also supply some input. Also provides control over whether the binary/text --- mode of the input and output. +-- | Execute the given command with the given arguments, returning +-- the command's output, errors and exit code. +-- +-- Optional arguments allow setting working directory, environment +-- and command input. +-- +-- Provides control over the binary/text mode of the input and output. -- rawSystemStdInOut :: KnownIODataMode mode => Verbosity @@ -877,13 +903,16 @@ rawSystemStdInOut :: KnownIODataMode mode -> IODataMode mode -- ^ iodata mode, acts as proxy -> IO (mode, String, ExitCode) -- ^ output, errors, exit rawSystemStdInOut verbosity path args mcwd menv input _ = withFrozenCallStack $ do - printRawCommandAndArgs verbosity path args - - Exception.bracket - (runInteractiveProcess path args mcwd menv) - (\(inh,outh,errh,_) -> hClose inh >> hClose outh >> hClose errh) - $ \(inh,outh,errh,pid) -> do - + let cp = (proc path args) { Process.cwd = mcwd + , Process.env = menv + , Process.std_in = Process.CreatePipe + , Process.std_out = Process.CreatePipe + , Process.std_err = Process.CreatePipe + } + + (exitcode, (mberr1, mberr2)) <- rawSystemProcAction verbosity cp $ \mb_in mb_out mb_err -> do + let (inh, outh, errh) = (fromCreatePipe mb_in, fromCreatePipe mb_out, fromCreatePipe mb_err) + flip Exception.finally (hClose inh >> hClose outh >> hClose errh) $ do -- output mode depends on what the caller wants -- but the errors are always assumed to be text (in the current locale) hSetBinaryMode errh False @@ -900,28 +929,26 @@ rawSystemStdInOut verbosity path args mcwd menv input _ = withFrozenCallStack $ -- wait for both to finish mberr1 <- waitCatch outA mberr2 <- waitCatch errA + return (mberr1, mberr2) - -- wait for the program to terminate - exitcode <- waitForProcess pid - - -- get the stderr, so it can be added to error message - err <- reportOutputIOError mberr2 - - unless (exitcode == ExitSuccess) $ - debug verbosity $ path ++ " returned " ++ show exitcode - ++ if null err then "" else - " with error message:\n" ++ err - ++ case input of - Nothing -> "" - Just d | IOData.null d -> "" - Just (IODataText inp) -> "\nstdin input:\n" ++ inp - Just (IODataBinary inp) -> "\nstdin input (binary):\n" ++ show inp + -- get the stderr, so it can be added to error message + err <- reportOutputIOError mberr2 - -- Check if we hit an exception while consuming the output - -- (e.g. a text decoding error) - out <- reportOutputIOError mberr1 - - return (out, err, exitcode) + unless (exitcode == ExitSuccess) $ + debug verbosity $ path ++ " returned " ++ show exitcode + ++ if null err then "" else + " with error message:\n" ++ err + ++ case input of + Nothing -> "" + Just d | IOData.null d -> "" + Just (IODataText inp) -> "\nstdin input:\n" ++ inp + Just (IODataBinary inp) -> "\nstdin input (binary):\n" ++ show inp + + -- Check if we hit an exception while consuming the output + -- (e.g. a text decoding error) + out <- reportOutputIOError mberr1 + + return (out, err, exitcode) where reportOutputIOError :: Either Exception.SomeException a -> IO a reportOutputIOError (Right x) = return x diff --git a/cabal-install/cabal-install.cabal b/cabal-install/cabal-install.cabal index 1a2a49deebaa27ea4a11ca574d42c1e50ad2a7dc..9f68b0137b1716952d852a77b263c3f328480bd8 100644 --- a/cabal-install/cabal-install.cabal +++ b/cabal-install/cabal-install.cabal @@ -98,7 +98,6 @@ library Distribution.Client.Compat.ExecutablePath Distribution.Client.Compat.Orphans Distribution.Client.Compat.Prelude - Distribution.Client.Compat.Process Distribution.Client.Compat.Semaphore Distribution.Client.Config Distribution.Client.Configure diff --git a/cabal-install/src/Distribution/Client/Compat/Process.hs b/cabal-install/src/Distribution/Client/Compat/Process.hs deleted file mode 100644 index c8039c3e0389a77dac09d11db4804bb074e32522..0000000000000000000000000000000000000000 --- a/cabal-install/src/Distribution/Client/Compat/Process.hs +++ /dev/null @@ -1,48 +0,0 @@ ------------------------------------------------------------------------------ --- | --- Module : Distribution.Client.Compat.Process --- Copyright : (c) 2013 Liu Hao, Brent Yorgey --- License : BSD-style (see the file LICENSE) --- --- Maintainer : cabal-devel@haskell.org --- Stability : provisional --- Portability : portable --- --- Cross-platform utilities for invoking processes. --- ------------------------------------------------------------------------------ - -module Distribution.Client.Compat.Process ( - readProcessWithExitCode -) where - -import Prelude (FilePath, IO, String, return, (||)) - -import Control.Exception (catch, throw) -import System.Exit (ExitCode (ExitFailure)) -import System.IO.Error (isDoesNotExistError, isPermissionError) -import qualified System.Process as P - --- | @readProcessWithExitCode@ creates an external process, reads its --- standard output and standard error strictly, waits until the --- process terminates, and then returns the @ExitCode@ of the --- process, the standard output, and the standard error. --- --- See the documentation of the version from @System.Process@ for --- more information. --- --- The version from @System.Process@ behaves inconsistently across --- platforms when an executable with the given name is not found: in --- some cases it returns an @ExitFailure@, in others it throws an --- exception. This variant catches \"does not exist\" and --- \"permission denied\" exceptions and turns them into --- @ExitFailure@s. --- --- TODO: this doesn't use 'Distrubution.Compat.Process'. --- -readProcessWithExitCode :: FilePath -> [String] -> String -> IO (ExitCode, String, String) -readProcessWithExitCode cmd args input = - P.readProcessWithExitCode cmd args input - `catch` \e -> if isDoesNotExistError e || isPermissionError e - then return (ExitFailure 127, "", "") - else throw e diff --git a/cabal-install/src/Distribution/Client/Init/Types.hs b/cabal-install/src/Distribution/Client/Init/Types.hs index 4408a32d3b76a75dffd4e945526653443287a29d..3b998adb510f31485bbe72d7694328833874d7c0 100644 --- a/cabal-install/src/Distribution/Client/Init/Types.hs +++ b/cabal-install/src/Distribution/Client/Init/Types.hs @@ -73,7 +73,7 @@ import Language.Haskell.Extension ( Language(..), Extension ) import qualified System.IO import qualified System.Directory as P -import qualified System.Process as P +import qualified System.Process as Process import qualified Distribution.Compat.Environment as P import System.FilePath @@ -342,7 +342,7 @@ instance Interactive IO where doesDirectoryExist = P.doesDirectoryExist doesFileExist = P.doesFileExist canonicalizePathNoThrow = P.canonicalizePathNoThrow - readProcessWithExitCode = P.readProcessWithExitCode + readProcessWithExitCode = Process.readProcessWithExitCode getEnvironment = P.getEnvironment getCurrentYear = P.getCurrentYear listFilesInside = P.listFilesInside diff --git a/cabal-install/src/Distribution/Client/Manpage.hs b/cabal-install/src/Distribution/Client/Manpage.hs index 736e82d9b81c860c77cda59eee3ee4f2f26cb439..f93c711753ce8239bc4eb6b768eadbe8ac9ac0f9 100644 --- a/cabal-install/src/Distribution/Client/Manpage.hs +++ b/cabal-install/src/Distribution/Client/Manpage.hs @@ -27,15 +27,14 @@ import qualified Data.List.NonEmpty as List1 import Distribution.Client.Init.Utils (trim) import Distribution.Client.ManpageFlags import Distribution.Client.Setup (globalCommand) +import Distribution.Compat.Process (proc) import Distribution.Simple.Command -import Distribution.Simple.Flag (fromFlagOrDefault) +import Distribution.Simple.Flag (fromFlag, fromFlagOrDefault) import Distribution.Simple.Utils - ( IOData(..), IODataMode(..), createProcessWithEnv, ignoreSigPipe, rawSystemStdInOut ) -import qualified Distribution.Verbosity as Verbosity + ( IOData(..), IODataMode(..), ignoreSigPipe, rawSystemStdInOut, rawSystemProcAction, fromCreatePipe ) import System.IO (hClose, hPutStr) import System.Environment (lookupEnv) import System.FilePath (takeFileName) - import qualified System.Process as Process data FileInfo = FileInfo String String -- ^ path, description @@ -69,7 +68,7 @@ manpageCmd pname commands flags -- Feed contents into @nroff -man /dev/stdin@ (formatted, _errors, ec1) <- rawSystemStdInOut - Verbosity.normal + verbosity "nroff" [ "-man", "/dev/stdin" ] Nothing -- Inherit working directory @@ -83,22 +82,17 @@ manpageCmd pname commands flags -- 'less' is borked with color sequences otherwise let pagerArgs = if takeFileName pager == "less" then ["-R"] else [] -- Pipe output of @nroff@ into @less@ - (Just inLess, _, _, procLess) <- createProcessWithEnv - Verbosity.normal - pager - pagerArgs - Nothing -- Inherit working directory - Nothing -- Inherit environment - Process.CreatePipe -- in - Process.Inherit -- out - Process.Inherit -- err - - hPutStr inLess formatted - hClose inLess - exitWith =<< Process.waitForProcess procLess + (ec2, _) <- rawSystemProcAction verbosity + (proc pager pagerArgs) { Process.std_in = Process.CreatePipe } + $ \mIn _ _ -> do + let wIn = fromCreatePipe mIn + hPutStr wIn formatted + hClose wIn + exitWith ec2 where contents :: String contents = manpage pname commands + verbosity = fromFlag $ manpageVerbosity flags -- | Produces a manual page with @troff@ markup. manpage :: String -> [CommandSpec a] -> String diff --git a/cabal-install/src/Distribution/Client/SetupWrapper.hs b/cabal-install/src/Distribution/Client/SetupWrapper.hs index 69e936d7691f47c6ba158a3390f327aef395116c..1ac82efcbd746a6b795ed9cdea9a0f32046a40e3 100644 --- a/cabal-install/src/Distribution/Client/SetupWrapper.hs +++ b/cabal-install/src/Distribution/Client/SetupWrapper.hs @@ -87,8 +87,8 @@ import Distribution.Simple.Setup import Distribution.Utils.Generic ( safeHead ) import Distribution.Simple.Utils - ( die', debug, info, infoNoWrap - , cabalVersion, tryFindPackageDesc + ( die', debug, info, infoNoWrap, maybeExit + , cabalVersion, tryFindPackageDesc, rawSystemProc , createDirectoryIfMissingVerbose, installExecutableFile , copyFileVerbose, rewriteFileEx, rewriteFileLBS ) import Distribution.Client.Utils @@ -109,9 +109,8 @@ import Distribution.Compat.Stack import System.Directory ( doesFileExist ) import System.FilePath ( (</>), (<.>) ) import System.IO ( Handle, hPutStr ) -import Distribution.Compat.Process (createProcess) -import System.Process ( StdStream(..), proc, waitForProcess - , ProcessHandle ) +import Distribution.Compat.Process (proc) +import System.Process ( StdStream(..) ) import qualified System.Process as Process import Data.List ( foldl1' ) import Distribution.Client.Compat.ExecutablePath ( getExecutablePath ) @@ -437,34 +436,31 @@ buildTypeAction Configure = Simple.defaultMainWithHooksArgs buildTypeAction Make = Make.defaultMainArgs buildTypeAction Custom = error "buildTypeAction Custom" +invoke :: Verbosity -> FilePath -> [String] -> SetupScriptOptions -> IO () +invoke verbosity path args options = do + info verbosity $ unwords (path : args) + case useLoggingHandle options of + Nothing -> return () + Just logHandle -> info verbosity $ "Redirecting build log to " ++ show logHandle --- | @runProcess'@ is a version of @runProcess@ where we have --- the additional option to decide whether or not we should --- delegate CTRL+C to the spawned process. -runProcess' :: FilePath -- ^ Filename of the executable - -> [String] -- ^ Arguments to pass to executable - -> Maybe FilePath -- ^ Optional path to working directory - -> Maybe [(String, String)] -- ^ Optional environment - -> Maybe Handle -- ^ Handle for @stdin@ - -> Maybe Handle -- ^ Handle for @stdout@ - -> Maybe Handle -- ^ Handle for @stderr@ - -> Bool -- ^ Delegate Ctrl+C ? - -> IO ProcessHandle -runProcess' cmd args mb_cwd mb_env mb_stdin mb_stdout mb_stderr _delegate = do - (_,_,_,ph) <- - createProcess - (proc cmd args){ Process.cwd = mb_cwd - , Process.env = mb_env - , Process.std_in = mbToStd mb_stdin - , Process.std_out = mbToStd mb_stdout - , Process.std_err = mbToStd mb_stderr - , Process.delegate_ctlc = _delegate - } - return ph - where - mbToStd :: Maybe Handle -> StdStream - mbToStd Nothing = Inherit - mbToStd (Just hdl) = UseHandle hdl + searchpath <- programSearchPathAsPATHVar + (map ProgramSearchPathDir (useExtraPathEnv options) ++ + getProgramSearchPath (useProgramDb options)) + env <- getEffectiveEnvironment $ + [ ("PATH", Just searchpath) + , ("HASKELL_DIST_DIR", Just (useDistPref options)) + ] ++ useExtraEnvOverrides options + + let loggingHandle = case useLoggingHandle options of + Nothing -> Inherit + Just hdl -> UseHandle hdl + cp = (proc path args) { Process.cwd = useWorkingDir options + , Process.env = env + , Process.std_out = loggingHandle + , Process.std_err = loggingHandle + , Process.delegate_ctlc = isInteractive options + } + maybeExit $ rawSystemProc verbosity cp -- ------------------------------------------------------------ -- * Self-Exec SetupMethod @@ -478,83 +474,43 @@ selfExecSetupMethod verbosity options bt args0 = do info verbosity $ "Using self-exec internal setup method with build-type " ++ show bt ++ " and args:\n " ++ show args path <- getExecutablePath - info verbosity $ unwords (path : args) - case useLoggingHandle options of - Nothing -> return () - Just logHandle -> info verbosity $ "Redirecting build log to " - ++ show logHandle - - searchpath <- programSearchPathAsPATHVar - (map ProgramSearchPathDir (useExtraPathEnv options) ++ - getProgramSearchPath (useProgramDb options)) - env <- getEffectiveEnvironment $ - [ ("PATH", Just searchpath) - , ("HASKELL_DIST_DIR", Just (useDistPref options)) - ] ++ useExtraEnvOverrides options - process <- runProcess' path args - (useWorkingDir options) env Nothing - (useLoggingHandle options) (useLoggingHandle options) - (isInteractive options) - exitCode <- waitForProcess process - unless (exitCode == ExitSuccess) $ exitWith exitCode + invoke verbosity path args options -- ------------------------------------------------------------ -- * External SetupMethod -- ------------------------------------------------------------ externalSetupMethod :: WithCallStack (FilePath -> SetupRunner) -externalSetupMethod path verbosity options _ args = do - info verbosity $ unwords (path : args) - case useLoggingHandle options of - Nothing -> return () - Just logHandle -> info verbosity $ "Redirecting build log to " - ++ show logHandle - - -- See 'Note: win32 clean hack' above. -#ifdef mingw32_HOST_OS - if useWin32CleanHack options then doWin32CleanHack path else doInvoke path +externalSetupMethod path verbosity options _ args = +#ifndef mingw32_HOST_OS + invoke verbosity path args options #else - doInvoke path -#endif - + -- See 'Note: win32 clean hack' above. + if useWin32CleanHack options + then invokeWithWin32CleanHack path + else invoke' path where - doInvoke path' = do - searchpath <- programSearchPathAsPATHVar - (map ProgramSearchPathDir (useExtraPathEnv options) ++ - getProgramSearchPath (useProgramDb options)) - env <- getEffectiveEnvironment $ - [ ("PATH", Just searchpath) - , ("HASKELL_DIST_DIR", Just (useDistPref options)) - ] ++ useExtraEnvOverrides options - - debug verbosity $ "Setup arguments: "++unwords args - process <- runProcess' path' args - (useWorkingDir options) env Nothing - (useLoggingHandle options) (useLoggingHandle options) - (isInteractive options) - exitCode <- waitForProcess process - unless (exitCode == ExitSuccess) $ exitWith exitCode + invoke' p = invoke verbosity p args options -#ifdef mingw32_HOST_OS - doWin32CleanHack path' = do + invokeWithWin32CleanHack origPath = do info verbosity $ "Using the Win32 clean hack." -- Recursively removes the temp dir on exit. withTempDirectory verbosity (workingDir options) "cabal-tmp" $ \tmpDir -> - bracket (moveOutOfTheWay tmpDir path') - (maybeRestore path') - doInvoke - - moveOutOfTheWay tmpDir path' = do - let newPath = tmpDir </> "setup" <.> exeExtension buildPlatform - Win32.moveFile path' newPath - return newPath - - maybeRestore oldPath path' = do - let oldPathDir = takeDirectory oldPath - oldPathDirExists <- doesDirectoryExist oldPathDir + bracket (moveOutOfTheWay tmpDir origPath) + (\tmpPath -> maybeRestore origPath tmpPath) + (\tmpPath -> invoke' tmpPath) + + moveOutOfTheWay tmpDir origPath = do + let tmpPath = tmpDir </> "setup" <.> exeExtension buildPlatform + Win32.moveFile origPath tmpPath + return tmpPath + + maybeRestore origPath tmpPath = do + let origPathDir = takeDirectory origPath + origPathDirExists <- doesDirectoryExist origPathDir -- 'setup clean' didn't complete, 'dist/setup' still exists. - when oldPathDirExists $ - Win32.moveFile path' oldPath + when origPathDirExists $ + Win32.moveFile tmpPath origPath #endif getExternalSetupMethod diff --git a/cabal-testsuite/src/Test/Cabal/Server.hs b/cabal-testsuite/src/Test/Cabal/Server.hs index 172f6708775d20ec402a43a88c1dd07a3c22bf56..5dc4f26a122ffa301a71c82da38b5e41e7e054a5 100644 --- a/cabal-testsuite/src/Test/Cabal/Server.hs +++ b/cabal-testsuite/src/Test/Cabal/Server.hs @@ -226,7 +226,6 @@ startServer chan senv = do std_out = CreatePipe, std_err = CreatePipe } - -- printRawCommandAndArgsAndEnv (runnerVerbosity senv) (programPath prog) ghc_args Nothing when (verbosity >= verbose) $ writeChan chan (ServerLogMsg AllServers (showCommandForUser (programPath prog) ghc_args)) (Just hin, Just hout, Just herr, proch) <- createProcess proc_spec diff --git a/changelog.d/pr-7995 b/changelog.d/pr-7995 new file mode 100644 index 0000000000000000000000000000000000000000..a92ae53acd34bde7989859bbcc977ba5aa43a84c --- /dev/null +++ b/changelog.d/pr-7995 @@ -0,0 +1,12 @@ +synopsis: Cleanup subprocess helpers, remove obsolete functions +packages: Cabal +prs: #7995 + +description: { + +- Distribution.Compat.Process: Remove createProcess, runInteractiveProcess + and rawSystem. +- Distribution.Simple.Utils: Remove printRawCommandAndArgs, + printRawCommandAndArgsAndEnv and createProcessWithEnv. + +}