module SysTools (
initSysTools,
runUnlit, runCpp, runCc,
runPp,
runSplit,
runAs, runLink, runLibtool,
runMkDLL,
runWindres,
runLlvmOpt,
runLlvmLlc,
runClang,
figureLlvmVersion,
readElfSection,
getLinkerInfo,
getCompilerInfo,
linkDynLib,
askCc,
touch,
copy,
copyWithHeader,
setTmpDir,
newTempName,
cleanTempDirs, cleanTempFiles, cleanTempFilesExcept,
addFilesToClean,
Option(..)
) where
#include "HsVersions.h"
import DriverPhases
import Module
import Packages
import Config
import Outputable
import ErrUtils
import Panic
import Platform
import Util
import DynFlags
import Exception
import Data.IORef
import Control.Monad
import System.Exit
import System.Environment
import System.FilePath
import System.IO
import System.IO.Error as IO
import System.Directory
import Data.Char
import Data.List
import qualified Data.Map as Map
import Text.ParserCombinators.ReadP hiding (char)
import qualified Text.ParserCombinators.ReadP as R
#ifndef mingw32_HOST_OS
import qualified System.Posix.Internals
#else /* Must be Win32 */
import Foreign
import Foreign.C.String
#endif
import System.Process
import Control.Concurrent
import FastString
import SrcLoc ( SrcLoc, mkSrcLoc, noSrcSpan, mkSrcSpan )
#ifdef mingw32_HOST_OS
# if defined(i386_HOST_ARCH)
# define WINDOWS_CCONV stdcall
# elif defined(x86_64_HOST_ARCH)
# define WINDOWS_CCONV ccall
# else
# error Unknown mingw32 arch
# endif
#endif
initSysTools :: Maybe String
-> IO Settings
initSysTools mbMinusB
= do top_dir <- findTopDir mbMinusB
let settingsFile = top_dir </> "settings"
platformConstantsFile = top_dir </> "platformConstants"
installed :: FilePath -> FilePath
installed file = top_dir </> file
settingsStr <- readFile settingsFile
platformConstantsStr <- readFile platformConstantsFile
mySettings <- case maybeReadFuzzy settingsStr of
Just s ->
return s
Nothing ->
pgmError ("Can't parse " ++ show settingsFile)
platformConstants <- case maybeReadFuzzy platformConstantsStr of
Just s ->
return s
Nothing ->
pgmError ("Can't parse " ++
show platformConstantsFile)
let getSetting key = case lookup key mySettings of
Just xs ->
return $ case stripPrefix "$topdir" xs of
Just [] ->
top_dir
Just xs'@(c:_)
| isPathSeparator c ->
top_dir ++ xs'
_ ->
xs
Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
getBooleanSetting key = case lookup key mySettings of
Just "YES" -> return True
Just "NO" -> return False
Just xs -> pgmError ("Bad value for " ++ show key ++ ": " ++ show xs)
Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
readSetting key = case lookup key mySettings of
Just xs ->
case maybeRead xs of
Just v -> return v
Nothing -> pgmError ("Failed to read " ++ show key ++ " value " ++ show xs)
Nothing -> pgmError ("No entry for " ++ show key ++ " in " ++ show settingsFile)
targetArch <- readSetting "target arch"
targetOS <- readSetting "target os"
targetWordSize <- readSetting "target word size"
targetUnregisterised <- getBooleanSetting "Unregisterised"
targetHasGnuNonexecStack <- readSetting "target has GNU nonexec stack"
targetHasIdentDirective <- readSetting "target has .ident directive"
targetHasSubsectionsViaSymbols <- readSetting "target has subsections via symbols"
myExtraGccViaCFlags <- getSetting "GCC extra via C opts"
gcc_prog <- getSetting "C compiler command"
gcc_args_str <- getSetting "C compiler flags"
cpp_prog <- getSetting "Haskell CPP command"
cpp_args_str <- getSetting "Haskell CPP flags"
let unreg_gcc_args = if targetUnregisterised
then ["-DNO_REGS", "-DUSE_MINIINTERPRETER"]
else []
tntc_gcc_args
| mkTablesNextToCode targetUnregisterised
= ["-DTABLES_NEXT_TO_CODE"]
| otherwise = []
cpp_args= map Option (words cpp_args_str)
gcc_args = map Option (words gcc_args_str
++ unreg_gcc_args
++ tntc_gcc_args)
ldSupportsCompactUnwind <- getBooleanSetting "ld supports compact unwind"
ldSupportsBuildId <- getBooleanSetting "ld supports build-id"
ldSupportsFilelist <- getBooleanSetting "ld supports filelist"
ldIsGnuLd <- getBooleanSetting "ld is GNU ld"
perl_path <- getSetting "perl command"
let pkgconfig_path = installed "package.conf.d"
ghc_usage_msg_path = installed "ghc-usage.txt"
ghci_usage_msg_path = installed "ghci-usage.txt"
unlit_path = installed cGHC_UNLIT_PGM
split_script = installed cGHC_SPLIT_PGM
windres_path <- getSetting "windres command"
libtool_path <- getSetting "libtool command"
tmpdir <- getTemporaryDirectory
touch_path <- getSetting "touch command"
let
(split_prog, split_args)
| isWindowsHost = (perl_path, [Option split_script])
| otherwise = (split_script, [])
mkdll_prog <- getSetting "dllwrap command"
let mkdll_args = []
gcc_link_args_str <- getSetting "C compiler link flags"
let as_prog = gcc_prog
as_args = gcc_args
ld_prog = gcc_prog
ld_args = gcc_args ++ map Option (words gcc_link_args_str)
lc_prog <- getSetting "LLVM llc command"
lo_prog <- getSetting "LLVM opt command"
let platform = Platform {
platformArch = targetArch,
platformOS = targetOS,
platformWordSize = targetWordSize,
platformUnregisterised = targetUnregisterised,
platformHasGnuNonexecStack = targetHasGnuNonexecStack,
platformHasIdentDirective = targetHasIdentDirective,
platformHasSubsectionsViaSymbols = targetHasSubsectionsViaSymbols
}
return $ Settings {
sTargetPlatform = platform,
sTmpDir = normalise tmpdir,
sGhcUsagePath = ghc_usage_msg_path,
sGhciUsagePath = ghci_usage_msg_path,
sTopDir = top_dir,
sRawSettings = mySettings,
sExtraGccViaCFlags = words myExtraGccViaCFlags,
sSystemPackageConfig = pkgconfig_path,
sLdSupportsCompactUnwind = ldSupportsCompactUnwind,
sLdSupportsBuildId = ldSupportsBuildId,
sLdSupportsFilelist = ldSupportsFilelist,
sLdIsGnuLd = ldIsGnuLd,
sProgramName = "ghc",
sProjectVersion = cProjectVersion,
sPgm_L = unlit_path,
sPgm_P = (cpp_prog, cpp_args),
sPgm_F = "",
sPgm_c = (gcc_prog, gcc_args),
sPgm_s = (split_prog,split_args),
sPgm_a = (as_prog, as_args),
sPgm_l = (ld_prog, ld_args),
sPgm_dll = (mkdll_prog,mkdll_args),
sPgm_T = touch_path,
sPgm_sysman = top_dir ++ "/ghc/rts/parallel/SysMan",
sPgm_windres = windres_path,
sPgm_libtool = libtool_path,
sPgm_lo = (lo_prog,[]),
sPgm_lc = (lc_prog,[]),
sOpt_L = [],
sOpt_P = [],
sOpt_F = [],
sOpt_c = [],
sOpt_a = [],
sOpt_l = [],
sOpt_windres = [],
sOpt_lo = [],
sOpt_lc = [],
sPlatformConstants = platformConstants
}
findTopDir :: Maybe String
-> IO String
findTopDir (Just minusb) = return (normalise minusb)
findTopDir Nothing
= do
maybe_exec_dir <- getBaseDir
case maybe_exec_dir of
Nothing -> throwGhcExceptionIO (InstallationError "missing -B<dir> option")
Just dir -> return dir
runUnlit :: DynFlags -> [Option] -> IO ()
runUnlit dflags args = do
let prog = pgm_L dflags
opts = getOpts dflags opt_L
runSomething dflags "Literate pre-processor" prog
(map Option opts ++ args)
runCpp :: DynFlags -> [Option] -> IO ()
runCpp dflags args = do
let (p,args0) = pgm_P dflags
args1 = map Option (getOpts dflags opt_P)
args2 = if gopt Opt_WarnIsError dflags
then [Option "-Werror"]
else []
mb_env <- getGccEnv args2
runSomethingFiltered dflags id "C pre-processor" p
(args0 ++ args1 ++ args2 ++ args) mb_env
runPp :: DynFlags -> [Option] -> IO ()
runPp dflags args = do
let prog = pgm_F dflags
opts = map Option (getOpts dflags opt_F)
runSomething dflags "Haskell pre-processor" prog (args ++ opts)
runCc :: DynFlags -> [Option] -> IO ()
runCc dflags args = do
let (p,args0) = pgm_c dflags
args1 = map Option (getOpts dflags opt_c)
args2 = args0 ++ args1 ++ args
mb_env <- getGccEnv args2
runSomethingFiltered dflags cc_filter "C Compiler" p args2 mb_env
where
cc_filter = unlines . doFilter . lines
doFilter = unChunkWarnings . filterWarnings . chunkWarnings []
chunkWarnings :: [String]
-> [String]
-> [([String], [String])]
chunkWarnings loc_stack [] = [(loc_stack, [])]
chunkWarnings loc_stack xs
= case break loc_stack_start xs of
(warnings, lss:xs') ->
case span loc_start_continuation xs' of
(lsc, xs'') ->
(loc_stack, warnings) : chunkWarnings (lss : lsc) xs''
_ -> [(loc_stack, xs)]
filterWarnings :: [([String], [String])] -> [([String], [String])]
filterWarnings [] = []
filterWarnings ((xs, []) : zs) = (xs, []) : filterWarnings zs
filterWarnings ((xs, ys) : zs) = case filter wantedWarning ys of
[] -> filterWarnings zs
ys' -> (xs, ys') : filterWarnings zs
unChunkWarnings :: [([String], [String])] -> [String]
unChunkWarnings [] = []
unChunkWarnings ((xs, ys) : zs) = xs ++ ys ++ unChunkWarnings zs
loc_stack_start s = "In file included from " `isPrefixOf` s
loc_start_continuation s = " from " `isPrefixOf` s
wantedWarning w
| "warning: call-clobbered register used" `isContainedIn` w = False
| otherwise = True
isContainedIn :: String -> String -> Bool
xs `isContainedIn` ys = any (xs `isPrefixOf`) (tails ys)
askCc :: DynFlags -> [Option] -> IO String
askCc dflags args = do
let (p,args0) = pgm_c dflags
args1 = map Option (getOpts dflags opt_c)
args2 = args0 ++ args1 ++ args
mb_env <- getGccEnv args2
runSomethingWith dflags "gcc" p args2 $ \real_args ->
readCreateProcess (proc p real_args){ env = mb_env }
readCreateProcess
:: CreateProcess
-> IO (ExitCode, String)
readCreateProcess proc = do
(_, Just outh, _, pid) <-
createProcess proc{ std_out = CreatePipe }
output <- hGetContents outh
outMVar <- newEmptyMVar
_ <- forkIO $ evaluate (length output) >> putMVar outMVar ()
takeMVar outMVar
hClose outh
ex <- waitForProcess pid
return (ex, output)
readProcessEnvWithExitCode
:: String
-> [String]
-> [(String, String)]
-> IO (ExitCode, String, String)
readProcessEnvWithExitCode prog args env_update = do
current_env <- getEnvironment
let new_env = env_update ++ [ (k, v)
| let overriden_keys = map fst env_update
, (k, v) <- current_env
, k `notElem` overriden_keys
]
p = proc prog args
(_stdin, Just stdoh, Just stdeh, pid) <-
createProcess p{ std_out = CreatePipe
, std_err = CreatePipe
, env = Just new_env
}
outMVar <- newEmptyMVar
errMVar <- newEmptyMVar
_ <- forkIO $ do
stdo <- hGetContents stdoh
_ <- evaluate (length stdo)
putMVar outMVar stdo
_ <- forkIO $ do
stde <- hGetContents stdeh
_ <- evaluate (length stde)
putMVar errMVar stde
out <- takeMVar outMVar
hClose stdoh
err <- takeMVar errMVar
hClose stdeh
ex <- waitForProcess pid
return (ex, out, err)
en_locale_env :: [(String, String)]
en_locale_env = [("LANGUAGE", "en")]
getGccEnv :: [Option] -> IO (Maybe [(String,String)])
getGccEnv opts =
if null b_dirs
then return Nothing
else do env <- getEnvironment
return (Just (map mangle_path env))
where
(b_dirs, _) = partitionWith get_b_opt opts
get_b_opt (Option ('-':'B':dir)) = Left dir
get_b_opt other = Right other
mangle_path (path,paths) | map toUpper path == "PATH"
= (path, '\"' : head b_dirs ++ "\";" ++ paths)
mangle_path other = other
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
args1 = map Option (getOpts dflags opt_a)
args2 = args0 ++ args1 ++ args
mb_env <- getGccEnv args2
runSomethingFiltered dflags id "Assembler" p args2 mb_env
runLlvmOpt :: DynFlags -> [Option] -> IO ()
runLlvmOpt dflags args = do
let (p,args0) = pgm_lo dflags
args1 = map Option (getOpts dflags opt_lo)
runSomething dflags "LLVM Optimiser" p (args0 ++ args1 ++ args)
runLlvmLlc :: DynFlags -> [Option] -> IO ()
runLlvmLlc dflags args = do
let (p,args0) = pgm_lc dflags
args1 = map Option (getOpts dflags opt_lc)
runSomething dflags "LLVM Compiler" p (args0 ++ args1 ++ args)
runClang :: DynFlags -> [Option] -> IO ()
runClang dflags args = do
let clang = "clang"
(_,args0) = pgm_a dflags
args1 = map Option (getOpts dflags opt_a)
args2 = args0 ++ args1 ++ args
mb_env <- getGccEnv args2
Exception.catch (do
runSomethingFiltered dflags id "Clang (Assembler)" clang args2 mb_env
)
(\(err :: SomeException) -> do
errorMsg dflags $
text ("Error running clang! you need clang installed to use the" ++
"LLVM backend") $+$
text "(or GHC tried to execute clang incorrectly)"
throwIO err
)
figureLlvmVersion :: DynFlags -> IO (Maybe Int)
figureLlvmVersion dflags = do
let (pgm,opts) = pgm_lc dflags
args = filter notNull (map showOpt opts)
args' = args ++ ["-version"]
ver <- catchIO (do
(pin, pout, perr, _) <- runInteractiveProcess pgm args'
Nothing Nothing
hSetBinaryMode pout False
_ <- hGetLine pout
vline <- hGetLine pout
v <- case filter isDigit vline of
[] -> fail "no digits!"
[x] -> fail $ "only 1 digit! (" ++ show x ++ ")"
(x:y:_) -> return ((read [x,y]) :: Int)
hClose pin
hClose pout
hClose perr
return $ Just v
)
(\err -> do
debugTraceMsg dflags 2
(text "Error (figuring out LLVM version):" <+>
text (show err))
errorMsg dflags $ vcat
[ text "Warning:", nest 9 $
text "Couldn't figure out LLVM version!" $$
text "Make sure you have installed LLVM"]
return Nothing)
return ver
neededLinkArgs :: LinkerInfo -> [Option]
neededLinkArgs (GnuLD o) = o
neededLinkArgs (GnuGold o) = o
neededLinkArgs (DarwinLD o) = o
neededLinkArgs (SolarisLD o) = o
neededLinkArgs UnknownLD = []
getLinkerInfo :: DynFlags -> IO LinkerInfo
getLinkerInfo dflags = do
info <- readIORef (rtldInfo dflags)
case info of
Just v -> return v
Nothing -> do
v <- getLinkerInfo' dflags
writeIORef (rtldInfo dflags) (Just v)
return v
getLinkerInfo' :: DynFlags -> IO LinkerInfo
getLinkerInfo' dflags = do
let platform = targetPlatform dflags
os = platformOS platform
(pgm,args0) = pgm_l dflags
args1 = map Option (getOpts dflags opt_l)
args2 = args0 ++ args1
args3 = filter notNull (map showOpt args2)
parseLinkerInfo stdo _stde _exitc
| any ("GNU ld" `isPrefixOf`) stdo =
return (GnuLD $ map Option ["-Wl,--hash-size=31",
"-Wl,--reduce-memory-overheads"])
| any ("GNU gold" `isPrefixOf`) stdo =
return (GnuGold [])
| otherwise = fail "invalid --version output, or linker is unsupported"
info <- catchIO (do
case os of
OSSolaris2 ->
return $ SolarisLD []
OSDarwin ->
return $ DarwinLD []
OSiOS ->
return $ DarwinLD []
OSMinGW32 ->
return $ GnuLD $ map Option
[
"-Wl,--hash-size=31"
, "-Wl,--reduce-memory-overheads"
, "-Xlinker", "--stack=0x800000,0x800000" ]
_ -> do
(exitc, stdo, stde) <- readProcessEnvWithExitCode pgm
(["-Wl,--version"] ++ args3)
en_locale_env
parseLinkerInfo (lines stdo) (lines stde) exitc
)
(\err -> do
debugTraceMsg dflags 2
(text "Error (figuring out linker information):" <+>
text (show err))
errorMsg dflags $ hang (text "Warning:") 9 $
text "Couldn't figure out linker information!" $$
text "Make sure you're using GNU ld, GNU gold" <+>
text "or the built in OS X linker, etc."
return UnknownLD)
return info
getCompilerInfo :: DynFlags -> IO CompilerInfo
getCompilerInfo dflags = do
info <- readIORef (rtccInfo dflags)
case info of
Just v -> return v
Nothing -> do
v <- getCompilerInfo' dflags
writeIORef (rtccInfo dflags) (Just v)
return v
getCompilerInfo' :: DynFlags -> IO CompilerInfo
getCompilerInfo' dflags = do
let (pgm,_) = pgm_c dflags
parseCompilerInfo _stdo stde _exitc
| any ("gcc version" `isPrefixOf`) stde =
return GCC
| any ("clang version" `isPrefixOf`) stde =
return Clang
| any ("Apple LLVM version 5.1" `isPrefixOf`) stde =
return AppleClang51
| any ("Apple LLVM version" `isPrefixOf`) stde =
return AppleClang
| any ("Apple clang version" `isPrefixOf`) stde =
return AppleClang
| otherwise = fail "invalid -v output, or compiler is unsupported"
info <- catchIO (do
(exitc, stdo, stde) <-
readProcessEnvWithExitCode pgm ["-v"] en_locale_env
parseCompilerInfo (lines stdo) (lines stde) exitc
)
(\err -> do
debugTraceMsg dflags 2
(text "Error (figuring out C compiler information):" <+>
text (show err))
errorMsg dflags $ hang (text "Warning:") 9 $
text "Couldn't figure out C compiler information!" $$
text "Make sure you're using GNU gcc, or clang"
return UnknownCC)
return info
runLink :: DynFlags -> [Option] -> IO ()
runLink dflags args = do
linkargs <- neededLinkArgs `fmap` getLinkerInfo dflags
let (p,args0) = pgm_l dflags
args1 = map Option (getOpts dflags opt_l)
args2 = args0 ++ args1 ++ args ++ linkargs
mb_env <- getGccEnv args2
runSomethingFiltered dflags ld_filter "Linker" p args2 mb_env
where
ld_filter = case (platformOS (targetPlatform dflags)) of
OSSolaris2 -> sunos_ld_filter
_ -> id
sunos_ld_filter :: String -> String
sunos_ld_filter = unlines . sunos_ld_filter' . lines
sunos_ld_filter' x = if (undefined_found x && ld_warning_found x)
then (ld_prefix x) ++ (ld_postfix x)
else x
breakStartsWith x y = break (isPrefixOf x) y
ld_prefix = fst . breakStartsWith "Undefined"
undefined_found = not . null . snd . breakStartsWith "Undefined"
ld_warn_break = breakStartsWith "ld: warning: symbol referencing errors"
ld_postfix = tail . snd . ld_warn_break
ld_warning_found = not . null . snd . ld_warn_break
runLibtool :: DynFlags -> [Option] -> IO ()
runLibtool dflags args = do
linkargs <- neededLinkArgs `fmap` getLinkerInfo dflags
let args1 = map Option (getOpts dflags opt_l)
args2 = [Option "-static"] ++ args1 ++ args ++ linkargs
libtool = pgm_libtool dflags
mb_env <- getGccEnv args2
runSomethingFiltered dflags id "Linker" libtool args2 mb_env
runMkDLL :: DynFlags -> [Option] -> IO ()
runMkDLL dflags args = do
let (p,args0) = pgm_dll dflags
args1 = args0 ++ args
mb_env <- getGccEnv (args0++args)
runSomethingFiltered dflags id "Make DLL" p args1 mb_env
runWindres :: DynFlags -> [Option] -> IO ()
runWindres dflags args = do
let (gcc, gcc_args) = pgm_c dflags
windres = pgm_windres dflags
opts = map Option (getOpts dflags opt_windres)
quote x = "\"" ++ x ++ "\""
args' =
Option ("--preprocessor=" ++
unwords (map quote (gcc :
map showOpt gcc_args ++
map showOpt opts ++
["-E", "-xc", "-DRC_INVOKED"])))
: Option "--use-temp-file"
: args
mb_env <- getGccEnv gcc_args
runSomethingFiltered dflags id "Windres" windres args' mb_env
touch :: DynFlags -> String -> String -> IO ()
touch dflags purpose arg =
runSomething dflags purpose (pgm_T dflags) [FileOption "" arg]
copy :: DynFlags -> String -> FilePath -> FilePath -> IO ()
copy dflags purpose from to = copyWithHeader dflags purpose Nothing from to
copyWithHeader :: DynFlags -> String -> Maybe String -> FilePath -> FilePath
-> IO ()
copyWithHeader dflags purpose maybe_header from to = do
showPass dflags purpose
hout <- openBinaryFile to WriteMode
hin <- openBinaryFile from ReadMode
ls <- hGetContents hin
maybe (return ()) (header hout) maybe_header
hPutStr hout ls
hClose hout
hClose hin
where
header h str = do
hSetEncoding h utf8
hPutStr h str
hSetBinaryMode h True
readElfSection :: DynFlags -> String -> FilePath -> IO (Maybe String)
readElfSection _dflags section exe = do
let
prog = "readelf"
args = [Option "-p", Option section, FileOption "" exe]
r <- readProcessEnvWithExitCode prog (filter notNull (map showOpt args))
en_locale_env
case r of
(ExitSuccess, out, _err) -> return (doFilter (lines out))
_ -> return Nothing
where
doFilter [] = Nothing
doFilter (s:r) = case readP_to_S parse s of
[(p,"")] -> Just p
_r -> doFilter r
where parse = do
skipSpaces
_ <- R.char '['
skipSpaces
_ <- string "0]"
skipSpaces
munch (const True)
cleanTempDirs :: DynFlags -> IO ()
cleanTempDirs dflags
= unless (gopt Opt_KeepTmpFiles dflags)
$ mask_
$ do let ref = dirsToClean dflags
ds <- atomicModifyIORef ref $ \ds -> (Map.empty, ds)
removeTmpDirs dflags (Map.elems ds)
cleanTempFiles :: DynFlags -> IO ()
cleanTempFiles dflags
= unless (gopt Opt_KeepTmpFiles dflags)
$ mask_
$ do let ref = filesToClean dflags
fs <- atomicModifyIORef ref $ \fs -> ([],fs)
removeTmpFiles dflags fs
cleanTempFilesExcept :: DynFlags -> [FilePath] -> IO ()
cleanTempFilesExcept dflags dont_delete
= unless (gopt Opt_KeepTmpFiles dflags)
$ mask_
$ do let ref = filesToClean dflags
to_delete <- atomicModifyIORef ref $ \files ->
let (to_keep,to_delete) = partition (`elem` dont_delete) files
in (to_keep,to_delete)
removeTmpFiles dflags to_delete
newTempSuffix :: DynFlags -> IO Int
newTempSuffix dflags = atomicModifyIORef (nextTempSuffix dflags) $ \n -> (n+1,n)
newTempName :: DynFlags -> Suffix -> IO FilePath
newTempName dflags extn
= do d <- getTempDir dflags
x <- getProcessID
findTempName (d </> "ghc" ++ show x ++ "_")
where
findTempName :: FilePath -> IO FilePath
findTempName prefix
= do n <- newTempSuffix dflags
let filename = prefix ++ show n <.> extn
b <- doesFileExist filename
if b then findTempName prefix
else do
consIORef (filesToClean dflags) filename
return filename
getTempDir :: DynFlags -> IO FilePath
getTempDir dflags = do
mapping <- readIORef dir_ref
case Map.lookup tmp_dir mapping of
Nothing -> do
pid <- getProcessID
let prefix = tmp_dir </> "ghc" ++ show pid ++ "_"
mask_ $ mkTempDir prefix
Just dir -> return dir
where
tmp_dir = tmpDir dflags
dir_ref = dirsToClean dflags
mkTempDir :: FilePath -> IO FilePath
mkTempDir prefix = do
n <- newTempSuffix dflags
let our_dir = prefix ++ show n
createDirectory our_dir
their_dir <- atomicModifyIORef dir_ref $ \mapping ->
case Map.lookup tmp_dir mapping of
Just dir -> (mapping, Just dir)
Nothing -> (Map.insert tmp_dir our_dir mapping, Nothing)
case their_dir of
Nothing -> do
debugTraceMsg dflags 2 $
text "Created temporary directory:" <+> text our_dir
return our_dir
Just dir -> do
removeDirectory our_dir
return dir
`catchIO` \e -> if isAlreadyExistsError e
then mkTempDir prefix else ioError e
addFilesToClean :: DynFlags -> [FilePath] -> IO ()
addFilesToClean dflags new_files
= atomicModifyIORef (filesToClean dflags) $ \files -> (new_files++files, ())
removeTmpDirs :: DynFlags -> [FilePath] -> IO ()
removeTmpDirs dflags ds
= traceCmd dflags "Deleting temp dirs"
("Deleting: " ++ unwords ds)
(mapM_ (removeWith dflags removeDirectory) ds)
removeTmpFiles :: DynFlags -> [FilePath] -> IO ()
removeTmpFiles dflags fs
= warnNon $
traceCmd dflags "Deleting temp files"
("Deleting: " ++ unwords deletees)
(mapM_ (removeWith dflags removeFile) deletees)
where
warnNon act
| null non_deletees = act
| otherwise = do
putMsg dflags (text "WARNING - NOT deleting source files:" <+> hsep (map text non_deletees))
act
(non_deletees, deletees) = partition isHaskellUserSrcFilename fs
removeWith :: DynFlags -> (FilePath -> IO ()) -> FilePath -> IO ()
removeWith dflags remover f = remover f `catchIO`
(\e ->
let msg = if isDoesNotExistError e
then ptext (sLit "Warning: deleting non-existent") <+> text f
else ptext (sLit "Warning: exception raised when deleting")
<+> text f <> colon
$$ text (show e)
in debugTraceMsg dflags 2 msg
)
runSomething :: DynFlags
-> String
-> String
-> [Option]
-> IO ()
runSomething dflags phase_name pgm args =
runSomethingFiltered dflags id phase_name pgm args Nothing
runSomethingFiltered
:: DynFlags -> (String->String) -> String -> String -> [Option]
-> Maybe [(String,String)] -> IO ()
runSomethingFiltered dflags filter_fn phase_name pgm args mb_env = do
runSomethingWith dflags phase_name pgm args $ \real_args -> do
r <- builderMainLoop dflags filter_fn pgm real_args mb_env
return (r,())
runSomethingWith
:: DynFlags -> String -> String -> [Option]
-> ([String] -> IO (ExitCode, a))
-> IO a
runSomethingWith dflags phase_name pgm args io = do
let real_args = filter notNull (map showOpt args)
cmdLine = showCommandForUser pgm real_args
traceCmd 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
| n == 127 -> does_not_exist
| otherwise -> throwGhcExceptionIO (PhaseFailed phase_name rc)
where
handler err =
if IO.isDoesNotExistError err
then does_not_exist
else IO.ioError err
does_not_exist = throwGhcExceptionIO (InstallationError ("could not execute: " ++ pgm))
builderMainLoop :: DynFlags -> (String -> String) -> FilePath
-> [String] -> Maybe [(String, String)]
-> IO ExitCode
builderMainLoop dflags filter_fn pgm real_args mb_env = do
chan <- newChan
(hStdIn, hStdOut, hStdErr, hProcess) <- runInteractiveProcess pgm real_args Nothing mb_env
hSetBuffering hStdOut LineBuffering
hSetBuffering hStdErr LineBuffering
_ <- forkIO (readerProc chan hStdOut filter_fn)
_ <- forkIO (readerProc chan hStdErr filter_fn)
rc <- loop chan hProcess (2::Integer) (1::Integer) ExitSuccess
hClose hStdIn
hClose hStdOut
hClose hStdErr
return rc
where
loop _ _ 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 (p1) code
Nothing
| t > 0 -> do
msg <- readChan chan
case msg of
BuildMsg msg -> do
log_action dflags dflags SevInfo noSrcSpan defaultUserStyle msg
loop chan hProcess t p exitcode
BuildError loc msg -> do
log_action dflags dflags SevError (mkSrcSpan loc loc) defaultUserStyle msg
loop chan hProcess t p exitcode
EOF ->
loop chan hProcess (t1) p exitcode
| otherwise -> loop chan hProcess t p exitcode
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 -> do
loop ls (Just (BuildError srcLoc (msg $$ text l)))
| otherwise -> do
writeChan chan err
checkError l ls
Nothing -> do
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 xs = case break (':' ==) xs of
(ys, _:zs) -> Just (ys, zs)
_ -> 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
traceCmd :: DynFlags -> String -> String -> IO a -> IO a
traceCmd dflags phase_name cmd_line action
= do { let verb = verbosity dflags
; showPass dflags phase_name
; debugTraceMsg dflags 3 (text cmd_line)
; case flushErr dflags of
FlushErr io -> io
; action `catchIO` handle_exn verb
}
where
handle_exn _verb exn = do { debugTraceMsg dflags 2 (char '\n')
; debugTraceMsg dflags 2 (ptext (sLit "Failed:") <+> text cmd_line <+> text (show exn))
; throwGhcExceptionIO (PhaseFailed phase_name (ExitFailure 1)) }
getBaseDir :: IO (Maybe String)
#if defined(mingw32_HOST_OS)
getBaseDir = try_size 2048
where
try_size size = allocaArray (fromIntegral size) $ \buf -> do
ret <- c_GetModuleFileName nullPtr buf size
case ret of
0 -> return Nothing
_ | ret < size -> fmap (Just . rootDir) $ peekCWString buf
| otherwise -> try_size (size * 2)
rootDir s = case splitFileName $ normalise s of
(d, ghc_exe)
| lower ghc_exe `elem` ["ghc.exe",
"ghc-stage1.exe",
"ghc-stage2.exe",
"ghc-stage3.exe"] ->
case splitFileName $ takeDirectory d of
(d', bin) | lower bin == "bin" -> takeDirectory d' </> "lib"
_ -> fail
_ -> fail
where fail = panic ("can't decompose ghc.exe path: " ++ show s)
lower = map toLower
foreign import WINDOWS_CCONV unsafe "windows.h GetModuleFileNameW"
c_GetModuleFileName :: Ptr () -> CWString -> Word32 -> IO Word32
#else
getBaseDir = return Nothing
#endif
#ifdef mingw32_HOST_OS
foreign import ccall unsafe "_getpid" getProcessID :: IO Int
#else
getProcessID :: IO Int
getProcessID = System.Posix.Internals.c_getpid >>= return . fromIntegral
#endif
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
linkDynLib :: DynFlags -> [String] -> [PackageKey] -> IO ()
linkDynLib dflags0 o_files dep_packages
= do
let
dflags1 = if cGhcThreaded then addWay' WayThreaded dflags0
else dflags0
dflags2 = if cGhcDebugged then addWay' WayDebug dflags1
else dflags1
dflags = updateWays dflags2
verbFlags = getVerbFlags dflags
o_file = outputFile dflags
pkgs <- getPreloadPackagesAnd dflags dep_packages
let pkg_lib_paths = collectLibraryPaths pkgs
let pkg_lib_path_opts = concatMap get_pkg_lib_path_opts pkg_lib_paths
get_pkg_lib_path_opts l
| ( osElfTarget (platformOS (targetPlatform dflags)) ||
osMachOTarget (platformOS (targetPlatform dflags)) ) &&
dynLibLoader dflags == SystemDependent &&
not (gopt Opt_Static dflags)
= ["-L" ++ l, "-Wl,-rpath", "-Wl," ++ l]
| otherwise = ["-L" ++ l]
let lib_paths = libraryPaths dflags
let lib_path_opts = map ("-L"++) lib_paths
let platform = targetPlatform dflags
os = platformOS platform
pkgs_no_rts = case os of
OSMinGW32 ->
pkgs
_ ->
filter ((/= rtsPackageKey) . packageConfigId) pkgs
let pkg_link_opts = let (package_hs_libs, extra_libs, other_flags) = collectLinkOpts dflags pkgs_no_rts
in package_hs_libs ++ extra_libs ++ other_flags
let extra_ld_inputs = ldInputs dflags
case os of
OSMinGW32 -> do
let output_fn = case o_file of
Just s -> s
Nothing -> "HSdll.dll"
runLink dflags (
map Option verbFlags
++ [ Option "-o"
, FileOption "" output_fn
, Option "-shared"
] ++
[ FileOption "-Wl,--out-implib=" (output_fn ++ ".a")
| gopt Opt_SharedImplib dflags
]
++ map (FileOption "") o_files
++ [Option "-Wl,--enable-auto-import"]
++ extra_ld_inputs
++ map Option (
lib_path_opts
++ pkg_lib_path_opts
++ pkg_link_opts
))
OSDarwin -> do
let output_fn = case o_file of { Just s -> s; Nothing -> "a.out"; }
instName <- case dylibInstallName dflags of
Just n -> return n
Nothing -> return $ "@rpath" `combine` (takeFileName output_fn)
runLink dflags (
map Option verbFlags
++ [ Option "-dynamiclib"
, Option "-o"
, FileOption "" output_fn
]
++ map Option o_files
++ [ Option "-undefined",
Option "dynamic_lookup",
Option "-single_module" ]
++ (if platformArch platform == ArchX86_64
then [ ]
else [ Option "-Wl,-read_only_relocs,suppress" ])
++ [ Option "-install_name", Option instName ]
++ map Option lib_path_opts
++ extra_ld_inputs
++ map Option pkg_lib_path_opts
++ map Option pkg_link_opts
)
OSiOS -> throwGhcExceptionIO (ProgramError "dynamic libraries are not supported on iOS target")
_ -> do
let output_fn = case o_file of { Just s -> s; Nothing -> "a.out"; }
let buildingRts = thisPackage dflags == rtsPackageKey
let bsymbolicFlag = if buildingRts
then
[]
else
["-Wl,-Bsymbolic"]
runLink dflags (
map Option verbFlags
++ [ Option "-o"
, FileOption "" output_fn
]
++ map Option o_files
++ [ Option "-shared" ]
++ map Option bsymbolicFlag
++ [ Option ("-Wl,-h," ++ takeFileName output_fn) ]
++ extra_ld_inputs
++ map Option lib_path_opts
++ map Option pkg_lib_path_opts
++ map Option pkg_link_opts
)