X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=ghc%2Fcompiler%2Fmain%2FSysTools.lhs;h=05153ceb82c536d735931b62ae1c562b2cdfd384;hb=fd0529940130b3991e6bb51d1e2a870e614f829b;hp=a2dae88f1b79065122ce103e32fb77f1fb184d54;hpb=75bbf3cc3565ae1a8bae12cc08bbd23d346c17b7;p=ghc-hetmet.git diff --git a/ghc/compiler/main/SysTools.lhs b/ghc/compiler/main/SysTools.lhs index a2dae88..05153ce 100644 --- a/ghc/compiler/main/SysTools.lhs +++ b/ghc/compiler/main/SysTools.lhs @@ -11,23 +11,7 @@ module SysTools ( -- Initialisation initSysTools, - setPgmL, -- String -> IO () - setPgmP, - setPgmF, - setPgmc, - setPgmm, - setPgms, - setPgma, - setPgml, - setPgmDLL, -#ifdef ILX - setPgmI, - setPgmi, -#endif - -- Command-line override - setDryRun, - - getTopDir, -- IO String -- The value of $libdir + getTopDir, -- IO String -- The value of $topdir getPackageConfigPath, -- IO String -- Where package.conf is getUsageMsgPaths, -- IO (String,String) @@ -37,10 +21,6 @@ module SysTools ( runMangle, runSplit, -- [Option] -> IO () runAs, runLink, -- [Option] -> IO () runMkDLL, -#ifdef ILX - runIlx2il, runIlasm, -- [String] -> IO () -#endif - touch, -- String -> String -> IO () copy, -- String -> String -> String -> IO () @@ -64,13 +44,15 @@ module SysTools ( #include "HsVersions.h" -import DriverUtil import DriverPhases ( isHaskellUserSrcFilename ) import Config import Outputable +import ErrUtils ( putMsg, debugTraceMsg, showPass, Severity(..), Messages ) import Panic ( GhcException(..) ) -import Util ( global, notNull ) -import CmdLineOpts ( dynFlag, verbosity ) +import Util ( Suffix, global, notNull, consIORef, joinFileName, + normalisePath, pgmPath, platformPath, joinFileExt ) +import DynFlags ( DynFlags(..), DynFlag(..), dopt, Option(..), + setTmpDir, defaultDynFlags ) import EXCEPTION ( throwDyn ) import DATA_IOREF ( IORef, readIORef, writeIORef ) @@ -79,13 +61,11 @@ import DATA_INT import Monad ( when, unless ) import System ( ExitCode(..), getEnv, system ) import IO ( try, catch, - openFile, hPutStrLn, hPutStr, hClose, hFlush, IOMode(..), - stderr ) + openFile, hPutStr, hClose, hFlush, IOMode(..), + stderr, ioError, isDoesNotExistError ) import Directory ( doesFileExist, removeFile ) import List ( partition ) -#include "../includes/ghcconfig.h" - -- GHC <= 4.08 didn't have rawSystem, and runs into problems with long command -- lines on mingw32, so we disallow it now. #if __GLASGOW_HASKELL__ < 500 @@ -106,10 +86,18 @@ import CString ( CString, peekCString ) #endif #if __GLASGOW_HASKELL__ < 603 -import Foreign ( withMany, withArray0, nullPtr, Ptr ) -import CForeign ( CString, withCString, throwErrnoIfMinus1 ) +-- rawSystem comes from libghccompat.a in stage1 +import Compat.RawSystem ( rawSystem ) +import GHC.IOBase ( IOErrorType(..) ) +import System.IO.Error ( ioeGetErrorType ) #else -import System.Cmd ( rawSystem ) +import System.Process ( runInteractiveProcess, getProcessExitCode ) +import System.IO ( hSetBuffering, hGetLine, BufferMode(..) ) +import Control.Concurrent( forkIO, newChan, readChan, writeChan ) +import Text.Regex +import Data.Char ( isSpace ) +import FastString ( mkFastString ) +import SrcLoc ( SrcLoc, mkSrcLoc, noSrcSpan, mkSrcSpan ) #endif \end{code} @@ -166,7 +154,7 @@ Package {name = "tools", import_dirs = [], source_dirs = [], library_dirs = [], hs_libraries = [], extra_libraries = [], include_dirs = [], c_includes = [], package_deps = [], - extra_ghc_opts = ["-pgmc/usr/bin/gcc","-pgml${libdir}/bin/unlit", ... etc.], + extra_ghc_opts = ["-pgmc/usr/bin/gcc","-pgml${topdir}/bin/unlit", ... etc.], extra_cc_opts = [], extra_ld_opts = []} Which would have the advantage that we get to collect together in one @@ -186,20 +174,6 @@ All these pathnames are maintained IN THE NATIVE FORMAT OF THE HOST MACHINE. (See remarks under pathnames below) \begin{code} -GLOBAL_VAR(v_Pgm_L, error "pgm_L", String) -- unlit -GLOBAL_VAR(v_Pgm_P, error "pgm_P", (String,[Option])) -- cpp -GLOBAL_VAR(v_Pgm_F, error "pgm_F", String) -- pp -GLOBAL_VAR(v_Pgm_c, error "pgm_c", (String,[Option])) -- gcc -GLOBAL_VAR(v_Pgm_m, error "pgm_m", (String,[Option])) -- asm code mangler -GLOBAL_VAR(v_Pgm_s, error "pgm_s", (String,[Option])) -- asm code splitter -GLOBAL_VAR(v_Pgm_a, error "pgm_a", (String,[Option])) -- as -#ifdef ILX -GLOBAL_VAR(v_Pgm_I, error "pgm_I", String) -- ilx2il -GLOBAL_VAR(v_Pgm_i, error "pgm_i", String) -- ilasm -#endif -GLOBAL_VAR(v_Pgm_l, error "pgm_l", (String,[Option])) -- ld -GLOBAL_VAR(v_Pgm_MkDLL, error "pgm_dll", (String,[Option])) -- mkdll - GLOBAL_VAR(v_Pgm_T, error "pgm_T", String) -- touch GLOBAL_VAR(v_Pgm_CP, error "pgm_CP", String) -- cp @@ -224,16 +198,17 @@ getTopDir = readIORef v_TopDir %************************************************************************ \begin{code} -initSysTools :: [String] -- Command-line arguments starting "-B" +initSysTools :: Maybe String -- Maybe TopDir path (without the '-B' prefix) - -> IO () -- Set all the mutable variables above, holding + -> DynFlags + -> IO DynFlags -- Set all the mutable variables above, holding -- (a) the system programs -- (b) the package-config file -- (c) the GHC usage message -initSysTools minusB_args - = do { (am_installed, top_dir) <- findTopDir minusB_args +initSysTools mbMinusB dflags + = do { (am_installed, top_dir) <- findTopDir mbMinusB ; writeIORef v_TopDir top_dir -- top_dir -- for "installed" this is the root of GHC's support files @@ -243,8 +218,8 @@ initSysTools minusB_args ; let installed, installed_bin :: FilePath -> FilePath installed_bin pgm = pgmPath top_dir pgm installed file = pgmPath top_dir file - inplace dir pgm = pgmPath (top_dir `slash` - cPROJECT_DIR `slash` dir) pgm + inplace dir pgm = pgmPath (top_dir `joinFileName` + cPROJECT_DIR `joinFileName` dir) pgm ; let pkgconfig_path | am_installed = installed "package.conf" @@ -273,32 +248,32 @@ initSysTools minusB_args | am_installed = installed_bin cGHC_MANGLER_PGM | otherwise = inplace cGHC_MANGLER_DIR_REL cGHC_MANGLER_PGM + ; let dflags0 = defaultDynFlags #ifndef mingw32_HOST_OS -- check whether TMPDIR is set in the environment - ; IO.try (do dir <- getEnv "TMPDIR" -- fails if not set - setTmpDir dir - return () - ) + ; e_tmpdir <- IO.try (getEnv "TMPDIR") -- fails if not set #else -- On Win32, consult GetTempPath() for a temp dir. -- => it first tries TMP, TEMP, then finally the -- Windows directory(!). The directory is in short-path -- form. - ; IO.try (do + ; e_tmpdir <- + IO.try (do let len = (2048::Int) buf <- mallocArray len ret <- getTempPath len buf - tdir <- - if ret == 0 then do + if ret == 0 then do -- failed, consult TMPDIR. free buf getEnv "TMPDIR" - else do + else do s <- peekCString buf free buf - return s - setTmpDir tdir) + return s) #endif + ; let dflags1 = case e_tmpdir of + Left _ -> dflags0 + Right d -> setTmpDir d dflags0 -- Check that the package config exists ; config_exists <- doesFileExist pkgconfig_path @@ -388,12 +363,6 @@ initSysTools minusB_args ; let (as_prog,as_args) = (gcc_prog,gcc_args) (ld_prog,ld_args) = (gcc_prog,gcc_args) -#ifdef ILX - -- ilx2il and ilasm are specified in Config.hs - ; let ilx2il_path = cILX2IL - ilasm_path = cILASM -#endif - -- Initialise the global vars ; writeIORef v_Path_package_config pkgconfig_path ; writeIORef v_Path_usages (ghc_usage_msg_path, @@ -403,23 +372,19 @@ initSysTools minusB_args -- Hans: this isn't right in general, but you can -- elaborate it in the same way as the others - ; writeIORef v_Pgm_L unlit_path - ; writeIORef v_Pgm_P cpp_path - ; writeIORef v_Pgm_F "" - ; writeIORef v_Pgm_c (gcc_prog,gcc_args) - ; writeIORef v_Pgm_m (mangle_prog,mangle_args) - ; writeIORef v_Pgm_s (split_prog,split_args) - ; writeIORef v_Pgm_a (as_prog,as_args) -#ifdef ILX - ; writeIORef v_Pgm_I ilx2il_path - ; writeIORef v_Pgm_i ilasm_path -#endif - ; writeIORef v_Pgm_l (ld_prog,ld_args) - ; writeIORef v_Pgm_MkDLL (mkdll_prog,mkdll_args) ; writeIORef v_Pgm_T touch_path ; writeIORef v_Pgm_CP cp_path - ; return () + ; return dflags1{ + pgm_L = unlit_path, + pgm_P = cpp_path, + pgm_F = "", + pgm_c = (gcc_prog,gcc_args), + pgm_m = (mangle_prog,mangle_args), + pgm_s = (split_prog,split_args), + pgm_a = (as_prog,as_args), + pgm_l = (ld_prog,ld_args), + pgm_dll = (mkdll_prog,mkdll_args) } } #if defined(mingw32_HOST_OS) @@ -427,32 +392,6 @@ foreign import stdcall unsafe "GetTempPathA" getTempPath :: Int -> CString -> IO #endif \end{code} -The various setPgm functions are called when a command-line option -like - - -pgmLld - -is used to override a particular program with a new one - -\begin{code} -setPgmL = writeIORef v_Pgm_L --- XXX HACK: Prelude> words "'does not' work" ===> ["'does","not'","work"] --- Config.hs should really use Option. -setPgmP arg = let (pgm:args) = words arg in writeIORef v_Pgm_P (pgm,map Option args) -setPgmF = writeIORef v_Pgm_F -setPgmc prog = writeIORef v_Pgm_c (prog,[]) -setPgmm prog = writeIORef v_Pgm_m (prog,[]) -setPgms prog = writeIORef v_Pgm_s (prog,[]) -setPgma prog = writeIORef v_Pgm_a (prog,[]) -setPgml prog = writeIORef v_Pgm_l (prog,[]) -setPgmDLL prog = writeIORef v_Pgm_MkDLL (prog,[]) -#ifdef ILX -setPgmI = writeIORef v_Pgm_I -setPgmi = writeIORef v_Pgm_i -#endif -\end{code} - - \begin{code} -- Find TopDir -- for "installed" this is the root of GHC's support files @@ -460,9 +399,8 @@ setPgmi = writeIORef v_Pgm_i -- -- Plan of action: -- 1. Set proto_top_dir --- a) look for (the last) -B flag, and use it --- b) if there are no -B flags, get the directory --- where GHC is running (only on Windows) +-- if there is no given TopDir path, get the directory +-- where GHC is running (only on Windows) -- -- 2. If package.conf exists in proto_top_dir, we are running -- installed; and TopDir = proto_top_dir @@ -473,56 +411,28 @@ setPgmi = writeIORef v_Pgm_i -- -- This is very gruesome indeed -findTopDir :: [String] - -> IO (Bool, -- True <=> am installed, False <=> in-place - String) -- TopDir (in Unix format '/' separated) +findTopDir :: Maybe String -- Maybe TopDir path (without the '-B' prefix). + -> IO (Bool, -- True <=> am installed, False <=> in-place + String) -- TopDir (in Unix format '/' separated) -findTopDir minusbs +findTopDir mbMinusB = do { top_dir <- get_proto -- Discover whether we're running in a build tree or in an installation, -- by looking for the package configuration file. - ; am_installed <- doesFileExist (top_dir `slash` "package.conf") + ; am_installed <- doesFileExist (top_dir `joinFileName` "package.conf") ; return (am_installed, top_dir) } where -- get_proto returns a Unix-format path (relying on getBaseDir to do so too) - get_proto | notNull minusbs - = return (normalisePath (drop 2 (last minusbs))) -- 2 for "-B" - | otherwise - = do { maybe_exec_dir <- getBaseDir -- Get directory of executable - ; case maybe_exec_dir of -- (only works on Windows; - -- returns Nothing on Unix) - Nothing -> throwDyn (InstallationError "missing -B option") - Just dir -> return dir - } -\end{code} - - -%************************************************************************ -%* * -\subsection{Command-line options} -n%* * -%************************************************************************ - -When invoking external tools as part of the compilation pipeline, we -pass these a sequence of options on the command-line. Rather than -just using a list of Strings, we use a type that allows us to distinguish -between filepaths and 'other stuff'. [The reason being, of course, that -this type gives us a handle on transforming filenames, and filenames only, -to whatever format they're expected to be on a particular platform.] - -\begin{code} -data Option - = FileOption -- an entry that _contains_ filename(s) / filepaths. - String -- a non-filepath prefix that shouldn't be transformed (e.g., "/out=" - String -- the filepath/filename portion - | Option String - -showOpt (FileOption pre f) = pre ++ platformPath f -showOpt (Option "") = "" -showOpt (Option s) = s - + get_proto = case mbMinusB of + Just minusb -> return (normalisePath minusb) + Nothing + -> do maybe_exec_dir <- getBaseDir -- Get directory of executable + case maybe_exec_dir of -- (only works on Windows; + -- returns Nothing on Unix) + Nothing -> throwDyn (InstallationError "missing -B option") + Just dir -> return dir \end{code} @@ -534,66 +444,66 @@ showOpt (Option s) = s \begin{code} -runUnlit :: [Option] -> IO () -runUnlit args = do p <- readIORef v_Pgm_L - runSomething "Literate pre-processor" p args - -runCpp :: [Option] -> IO () -runCpp args = do (p,baseArgs) <- readIORef v_Pgm_P - runSomething "C pre-processor" p (baseArgs ++ args) - -runPp :: [Option] -> IO () -runPp args = do p <- readIORef v_Pgm_F - runSomething "Haskell pre-processor" p args - -runCc :: [Option] -> IO () -runCc args = do (p,args0) <- readIORef v_Pgm_c - runSomething "C Compiler" p (args0++args) - -runMangle :: [Option] -> IO () -runMangle args = do (p,args0) <- readIORef v_Pgm_m - runSomething "Mangler" p (args0++args) - -runSplit :: [Option] -> IO () -runSplit args = do (p,args0) <- readIORef v_Pgm_s - runSomething "Splitter" p (args0++args) - -runAs :: [Option] -> IO () -runAs args = do (p,args0) <- readIORef v_Pgm_a - runSomething "Assembler" p (args0++args) - -runLink :: [Option] -> IO () -runLink args = do (p,args0) <- readIORef v_Pgm_l - runSomething "Linker" p (args0++args) - -#ifdef ILX -runIlx2il :: [Option] -> IO () -runIlx2il args = do p <- readIORef v_Pgm_I - runSomething "Ilx2Il" p args - -runIlasm :: [Option] -> IO () -runIlasm args = do p <- readIORef v_Pgm_i - runSomething "Ilasm" p args -#endif - -runMkDLL :: [Option] -> IO () -runMkDLL args = do (p,args0) <- readIORef v_Pgm_MkDLL - runSomething "Make DLL" p (args0++args) - -touch :: String -> String -> IO () -touch purpose arg = do p <- readIORef v_Pgm_T - runSomething purpose p [FileOption "" arg] - -copy :: String -> String -> String -> IO () -copy purpose from to = do - verb <- dynFlag verbosity - when (verb >= 2) $ hPutStrLn stderr ("*** " ++ purpose) +runUnlit :: DynFlags -> [Option] -> IO () +runUnlit dflags args = do + let p = pgm_L dflags + runSomething dflags "Literate pre-processor" p args + +runCpp :: DynFlags -> [Option] -> IO () +runCpp dflags args = do + let (p,args0) = pgm_P dflags + runSomething dflags "C pre-processor" p (args0 ++ args) + +runPp :: DynFlags -> [Option] -> IO () +runPp dflags args = do + let p = pgm_F dflags + runSomething dflags "Haskell pre-processor" p args + +runCc :: DynFlags -> [Option] -> IO () +runCc dflags args = do + let (p,args0) = pgm_c dflags + runSomething dflags "C Compiler" p (args0++args) + +runMangle :: DynFlags -> [Option] -> IO () +runMangle dflags args = do + let (p,args0) = pgm_m dflags + runSomething dflags "Mangler" p (args0++args) + +runSplit :: DynFlags -> [Option] -> IO () +runSplit dflags args = do + let (p,args0) = pgm_s dflags + runSomething dflags "Splitter" p (args0++args) + +runAs :: DynFlags -> [Option] -> IO () +runAs dflags args = do + let (p,args0) = pgm_a dflags + runSomething dflags "Assembler" p (args0++args) + +runLink :: DynFlags -> [Option] -> IO () +runLink dflags args = do + let (p,args0) = pgm_l dflags + runSomething dflags "Linker" p (args0++args) + +runMkDLL :: DynFlags -> [Option] -> IO () +runMkDLL dflags args = do + let (p,args0) = pgm_dll dflags + runSomething dflags "Make DLL" p (args0++args) + +touch :: DynFlags -> String -> String -> IO () +touch dflags purpose arg = do + p <- readIORef v_Pgm_T + runSomething dflags purpose p [FileOption "" arg] + +copy :: DynFlags -> String -> String -> String -> IO () +copy dflags purpose from to = do + showPass dflags purpose h <- openFile to WriteMode ls <- readFile from -- inefficient, but it'll do for now. -- ToDo: speed up via slurping. hPutStr h ls hClose h + \end{code} \begin{code} @@ -617,78 +527,44 @@ getUsageMsgPaths = readIORef v_Path_usages \begin{code} GLOBAL_VAR(v_FilesToClean, [], [String] ) -GLOBAL_VAR(v_TmpDir, cDEFAULT_TMPDIR, String ) - -- v_TmpDir has no closing '/' \end{code} \begin{code} -setTmpDir dir = writeIORef v_TmpDir (canonicalise dir) - where -#if !defined(mingw32_HOST_OS) - canonicalise p = normalisePath p -#else - -- Canonicalisation of temp path under win32 is a bit more - -- involved: (a) strip trailing slash, - -- (b) normalise slashes - -- (c) just in case, if there is a prefix /cygdrive/x/, change to x: - -- - canonicalise path = normalisePath (xltCygdrive (removeTrailingSlash path)) - - -- if we're operating under cygwin, and TMP/TEMP is of - -- the form "/cygdrive/drive/path", translate this to - -- "drive:/path" (as GHC isn't a cygwin app and doesn't - -- understand /cygdrive paths.) - xltCygdrive path - | "/cygdrive/" `isPrefixOf` path = - case drop (length "/cygdrive/") path of - drive:xs@('/':_) -> drive:':':xs - _ -> path - | otherwise = path - - -- strip the trailing backslash (awful, but we only do this once). - removeTrailingSlash path = - case last path of - '/' -> init path - '\\' -> init path - _ -> path -#endif - -cleanTempFiles :: Int -> IO () -cleanTempFiles verb +cleanTempFiles :: DynFlags -> IO () +cleanTempFiles dflags = do fs <- readIORef v_FilesToClean - removeTmpFiles verb fs + removeTmpFiles dflags fs writeIORef v_FilesToClean [] -cleanTempFilesExcept :: Int -> [FilePath] -> IO () -cleanTempFilesExcept verb dont_delete +cleanTempFilesExcept :: DynFlags -> [FilePath] -> IO () +cleanTempFilesExcept dflags dont_delete = do files <- readIORef v_FilesToClean let (to_keep, to_delete) = partition (`elem` dont_delete) files - removeTmpFiles verb to_delete + removeTmpFiles dflags to_delete writeIORef v_FilesToClean to_keep -- find a temporary name that doesn't already exist. -newTempName :: Suffix -> IO FilePath -newTempName extn +newTempName :: DynFlags -> Suffix -> IO FilePath +newTempName DynFlags{tmpDir=tmp_dir} extn = do x <- getProcessID - tmp_dir <- readIORef v_TmpDir - findTempName tmp_dir x + findTempName (tmp_dir ++ "/ghc" ++ show x ++ "_") 0 where - findTempName tmp_dir x - = do let filename = tmp_dir ++ "/ghc" ++ show x ++ '.':extn + findTempName prefix x + = do let filename = (prefix ++ show x) `joinFileExt` extn b <- doesFileExist filename - if b then findTempName tmp_dir (x+1) - else do add v_FilesToClean filename -- clean it up later + if b then findTempName prefix (x+1) + else do consIORef v_FilesToClean filename -- clean it up later return filename addFilesToClean :: [FilePath] -> IO () -- May include wildcards [used by DriverPipeline.run_phase SplitMangle] -addFilesToClean files = mapM_ (add v_FilesToClean) files +addFilesToClean files = mapM_ (consIORef v_FilesToClean) files -removeTmpFiles :: Int -> [FilePath] -> IO () -removeTmpFiles verb fs +removeTmpFiles :: DynFlags -> [FilePath] -> IO () +removeTmpFiles dflags fs = warnNon $ - traceCmd "Deleting temp files" + traceCmd dflags "Deleting temp files" ("Deleting: " ++ unwords deletees) (mapM_ rm deletees) where @@ -701,141 +577,178 @@ removeTmpFiles verb fs warnNon act | null non_deletees = act | otherwise = do - hPutStrLn stderr ("WARNING - NOT deleting source files: " ++ unwords non_deletees) + putMsg dflags (text "WARNING - NOT deleting source files:" <+> hsep (map text non_deletees)) act (non_deletees, deletees) = partition isHaskellUserSrcFilename fs rm f = removeFile f `IO.catch` (\_ignored -> - when (verb >= 2) $ - hPutStrLn stderr ("Warning: deleting non-existent " ++ f) + debugTraceMsg dflags 2 (ptext SLIT("Warning: deleting non-existent") <+> text f) ) -\end{code} - - -%************************************************************************ -%* * -\subsection{Running a program} -%* * -%************************************************************************ - -\begin{code} -GLOBAL_VAR(v_Dry_run, False, Bool) - -setDryRun :: IO () -setDryRun = writeIORef v_Dry_run True ----------------------------------------------------------------------------- -- Running an external program -runSomething :: String -- For -v message +runSomething :: DynFlags + -> String -- For -v message -> String -- Command name (possibly a full path) -- assumed already dos-ified -> [Option] -- Arguments -- runSomething will dos-ify them -> IO () -runSomething phase_name pgm args = do +runSomething dflags phase_name pgm args = do let real_args = filter notNull (map showOpt args) - traceCmd phase_name (unwords (pgm:real_args)) $ do - exit_code <- rawSystem pgm real_args - if (exit_code /= ExitSuccess) - then throwDyn (PhaseFailed phase_name exit_code) - else return () + traceCmd dflags phase_name (unwords (pgm:real_args)) $ do + (exit_code, doesn'tExist) <- + IO.catch (do + rc <- builderMainLoop dflags pgm real_args + case rc of + ExitSuccess{} -> return (rc, False) + ExitFailure n + -- rawSystem returns (ExitFailure 127) if the exec failed for any + -- reason (eg. the program doesn't exist). This is the only clue + -- we have, but we need to report something to the user because in + -- the case of a missing program there will otherwise be no output + -- at all. + | n == 127 -> return (rc, True) + | otherwise -> return (rc, False)) + -- Should 'rawSystem' generate an IO exception indicating that + -- 'pgm' couldn't be run rather than a funky return code, catch + -- this here (the win32 version does this, but it doesn't hurt + -- to test for this in general.) + (\ err -> + if IO.isDoesNotExistError err +#if defined(mingw32_HOST_OS) && __GLASGOW_HASKELL__ < 604 + -- the 'compat' version of rawSystem under mingw32 always + -- maps 'errno' to EINVAL to failure. + || case (ioeGetErrorType err ) of { InvalidArgument{} -> True ; _ -> False} +#endif + then return (ExitFailure 1, True) + else IO.ioError err) + case (doesn'tExist, exit_code) of + (True, _) -> throwDyn (InstallationError ("could not execute: " ++ pgm)) + (_, ExitSuccess) -> return () + _ -> throwDyn (PhaseFailed phase_name exit_code) + + + +#if __GLASGOW_HASKELL__ < 603 +builderMainLoop dflags pgm real_args = do + rawSystem pgm real_args +#else +builderMainLoop dflags pgm real_args = do + chan <- newChan + (hStdIn, hStdOut, hStdErr, hProcess) <- runInteractiveProcess pgm real_args Nothing Nothing + + -- and run a loop piping the output from the compiler to the log_action in DynFlags + hSetBuffering hStdOut LineBuffering + hSetBuffering hStdErr LineBuffering + forkIO (readerProc chan hStdOut) + forkIO (readerProc chan hStdErr) + rc <- loop chan hProcess 2 1 ExitSuccess + hClose hStdIn + hClose hStdOut + hClose hStdErr + return rc + where + -- status starts at zero, and increments each time either + -- a reader process gets EOF, or the build proc exits. We wait + -- for all of these to happen (status==3). + -- ToDo: we should really have a contingency plan in case any of + -- the threads dies, such as a timeout. + loop chan hProcess 0 0 exitcode = return exitcode + loop chan hProcess t p exitcode = do + mb_code <- if p > 0 + then getProcessExitCode hProcess + else return Nothing + case mb_code of + Just code -> loop chan hProcess t (p-1) code + Nothing + | t > 0 -> do + msg <- readChan chan + case msg of + BuildMsg msg -> do + log_action dflags SevInfo noSrcSpan defaultUserStyle msg + loop chan hProcess t p exitcode + BuildError loc msg -> do + log_action dflags SevError (mkSrcSpan loc loc) defaultUserStyle msg + loop chan hProcess t p exitcode + EOF -> + loop chan hProcess (t-1) p exitcode + | otherwise -> loop chan hProcess t p exitcode + +readerProc chan hdl = loop Nothing `catch` \e -> writeChan chan EOF + -- ToDo: check errors more carefully + where + loop in_err = do + l <- hGetLine hdl `catch` \e -> do + case in_err of + Just err -> writeChan chan err + Nothing -> return () + ioError e + case in_err of + Just err@(BuildError srcLoc msg) + | leading_whitespace l -> do + loop (Just (BuildError srcLoc (msg $$ text l))) + | otherwise -> do + writeChan chan err + checkError l + Nothing -> do + checkError l + + checkError l + = case matchRegex errRegex l of + Nothing -> do + writeChan chan (BuildMsg (text l)) + loop Nothing + Just (file':lineno':colno':msg:_) -> do + let file = mkFastString file' + lineno = read lineno'::Int + colno = case colno' of + "" -> 0 + _ -> read (init colno') :: Int + srcLoc = mkSrcLoc file lineno colno + loop (Just (BuildError srcLoc (text msg))) + + leading_whitespace [] = False + leading_whitespace (x:_) = isSpace x + +errRegex = mkRegex "^([^:]*):([0-9]+):([0-9]+:)?(.*)" + +data BuildMessage + = BuildMsg !SDoc + | BuildError !SrcLoc !SDoc + | EOF +#endif + +showOpt (FileOption pre f) = pre ++ platformPath f +showOpt (Option "") = "" +showOpt (Option s) = s -traceCmd :: String -> String -> IO () -> IO () +traceCmd :: DynFlags -> String -> String -> IO () -> IO () -- a) trace the command (at two levels of verbosity) -- b) don't do it at all if dry-run is set -traceCmd phase_name cmd_line action - = do { verb <- dynFlag verbosity - ; when (verb >= 2) $ hPutStrLn stderr ("*** " ++ phase_name) - ; when (verb >= 3) $ hPutStrLn stderr cmd_line +traceCmd dflags phase_name cmd_line action + = do { let verb = verbosity dflags + ; showPass dflags phase_name + ; debugTraceMsg dflags 3 (text cmd_line) ; hFlush stderr -- Test for -n flag - ; n <- readIORef v_Dry_run - ; unless n $ do { + ; unless (dopt Opt_DryRun dflags) $ do { -- And run it! ; action `IO.catch` handle_exn verb }} where - handle_exn verb exn = do { when (verb >= 2) (hPutStr stderr "\n") - ; when (verb >= 3) (hPutStrLn stderr ("Failed: " ++ cmd_line ++ (show exn))) + handle_exn verb exn = do { debugTraceMsg dflags 2 (char '\n') + ; debugTraceMsg dflags 2 (ptext SLIT("Failed:") <+> text cmd_line <+> text (show exn)) ; throwDyn (PhaseFailed phase_name (ExitFailure 1)) } - --- ----------------------------------------------------------------------------- --- rawSystem: run an external command --- --- In GHC 6.2.1 there's a correct implementation of rawSystem in the --- library System.Cmd. If we are compiling with an earlier version of --- GHC than this, we'd better have a copy of the correct implementation --- right here. - -#if __GLASGOW_HASKELL__ < 603 -#include "../../libraries/base/System/RawSystem.hs-inc" -#endif \end{code} - -%************************************************************************ -%* * -\subsection{Path names} -%* * -%************************************************************************ - -We maintain path names in Unix form ('/'-separated) right until -the last moment. On Windows we dos-ify them just before passing them -to the Windows command. - -The alternative, of using '/' consistently on Unix and '\' on Windows, -proved quite awkward. There were a lot more calls to platformPath, -and even on Windows we might invoke a unix-like utility (eg 'sh'), which -interpreted a command line 'foo\baz' as 'foobaz'. - -\begin{code} ------------------------------------------------------------------------------ --- Convert filepath into platform / MSDOS form. - -normalisePath :: String -> String --- Just changes '\' to '/' - -pgmPath :: String -- Directory string in Unix format - -> String -- Program name with no directory separators - -- (e.g. copy /y) - -> String -- Program invocation string in native format - - - -#if defined(mingw32_HOST_OS) ---------------------- Windows version ------------------ -normalisePath xs = subst '\\' '/' xs -platformPath p = subst '/' '\\' p -pgmPath dir pgm = platformPath dir ++ '\\' : pgm - -subst a b ls = map (\ x -> if x == a then b else x) ls -#else ---------------------- Non-Windows version -------------- -normalisePath xs = xs -pgmPath dir pgm = dir ++ '/' : pgm -platformPath stuff = stuff --------------------------------------------------------- -#endif - -\end{code} - - ------------------------------------------------------------------------------ - Path name construction - -\begin{code} -slash :: String -> String -> String -slash s1 s2 = s1 ++ ('/' : s2) -\end{code} - - %************************************************************************ %* * \subsection{Support code} @@ -846,8 +759,8 @@ slash s1 s2 = s1 ++ ('/' : s2) ----------------------------------------------------------------------------- -- Define getBaseDir :: IO (Maybe String) -#if defined(mingw32_HOST_OS) getBaseDir :: IO (Maybe String) +#if defined(mingw32_HOST_OS) -- Assuming we are running ghc, accessed by path $()/bin/ghc.exe, -- return the path $(stuff). Note that we drop the "bin/" directory too. getBaseDir = do let len = (2048::Int) -- plenty, PATH_MAX is 512 under Win32. @@ -863,7 +776,7 @@ getBaseDir = do let len = (2048::Int) -- plenty, PATH_MAX is 512 under Win32. foreign import stdcall unsafe "GetModuleFileNameA" getModuleFileName :: Ptr () -> CString -> Int -> IO Int32 #else -getBaseDir :: IO (Maybe String) = do return Nothing +getBaseDir = return Nothing #endif #ifdef mingw32_HOST_OS