{-# OPTIONS -w -funbox-strict-fields #-} {-# OPTIONS_GHC -pgmF drift-ghc -F #-} module Options( processOptions, Opt(..), options, Mode(..), StopCondition(..), putVerbose, putVerboseLn, putProgress, putProgressLn, getArguments, findHoCache, verbose, verbose2, progress, dump, wdump, fopts, flint, fileOptions, withOptions, withOptionsT, getArgString, outputName, OptM(), OptT(), OptionMonad(..), flagOpt ) where import Control.Monad.Error() -- IO MonadPlus instance import Control.Monad.Identity import Control.Monad.Reader import Data.List(nub) import Data.Maybe import System.Console.GetOpt import System.Directory import System.IO.Unsafe import System.Environment (getArgs, getProgName, getEnv) import System.Exit import qualified Data.ByteString.UTF8 as BS import qualified Data.Map as M import qualified Data.Set as S import RawFiles(targets_ini) import Support.IniParse import Support.TempDir import Support.Cabal import Util.ExitCodes import Util.Gen import Util.YAML import Version.Config import Version.Version(versionString,versionContext) import qualified FlagDump import qualified FlagOpts import qualified Version.Config as VC {-@CrossCompilation # Basics Unlike many other compilers, Ajhc is a native cross compiler. What this means is that every compile of Ajhc is able to create code for all possible target systems. This leads to many simplifications when it comes to cross compiling with Ajhc. Basically in order to cross compile, you need only pass the flag '--cross' to Ajhc, and pass an appropriate '-m' option to tell Ajhc what machine you are targetting. An example would be ; ajhc --cross -mwin32 test/HelloWorld.hs The targets list is extensible at run-time via the targets.ini file explained below. # targets.ini This file determines what targets are available. The format consists of entries as follows. [targetname] key1=value key2=value key3+=value merge=targetname2 merge is a special key meaning to merge the contents of another target into the current one. The configuration file is read in order, and the final value set for a given key is the one that is used. An example describing how to cross compile for windows is as follows: [win32] cc=i386-mingw32-gcc cflags+=-mwindows -mno-cygwin executable_extension=.exe merge=i686 This sets the compiler to use as well as a few other options then jumps to the generic i686 routine. The special target [default] is always read before all other targets. If '--cross' is specified on the command line then this is the only implicitly included configuration, otherwise Ajhc will assume you are compiling for the current architecture and choose an appropriate target to include in addition to default. Ajhc will attempt to read several targets.ini files in order. they are $PREFIX/etc/ajhc-\$VERSION/targets.ini : this is the targets.ini that is included with Ajhc and contains the default options. $PREFIX/etc/ajhc-\$VERSION/targets-local.ini : Ajhc will read this if it exists, it is used to specify custom system wide configuration options, such as the name of local compilers. $HOME/.ajhc/targets.ini : this is where a users local configuration information goes. $HOME/etc/ajhc/targets.ini : this is simply for people that prefer to not use hidden directories for configuration The last value specified for an option is the one used, so a users local configuration overrides the system local version which overrides the built in options. # Options available Option Meaning ------ --------------------------------------------------------------------------- _cc_ what c compiler to use. generally this will be gcc for local builds and something like $ARCH-$HOST-gcc for cross compiles _byteorder_ one of *le* or *be* for little or big endian _gc_ what garbage collector to use. It should be one of *static* or *boehm*. _cflags_ options to pass to the c compiler _cflags\_debug_ options to pass to the c compiler only when debugging is enabled _cflags\_nodebug_ options to pass to the c compiler only when debugging is disabled _profile_ whether to include profiling code in the generated executable _autoload_ what haskell libraries to autoload, seperated by commas. _executable\_extension_ specifies an extension that should be appended to executable files, (i.e. .EXE on windows) _merge_ a special option that merges the contents of another configuration target into the currrent one. _bits_ the number of bits a pointer contains on this architecture _bits\_max_ the number of bits in the largest integral type. should be the number of bits in the 'intmax_t' C type. _arch_ what to pass to gcc as the architecture # Special defines to set cflags Define Meaning ------ --------------------------------------------------------------------------- \_JHC\_ARM\_STAY\_IN\_THUMB\_MODE set bit0 to any function pointers, for Cortex-M*. ([more detail](http://communities.mentor.com/community/cs/archives/arm-gnu/msg01904.html)) \_JHC\_JGC\_NAIVEGC run gc when have no more blocks. \_JHC\_JGC\_SAVING\_MALLOC\_HEAP get smaller malloc heap. \_JHC\_JGC\_LIMITED\_NUM\_GC\_STACK number of limited gc_stack entries. \_JHC\_JGC\_GC\_STACK\_SHIFT bit shift to specify gc_stack size. Use it internally like this: (1 << (\_JHC\_JGC\_GC\_STACK\_SHIFT)). \_JHC\_JGC\_STACKGROW number of stack entry growed when run short of it. \_JHC\_JGC\_LIMITED\_NUM\_MEGABLOCK number of limited megablock entries. \_JHC\_JGC\_BLOCK\_SHIFT bit shift to specify block size. Use it internally like this: (1 << (\_JHC\_JGC\_BLOCK\_SHIFT)). \_JHC\_JGC\_MEGABLOCK\_SHIFT bit shift to specify megablock size. Use it internally like this: (1 << (\_JHC\_JGC\_MEGABLOCK_SHIFT)). \_JHC\_USE\_OWN\_STDIO force you should implement your own printf,fputs,fflush,getchar,getc,putchar,putc. -} data Mode = BuildHl FilePath -- ^ Build the specified hl-file given a description file. | Interactive -- ^ Run interactively. | Version -- ^ Print version and die. | VersionCtx -- ^ Print version context and die. | ShowHelp -- ^ Show help message and die. | ShowConfig -- ^ Show configuration info. | CompileExe -- ^ Compile executable | ShowHo String -- ^ Show ho-file. | ListLibraries -- ^ List libraries | PrintHscOptions -- ^ Print options for hsc2hs | PurgeCache -- ^ Purge the cache | Preprocess -- ^ Filter through preprocessor deriving(Eq) data StopCondition = StopError String -- ^ error | StopParse -- ^ Just parse and rename modules then exit | StopTypeCheck -- ^ Stop after type checking | StopC -- ^ Stop after producing C code. | CompileHo -- ^ Compile ho | StopNot -- ^ Don't stop believing. deriving(Eq) data Opt = Opt { optMode :: Mode, -- ^ Mode of interaction optColumns :: !Int, -- ^ Width of terminal. optDump :: [String], -- ^ Dump options (raw). optStmts :: [String], -- ^ statements to execute optFOpts :: [String], -- ^ Flag options (raw). optIncdirs :: [String], -- ^ Include directories. optCCargs :: [String], -- ^ Optional arguments to the C compiler. optHls :: [String], -- ^ Load the specified hl-files (haskell libraries). optAutoLoads :: [String], -- ^ AutoLoaded haskell libraries. optHlPath :: [String], -- ^ Path to look for libraries. optIncs :: [String], optDefs :: [String], optExtensions :: [String], optStop :: StopCondition, optWorkDir :: Maybe FilePath, optAnnotate :: Maybe FilePath, optDeps :: Maybe FilePath, optHoDir :: Maybe FilePath, optHoCache :: Maybe FilePath, optTargetsIni :: Maybe FilePath, optArgs :: [String], optStale :: [String], -- ^ treat these modules as stale optKeepGoing :: !Bool, -- ^ Keep going when encountering errors. optMainFunc :: Maybe (Bool,String), -- ^ Entry point name for the main function. optArch :: [String], -- ^ target architecture optCross :: Bool, optOutName :: Maybe String, -- ^ Name of output file. optIgnoreHo :: !Bool, -- ^ Ignore ho-files. optNoWriteHo :: !Bool, -- ^ Don't write ho-files. optNoAuto :: !Bool, -- ^ Don't autoload packages optVerbose :: !Int, -- ^ Verbosity optStatLevel :: !Int, -- ^ Level to print statistics optInis :: M.Map String String, -- ^ options read from ini files optDumpSet :: S.Set FlagDump.Flag, -- ^ Dump flags. optFOptsSet :: S.Set FlagOpts.Flag -- ^ Flag options (-f\). } {-!derive: update !-} emptyOpt = Opt { optMode = CompileExe, optColumns = getColumns, optCross = False, optIncdirs = initialIncludes, optAnnotate = Nothing, optDeps = Nothing, optHls = [], optAutoLoads = [], optHlPath = initialLibIncludes, optIncs = [], optDefs = [], optExtensions = [], optStop = StopNot, optDump = [], optStale = [], optStmts = [], optFOpts = ["default"], optCCargs = [], optWorkDir = Nothing, optHoDir = Nothing, optHoCache = Nothing, optTargetsIni = Nothing, optArgs = [], optIgnoreHo = False, optNoWriteHo = False, optKeepGoing = False, optMainFunc = Nothing, optArch = ["default"], optOutName = Nothing, optVerbose = 0, optStatLevel = 1, optNoAuto = False, optDumpSet = S.singleton FlagDump.Progress, optFOptsSet = S.empty } idu "-" _ = [] idu d ds = ds ++ [d] theoptions :: [OptDescr (Opt -> Opt)] theoptions = [ Option ['V'] ["version"] (NoArg (optMode_s Version)) "print version info and exit" , Option [] ["version-context"] (NoArg (optMode_s VersionCtx)) "print version context info and exit" , Option [] ["help"] (NoArg (optMode_s ShowHelp)) "print help information and exit" , Option [] ["info"] (NoArg (optMode_s ShowConfig)) "show compiler configuration information and exit" , Option [] ["purge-cache"] (NoArg (optMode_s PurgeCache)) "clean out Ajhc compilation cache" , Option ['v'] ["verbose"] (NoArg (optVerbose_u (+1))) "chatty output on stderr" , Option ['z'] [] (NoArg (optStatLevel_u (+1))) "Increase verbosity of statistics" , Option ['d'] [] (ReqArg (optDump_u . (:)) "[no-]flag") "dump specified data during compilation" , Option ['f'] [] (ReqArg (optFOpts_u . (:)) "[no-]flag") "set or clear compilation options" , Option ['X'] [] (ReqArg (optExtensions_u . (:)) "ExtensionName") "enable the given language extension" , Option ['o'] ["output"] (ReqArg (optOutName_s . Just) "FILE") "output to FILE" , Option ['i'] ["include"] (ReqArg (optIncdirs_u . idu) "DIR") "where to look for source files" , Option ['I'] [] (ReqArg (optIncs_u . idu) "DIR") "add to preprocessor include path" , Option ['D'] [] (ReqArg (optDefs_u . (:)) "NAME=VALUE") "add new definitions to set in preprocessor" , Option [] ["optc"] (ReqArg (optCCargs_u . idu) "option") "extra options to pass to c compiler" , Option ['c'] [] (NoArg (optStop_s CompileHo)) "just compile the modules, caching the results." , Option ['C'] [] (NoArg (optStop_s StopC)) "compile to C code" , Option ['E'] [] (NoArg (optMode_s Preprocess)) "preprocess the input and print result to stdout" , Option ['k'] ["keepgoing"] (NoArg (optKeepGoing_s True)) "keep going on errors" , Option [] ["cross"] (NoArg (optCross_s True)) "enable cross-compilation, choose target with the -m flag" , Option [] ["stop"] (ReqArg (optStop_s . stop) "parse/typecheck/c") "stop after the given pass, parse/typecheck/c" , Option [] ["width"] (ReqArg (optColumns_s . read) "COLUMNS") "width of screen for debugging output" , Option [] ["main"] (ReqArg (optMainFunc_s . Just . (,) False) "Main.main") "main entry point" , Option ['m'] ["arch"] (ReqArg (optArch_u . idu ) "arch") "target architecture options" , Option [] ["entry"] (ReqArg (optMainFunc_s . Just . (,) True) "") "main entry point, showable expression" -- , Option ['e'] [] (ReqArg (\d -> optStmts_u ( d:)) "") "run given statement as if on jhci prompt" , Option [] ["show-ho"] (ReqArg (optMode_s . ShowHo) "file.ho") "Show ho file" , Option [] ["noauto"] (NoArg (optNoAuto_s True)) "Don't automatically load base and haskell98 packages" , Option ['p'] [] (ReqArg (optHls_u . (:)) "package") "Load given haskell library package" , Option ['L'] [] (ReqArg (optHlPath_u . idu) "path") "Look for haskell libraries in the given directory" , Option [] ["build-hl"] (ReqArg (optMode_s . BuildHl) "desc.yaml") "Build hakell library from given library description file" , Option [] ["annotate-source"] (ReqArg (optAnnotate_s . Just) "") "Write preprocessed and annotated source code to the directory specified" , Option [] ["deps"] (ReqArg (optDeps_s . Just) "") "Write dependency information to file specified" , Option [] ["interactive"] (NoArg (optMode_s Interactive)) "run interactivly ( for debugging only)" , Option [] ["ignore-cache"] (NoArg (optIgnoreHo_s True)) "Ignore existing compilation cache entries." , Option [] ["readonly-cache"] (NoArg (optNoWriteHo_s True)) "Do not write new information to the compilation cache." , Option [] ["no-cache"] (NoArg (optNoWriteHo_s True . optIgnoreHo_s True)) "Do not use or update the cache." , Option [] ["cache-dir"] (ReqArg (optHoCache_s . Just ) "AJHC_CACHE") "Use a global cache located in the directory passed as an argument." -- , Option [] ["ho-dir"] (ReqArg (optHoDir_s . Just ) "") "Where to place and look for ho files" , Option [] ["stale"] (ReqArg (optStale_u . idu) "Module") "Treat these modules as stale, even if they exist in the cache" , Option [] ["list-libraries"] (NoArg (optMode_s ListLibraries)) "List of installed libraries" , Option [] ["tdir"] (ReqArg (optWorkDir_s . Just) "dir/") "specify the directory where all intermediate files/dumps will be placed." -- , Option [] ["print-hsc-options"] (NoArg (optMode_s PrintHscOptions)) "print options to pass to hsc2hs" , Option [] ["targetsini"] (ReqArg (optTargetsIni_s . Just) "targets.ini") "specify the targets.ini file." ] stop "parse" = StopParse stop "deps" = StopParse stop "typecheck" = StopTypeCheck stop "c" = StopC stop s = StopError s -- | Width of terminal. getColumns :: Int getColumns = read $ unsafePerformIO (getEnv "COLUMNS" `mplus` return "80") postProcessFD :: Monad m => Opt -> m Opt postProcessFD o = case FlagDump.process (optDumpSet o) (optDump o ++ vv) of (s,[]) -> return $ o { optDumpSet = s, optDump = [] } (_,xs) -> fail ("Unrecognized dump flag passed to '-d': " ++ unwords xs ++ "\nValid dump flags:\n\n" ++ FlagDump.helpMsg) where vv | optVerbose o >= 2 = ["veryverbose"] | optVerbose o >= 1 = ["verbose"] | otherwise = [] postProcessFO :: Monad m => Opt -> m Opt postProcessFO o = case FlagOpts.process (optFOptsSet o) (optFOpts o) of (s,[]) -> return $ o { optFOptsSet = s, optFOpts = [] } (_,xs) -> fail ("Unrecognized flag passed to '-f': " ++ unwords xs ++ "\nValid flags:\n\n" ++ FlagOpts.helpMsg) getArguments = do x <- lookupEnv "AJHC_OPTS" let eas = maybe [] words x as <- getArgs return (eas ++ as) pfill :: Int -- ^ maximum width -> (a -> Int) -- ^ find width of any element -> [a] -- ^ input elements -> [[a]] -- ^ output element pfill maxn length xs = f maxn xs [] [] where f n (x:xs) ws ls | lx < n = f (n - lx) xs (x:ws) ls where lx = length x f _ (x:xs) [] ls = f (maxn - length x) xs [x] ls f _ (x:xs) ws ls = f (maxn - length x) xs [x] (ws:ls) f _ [] [] ls = reverse (map reverse ls) f _ [] ws ls = reverse (map reverse (ws:ls)) helpUsage = usageInfo header theoptions ++ trailer where header = "Usage: ajhc [OPTION...] Main.hs" trailer = "\n" ++ mkoptlist "-d" FlagDump.helpFlags ++ "\n" ++ mkoptlist "-f" FlagOpts.helpFlags mkoptlist d os = "valid " ++ d ++ " arguments: 'help' for more info\n " ++ intercalate "\n " (map (intercalate ", ") $ pfill 100 ((2 +) . length) os) ++ "\n" {-# NOINLINE processOptions #-} -- | Parse commandline options. processOptions :: IO Opt processOptions = do -- initial argument processing argv <- getArguments let (o,ns,rc) = getOpt Permute theoptions argv o <- return (foldl (flip ($)) emptyOpt o) when (rc /= []) $ putErrLn (concat rc ++ helpUsage) >> exitWith exitCodeUsage case optStop o of StopError s -> putErrLn "bad option passed to --stop should be one of parse, deps, typecheck, or c" >> exitWith exitCodeUsage _ -> return () case optMode o of ShowHelp -> doShowHelp ShowConfig -> doShowConfig Version -> putStrLn versionString >> exitSuccess VersionCtx -> putStrLn (versionString ++ BS.toString versionContext) >> exitSuccess PrintHscOptions -> do putStrLn $ "-I" ++ VC.datadir ++ "/" ++ VC.package ++ "-" ++ VC.shortVersion ++ "/include" exitSuccess _ -> return () -- read targets.ini file cabalEtc <- getDataFileNameMaybe "etc" home <- fmap (++ "/") getHomeDirectory let fromMaybeToList Nothing = [] fromMaybeToList (Just s) = [s] oTarget = fromMaybeToList $ optTargetsIni o etcDir = fromMaybe confDir cabalEtc iniFiles = [etcDir ++ "/targets.ini", etcDir ++ "/targets-local.ini", home ++ "/etc/ajhc/targets.ini", home ++ "/.ajhc/targets.ini"] ++ oTarget inis <- parseIniFiles (optVerbose o > 0) (BS.toString targets_ini) iniFiles (optArch o) -- process dump flags o <- either putErrDie return $ postProcessFD o when (FlagDump.Ini `S.member` optDumpSet o) $ flip mapM_ (M.toList inis) $ \(a,b) -> putStrLn (a ++ "=" ++ b) -- set flags based on ini options let o1 = case M.lookup "gc" inis of Just "jgc" -> optFOptsSet_u (S.insert FlagOpts.Jgc) o Just "boehm" -> optFOptsSet_u (S.insert FlagOpts.Boehm) o _ -> o o2 <- either putErrDie return $ postProcessFO o1 when (FlagDump.Ini `S.member` optDumpSet o) $ do putStrLn (show $ optDumpSet o) putStrLn (show $ optFOptsSet o) -- add autoloads based on ini options let autoloads = maybe [] (tokens (',' ==)) (M.lookup "autoload" inis) return o2 { optArgs = ns, optInis = inis, optAutoLoads = autoloads } doShowHelp = do putStrLn helpUsage exitSuccess doShowConfig = do --mapM_ (\ (x,y) -> putStrLn (x ++ ": " ++ y)) configs putStrLn $ showYAML configs exitSuccess findHoCache :: IO (Maybe FilePath) findHoCache = do cd <- lookupEnv "AJHC_CACHE" case optHoCache options `mplus` cd of Just s -> do return (Just s) Just "-" -> do return Nothing Nothing | isNothing (optHoDir options) -> do home <- fmap (++ "/") getHomeDirectory let cd = home ++ "/.ajhc/cache" createDirectoryIfMissing True cd return (Just cd) _ -> return Nothing configs :: Node configs = toNode [ "jhclibpath" ==> initialLibIncludes, "version" ==> version, "package" ==> package, "libdir" ==> libdir, "datadir" ==> datadir, "libraryInstall" ==> libraryInstall, "host" ==> host ] where (==>) :: ToNode b => String -> b -> (String,Node) a ==> b = (a,toNode b) {-# NOINLINE fileOptions #-} fileOptions :: Monad m => Opt -> [String] -> m Opt fileOptions options xs = case getOpt Permute theoptions xs of (os,[],[]) -> postProcessFD (foldl (flip ($)) options os) >>= postProcessFO (_,_,errs) -> fail (concat errs) {-# NOINLINE options #-} -- | The global options currently used. options :: Opt options = unsafePerformIO processOptions -- | Put a string to stderr when running verbose. putVerbose :: String -> IO () putVerbose s = when (optVerbose options > 0) $ putErr s -- | Put a line to stderr when running verbose. putVerboseLn :: String -> IO () putVerboseLn s = putVerbose (s ++ "\n") putProgress :: String -> IO () putProgress s = when progress $ putErr s -- | Put a line to stderr when running verbose. putProgressLn :: String -> IO () putProgressLn s = putProgress (s ++ "\n") -- | Is verbose > 0? progress :: Bool progress = dump FlagDump.Progress -- | Is verbose > 0? verbose :: Bool verbose = optVerbose options > 0 -- | Is verbose > 1? verbose2 :: Bool verbose2 = optVerbose options > 1 -- | Test whether a dump flag is set. dump :: FlagDump.Flag -> Bool dump s = s `S.member` optDumpSet options -- | Test whether an option flag is set. fopts :: FlagOpts.Flag -> Bool fopts s = s `S.member` optFOptsSet options -- | Do the action when the suplied dump flag is set. wdump :: (Monad m) => FlagDump.Flag -> m () -> m () wdump f = when (dump f) -- | Is the \"lint\" option flag set? flint :: Bool flint = FlagOpts.Lint `S.member` optFOptsSet options -- | Include directories taken from JHCPATH enviroment variable. initialIncludes :: [String] initialIncludes = unsafePerformIO $ do p <- lookupEnv "AJHC_PATH" let x = fromMaybe "" p return (".":(tokens (== ':') x)) -- | Include directories taken from JHCLIBPATH enviroment variable. initialLibIncludes :: [String] initialLibIncludes = unsafePerformIO $ do ps <- lookupEnv "AJHC_LIBRARY_PATH" h <- fmap return getHomeDirectory let paths = h ++ ["/usr/local","/usr"] bases = ["/lib","/share"] vers = ["/ajhc-" ++ shortVersion, "/ajhc"] dat <- getDataFileNameMaybe "lib" return $ nub $ maybe [] (tokens (':' ==)) ps ++ [ p ++ b ++ v | p <- paths, v <- vers, b <- bases ] ++ [d ++ v | d <- [libdir,datadir], v <- vers] ++ [libraryInstall] ++ maybeToList dat class Monad m => OptionMonad m where getOptions :: m Opt getOptions = return options instance OptionMonad Identity newtype OptT m a = OptT (ReaderT Opt m a) deriving(MonadIO,Monad,Functor,MonadTrans) type OptM = OptT Identity instance Monad m => OptionMonad (OptT m) where getOptions = OptT ask withOptions :: Opt -> OptM a -> a withOptions opt (OptT x) = runIdentity (runReaderT x opt) withOptionsT :: Opt -> OptT m a -> m a withOptionsT opt (OptT x) = runReaderT x opt outputName = fromMaybe "hs.out" (optOutName options) flagOpt :: OptionMonad m => FlagOpts.Flag -> m Bool flagOpt flag = do opt <- getOptions return (flag `S.member` optFOptsSet opt) getArgString = do name <- getProgName args <- getArguments return (simpleQuote (name:args),head $ lines versionString)