1module Mach.Main (run) where23import Control.Exception (throwIO)4import Data.Maybe (fromMaybe)5import Mach.Error (MakeErr (..), TargetError (NoSuchTarget, ZeroTargetsDefined))6import Mach.Eval (MkDef, eval, firstTarget)7import Mach.Exec (maybeBuild, mkConfig, targetOrFile)8import Mach.Parser (cmdLine, parseMkFile)9import qualified Mach.Types as T10import Mach.Util (getEnvMarcos)11import Paths_mach (getDataFileName)12import System.Console.GetOpt13 ( ArgDescr (NoArg, ReqArg),14 ArgOrder (Permute),15 OptDescr (Option),16 getOpt,17 usageInfo,18 )19import System.Environment (lookupEnv)20import System.Exit (ExitCode (ExitFailure, ExitSuccess))21import System.IO (Handle)2223options :: [OptDescr T.Flag]24options =25 [ Option ['f'] [] (ReqArg T.Makefile "makefile") "Specify a different makefile",26 Option ['e'] [] (NoArg T.EnvOverwrite) "Overwrite macro assignments with environment variables",27 Option ['j'] [] (ReqArg T.Jobs "jobs") "Allow given amount of execution jobs at once",28 Option ['i'] [] (NoArg T.IgnoreAll) "Ignore exit status of executed commands",29 Option ['s'] [] (NoArg T.SilentAll) "Do not write command lines to stdout",30 Option ['k'] [] (NoArg T.ExecCont) "On error keep executing independent targets",31 Option ['n'] [] (NoArg T.DryRun) "Write commands to be executed to stdout",32 Option ['S'] [] (NoArg T.TermOnErr) "Terminate on error (the default)",33 Option ['r'] [] (NoArg T.NoBuiltin) "Do not use the builtin rules"34 ]3536makeOpts :: [String] -> IO ([T.Flag], [String])37makeOpts argv =38 case getOpt Permute options argv of39 (o, n, []) -> return (o, n)40 (_, _, errs) -> ioError (userError (concat errs ++ usageInfo header options))41 where42 header = "Usage: mach [-f makefile] [-j jobs] [target_name...]"4344------------------------------------------------------------------------4546makefile :: [T.Flag] -> T.MkFile -> T.MkFile -> FilePath -> IO MkDef47makefile my_flags extra environ path = do48 f <- parseMkFile path4950 -- If -e is specified, overwrite macro assignments with environment.51 let mk =52 if null [() | T.EnvOverwrite <- my_flags]53 then extra ++ environ ++ f54 else extra ++ f ++ environ5556 -- TODO: evaluate extra and environ once57 eval (mk)5859runMk :: Handle -> [T.Flag] -> T.MkFile -> T.MkFile -> [String] -> FilePath -> IO Bool60runMk handle my_flags extra environ my_targets path = do61 mk <- makefile my_flags extra environ path62 targets <-63 if null my_targets64 then (: []) <$> firstTarget' mk65 else pure my_targets6667 let conf = mkConfig mk handle my_flags68 (not . any (== False))69 <$> (mapM (targetOrFile' mk) targets >>= mapM (maybeBuild conf mk))70 where71 firstTarget' mk = case firstTarget mk of72 Nothing -> throwIO $ TargetErr ZeroTargetsDefined73 Just tg -> pure tg7475 targetOrFile' mk t = do76 tgt <- targetOrFile mk t77 case tgt of78 Nothing -> throwIO $ TargetErr (NoSuchTarget t)79 Just tg -> pure tg8081run :: Handle -> [String] -> IO ExitCode82run handle args = do83 (flagsCmd, remain) <- makeOpts args84 (vars, targets) <- cmdLine $ unwords remain8586 (flagsEnv, remainEnv) <- (fromMaybe "" <$> lookupEnv "MAKEFLAGS") >>= makeOpts . words87 (envMacros, _) <- cmdLine $ unwords remainEnv8889 environs <- getEnvMarcos90 builtins <- getDataFileName "share/builtin.mk" >>= parseMkFile9192 let my_flags = flagsCmd ++ flagsEnv93 let extra =94 if null [() | T.NoBuiltin <- my_flags]95 then builtins ++ vars ++ envMacros96 else vars ++ envMacros9798 res <- mapM (runMk handle my_flags extra environs targets) $99 case [f | T.Makefile f <- flagsCmd] of100 [] -> ["Makefile"]101 fs -> fs102103 pure $104 if (any (== False) res)105 then ExitFailure 1106 else ExitSuccess