{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
module Data.Array.Accelerate.Examples.Internal.ParseArgs (
parseArgs,
Options, optBackend, optTest, optBenchmark, optCodespeed, optHostname,
optVariant, optHelp, optCriterion, optTestFramework,
module System.Console.GetOpt,
module Data.Array.Accelerate.Examples.Internal.Backend,
) where
import Data.Array.Accelerate.Debug ( accInit )
import Data.Array.Accelerate.Examples.Internal.Backend
import qualified Data.Array.Accelerate.Examples.Internal.Criterion.Config as Criterion
import qualified Data.Array.Accelerate.Examples.Internal.TestFramework.Config as TestFramework
import Data.List
import Data.Label
import Data.Monoid
import Control.Monad
import System.Exit
import System.Environment
import System.Console.GetOpt
import Text.PrettyPrint.ANSI.Leijen
import Prelude
#ifdef ACCELERATE_ENABLE_CODESPEED
import Data.Char
import Network.BSD
import System.IO.Unsafe
#endif
data Options = Options
{
_optBackend :: Backend
, _optTest :: Bool
, _optBenchmark :: Bool
, _optCodespeed :: Maybe String
, _optHostname :: String
, _optVariant :: String
, _optHelp :: Bool
, _optTestFramework :: TestFramework.Config
, _optCriterion :: Criterion.Config
}
$(mkLabels [''Options])
-- Options parsing infrastructure
-- ------------------------------
defaultOptions :: Options
defaultOptions = Options
{
_optBackend = backend
, _optTest = True
#ifndef ACCELERATE_ENABLE_GUI
, _optBenchmark = True
#else
, _optBenchmark = False
#endif
, _optCodespeed = Nothing
, _optVariant = variant
, _optHostname = hostname
, _optHelp = False
, _optCriterion = Criterion.defaultConfig
, _optTestFramework = TestFramework.defaultConfig backend
}
where
backend = defaultBackend
#ifdef ACCELERATE_ENABLE_CODESPEED
variant = "accelerate-" ++ show (_optBackend defaultOptions)
hostname = unsafePerformIO $ do
h <- getHostName
return $ map toLower $ takeWhile (/= '.') h
#else
variant = []
hostname = []
#endif
options :: [OptDescr (Options -> Options)]
options = availableBackends optBackend ++
[
Option [] ["benchmark"]
(OptArg (set optBenchmark . maybe True read) "BOOL")
(describe optBenchmark "enable benchmark mode")
, Option [] ["test"]
(OptArg (set optTest . maybe True read) "BOOL")
(describe optTest "enable test mode")
#ifdef ACCELERATE_ENABLE_CODESPEED
, Option [] ["upload"]
(ReqArg (set optCodespeed . Just) "URL")
"address of codespeed server to upload benchmark results"
, Option [] ["hostname"]
(ReqArg (set optHostname) "HOSTNAME")
(describe optHostname "hostname to use for reported results")
, Option [] ["variant"]
(ReqArg (set optVariant) "STRING")
(describe optVariant "variant to use for reported results")
#endif
, Option "h?" ["help"]
(NoArg (set optHelp True))
"show help message"
]
where
describe f msg
= msg ++ " (" ++ show (get f defaultOptions) ++ ")"
-- | Format a (console) string as bold text. Assume the user has configured
-- their terminal colours to something that looks good (and avoids the light vs.
-- dark background debate).
--
sectionHeader :: String -> String
sectionHeader = show . bold . text
-- | Generate the list of available (and the selected) Accelerate backends.
--
fancyHeader :: Options -> [String] -> [String] -> String
fancyHeader opts header footer = intercalate "\n" (header ++ body ++ footer)
where
active this = if this == show (get optBackend opts) then "*" else ""
(ss,bs,ds) = unzip3 $ map (\(b,d) -> (active b, b, d)) $ concatMap extract (availableBackends optBackend)
table = zipWith3 paste (sameLen ss) (sameLen bs) ds
paste x y z = " " ++ x ++ " " ++ y ++ " " ++ z
sameLen xs = flushLeft ((maximum . map length) xs) xs
flushLeft n xs = [ take n (x ++ repeat ' ') | x <- xs ]
--
extract (Option _ los _ descr) =
let losFmt = intercalate ", " los
in case lines descr of
[] -> [(losFmt, "")]
(x:xs) -> (losFmt, x) : [ ("",x') | x' <- xs ]
--
body = "Available backends:" : table
-- | Strip the short option arguments that have a required or optional argument.
-- Because we use several different options groups, the flag and its argument
-- get separated. The user is required to instead use a --flag=value format.
--
stripShortOpts :: [OptDescr a] -> [OptDescr a]
stripShortOpts = map strip
where
strip (Option _ long arg@(ReqArg _ _) desc) = Option [] long arg desc
strip (Option _ long arg@(OptArg _ _) desc) = Option [] long arg desc
strip x = x
-- | Strip the operational part of the options description structure, so that
-- the option lists can be combined for the purposes of displaying the usage
-- information.
--
_stripArgDescr :: [OptDescr a] -> [OptDescr b]
_stripArgDescr = map strip
where
strip (Option s l (NoArg _) d) = Option s l (NoArg undefined) d
strip (Option s l (ReqArg _ a) d) = Option s l (ReqArg undefined a) d
strip (Option s l (OptArg _ a) d) = Option s l (OptArg undefined a) d
-- | Extract the option flags
--
extractOptFlags :: [OptDescr a] -> [String]
extractOptFlags = concatMap extract
where
extract (Option short long _ _) = map (\s -> '-':s:[]) short ++ map ("--"++) long
-- | Process the command line arguments and return a tuple consisting of the
-- user options structure, accelerate-examples options (including options for
-- criterion and test-framework), and a list of unrecognised command line
-- arguments.
--
-- Since criterion and test-framework both bail if they encounter unrecognised
-- options, we run getOpt' ourselves. This means that the error messages might
-- be slightly different.
--
-- Any command line arguments following a "--" are not processed, but are
-- included as part of the unprocessed arguments returned on output.
--
parseArgs :: [OptDescr (config -> config)] -- ^ the user option descriptions
-> config -- ^ user default option set
-> [String] -- ^ header text
-> [String] -- ^ footer text
-> IO (config, Options, [String])
parseArgs programOptions programConfig header footer = do
accInit
args <- getArgs
let
-- The option "--list" is ambiguous. It is handled by criterion only when
-- benchmarks are being run, but if passed to test framework during option
-- processing it will be consumed and treated as the "--list-tests" flag.
--
(argv, rest) =
let (x, y) = span (/= "--") args
(ls, x') = partition (== "--list") x
in
(x', ls ++ dropWhile (== "--") y)
criterionOptions = stripShortOpts $ Criterion.defaultOptions ++ Criterion.extraOptions
testframeworkOptions = stripShortOpts $ TestFramework.defaultOptions
helpMsg [] = helpMsg'
helpMsg err = unlines [ concat err, helpMsg' ]
section (sectionHeader -> str) opts = usageInfo str opts
helpMsg' = unlines
[ fancyHeader defaultOptions header []
, ""
, section "Options:" options
, section "Program options:" programOptions
, section "Criterion options:" criterionOptions
, Criterion.regressHelp
, ""
, section "Test-Framework options:" testframeworkOptions
]
-- In the first round process options for the user program. Processing the
-- user options first means that we can still handle any short or long options
-- that take arguments but which were not joined with an equals sign; e.g.
--
-- "-f blah" or "--foo blah".
--
-- Following this phase we must disallow short options with arguments, and
-- only long options in the form "--foo=blah" will work correctly. This is
-- because getOpt is splitting the unrecognised options ("--foo") from the
-- non-option arguments ("blah").
--
(c1,non,u1) <- case getOpt' Permute programOptions argv of
(opts,n,u,[]) -> case foldr id programConfig opts of
conf -> return (conf,n,u)
(_,_,_,err) -> error (helpMsg err)
-- The standard accelerate-examples options
--
(c2,u2) <- case getOpt' Permute options u1 of
(opts,_,u,[]) -> return (foldr id defaultOptions opts, u)
(_,_,_,err) -> error (helpMsg err)
-- Criterion options
--
(c3,u3) <- case getOpt' Permute Criterion.defaultOptions u2 of
(opts,_,u,[]) -> return (foldr id Criterion.defaultConfig opts, u)
(_,_,_,err) -> error (helpMsg err)
-- Test framework options
--
(c4,u4) <- case getOpt' Permute testframeworkOptions u3 of
(opts,_,u,[]) | Just os <- sequence opts
-> return (mconcat (TestFramework.defaultConfig (_optBackend c2) : os), u)
(_,_,_,err) -> error (helpMsg err)
-- Show the help message if that was requested. This is done last so that the
-- header is not shown twice in the case of a subsequent options parse error.
--
if get optHelp c2
then putStr (helpMsg []) >> exitSuccess
else putStrLn (fancyHeader c2 header footer)
-- Issue a warning if there are any unrecognised options. Criterion will error
-- if we are in benchmark mode and there is anything it doesn't understand,
-- and the error message is somewhat confusing.
--
let eco = extractOptFlags Criterion.extraOptions
(yes,no) = partition (\x -> takeWhile (/= '=') x `elem` eco) u4
unless (null no) $ do
putStrLn "Warning: unrecognised options"
putStrLn $ unlines $ map (" "++) no
return (c1, c2 { _optCriterion = c3, _optTestFramework = c4 }, yes ++ non ++ rest)