ghc-make-0.2: Arguments.hs
{-# LANGUAGE PatternGuards, RecordWildCards #-}
module Arguments(Arguments(..), getArguments) where
import Control.Monad
import Data.Char
import Data.Either
import Data.List
import Data.Maybe
import System.Environment
import System.Exit
import Development.Shake.FilePath
import Makefile
data Arguments = Arguments
{argsGHC :: [String] -- ^ Arguments to pass to ghc, does not include --make
,argsShake :: [String] -- ^ Arguments to pass to shake
,threads :: Int -- ^ Number of threads to use
-- Interpretation of the flags
,modeGHC :: Bool -- ^ Are these flags which should go direct to GHC, not a --make/-M mode
,prefix :: FilePath -- ^ Where make should put its files, e.g. .ghc-make
-- Where should things live
,outputFile :: FilePath -> FilePath -- ^ Root source file, .exe file
,hiDir :: FilePath -- ^ -hidir
,oDir :: FilePath -- ^ -odir
,hiFile :: Module -> FilePath -- ^ .hi files
,oFile :: Module -> FilePath -- ^ .o files
,hiModule :: FilePath -> Maybe Module
,oModule :: FilePath -> Maybe Module
}
helpMessage =
["ghc-make, (C) Neil Mitchell"
,""
," ghc-make [ghc-options] --shake[shake-options] [-jN]"
,""
,"ghc-make is a drop-in replacement for 'ghc', and accepts GHC arugments."
,"For GHC arguments, see 'ghc --help' or <http://haskell.org/haskellwiki/GHC>."
,""
,"ghc-make uses 'shake', and accepts Shake arguments prefixed by '--shake'."
,"For Shake arguments, see 'ghc-make --shake--help'."
,"As an example, to write a profile report to stdout pass '--shake--report=-'."
,""
,"In addition, 'ghc-make' accepts the following option:"
,""
," -jN --threads=N Allow N modules to compile in parallel (defaults to 1)."
]
getArguments :: IO Arguments
getArguments = do
args <- getArgs
when (any (`elem` helpFlags) args) $ do
putStrLn $ unlines helpMessage
exitSuccess
let (argsThreads, argsRest) = partition (isJust . parseThreads) args
let threads = max 1 $ fromMaybe 1 $ msum $ map parseThreads argsThreads
let (argsShake, argsGHC) = splitFlags $ delete "--make" argsRest
let hasArg x = x `elem` argsGHC
let getArg b x = findArg b x argsGHC
let modeGHC = any hasArg $ "--version" : "--numeric-version" : flagsConflictingWithM
let prefix = fromMaybe "" (getArg True ["-outputdir","-odir"] `mplus` getArg True ["-outputdir","-hidir"]) </> ".ghc-make"
let outputFile file = let s = fromMaybe (dropExtension file) (getArg False ["-o"])
in if null $ takeExtension s then s <.> exe else s
let (hiDir, hiFile, hiModule) = extFileModule getArg "hi"
let ( oDir, oFile, oModule) = extFileModule getArg "o"
return Arguments{..}
extFileModule :: (Bool -> [String] -> Maybe String) -> String -> (FilePath, Module -> FilePath, FilePath -> Maybe Module)
extFileModule getArg ext = (extDir, extFile, extModule)
where
extDir = fromMaybe "" $ getArg True ["-outputdir","-" ++ ext ++ "dir"]
extSuf = fromMaybe ext $ getArg True ["-" ++ ext ++ "suf"]
extFile (Module name boot) = extDir </> joinPath name <.> extSuf ++ (if boot then "-boot" else "")
extModule s
| "-boot" `isSuffixOf` s, Just (Module name _) <- extModule $ take (length s - 5) s = newModule name True
| extDir `isPrefixOf` s && extSuf `isSuffixOf` s
= newModule (splitDirectories $ dropWhile isPathSeparator $ dropExtensions $ drop (length extDir) s) False
| otherwise = Nothing
newModule :: [String] -> Bool -> Maybe Module
newModule xs y = if all isValid xs then Just $ Module xs y else Nothing
where isValid (x:xs) = isUpper x && all (\x -> isAlphaNum x || x `elem` "\'_") xs
isValid [] = False
parseThreads :: String -> Maybe Int
parseThreads x = do
x <- msum $ map (`stripPrefix` x) ["-threads","--threads","-j"]
x <- return $ fromMaybe x $ stripPrefix "=" x
[(i,"")] <- return $ reads x
return i
-- | -odir is implicit since -odirfoo works, but -o is explicit
findArg :: Bool -> [String] -> [String] -> Maybe String
findArg implicit flags xs
| x1:x2:xs <- xs, x1 `elem` flags = add x2 $ rec xs
| implicit, x:xs <- xs, Just x <- msum $ map (`stripPrefix` x) flags
= add (if "=" `isPrefixOf` x then drop 1 x else x) $ rec xs
| x:xs <- xs = rec xs
| otherwise = Nothing
where add a b = Just $ fromMaybe a b
rec = findArg implicit flags
helpFlags = words "-? --help"
-- Obtained from the man page (listed in the same order as they appear there)
-- and ghc/Main.hs, `data PostLoadMode`:
flagsConflictingWithM = words $
-- "Help and verbosity options"
"-? --help -V " ++
"--supported-extensions --supported-languages " ++
"--info --version --numeric-version --print-libdir " ++
-- "Which phases to run"
"-E -C -S -c " ++
-- "Alternative modes of operation"
"--interactive -e " ++
-- "Interface file options"
"--show-iface " ++
-- Undocumented?
"--abi-hash"
-- | Split flags into (Shake flags, GHC flags)
splitFlags :: [String] -> ([String], [String])
splitFlags = partitionEithers . map f
where
f x | Just x <- stripPrefix "--shake-" x = Left $ '-':x
| Just x <- stripPrefix "-shake-" x = Left $ '-':x
| otherwise = Right x