ghc-make-0.3.3: src/Main.hs
{-# LANGUAGE RecordWildCards, DeriveDataTypeable, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Main(main) where
import Control.Monad
import Data.Either
import Data.Maybe
import Data.Functor
import Development.Shake
import Development.Shake.Classes
import Development.Shake.FilePath
import System.Environment
import System.Exit
import System.Process
import qualified Data.HashMap.Strict as Map
import Arguments
import Makefile
import Prelude
-- | Increment every time I change the rules in an incompatible way
ghcMakeVer :: Int
ghcMakeVer = 3
newtype AskImports = AskImports Module deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
type instance RuleResult AskImports = [Either FilePath Module]
newtype AskSource = AskSource Module deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
type instance RuleResult AskSource = String
main :: IO ()
main = do
Arguments{..} <- getArguments
when modeGHC $
exitWith =<< rawSystem "ghc" argsGHC
let opts = shakeOptions
{shakeThreads=threads
,shakeFiles=prefix
,shakeVerbosity=if threads == 1 then Quiet else Normal
,shakeVersion=show ghcMakeVer}
withArgs argsShake $ shakeArgs opts $ do
want [prefix <.> "result"]
-- A file containing the GHC arguments
prefix <.> "args" %> \out -> do
alwaysRerun
writeFileChanged out $ unlines argsGHC
let needArgs = do need [prefix <.> "args"]; return argsGHC
-- A file containing the ghc-pkg list output
prefix <.> "pkgs" %> \out -> do
alwaysRerun
(Stdout s, Stderr (_ :: String)) <- cmd "ghc-pkg list --verbose"
writeFileChanged out s
let needPkgs = need [prefix <.> "pkgs"]
-- A file containing the output of -M
prefix <.> "makefile" %> \out -> do
args <- needArgs
needPkgs
-- Use the default o/hi settings so we can parse the makefile properly
() <- cmd "ghc -M -include-pkg-deps -dep-suffix=" [""] "-dep-makefile" [out] args "-odir. -hidir. -hisuf=hi -osuf=o"
mk <- liftIO $ makefile out
need $ Map.elems $ source mk
needMk <- do cache <- newCache (\x -> do need [x]; liftIO $ makefile x); return $ cache $ prefix <.> "makefile"
askImports <- addOracle $ \(AskImports x) -> do mk <- needMk; return $ Map.lookupDefault [] x $ imports mk
askSource <- addOracle $ \(AskSource x) -> do mk <- needMk; return $ source mk Map.! x
-- The result, we can't want the object directly since it is painful to
-- define a build rule for it because its name depends on both args and makefile
prefix <.> "result" %> \out -> do
args <- needArgs
mk <- needMk
let output = if "-no-link" `elem` argsGHC then Nothing
else fmap outputFile $ Map.lookup (Module ["Main"] False) $ source mk
-- if you don't specify an odir/hidir then impossible to reverse from the file name to the module
let exec = when (isJust output || threads == 1) $
cmd "ghc --make -odir. -hidir." args
grab = need $ map oFile $ Map.keys $ source mk
if threads == 1 then exec >> grab else grab >> exec
case output of
Nothing -> return ()
Just output -> do
-- ensure that if the file gets deleted we rerun this rule without first trying to
-- need the output, since we don't have a rule to build the output
b <- doesFileExist output
unless b $
error $ "Failed to build output file: " ++ output ++ "\n" ++
"Most likely ghc-make has guessed the output location wrongly."
need [output]
writeFile' out ""
let match x = do m <- oModule x `mplus` hiModule x; return [oFile m, hiFile m]
match &?> \[o,hi] -> do
let Just m = oModule o
source <- askSource (AskSource m)
(files,mods) <- partitionEithers <$> askImports (AskImports m)
need $ source : map hiFile mods ++ files
when (threads /= 1) $ do
args <- needArgs
let isRoot x = x == "Main" || takeExtension x `elem` [".hs",".lhs"]
cmd "ghc -odir. -hidir." (filter (not . isRoot) args) (if hiDir == "" then [] else ["-i" ++ hiDir]) "-o" [o] "-c" [source]