    {-# LANGUAGE CPP           #-}
    {-# LANGUAGE DeriveGeneric #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Distribution.Simple.Setup
    -- Copyright   :  Isaac Jones 2003-2004
    --                Duncan Coutts 2007
    -- License     :  BSD3
    --
    -- Maintainer  :  cabal-devel@haskell.org
    -- Portability :  portable
    --
    -- This is a big module, but not very complicated. The code is very regular
    -- and repetitive. It defines the command line interface for all the Cabal
    -- commands. For each command (like @configure@, @build@ etc) it defines a type
    -- that holds all the flags, the default set of flags and a 'CommandUI' that
    -- maps command line flags to and from the corresponding flags type.
    --
    -- All the flags types are instances of 'Monoid', see
    -- <http://www.haskell.org/pipermail/cabal-devel/2007-December/001509.html>
    -- for an explanation.
    --
    -- The types defined here get used in the front end and especially in
    -- @cabal-install@ which has to do quite a bit of manipulating sets of command
    -- line flags.
    --
    -- This is actually relatively nice, it works quite well. The main change it
    -- needs is to unify it with the code for managing sets of fields that can be
    -- read and written from files. This would allow us to save configure flags in
    -- config files.

    {-# LANGUAGE CPP #-}

    module Distribution.Simple.Setup (

      GlobalFlags(..),   emptyGlobalFlags,   defaultGlobalFlags,   globalCommand,
      ConfigFlags(..),   emptyConfigFlags,   defaultConfigFlags,   configureCommand,
      configAbsolutePaths, readPackageDbList, showPackageDbList,
      CopyFlags(..),     emptyCopyFlags,     defaultCopyFlags,     copyCommand,
      InstallFlags(..),  emptyInstallFlags,  defaultInstallFlags,  installCommand,
      HaddockFlags(..),  emptyHaddockFlags,  defaultHaddockFlags,  haddockCommand,
      HscolourFlags(..), emptyHscolourFlags, defaultHscolourFlags, hscolourCommand,
      BuildFlags(..),    emptyBuildFlags,    defaultBuildFlags,    buildCommand,
      buildVerbose,
      ReplFlags(..),                         defaultReplFlags,     replCommand,
      CleanFlags(..),    emptyCleanFlags,    defaultCleanFlags,    cleanCommand,
      RegisterFlags(..), emptyRegisterFlags, defaultRegisterFlags, registerCommand,
                                                                   unregisterCommand,
      SDistFlags(..),    emptySDistFlags,    defaultSDistFlags,    sdistCommand,
      TestFlags(..),     emptyTestFlags,     defaultTestFlags,     testCommand,
      TestShowDetails(..),
      BenchmarkFlags(..), emptyBenchmarkFlags,
      defaultBenchmarkFlags, benchmarkCommand,
      CopyDest(..),
      configureArgs, configureOptions, configureCCompiler, configureLinker,
      buildOptions, haddockOptions, installDirsOptions,
      programConfigurationOptions, programConfigurationPaths',

      defaultDistPref,

      Flag(..),
      toFlag,
      fromFlag,
      fromFlagOrDefault,
      flagToMaybe,
      flagToList,
      boolOpt, boolOpt', trueArg, falseArg, optionVerbosity, optionNumJobs ) where

    import Distribution.Compiler ()
    import Distribution.ReadE
    import Distribution.Text
             ( Text(..), display )
    import qualified Distribution.Compat.ReadP as Parse
    import qualified Text.PrettyPrint as Disp
    import Distribution.ModuleName
    import Distribution.Package ( Dependency(..)
                                , PackageName
                                , InstalledPackageId )
    import Distribution.PackageDescription
             ( FlagName(..), FlagAssignment )
    import Distribution.Simple.Command hiding (boolOpt, boolOpt')
    import qualified Distribution.Simple.Command as Command
    import Distribution.Simple.Compiler
             ( CompilerFlavor(..), defaultCompilerFlavor, PackageDB(..)
             , DebugInfoLevel(..), flagToDebugInfoLevel
             , OptimisationLevel(..), flagToOptimisationLevel
             , absolutePackageDBPath )
    import Distribution.Simple.Utils
             ( wrapText, wrapLine, lowercase, intercalate )
    import Distribution.Simple.Program (Program(..), ProgramConfiguration,
                                 requireProgram,
                                 programInvocation, progInvokePath, progInvokeArgs,
                                 knownPrograms,
                                 addKnownProgram, emptyProgramConfiguration,
                                 haddockProgram, ghcProgram, gccProgram, ldProgram)
    import Distribution.Simple.InstallDirs
             ( InstallDirs(..), CopyDest(..),
               PathTemplate, toPathTemplate, fromPathTemplate )
    import Distribution.Verbosity
    import Distribution.Utils.NubList

    import Control.Monad (liftM)
    import Distribution.Compat.Binary (Binary)
    import Data.List   ( sort )
    import Data.Char   ( isSpace, isAlpha )
    #if __GLASGOW_HASKELL__ < 710
    import Data.Monoid ( Monoid(..) )
    #endif
    import GHC.Generics (Generic)

    -- FIXME Not sure where this should live
    defaultDistPref :: FilePath
    defaultDistPref = "dist"

    -- ------------------------------------------------------------
    -- * Flag type
    -- ------------------------------------------------------------

    -- | All flags are monoids, they come in two flavours:
    --
    -- 1. list flags eg
    --
    -- > --ghc-option=foo --ghc-option=bar
    --
    -- gives us all the values ["foo", "bar"]
    --
    -- 2. singular value flags, eg:
    --
    -- > --enable-foo --disable-foo
    --
    -- gives us Just False
    -- So this Flag type is for the latter singular kind of flag.
    -- Its monoid instance gives us the behaviour where it starts out as
    -- 'NoFlag' and later flags override earlier ones.
    --
    data Flag a = Flag a | NoFlag deriving (Eq, Generic, Show, Read)

    instance Binary a => Binary (Flag a)

    instance Functor Flag where
      fmap f (Flag x) = Flag (f x)
      fmap _ NoFlag  = NoFlag

    instance Monoid (Flag a) where
      mempty = NoFlag
      _ `mappend` f@(Flag _) = f
      f `mappend` NoFlag     = f

    instance Bounded a => Bounded (Flag a) where
      minBound = toFlag minBound
      maxBound = toFlag maxBound

    instance Enum a => Enum (Flag a) where
      fromEnum = fromEnum . fromFlag
      toEnum   = toFlag   . toEnum
      enumFrom (Flag a) = map toFlag . enumFrom $ a
      enumFrom _        = []
      enumFromThen (Flag a) (Flag b) = toFlag `map` enumFromThen a b
      enumFromThen _        _        = []
      enumFromTo   (Flag a) (Flag b) = toFlag `map` enumFromTo a b
      enumFromTo   _        _        = []
      enumFromThenTo (Flag a) (Flag b) (Flag c) = toFlag `map` enumFromThenTo a b c
      enumFromThenTo _        _        _        = []

    toFlag :: a -> Flag a
    toFlag = Flag

    fromFlag :: Flag a -> a
    fromFlag (Flag x) = x
    fromFlag NoFlag   = error "fromFlag NoFlag. Use fromFlagOrDefault"

    fromFlagOrDefault :: a -> Flag a -> a
    fromFlagOrDefault _   (Flag x) = x
    fromFlagOrDefault def NoFlag   = def

    flagToMaybe :: Flag a -> Maybe a
    flagToMaybe (Flag x) = Just x
    flagToMaybe NoFlag   = Nothing

    flagToList :: Flag a -> [a]
    flagToList (Flag x) = [x]
    flagToList NoFlag   = []

    allFlags :: [Flag Bool] -> Flag Bool
    allFlags flags = if all (\f -> fromFlagOrDefault False f) flags
                     then Flag True
                     else NoFlag

    -- ------------------------------------------------------------
    -- * Global flags
    -- ------------------------------------------------------------

    -- In fact since individual flags types are monoids and these are just sets of
    -- flags then they are also monoids pointwise. This turns out to be really
    -- useful. The mempty is the set of empty flags and mappend allows us to
    -- override specific flags. For example we can start with default flags and
    -- override with the ones we get from a file or the command line, or both.

    -- | Flags that apply at the top level, not to any sub-command.
    data GlobalFlags = GlobalFlags {
        globalVersion        :: Flag Bool,
        globalNumericVersion :: Flag Bool
      }

    defaultGlobalFlags :: GlobalFlags
    defaultGlobalFlags  = GlobalFlags {
        globalVersion        = Flag False,
        globalNumericVersion = Flag False
      }

    globalCommand :: [Command action] -> CommandUI GlobalFlags
    globalCommand commands = CommandUI
      { commandName         = ""
      , commandSynopsis     = ""
      , commandUsage        = \pname ->
             "This Setup program uses the Haskell Cabal Infrastructure.\n"
          ++ "See http://www.haskell.org/cabal/ for more information.\n"
          ++ "\n"
          ++ "Usage: " ++ pname ++ " [GLOBAL FLAGS] [COMMAND [FLAGS]]\n"
      , commandDescription = Just $ \pname ->
          let
            commands' = commands ++ [commandAddAction helpCommandUI undefined]
            cmdDescs = getNormalCommandDescriptions commands'
            maxlen    = maximum $ [length name | (name, _) <- cmdDescs]
            align str = str ++ replicate (maxlen - length str) ' '
          in
             "Commands:\n"
          ++ unlines [ "  " ++ align name ++ "    " ++ description
                     | (name, description) <- cmdDescs ]
          ++ "\n"
          ++ "For more information about a command use\n"
          ++ "  " ++ pname ++ " COMMAND --help\n\n"
          ++ "Typical steps for installing Cabal packages:\n"
          ++ concat [ "  " ++ pname ++ " " ++ x ++ "\n"
                    | x <- ["configure", "build", "install"]]
      , commandNotes        = Nothing
      , commandDefaultFlags = defaultGlobalFlags
      , commandOptions      = \_ ->
          [option ['V'] ["version"]
             "Print version information"
             globalVersion (\v flags -> flags { globalVersion = v })
             trueArg
          ,option [] ["numeric-version"]
             "Print just the version number"
             globalNumericVersion (\v flags -> flags { globalNumericVersion = v })
             trueArg
          ]
      }

    emptyGlobalFlags :: GlobalFlags
    emptyGlobalFlags = mempty

    instance Monoid GlobalFlags where
      mempty = GlobalFlags {
        globalVersion        = mempty,
        globalNumericVersion = mempty
      }
      mappend a b = GlobalFlags {
        globalVersion        = combine globalVersion,
        globalNumericVersion = combine globalNumericVersion
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Config flags
    -- ------------------------------------------------------------

    -- | Flags to @configure@ command.
    --
    -- IMPORTANT: every time a new flag is added, 'D.C.Setup.filterConfigureFlags'
    -- should be updated.
    data ConfigFlags = ConfigFlags {
        --FIXME: the configPrograms is only here to pass info through to configure
        -- because the type of configure is constrained by the UserHooks.
        -- when we change UserHooks next we should pass the initial
        -- ProgramConfiguration directly and not via ConfigFlags
        configPrograms      :: ProgramConfiguration, -- ^All programs that cabal may
                                                     -- run

        configProgramPaths  :: [(String, FilePath)], -- ^user specified programs paths
        configProgramArgs   :: [(String, [String])], -- ^user specified programs args
        configProgramPathExtra :: NubList FilePath,  -- ^Extend the $PATH
        configHcFlavor      :: Flag CompilerFlavor, -- ^The \"flavor\" of the
                                                    -- compiler, such as GHC or
                                                    -- JHC.
        configHcPath        :: Flag FilePath, -- ^given compiler location
        configHcPkg         :: Flag FilePath, -- ^given hc-pkg location
        configVanillaLib    :: Flag Bool,     -- ^Enable vanilla library
        configProfLib       :: Flag Bool,     -- ^Enable profiling in the library
        configSharedLib     :: Flag Bool,     -- ^Build shared library
        configDynExe        :: Flag Bool,     -- ^Enable dynamic linking of the
                                              -- executables.
        configProfExe       :: Flag Bool,     -- ^Enable profiling in the
                                              -- executables.
        configConfigureArgs :: [String],      -- ^Extra arguments to @configure@
        configOptimization  :: Flag OptimisationLevel,  -- ^Enable optimization.
        configProgPrefix    :: Flag PathTemplate, -- ^Installed executable prefix.
        configProgSuffix    :: Flag PathTemplate, -- ^Installed executable suffix.
        configInstallDirs   :: InstallDirs (Flag PathTemplate), -- ^Installation
                                                                -- paths
        configScratchDir    :: Flag FilePath,
        configExtraLibDirs  :: [FilePath],   -- ^ path to search for extra libraries
        configExtraIncludeDirs :: [FilePath],   -- ^ path to search for header files

        configDistPref :: Flag FilePath, -- ^"dist" prefix
        configVerbosity :: Flag Verbosity, -- ^verbosity level
        configUserInstall :: Flag Bool,    -- ^The --user\/--global flag
        configPackageDBs :: [Maybe PackageDB], -- ^Which package DBs to use
        configGHCiLib   :: Flag Bool,      -- ^Enable compiling library for GHCi
        configSplitObjs :: Flag Bool,      -- ^Enable -split-objs with GHC
        configStripExes :: Flag Bool,      -- ^Enable executable stripping
        configStripLibs :: Flag Bool,      -- ^Enable library stripping
        configConstraints :: [Dependency], -- ^Additional constraints for
                                           -- dependencies.
        configDependencies :: [(PackageName, InstalledPackageId)],
        configInstantiateWith :: [(ModuleName, (InstalledPackageId, ModuleName))],
          -- ^The packages depended on.
        configConfigurationsFlags :: FlagAssignment,
        configTests               :: Flag Bool, -- ^Enable test suite compilation
        configBenchmarks          :: Flag Bool, -- ^Enable benchmark compilation
        configCoverage :: Flag Bool, -- ^Enable program coverage
        configLibCoverage :: Flag Bool, -- ^Enable program coverage (deprecated)
        configExactConfiguration  :: Flag Bool,
          -- ^All direct dependencies and flags are provided on the command line by
          -- the user via the '--dependency' and '--flags' options.
        configFlagError :: Flag String,
          -- ^Halt and show an error message indicating an error in flag assignment
        configRelocatable :: Flag Bool, -- ^ Enable relocatable package built
        configDebugInfo :: Flag DebugInfoLevel  -- ^ Emit debug info.
      }
      deriving (Generic, Read, Show)

    instance Binary ConfigFlags

    configAbsolutePaths :: ConfigFlags -> IO ConfigFlags
    configAbsolutePaths f =
      (\v -> f { configPackageDBs = v })
      `liftM` mapM (maybe (return Nothing) (liftM Just . absolutePackageDBPath))
      (configPackageDBs f)

    defaultConfigFlags :: ProgramConfiguration -> ConfigFlags
    defaultConfigFlags progConf = emptyConfigFlags {
        configPrograms     = progConf,
        configHcFlavor     = maybe NoFlag Flag defaultCompilerFlavor,
        configVanillaLib   = Flag True,
        configProfLib      = NoFlag,
        configSharedLib    = NoFlag,
        configDynExe       = Flag False,
        configProfExe      = NoFlag,
        configOptimization = Flag NormalOptimisation,
        configProgPrefix   = Flag (toPathTemplate ""),
        configProgSuffix   = Flag (toPathTemplate ""),
        configDistPref     = Flag defaultDistPref,
        configVerbosity    = Flag normal,
        configUserInstall  = Flag False,           --TODO: reverse this
    #if defined(mingw32_HOST_OS)
        -- See #1589.
        configGHCiLib      = Flag True,
    #else
        configGHCiLib      = NoFlag,
    #endif
        configSplitObjs    = Flag False, -- takes longer, so turn off by default
        configStripExes    = Flag True,
        configStripLibs    = Flag True,
        configTests        = Flag False,
        configBenchmarks   = Flag False,
        configCoverage     = Flag False,
        configLibCoverage  = NoFlag,
        configExactConfiguration = Flag False,
        configFlagError    = NoFlag,
        configRelocatable  = Flag False,
        configDebugInfo    = Flag NoDebugInfo
      }

    configureCommand :: ProgramConfiguration -> CommandUI ConfigFlags
    configureCommand progConf = CommandUI
      { commandName         = "configure"
      , commandSynopsis     = "Prepare to build the package."
      , commandDescription  = Just $ \_ -> wrapText $
             "Configure how the package is built by setting "
          ++ "package (and other) flags.\n"
          ++ "\n"
          ++ "The configuration affects several other commands, "
          ++ "including build, test, bench, run, repl.\n"
      , commandNotes        = Just (\_ -> programFlagsDescription progConf)
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " configure [FLAGS]\n"
      , commandDefaultFlags = defaultConfigFlags progConf
      , commandOptions      = \showOrParseArgs ->
             configureOptions showOrParseArgs
          ++ programConfigurationPaths   progConf showOrParseArgs
               configProgramPaths (\v fs -> fs { configProgramPaths = v })
          ++ programConfigurationOption progConf showOrParseArgs
               configProgramArgs (\v fs -> fs { configProgramArgs = v })
          ++ programConfigurationOptions progConf showOrParseArgs
               configProgramArgs (\v fs -> fs { configProgramArgs = v })
      }

    configureOptions :: ShowOrParseArgs -> [OptionField ConfigFlags]
    configureOptions showOrParseArgs =
          [optionVerbosity configVerbosity
           (\v flags -> flags { configVerbosity = v })
          ,optionDistPref
             configDistPref (\d flags -> flags { configDistPref = d })
             showOrParseArgs

          ,option [] ["compiler"] "compiler"
             configHcFlavor (\v flags -> flags { configHcFlavor = v })
             (choiceOpt [ (Flag GHC,   ("g", ["ghc"]),   "compile with GHC")
                        , (Flag GHCJS, ([] , ["ghcjs"]), "compile with GHCJS")
                        , (Flag JHC,   ([] , ["jhc"]),   "compile with JHC")
                        , (Flag LHC,   ([] , ["lhc"]),   "compile with LHC")
                        , (Flag UHC,   ([] , ["uhc"]),   "compile with UHC")
                        -- "haskell-suite" compiler id string will be replaced
                        -- by a more specific one during the configure stage
                        , (Flag (HaskellSuite "haskell-suite"), ([] , ["haskell-suite"]),
                            "compile with a haskell-suite compiler")])

          ,option "w" ["with-compiler"]
             "give the path to a particular compiler"
             configHcPath (\v flags -> flags { configHcPath = v })
             (reqArgFlag "PATH")

          ,option "" ["with-hc-pkg"]
             "give the path to the package tool"
             configHcPkg (\v flags -> flags { configHcPkg = v })
             (reqArgFlag "PATH")
          ]
       ++ map liftInstallDirs installDirsOptions
       ++ [option "" ["program-prefix"]
              "prefix to be applied to installed executables"
              configProgPrefix
              (\v flags -> flags { configProgPrefix = v })
              (reqPathTemplateArgFlag "PREFIX")

          ,option "" ["program-suffix"]
              "suffix to be applied to installed executables"
              configProgSuffix (\v flags -> flags { configProgSuffix = v } )
              (reqPathTemplateArgFlag "SUFFIX")

          ,option "" ["library-vanilla"]
             "Vanilla libraries"
             configVanillaLib (\v flags -> flags { configVanillaLib = v })
             (boolOpt [] [])

          ,option "p" ["library-profiling"]
             "Library profiling"
             configProfLib (\v flags -> flags { configProfLib = v })
             (boolOpt "p" [])

          ,option "" ["shared"]
             "Shared library"
             configSharedLib (\v flags -> flags { configSharedLib = v })
             (boolOpt [] [])

          ,option "" ["executable-dynamic"]
             "Executable dynamic linking"
             configDynExe (\v flags -> flags { configDynExe = v })
             (boolOpt [] [])

          ,option "" ["profiling"]
             "Executable profiling (requires library profiling)"
             -- HACK: See #2409. Thankfully, this is 1.22-specific.
             (\flags ->
               case (configProfLib flags, configProfExe flags) of
                 (Flag a, Flag b)
                   | (a == b)
                   && ("cabalConfProf", "/TRUE") `elem` configProgramPaths flags
                        -> configProfExe flags
                 _      -> NoFlag)
             (\v flags -> flags
                   { configProfLib = v, configProfExe = v
                   , configProgramPaths = ("cabalConfProf", "/TRUE")
                   : configProgramPaths flags })
             (boolOpt [] [])

          ,option "" ["executable-profiling"]
             "Executable profiling (DEPRECATED)"
             configProfExe (\v flags -> flags { configProfExe = v })
             (boolOpt [] [])

          ,multiOption "optimization"
             configOptimization (\v flags -> flags { configOptimization = v })
             [optArg' "n" (Flag . flagToOptimisationLevel)
                         (\f -> case f of
                                  Flag NoOptimisation      -> []
                                  Flag NormalOptimisation  -> [Nothing]
                                  Flag MaximumOptimisation -> [Just "2"]
                                  _                        -> [])
                     "O" ["enable-optimization","enable-optimisation"]
                     "Build with optimization (n is 0--2, default is 1)",
              noArg (Flag NoOptimisation) []
                    ["disable-optimization","disable-optimisation"]
                    "Build without optimization"
             ]

          ,multiOption "debug-info"
             configDebugInfo (\v flags -> flags { configDebugInfo = v })
             [optArg' "n" (Flag . flagToDebugInfoLevel)
                         (\f -> case f of
                                  Flag NoDebugInfo      -> []
                                  Flag MinimalDebugInfo -> [Just "1"]
                                  Flag NormalDebugInfo  -> [Nothing]
                                  Flag MaximalDebugInfo -> [Just "3"]
                                  _                     -> [])
                     "" ["enable-debug-info"]
                     "Emit debug info (n is 0--3, default is 0)",
              noArg (Flag NoDebugInfo) []
                    ["disable-debug-info"]
                    "Don't emit debug info"
             ]

          ,option "" ["library-for-ghci"]
             "compile library for use with GHCi"
             configGHCiLib (\v flags -> flags { configGHCiLib = v })
             (boolOpt [] [])

          ,option "" ["split-objs"]
             "split library into smaller objects to reduce binary sizes (GHC 6.6+)"
             configSplitObjs (\v flags -> flags { configSplitObjs = v })
             (boolOpt [] [])

          ,option "" ["executable-stripping"]
             "strip executables upon installation to reduce binary sizes"
             configStripExes (\v flags -> flags { configStripExes = v })
             (boolOpt [] [])

          ,option "" ["library-stripping"]
             "strip libraries upon installation to reduce binary sizes"
             configStripLibs (\v flags -> flags { configStripLibs = v })
             (boolOpt [] [])

          ,option "" ["configure-option"]
             "Extra option for configure"
             configConfigureArgs (\v flags -> flags { configConfigureArgs = v })
             (reqArg' "OPT" (\x -> [x]) id)

          ,option "" ["user-install"]
             "doing a per-user installation"
             configUserInstall (\v flags -> flags { configUserInstall = v })
             (boolOpt' ([],["user"]) ([], ["global"]))

          ,option "" ["package-db"]
             "Use a given package database (to satisfy dependencies and register in). May be a specific file, 'global', 'user' or 'clear'."
             configPackageDBs (\v flags -> flags { configPackageDBs = v })
             (reqArg' "DB" readPackageDbList showPackageDbList)

          ,option "f" ["flags"]
             "Force values for the given flags in Cabal conditionals in the .cabal file.  E.g., --flags=\"debug -usebytestrings\" forces the flag \"debug\" to true and \"usebytestrings\" to false."
             configConfigurationsFlags (\v flags -> flags { configConfigurationsFlags = v })
             (reqArg' "FLAGS" readFlagList showFlagList)

          ,option "" ["extra-include-dirs"]
             "A list of directories to search for header files"
             configExtraIncludeDirs (\v flags -> flags {configExtraIncludeDirs = v})
             (reqArg' "PATH" (\x -> [x]) id)

          ,option "" ["extra-lib-dirs"]
             "A list of directories to search for external libraries"
             configExtraLibDirs (\v flags -> flags {configExtraLibDirs = v})
             (reqArg' "PATH" (\x -> [x]) id)

          ,option "" ["extra-prog-path"]
             "A list of directories to search for required programs (in addition to the normal search locations)"
             configProgramPathExtra (\v flags -> flags {configProgramPathExtra = v})
             (reqArg' "PATH" (\x -> toNubList [x]) fromNubList)

          ,option "" ["constraint"]
             "A list of additional constraints on the dependencies."
             configConstraints (\v flags -> flags { configConstraints = v})
             (reqArg "DEPENDENCY"
                     (readP_to_E (const "dependency expected") ((\x -> [x]) `fmap` parse))
                     (map (\x -> display x)))

          ,option "" ["dependency"]
             "A list of exact dependencies. E.g., --dependency=\"void=void-0.5.8-177d5cdf20962d0581fe2e4932a6c309\""
             configDependencies (\v flags -> flags { configDependencies = v})
             (reqArg "NAME=ID"
                     (readP_to_E (const "dependency expected") ((\x -> [x]) `fmap` parseDependency))
                     (map (\x -> display (fst x) ++ "=" ++ display (snd x))))

          ,option "" ["instantiate-with"]
             "A mapping of signature names to concrete module instantiations. E.g., --instantiate-with=\"Map=Data.Map.Strict@containers-0.5.5.1-inplace\""
             configInstantiateWith (\v flags -> flags { configInstantiateWith = v })
             (reqArg "NAME=PKG:MOD"
                     (readP_to_E (const "signature mapping expected") ((\x -> [x]) `fmap` parseHoleMapEntry))
                     (map (\(n,(p,m)) -> display n ++ "=" ++ display m ++ "@" ++ display p)))

          ,option "" ["tests"]
             "dependency checking and compilation for test suites listed in the package description file."
             configTests (\v flags -> flags { configTests = v })
             (boolOpt [] [])

          ,option "" ["coverage"]
             "build package with Haskell Program Coverage. (GHC only)"
             configCoverage (\v flags -> flags { configCoverage = v })
             (boolOpt [] [])

          ,option "" ["library-coverage"]
             "build package with Haskell Program Coverage. (GHC only) (DEPRECATED)"
             configLibCoverage (\v flags -> flags { configLibCoverage = v })
             (boolOpt [] [])

          ,option "" ["exact-configuration"]
             "All direct dependencies and flags are provided on the command line."
             configExactConfiguration
             (\v flags -> flags { configExactConfiguration = v })
             trueArg

          ,option "" ["benchmarks"]
             "dependency checking and compilation for benchmarks listed in the package description file."
             configBenchmarks (\v flags -> flags { configBenchmarks = v })
             (boolOpt [] [])

          ,option "" ["relocatable"]
             "building a package that is relocatable. (GHC only)"
             configRelocatable (\v flags -> flags { configRelocatable = v})
             (boolOpt [] [])
          ]
      where
        readFlagList :: String -> FlagAssignment
        readFlagList = map tagWithValue . words
          where tagWithValue ('-':fname) = (FlagName (lowercase fname), False)
                tagWithValue fname       = (FlagName (lowercase fname), True)

        showFlagList :: FlagAssignment -> [String]
        showFlagList fs = [ if not set then '-':fname else fname
                          | (FlagName fname, set) <- fs]

        liftInstallDirs =
          liftOption configInstallDirs (\v flags -> flags { configInstallDirs = v })

        reqPathTemplateArgFlag title _sf _lf d get set =
          reqArgFlag title _sf _lf d
            (fmap fromPathTemplate . get) (set . fmap toPathTemplate)

    readPackageDbList :: String -> [Maybe PackageDB]
    readPackageDbList "clear"  = [Nothing]
    readPackageDbList "global" = [Just GlobalPackageDB]
    readPackageDbList "user"   = [Just UserPackageDB]
    readPackageDbList other    = [Just (SpecificPackageDB other)]

    showPackageDbList :: [Maybe PackageDB] -> [String]
    showPackageDbList = map showPackageDb
      where
        showPackageDb Nothing                       = "clear"
        showPackageDb (Just GlobalPackageDB)        = "global"
        showPackageDb (Just UserPackageDB)          = "user"
        showPackageDb (Just (SpecificPackageDB db)) = db

    parseDependency :: Parse.ReadP r (PackageName, InstalledPackageId)
    parseDependency = do
      x <- parse
      _ <- Parse.char '='
      y <- parse
      return (x, y)

    parseHoleMapEntry :: Parse.ReadP r (ModuleName, (InstalledPackageId, ModuleName))
    parseHoleMapEntry = do
      x <- parse
      _ <- Parse.char '='
      y <- parse
      _ <- Parse.char '@'
      z <- parse
      return (x, (z, y))

    installDirsOptions :: [OptionField (InstallDirs (Flag PathTemplate))]
    installDirsOptions =
      [ option "" ["prefix"]
          "bake this prefix in preparation of installation"
          prefix (\v flags -> flags { prefix = v })
          installDirArg

      , option "" ["bindir"]
          "installation directory for executables"
          bindir (\v flags -> flags { bindir = v })
          installDirArg

      , option "" ["libdir"]
          "installation directory for libraries"
          libdir (\v flags -> flags { libdir = v })
          installDirArg

      , option "" ["libsubdir"]
          "subdirectory of libdir in which libs are installed"
          libsubdir (\v flags -> flags { libsubdir = v })
          installDirArg

      , option "" ["libexecdir"]
          "installation directory for program executables"
          libexecdir (\v flags -> flags { libexecdir = v })
          installDirArg

      , option "" ["datadir"]
          "installation directory for read-only data"
          datadir (\v flags -> flags { datadir = v })
          installDirArg

      , option "" ["datasubdir"]
          "subdirectory of datadir in which data files are installed"
          datasubdir (\v flags -> flags { datasubdir = v })
          installDirArg

      , option "" ["docdir"]
          "installation directory for documentation"
          docdir (\v flags -> flags { docdir = v })
          installDirArg

      , option "" ["htmldir"]
          "installation directory for HTML documentation"
          htmldir (\v flags -> flags { htmldir = v })
          installDirArg

      , option "" ["haddockdir"]
          "installation directory for haddock interfaces"
          haddockdir (\v flags -> flags { haddockdir = v })
          installDirArg

      , option "" ["sysconfdir"]
          "installation directory for configuration files"
          sysconfdir (\v flags -> flags { sysconfdir = v })
          installDirArg
      ]
      where
        installDirArg _sf _lf d get set =
          reqArgFlag "DIR" _sf _lf d
            (fmap fromPathTemplate . get) (set . fmap toPathTemplate)

    emptyConfigFlags :: ConfigFlags
    emptyConfigFlags = mempty

    instance Monoid ConfigFlags where
      mempty = ConfigFlags {
        configPrograms      = error "FIXME: remove configPrograms",
        configProgramPaths  = mempty,
        configProgramArgs   = mempty,
        configProgramPathExtra = mempty,
        configHcFlavor      = mempty,
        configHcPath        = mempty,
        configHcPkg         = mempty,
        configVanillaLib    = mempty,
        configProfLib       = mempty,
        configSharedLib     = mempty,
        configDynExe        = mempty,
        configProfExe       = mempty,
        configConfigureArgs = mempty,
        configOptimization  = mempty,
        configProgPrefix    = mempty,
        configProgSuffix    = mempty,
        configInstallDirs   = mempty,
        configScratchDir    = mempty,
        configDistPref      = mempty,
        configVerbosity     = mempty,
        configUserInstall   = mempty,
        configPackageDBs    = mempty,
        configGHCiLib       = mempty,
        configSplitObjs     = mempty,
        configStripExes     = mempty,
        configStripLibs     = mempty,
        configExtraLibDirs  = mempty,
        configConstraints   = mempty,
        configDependencies  = mempty,
        configInstantiateWith     = mempty,
        configExtraIncludeDirs    = mempty,
        configConfigurationsFlags = mempty,
        configTests               = mempty,
        configCoverage         = mempty,
        configLibCoverage   = mempty,
        configExactConfiguration  = mempty,
        configBenchmarks          = mempty,
        configFlagError     = mempty,
        configRelocatable   = mempty,
        configDebugInfo     = mempty
      }
      mappend a b =  ConfigFlags {
        configPrograms      = configPrograms b,
        configProgramPaths  = combine configProgramPaths,
        configProgramArgs   = combine configProgramArgs,
        configProgramPathExtra = combine configProgramPathExtra,
        configHcFlavor      = combine configHcFlavor,
        configHcPath        = combine configHcPath,
        configHcPkg         = combine configHcPkg,
        configVanillaLib    = combine configVanillaLib,
        configProfLib       = combine configProfLib,
        configSharedLib     = combine configSharedLib,
        configDynExe        = combine configDynExe,
        configProfExe       = combine configProfExe,
        configConfigureArgs = combine configConfigureArgs,
        configOptimization  = combine configOptimization,
        configProgPrefix    = combine configProgPrefix,
        configProgSuffix    = combine configProgSuffix,
        configInstallDirs   = combine configInstallDirs,
        configScratchDir    = combine configScratchDir,
        configDistPref      = combine configDistPref,
        configVerbosity     = combine configVerbosity,
        configUserInstall   = combine configUserInstall,
        configPackageDBs    = combine configPackageDBs,
        configGHCiLib       = combine configGHCiLib,
        configSplitObjs     = combine configSplitObjs,
        configStripExes     = combine configStripExes,
        configStripLibs     = combine configStripLibs,
        configExtraLibDirs  = combine configExtraLibDirs,
        configConstraints   = combine configConstraints,
        configDependencies  = combine configDependencies,
        configInstantiateWith     = combine configInstantiateWith,
        configExtraIncludeDirs    = combine configExtraIncludeDirs,
        configConfigurationsFlags = combine configConfigurationsFlags,
        configTests               = combine configTests,
        configCoverage         = combine configCoverage,
        configLibCoverage         = combine configLibCoverage,
        configExactConfiguration  = combine configExactConfiguration,
        configBenchmarks          = combine configBenchmarks,
        configFlagError     = combine configFlagError,
        configRelocatable   = combine configRelocatable,
        configDebugInfo     = combine configDebugInfo
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Copy flags
    -- ------------------------------------------------------------

    -- | Flags to @copy@: (destdir, copy-prefix (backwards compat), verbosity)
    data CopyFlags = CopyFlags {
        copyDest      :: Flag CopyDest,
        copyDistPref  :: Flag FilePath,
        copyVerbosity :: Flag Verbosity
      }
      deriving Show

    defaultCopyFlags :: CopyFlags
    defaultCopyFlags  = CopyFlags {
        copyDest      = Flag NoCopyDest,
        copyDistPref  = Flag defaultDistPref,
        copyVerbosity = Flag normal
      }

    copyCommand :: CommandUI CopyFlags
    copyCommand = CommandUI
      { commandName         = "copy"
      , commandSynopsis     = "Copy the files into the install locations."
      , commandDescription  = Just $ \_ -> wrapText $
              "Does not call register, and allows a prefix at install time. "
           ++ "Without the --destdir flag, configure determines location.\n"
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " copy [FLAGS]\n"
      , commandDefaultFlags = defaultCopyFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity copyVerbosity (\v flags -> flags { copyVerbosity = v })

          ,optionDistPref
             copyDistPref (\d flags -> flags { copyDistPref = d })
             showOrParseArgs

          ,option "" ["destdir"]
             "directory to copy files to, prepended to installation directories"
             copyDest (\v flags -> flags { copyDest = v })
             (reqArg "DIR" (succeedReadE (Flag . CopyTo))
                           (\f -> case f of Flag (CopyTo p) -> [p]; _ -> []))
          ]
      }

    emptyCopyFlags :: CopyFlags
    emptyCopyFlags = mempty

    instance Monoid CopyFlags where
      mempty = CopyFlags {
        copyDest      = mempty,
        copyDistPref  = mempty,
        copyVerbosity = mempty
      }
      mappend a b = CopyFlags {
        copyDest      = combine copyDest,
        copyDistPref  = combine copyDistPref,
        copyVerbosity = combine copyVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Install flags
    -- ------------------------------------------------------------

    -- | Flags to @install@: (package db, verbosity)
    data InstallFlags = InstallFlags {
        installPackageDB :: Flag PackageDB,
        installDistPref  :: Flag FilePath,
        installUseWrapper :: Flag Bool,
        installInPlace    :: Flag Bool,
        installVerbosity :: Flag Verbosity
      }
      deriving Show

    defaultInstallFlags :: InstallFlags
    defaultInstallFlags  = InstallFlags {
        installPackageDB = NoFlag,
        installDistPref  = Flag defaultDistPref,
        installUseWrapper = Flag False,
        installInPlace    = Flag False,
        installVerbosity = Flag normal
      }

    installCommand :: CommandUI InstallFlags
    installCommand = CommandUI
      { commandName         = "install"
      , commandSynopsis     =
          "Copy the files into the install locations. Run register."
      , commandDescription  = Just $ \_ -> wrapText $
             "Unlike the copy command, install calls the register command."
          ++ "If you want to install into a location that is not what was"
          ++ "specified in the configure step, use the copy command.\n"
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " install [FLAGS]\n"
      , commandDefaultFlags = defaultInstallFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity installVerbosity (\v flags -> flags { installVerbosity = v })
          ,optionDistPref
             installDistPref (\d flags -> flags { installDistPref = d })
             showOrParseArgs

          ,option "" ["inplace"]
             "install the package in the install subdirectory of the dist prefix, so it can be used without being installed"
             installInPlace (\v flags -> flags { installInPlace = v })
             trueArg

          ,option "" ["shell-wrappers"]
             "using shell script wrappers around executables"
             installUseWrapper (\v flags -> flags { installUseWrapper = v })
             (boolOpt [] [])

          ,option "" ["package-db"] ""
             installPackageDB (\v flags -> flags { installPackageDB = v })
             (choiceOpt [ (Flag UserPackageDB, ([],["user"]),
                          "upon configuration register this package in the user's local package database")
                        , (Flag GlobalPackageDB, ([],["global"]),
                          "(default) upon configuration register this package in the system-wide package database")])
          ]
      }

    emptyInstallFlags :: InstallFlags
    emptyInstallFlags = mempty

    instance Monoid InstallFlags where
      mempty = InstallFlags{
        installPackageDB = mempty,
        installDistPref  = mempty,
        installUseWrapper = mempty,
        installInPlace    = mempty,
        installVerbosity = mempty
      }
      mappend a b = InstallFlags{
        installPackageDB = combine installPackageDB,
        installDistPref  = combine installDistPref,
        installUseWrapper = combine installUseWrapper,
        installInPlace    = combine installInPlace,
        installVerbosity = combine installVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * SDist flags
    -- ------------------------------------------------------------

    -- | Flags to @sdist@: (snapshot, verbosity)
    data SDistFlags = SDistFlags {
        sDistSnapshot    :: Flag Bool,
        sDistDirectory   :: Flag FilePath,
        sDistDistPref    :: Flag FilePath,
        sDistListSources :: Flag FilePath,
        sDistVerbosity   :: Flag Verbosity
      }
      deriving Show

    defaultSDistFlags :: SDistFlags
    defaultSDistFlags = SDistFlags {
        sDistSnapshot    = Flag False,
        sDistDirectory   = mempty,
        sDistDistPref    = Flag defaultDistPref,
        sDistListSources = mempty,
        sDistVerbosity   = Flag normal
      }

    sdistCommand :: CommandUI SDistFlags
    sdistCommand = CommandUI
      { commandName         = "sdist"
      , commandSynopsis     =
          "Generate a source distribution file (.tar.gz)."
      , commandDescription  = Nothing
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " sdist [FLAGS]\n"
      , commandDefaultFlags = defaultSDistFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity sDistVerbosity (\v flags -> flags { sDistVerbosity = v })
          ,optionDistPref
             sDistDistPref (\d flags -> flags { sDistDistPref = d })
             showOrParseArgs

          ,option "" ["list-sources"]
             "Just write a list of the package's sources to a file"
             sDistListSources (\v flags -> flags { sDistListSources = v })
             (reqArgFlag "FILE")

          ,option "" ["snapshot"]
             "Produce a snapshot source distribution"
             sDistSnapshot (\v flags -> flags { sDistSnapshot = v })
             trueArg

          ,option "" ["output-directory"]
           ("Generate a source distribution in the given directory, "
            ++ "without creating a tarball")
             sDistDirectory (\v flags -> flags { sDistDirectory = v })
             (reqArgFlag "DIR")
          ]
      }

    emptySDistFlags :: SDistFlags
    emptySDistFlags = mempty

    instance Monoid SDistFlags where
      mempty = SDistFlags {
        sDistSnapshot    = mempty,
        sDistDirectory   = mempty,
        sDistDistPref    = mempty,
        sDistListSources = mempty,
        sDistVerbosity   = mempty
      }
      mappend a b = SDistFlags {
        sDistSnapshot    = combine sDistSnapshot,
        sDistDirectory   = combine sDistDirectory,
        sDistDistPref    = combine sDistDistPref,
        sDistListSources = combine sDistListSources,
        sDistVerbosity   = combine sDistVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Register flags
    -- ------------------------------------------------------------

    -- | Flags to @register@ and @unregister@: (user package, gen-script,
    -- in-place, verbosity)
    data RegisterFlags = RegisterFlags {
        regPackageDB   :: Flag PackageDB,
        regGenScript   :: Flag Bool,
        regGenPkgConf  :: Flag (Maybe FilePath),
        regInPlace     :: Flag Bool,
        regDistPref    :: Flag FilePath,
        regPrintId     :: Flag Bool,
        regVerbosity   :: Flag Verbosity
      }
      deriving Show

    defaultRegisterFlags :: RegisterFlags
    defaultRegisterFlags = RegisterFlags {
        regPackageDB   = NoFlag,
        regGenScript   = Flag False,
        regGenPkgConf  = NoFlag,
        regInPlace     = Flag False,
        regDistPref    = Flag defaultDistPref,
        regPrintId     = Flag False,
        regVerbosity   = Flag normal
      }

    registerCommand :: CommandUI RegisterFlags
    registerCommand = CommandUI
      { commandName         = "register"
      , commandSynopsis     =
          "Register this package with the compiler."
      , commandDescription  = Nothing
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " register [FLAGS]\n"
      , commandDefaultFlags = defaultRegisterFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity regVerbosity (\v flags -> flags { regVerbosity = v })
          ,optionDistPref
             regDistPref (\d flags -> flags { regDistPref = d })
             showOrParseArgs

          ,option "" ["packageDB"] ""
             regPackageDB (\v flags -> flags { regPackageDB = v })
             (choiceOpt [ (Flag UserPackageDB, ([],["user"]),
                                    "upon registration, register this package in the user's local package database")
                        , (Flag GlobalPackageDB, ([],["global"]),
                                    "(default)upon registration, register this package in the system-wide package database")])

          ,option "" ["inplace"]
             "register the package in the build location, so it can be used without being installed"
             regInPlace (\v flags -> flags { regInPlace = v })
             trueArg

          ,option "" ["gen-script"]
             "instead of registering, generate a script to register later"
             regGenScript (\v flags -> flags { regGenScript = v })
             trueArg

          ,option "" ["gen-pkg-config"]
             "instead of registering, generate a package registration file"
             regGenPkgConf (\v flags -> flags { regGenPkgConf  = v })
             (optArg' "PKG" Flag flagToList)

          ,option "" ["print-ipid"]
             "print the installed package ID calculated for this package"
             regPrintId (\v flags -> flags { regPrintId = v })
             trueArg
          ]
      }

    unregisterCommand :: CommandUI RegisterFlags
    unregisterCommand = CommandUI
      { commandName         = "unregister"
      , commandSynopsis     =
          "Unregister this package with the compiler."
      , commandDescription  = Nothing
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " unregister [FLAGS]\n"
      , commandDefaultFlags = defaultRegisterFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity regVerbosity (\v flags -> flags { regVerbosity = v })
          ,optionDistPref
             regDistPref (\d flags -> flags { regDistPref = d })
              showOrParseArgs

          ,option "" ["user"] ""
             regPackageDB (\v flags -> flags { regPackageDB = v })
             (choiceOpt [ (Flag UserPackageDB, ([],["user"]),
                                  "unregister this package in the user's local package database")
                        , (Flag GlobalPackageDB, ([],["global"]),
                                  "(default) unregister this package in the  system-wide package database")])

          ,option "" ["gen-script"]
             "Instead of performing the unregister command, generate a script to unregister later"
             regGenScript (\v flags -> flags { regGenScript = v })
             trueArg
          ]
      }

    emptyRegisterFlags :: RegisterFlags
    emptyRegisterFlags = mempty

    instance Monoid RegisterFlags where
      mempty = RegisterFlags {
        regPackageDB   = mempty,
        regGenScript   = mempty,
        regGenPkgConf  = mempty,
        regInPlace     = mempty,
        regPrintId     = mempty,
        regDistPref    = mempty,
        regVerbosity   = mempty
      }
      mappend a b = RegisterFlags {
        regPackageDB   = combine regPackageDB,
        regGenScript   = combine regGenScript,
        regGenPkgConf  = combine regGenPkgConf,
        regInPlace     = combine regInPlace,
        regPrintId     = combine regPrintId,
        regDistPref    = combine regDistPref,
        regVerbosity   = combine regVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * HsColour flags
    -- ------------------------------------------------------------

    data HscolourFlags = HscolourFlags {
        hscolourCSS         :: Flag FilePath,
        hscolourExecutables :: Flag Bool,
        hscolourTestSuites  :: Flag Bool,
        hscolourBenchmarks  :: Flag Bool,
        hscolourDistPref    :: Flag FilePath,
        hscolourVerbosity   :: Flag Verbosity
      }
      deriving Show

    emptyHscolourFlags :: HscolourFlags
    emptyHscolourFlags = mempty

    defaultHscolourFlags :: HscolourFlags
    defaultHscolourFlags = HscolourFlags {
        hscolourCSS         = NoFlag,
        hscolourExecutables = Flag False,
        hscolourTestSuites  = Flag False,
        hscolourBenchmarks  = Flag False,
        hscolourDistPref    = Flag defaultDistPref,
        hscolourVerbosity   = Flag normal
      }

    instance Monoid HscolourFlags where
      mempty = HscolourFlags {
        hscolourCSS         = mempty,
        hscolourExecutables = mempty,
        hscolourTestSuites  = mempty,
        hscolourBenchmarks  = mempty,
        hscolourDistPref    = mempty,
        hscolourVerbosity   = mempty
      }
      mappend a b = HscolourFlags {
        hscolourCSS         = combine hscolourCSS,
        hscolourExecutables = combine hscolourExecutables,
        hscolourTestSuites  = combine hscolourTestSuites,
        hscolourBenchmarks  = combine hscolourBenchmarks,
        hscolourDistPref    = combine hscolourDistPref,
        hscolourVerbosity   = combine hscolourVerbosity
      }
        where combine field = field a `mappend` field b

    hscolourCommand :: CommandUI HscolourFlags
    hscolourCommand = CommandUI
      { commandName         = "hscolour"
      , commandSynopsis     =
          "Generate HsColour colourised code, in HTML format."
      , commandDescription  = Just (\_ -> "Requires the hscolour program.\n")
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " hscolour [FLAGS]\n"
      , commandDefaultFlags = defaultHscolourFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity hscolourVerbosity
           (\v flags -> flags { hscolourVerbosity = v })
          ,optionDistPref
             hscolourDistPref (\d flags -> flags { hscolourDistPref = d })
             showOrParseArgs

          ,option "" ["executables"]
             "Run hscolour for Executables targets"
             hscolourExecutables (\v flags -> flags { hscolourExecutables = v })
             trueArg

          ,option "" ["tests"]
             "Run hscolour for Test Suite targets"
             hscolourTestSuites (\v flags -> flags { hscolourTestSuites = v })
             trueArg

          ,option "" ["benchmarks"]
             "Run hscolour for Benchmark targets"
             hscolourBenchmarks (\v flags -> flags { hscolourBenchmarks = v })
             trueArg

          ,option "" ["all"]
             "Run hscolour for all targets"
             (\f -> allFlags [ hscolourExecutables f
                             , hscolourTestSuites  f
                             , hscolourBenchmarks  f])
             (\v flags -> flags { hscolourExecutables = v
                                , hscolourTestSuites  = v
                                , hscolourBenchmarks  = v })
             trueArg

          ,option "" ["css"]
             "Use a cascading style sheet"
             hscolourCSS (\v flags -> flags { hscolourCSS = v })
             (reqArgFlag "PATH")
          ]
      }

    -- ------------------------------------------------------------
    -- * Haddock flags
    -- ------------------------------------------------------------

    data HaddockFlags = HaddockFlags {
        haddockProgramPaths :: [(String, FilePath)],
        haddockProgramArgs  :: [(String, [String])],
        haddockHoogle       :: Flag Bool,
        haddockHtml         :: Flag Bool,
        haddockHtmlLocation :: Flag String,
        haddockExecutables  :: Flag Bool,
        haddockTestSuites   :: Flag Bool,
        haddockBenchmarks   :: Flag Bool,
        haddockInternal     :: Flag Bool,
        haddockCss          :: Flag FilePath,
        haddockHscolour     :: Flag Bool,
        haddockHscolourCss  :: Flag FilePath,
        haddockContents     :: Flag PathTemplate,
        haddockDistPref     :: Flag FilePath,
        haddockKeepTempFiles:: Flag Bool,
        haddockVerbosity    :: Flag Verbosity
      }
      deriving Show

    defaultHaddockFlags :: HaddockFlags
    defaultHaddockFlags  = HaddockFlags {
        haddockProgramPaths = mempty,
        haddockProgramArgs  = [],
        haddockHoogle       = Flag False,
        haddockHtml         = Flag False,
        haddockHtmlLocation = NoFlag,
        haddockExecutables  = Flag False,
        haddockTestSuites   = Flag False,
        haddockBenchmarks   = Flag False,
        haddockInternal     = Flag False,
        haddockCss          = NoFlag,
        haddockHscolour     = Flag False,
        haddockHscolourCss  = NoFlag,
        haddockContents     = NoFlag,
        haddockDistPref     = Flag defaultDistPref,
        haddockKeepTempFiles= Flag False,
        haddockVerbosity    = Flag normal
      }

    haddockCommand :: CommandUI HaddockFlags
    haddockCommand = CommandUI
      { commandName         = "haddock"
      , commandSynopsis     = "Generate Haddock HTML documentation."
      , commandDescription  = Just $ \_ ->
          "Requires the program haddock, version 2.x.\n"
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " haddock [FLAGS]\n"
      , commandDefaultFlags = defaultHaddockFlags
      , commandOptions      = \showOrParseArgs ->
             haddockOptions showOrParseArgs
          ++ programConfigurationPaths   progConf ParseArgs
                 haddockProgramPaths (\v flags -> flags { haddockProgramPaths = v})
          ++ programConfigurationOption  progConf showOrParseArgs
                 haddockProgramArgs (\v fs -> fs { haddockProgramArgs = v })
          ++ programConfigurationOptions progConf ParseArgs
                 haddockProgramArgs  (\v flags -> flags { haddockProgramArgs = v})
      }
      where
        progConf = addKnownProgram haddockProgram
                 $ addKnownProgram ghcProgram
                 $ emptyProgramConfiguration

    haddockOptions :: ShowOrParseArgs -> [OptionField HaddockFlags]
    haddockOptions showOrParseArgs =
      [optionVerbosity haddockVerbosity
       (\v flags -> flags { haddockVerbosity = v })
      ,optionDistPref
       haddockDistPref (\d flags -> flags { haddockDistPref = d })
       showOrParseArgs

      ,option "" ["keep-temp-files"]
       "Keep temporary files"
       haddockKeepTempFiles (\b flags -> flags { haddockKeepTempFiles = b })
       trueArg

      ,option "" ["hoogle"]
       "Generate a hoogle database"
       haddockHoogle (\v flags -> flags { haddockHoogle = v })
       trueArg

      ,option "" ["html"]
       "Generate HTML documentation (the default)"
       haddockHtml (\v flags -> flags { haddockHtml = v })
       trueArg

      ,option "" ["html-location"]
       "Location of HTML documentation for pre-requisite packages"
       haddockHtmlLocation (\v flags -> flags { haddockHtmlLocation = v })
       (reqArgFlag "URL")

      ,option "" ["executables"]
       "Run haddock for Executables targets"
       haddockExecutables (\v flags -> flags { haddockExecutables = v })
       trueArg

      ,option "" ["tests"]
       "Run haddock for Test Suite targets"
       haddockTestSuites (\v flags -> flags { haddockTestSuites = v })
       trueArg

      ,option "" ["benchmarks"]
       "Run haddock for Benchmark targets"
       haddockBenchmarks (\v flags -> flags { haddockBenchmarks = v })
       trueArg

      ,option "" ["all"]
       "Run haddock for all targets"
       (\f -> allFlags [ haddockExecutables f
                       , haddockTestSuites  f
                       , haddockBenchmarks  f])
             (\v flags -> flags { haddockExecutables = v
                                , haddockTestSuites  = v
                                , haddockBenchmarks  = v })
             trueArg

      ,option "" ["internal"]
       "Run haddock for internal modules and include all symbols"
       haddockInternal (\v flags -> flags { haddockInternal = v })
       trueArg

      ,option "" ["css"]
       "Use PATH as the haddock stylesheet"
       haddockCss (\v flags -> flags { haddockCss = v })
       (reqArgFlag "PATH")

      ,option "" ["hyperlink-source","hyperlink-sources"]
       "Hyperlink the documentation to the source code (using HsColour)"
       haddockHscolour (\v flags -> flags { haddockHscolour = v })
       trueArg

      ,option "" ["hscolour-css"]
       "Use PATH as the HsColour stylesheet"
       haddockHscolourCss (\v flags -> flags { haddockHscolourCss = v })
       (reqArgFlag "PATH")

      ,option "" ["contents-location"]
       "Bake URL in as the location for the contents page"
       haddockContents (\v flags -> flags { haddockContents = v })
       (reqArg' "URL"
        (toFlag . toPathTemplate)
        (flagToList . fmap fromPathTemplate))
      ]

    emptyHaddockFlags :: HaddockFlags
    emptyHaddockFlags = mempty

    instance Monoid HaddockFlags where
      mempty = HaddockFlags {
        haddockProgramPaths = mempty,
        haddockProgramArgs  = mempty,
        haddockHoogle       = mempty,
        haddockHtml         = mempty,
        haddockHtmlLocation = mempty,
        haddockExecutables  = mempty,
        haddockTestSuites   = mempty,
        haddockBenchmarks   = mempty,
        haddockInternal     = mempty,
        haddockCss          = mempty,
        haddockHscolour     = mempty,
        haddockHscolourCss  = mempty,
        haddockContents     = mempty,
        haddockDistPref     = mempty,
        haddockKeepTempFiles= mempty,
        haddockVerbosity    = mempty
      }
      mappend a b = HaddockFlags {
        haddockProgramPaths = combine haddockProgramPaths,
        haddockProgramArgs  = combine haddockProgramArgs,
        haddockHoogle       = combine haddockHoogle,
        haddockHtml         = combine haddockHoogle,
        haddockHtmlLocation = combine haddockHtmlLocation,
        haddockExecutables  = combine haddockExecutables,
        haddockTestSuites   = combine haddockTestSuites,
        haddockBenchmarks   = combine haddockBenchmarks,
        haddockInternal     = combine haddockInternal,
        haddockCss          = combine haddockCss,
        haddockHscolour     = combine haddockHscolour,
        haddockHscolourCss  = combine haddockHscolourCss,
        haddockContents     = combine haddockContents,
        haddockDistPref     = combine haddockDistPref,
        haddockKeepTempFiles= combine haddockKeepTempFiles,
        haddockVerbosity    = combine haddockVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Clean flags
    -- ------------------------------------------------------------

    data CleanFlags = CleanFlags {
        cleanSaveConf  :: Flag Bool,
        cleanDistPref  :: Flag FilePath,
        cleanVerbosity :: Flag Verbosity
      }
      deriving Show

    defaultCleanFlags :: CleanFlags
    defaultCleanFlags  = CleanFlags {
        cleanSaveConf  = Flag False,
        cleanDistPref  = Flag defaultDistPref,
        cleanVerbosity = Flag normal
      }

    cleanCommand :: CommandUI CleanFlags
    cleanCommand = CommandUI
      { commandName         = "clean"
      , commandSynopsis     = "Clean up after a build."
      , commandDescription  = Just $ \_ ->
          "Removes .hi, .o, preprocessed sources, etc.\n"
      , commandNotes        = Nothing
      , commandUsage        = \pname ->
          "Usage: " ++ pname ++ " clean [FLAGS]\n"
      , commandDefaultFlags = defaultCleanFlags
      , commandOptions      = \showOrParseArgs ->
          [optionVerbosity cleanVerbosity (\v flags -> flags { cleanVerbosity = v })
          ,optionDistPref
             cleanDistPref (\d flags -> flags { cleanDistPref = d })
             showOrParseArgs

          ,option "s" ["save-configure"]
             "Do not remove the configuration file (dist/setup-config) during cleaning.  Saves need to reconfigure."
             cleanSaveConf (\v flags -> flags { cleanSaveConf = v })
             trueArg
          ]
      }

    emptyCleanFlags :: CleanFlags
    emptyCleanFlags = mempty

    instance Monoid CleanFlags where
      mempty = CleanFlags {
        cleanSaveConf  = mempty,
        cleanDistPref  = mempty,
        cleanVerbosity = mempty
      }
      mappend a b = CleanFlags {
        cleanSaveConf  = combine cleanSaveConf,
        cleanDistPref  = combine cleanDistPref,
        cleanVerbosity = combine cleanVerbosity
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Build flags
    -- ------------------------------------------------------------

    data BuildFlags = BuildFlags {
        buildProgramPaths :: [(String, FilePath)],
        buildProgramArgs :: [(String, [String])],
        buildDistPref    :: Flag FilePath,
        buildVerbosity   :: Flag Verbosity,
        buildNumJobs     :: Flag (Maybe Int),
        -- TODO: this one should not be here, it's just that the silly
        -- UserHooks stop us from passing extra info in other ways
        buildArgs :: [String]
      }
      deriving Show

    {-# DEPRECATED buildVerbose "Use buildVerbosity instead" #-}
    buildVerbose :: BuildFlags -> Verbosity
    buildVerbose = fromFlagOrDefault normal . buildVerbosity

    defaultBuildFlags :: BuildFlags
    defaultBuildFlags  = BuildFlags {
        buildProgramPaths = mempty,
        buildProgramArgs = [],
        buildDistPref    = Flag defaultDistPref,
        buildVerbosity   = Flag normal,
        buildNumJobs     = mempty,
        buildArgs        = []
      }

    buildCommand :: ProgramConfiguration -> CommandUI BuildFlags
    buildCommand progConf = CommandUI
      { commandName         = "build"
      , commandSynopsis     = "Compile all/specific components."
      , commandDescription  = Just $ \_ -> wrapText $
             "Components encompass executables, tests, and benchmarks.\n"
          ++ "\n"
          ++ "Affected by configuration options, see `configure`.\n"
      , commandNotes        = Just $ \pname ->
           "Examples:\n"
            ++ "  " ++ pname ++ " build           "
            ++ "    All the components in the package\n"
            ++ "  " ++ pname ++ " build foo       "
            ++ "    A component (i.e. lib, exe, test suite)\n\n"
            ++ programFlagsDescription progConf
    --TODO: re-enable once we have support for module/file targets
    --        ++ "  " ++ pname ++ " build Foo.Bar   "
    --        ++ "    A module\n"
    --        ++ "  " ++ pname ++ " build Foo/Bar.hs"
    --        ++ "    A file\n\n"
    --        ++ "If a target is ambiguous it can be qualified with the component "
    --        ++ "name, e.g.\n"
    --        ++ "  " ++ pname ++ " build foo:Foo.Bar\n"
    --        ++ "  " ++ pname ++ " build testsuite1:Foo/Bar.hs\n"
      , commandUsage        = usageAlternatives "build" $
          [ "[FLAGS]"
          , "COMPONENTS [FLAGS]"
          ]
      , commandDefaultFlags = defaultBuildFlags
      , commandOptions      = \showOrParseArgs ->
          [ optionVerbosity
            buildVerbosity (\v flags -> flags { buildVerbosity = v })

          , optionDistPref
            buildDistPref (\d flags -> flags { buildDistPref = d }) showOrParseArgs
          ]
          ++ buildOptions progConf showOrParseArgs
      }

    buildOptions :: ProgramConfiguration -> ShowOrParseArgs
                    -> [OptionField BuildFlags]
    buildOptions progConf showOrParseArgs =
      [ optionNumJobs
          buildNumJobs (\v flags -> flags { buildNumJobs = v })
      ]

      ++ programConfigurationPaths progConf showOrParseArgs
           buildProgramPaths (\v flags -> flags { buildProgramPaths = v})

      ++ programConfigurationOption progConf showOrParseArgs
           buildProgramArgs (\v fs -> fs { buildProgramArgs = v })

      ++ programConfigurationOptions progConf showOrParseArgs
           buildProgramArgs (\v flags -> flags { buildProgramArgs = v})

    emptyBuildFlags :: BuildFlags
    emptyBuildFlags = mempty

    instance Monoid BuildFlags where
      mempty = BuildFlags {
        buildProgramPaths = mempty,
        buildProgramArgs = mempty,
        buildVerbosity   = mempty,
        buildDistPref    = mempty,
        buildNumJobs     = mempty,
        buildArgs        = mempty
      }
      mappend a b = BuildFlags {
        buildProgramPaths = combine buildProgramPaths,
        buildProgramArgs = combine buildProgramArgs,
        buildVerbosity   = combine buildVerbosity,
        buildDistPref    = combine buildDistPref,
        buildNumJobs     = combine buildNumJobs,
        buildArgs        = combine buildArgs
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * REPL Flags
    -- ------------------------------------------------------------

    data ReplFlags = ReplFlags {
        replProgramPaths :: [(String, FilePath)],
        replProgramArgs :: [(String, [String])],
        replDistPref    :: Flag FilePath,
        replVerbosity   :: Flag Verbosity,
        replReload      :: Flag Bool
      }
      deriving Show

    defaultReplFlags :: ReplFlags
    defaultReplFlags  = ReplFlags {
        replProgramPaths = mempty,
        replProgramArgs = [],
        replDistPref    = Flag defaultDistPref,
        replVerbosity   = Flag normal,
        replReload      = Flag False
      }

    instance Monoid ReplFlags where
      mempty = ReplFlags {
        replProgramPaths = mempty,
        replProgramArgs = mempty,
        replVerbosity   = mempty,
        replDistPref    = mempty,
        replReload      = mempty
      }
      mappend a b = ReplFlags {
        replProgramPaths = combine replProgramPaths,
        replProgramArgs = combine replProgramArgs,
        replVerbosity   = combine replVerbosity,
        replDistPref    = combine replDistPref,
        replReload      = combine replReload
      }
        where combine field = field a `mappend` field b

    replCommand :: ProgramConfiguration -> CommandUI ReplFlags
    replCommand progConf = CommandUI
      { commandName         = "repl"
      , commandSynopsis     =
          "Open an interpreter session for the given component."
      , commandDescription  = Just $ \pname -> wrapText $
             "If the current directory contains no package, ignores COMPONENT "
          ++ "parameters and opens an interactive interpreter session; if a "
          ++ "sandbox is present, its package database will be used.\n"
          ++ "\n"
          ++ "Otherwise, (re)configures with the given or default flags, and "
          ++ "loads the interpreter with the relevant modules. For executables, "
          ++ "tests and benchmarks, loads the main module (and its "
          ++ "dependencies); for libraries all exposed/other modules.\n"
          ++ "\n"
          ++ "The default component is the library itself, or the executable "
          ++ "if that is the only component.\n"
          ++ "\n"
          ++ "Support for loading specific modules is planned but not "
          ++ "implemented yet. For certain scenarios, `" ++ pname
          ++ " exec -- ghci :l Foo` may be used instead. Note that `exec` will "
          ++ "not (re)configure and you will have to specify the location of "
          ++ "other modules, if required.\n"

      , commandNotes        = Just $ \pname ->
             "Examples:\n"
          ++ "  " ++ pname ++ " repl           "
          ++ "    The first component in the package\n"
          ++ "  " ++ pname ++ " repl foo       "
          ++ "    A named component (i.e. lib, exe, test suite)\n"
          ++ "  " ++ pname ++ " repl --ghc-options=\"-lstdc++\""
          ++ "  Specifying flags for interpreter\n"
    --TODO: re-enable once we have support for module/file targets
    --        ++ "  " ++ pname ++ " repl Foo.Bar   "
    --        ++ "    A module\n"
    --        ++ "  " ++ pname ++ " repl Foo/Bar.hs"
    --        ++ "    A file\n\n"
    --        ++ "If a target is ambiguous it can be qualified with the component "
    --        ++ "name, e.g.\n"
    --        ++ "  " ++ pname ++ " repl foo:Foo.Bar\n"
    --        ++ "  " ++ pname ++ " repl testsuite1:Foo/Bar.hs\n"
      , commandUsage =  \pname -> "Usage: " ++ pname ++ " repl [COMPONENT] [FLAGS]\n"
      , commandDefaultFlags = defaultReplFlags
      , commandOptions = \showOrParseArgs ->
          optionVerbosity replVerbosity (\v flags -> flags { replVerbosity = v })
          : optionDistPref
              replDistPref (\d flags -> flags { replDistPref = d })
              showOrParseArgs

          : programConfigurationPaths   progConf showOrParseArgs
              replProgramPaths (\v flags -> flags { replProgramPaths = v})

         ++ programConfigurationOption progConf showOrParseArgs
              replProgramArgs (\v flags -> flags { replProgramArgs = v})

         ++ programConfigurationOptions progConf showOrParseArgs
              replProgramArgs (\v flags -> flags { replProgramArgs = v})

         ++ case showOrParseArgs of
              ParseArgs ->
                [ option "" ["reload"]
                  "Used from within an interpreter to update files."
                  replReload (\v flags -> flags { replReload = v })
                  trueArg
                ]
              _ -> []
      }

    -- ------------------------------------------------------------
    -- * Test flags
    -- ------------------------------------------------------------

    data TestShowDetails = Never | Failures | Always | Streaming
        deriving (Eq, Ord, Enum, Bounded, Show)

    knownTestShowDetails :: [TestShowDetails]
    knownTestShowDetails = [minBound..maxBound]

    instance Text TestShowDetails where
        disp  = Disp.text . lowercase . show

        parse = maybe Parse.pfail return . classify =<< ident
          where
            ident        = Parse.munch1 (\c -> isAlpha c || c == '_' || c == '-')
            classify str = lookup (lowercase str) enumMap
            enumMap     :: [(String, TestShowDetails)]
            enumMap      = [ (display x, x)
                           | x <- knownTestShowDetails ]

    --TODO: do we need this instance?
    instance Monoid TestShowDetails where
        mempty = Never
        mappend a b = if a < b then b else a

    data TestFlags = TestFlags {
        testDistPref    :: Flag FilePath,
        testVerbosity   :: Flag Verbosity,
        testHumanLog    :: Flag PathTemplate,
        testMachineLog  :: Flag PathTemplate,
        testShowDetails :: Flag TestShowDetails,
        testKeepTix     :: Flag Bool,
        -- TODO: think about if/how options are passed to test exes
        testOptions     :: [PathTemplate]
      }

    defaultTestFlags :: TestFlags
    defaultTestFlags  = TestFlags {
        testDistPref    = Flag defaultDistPref,
        testVerbosity   = Flag normal,
        testHumanLog    = toFlag $ toPathTemplate $ "$pkgid-$test-suite.log",
        testMachineLog  = toFlag $ toPathTemplate $ "$pkgid.log",
        testShowDetails = toFlag Failures,
        testKeepTix     = toFlag False,
        testOptions     = []
      }

    testCommand :: CommandUI TestFlags
    testCommand = CommandUI
      { commandName         = "test"
      , commandSynopsis     =
          "Run all/specific tests in the test suite."
      , commandDescription  = Just $ \pname -> wrapText $
             "If necessary (re)configures with `--enable-tests` flag and builds"
          ++ " the test suite.\n"
          ++ "\n"
          ++ "Remember that the tests' dependencies must be installed if there"
          ++ " are additional ones; e.g. with `" ++ pname
          ++ " install --only-dependencies --enable-tests`.\n"
          ++ "\n"
          ++ "By defining UserHooks in a custom Setup.hs, the package can"
          ++ " define actions to be executed before and after running tests.\n"
      , commandNotes        = Nothing
      , commandUsage        = usageAlternatives "test"
          [ "[FLAGS]"
          , "TESTCOMPONENTS [FLAGS]"
          ]
      , commandDefaultFlags = defaultTestFlags
      , commandOptions = \showOrParseArgs ->
          [ optionVerbosity testVerbosity (\v flags -> flags { testVerbosity = v })
          , optionDistPref
                testDistPref (\d flags -> flags { testDistPref = d })
                showOrParseArgs
          , option [] ["log"]
                ("Log all test suite results to file (name template can use "
                ++ "$pkgid, $compiler, $os, $arch, $test-suite, $result)")
                testHumanLog (\v flags -> flags { testHumanLog = v })
                (reqArg' "TEMPLATE"
                    (toFlag . toPathTemplate)
                    (flagToList . fmap fromPathTemplate))
          , option [] ["machine-log"]
                ("Produce a machine-readable log file (name template can use "
                ++ "$pkgid, $compiler, $os, $arch, $result)")
                testMachineLog (\v flags -> flags { testMachineLog = v })
                (reqArg' "TEMPLATE"
                    (toFlag . toPathTemplate)
                    (flagToList . fmap fromPathTemplate))
          , option [] ["show-details"]
                ("'always': always show results of individual test cases. "
                 ++ "'never': never show results of individual test cases. "
                 ++ "'failures': show results of failing test cases. "
                 ++ "'streaming': show results of test cases in real time.")
                testShowDetails (\v flags -> flags { testShowDetails = v })
                (reqArg "FILTER"
                    (readP_to_E (\_ -> "--show-details flag expects one of "
                                  ++ intercalate ", "
                                       (map display knownTestShowDetails))
                                (fmap toFlag parse))
                    (flagToList . fmap display))
          , option [] ["keep-tix-files"]
                "keep .tix files for HPC between test runs"
                testKeepTix (\v flags -> flags { testKeepTix = v})
                trueArg
          , option [] ["test-options"]
                ("give extra options to test executables "
                 ++ "(name templates can use $pkgid, $compiler, "
                 ++ "$os, $arch, $test-suite)")
                testOptions (\v flags -> flags { testOptions = v })
                (reqArg' "TEMPLATES" (map toPathTemplate . splitArgs)
                    (const []))
          , option [] ["test-option"]
                ("give extra option to test executables "
                 ++ "(no need to quote options containing spaces, "
                 ++ "name template can use $pkgid, $compiler, "
                 ++ "$os, $arch, $test-suite)")
                testOptions (\v flags -> flags { testOptions = v })
                (reqArg' "TEMPLATE" (\x -> [toPathTemplate x])
                    (map fromPathTemplate))
          ]
      }

    emptyTestFlags :: TestFlags
    emptyTestFlags  = mempty

    instance Monoid TestFlags where
      mempty = TestFlags {
        testDistPref    = mempty,
        testVerbosity   = mempty,
        testHumanLog    = mempty,
        testMachineLog  = mempty,
        testShowDetails = mempty,
        testKeepTix     = mempty,
        testOptions     = mempty
      }
      mappend a b = TestFlags {
        testDistPref    = combine testDistPref,
        testVerbosity   = combine testVerbosity,
        testHumanLog    = combine testHumanLog,
        testMachineLog  = combine testMachineLog,
        testShowDetails = combine testShowDetails,
        testKeepTix     = combine testKeepTix,
        testOptions     = combine testOptions
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Benchmark flags
    -- ------------------------------------------------------------

    data BenchmarkFlags = BenchmarkFlags {
        benchmarkDistPref  :: Flag FilePath,
        benchmarkVerbosity :: Flag Verbosity,
        benchmarkOptions   :: [PathTemplate]
      }

    defaultBenchmarkFlags :: BenchmarkFlags
    defaultBenchmarkFlags  = BenchmarkFlags {
        benchmarkDistPref  = Flag defaultDistPref,
        benchmarkVerbosity = Flag normal,
        benchmarkOptions   = []
      }

    benchmarkCommand :: CommandUI BenchmarkFlags
    benchmarkCommand = CommandUI
      { commandName         = "bench"
      , commandSynopsis     =
          "Run all/specific benchmarks."
      , commandDescription  = Just $ \pname -> wrapText $
             "If necessary (re)configures with `--enable-benchmarks` flag and"
          ++ " builds the benchmarks.\n"
          ++ "\n"
          ++ "Remember that the benchmarks' dependencies must be installed if"
          ++ " there are additional ones; e.g. with `" ++ pname
          ++ " install --only-dependencies --enable-benchmarks`.\n"
          ++ "\n"
          ++ "By defining UserHooks in a custom Setup.hs, the package can"
          ++ " define actions to be executed before and after running"
          ++ " benchmarks.\n"
      , commandNotes        = Nothing
      , commandUsage        = usageAlternatives "bench"
          [ "[FLAGS]"
          , "BENCHCOMPONENTS [FLAGS]"
          ]
      , commandDefaultFlags = defaultBenchmarkFlags
      , commandOptions = \showOrParseArgs ->
          [ optionVerbosity benchmarkVerbosity
            (\v flags -> flags { benchmarkVerbosity = v })
          , optionDistPref
                benchmarkDistPref (\d flags -> flags { benchmarkDistPref = d })
                showOrParseArgs
          , option [] ["benchmark-options"]
                ("give extra options to benchmark executables "
                 ++ "(name templates can use $pkgid, $compiler, "
                 ++ "$os, $arch, $benchmark)")
                benchmarkOptions (\v flags -> flags { benchmarkOptions = v })
                (reqArg' "TEMPLATES" (map toPathTemplate . splitArgs)
                    (const []))
          , option [] ["benchmark-option"]
                ("give extra option to benchmark executables "
                 ++ "(no need to quote options containing spaces, "
                 ++ "name template can use $pkgid, $compiler, "
                 ++ "$os, $arch, $benchmark)")
                benchmarkOptions (\v flags -> flags { benchmarkOptions = v })
                (reqArg' "TEMPLATE" (\x -> [toPathTemplate x])
                    (map fromPathTemplate))
          ]
      }

    emptyBenchmarkFlags :: BenchmarkFlags
    emptyBenchmarkFlags = mempty

    instance Monoid BenchmarkFlags where
      mempty = BenchmarkFlags {
        benchmarkDistPref  = mempty,
        benchmarkVerbosity = mempty,
        benchmarkOptions   = mempty
      }
      mappend a b = BenchmarkFlags {
        benchmarkDistPref  = combine benchmarkDistPref,
        benchmarkVerbosity = combine benchmarkVerbosity,
        benchmarkOptions   = combine benchmarkOptions
      }
        where combine field = field a `mappend` field b

    -- ------------------------------------------------------------
    -- * Shared options utils
    -- ------------------------------------------------------------

    programFlagsDescription :: ProgramConfiguration -> String
    programFlagsDescription progConf =
         "The flags --with-PROG and --PROG-option(s) can be used with"
      ++ " the following programs:"
      ++ (concatMap (\line -> "\n  " ++ unwords line) . wrapLine 77 . sort)
         [ programName prog | (prog, _) <- knownPrograms progConf ]
      ++ "\n"

    -- | For each known program @PROG@ in 'progConf', produce a @with-PROG@
    -- 'OptionField'.
    programConfigurationPaths
      :: ProgramConfiguration
      -> ShowOrParseArgs
      -> (flags -> [(String, FilePath)])
      -> ([(String, FilePath)] -> (flags -> flags))
      -> [OptionField flags]
    programConfigurationPaths progConf showOrParseArgs get set =
      programConfigurationPaths' ("with-" ++) progConf showOrParseArgs get set

    -- | Like 'programConfigurationPaths', but allows to customise the option name.
    programConfigurationPaths'
      :: (String -> String)
      -> ProgramConfiguration
      -> ShowOrParseArgs
      -> (flags -> [(String, FilePath)])
      -> ([(String, FilePath)] -> (flags -> flags))
      -> [OptionField flags]
    programConfigurationPaths' mkName progConf showOrParseArgs get set =
      case showOrParseArgs of
        -- we don't want a verbose help text list so we just show a generic one:
        ShowArgs  -> [withProgramPath "PROG"]
        ParseArgs -> map (withProgramPath . programName . fst)
                     (knownPrograms progConf)
      where
        withProgramPath prog =
          option "" [mkName prog]
            ("give the path to " ++ prog)
            get set
            (reqArg' "PATH" (\path -> [(prog, path)])
              (\progPaths -> [ path | (prog', path) <- progPaths, prog==prog' ]))

    -- | For each known program @PROG@ in 'progConf', produce a @PROG-option@
    -- 'OptionField'.
    programConfigurationOption
      :: ProgramConfiguration
      -> ShowOrParseArgs
      -> (flags -> [(String, [String])])
      -> ([(String, [String])] -> (flags -> flags))
      -> [OptionField flags]
    programConfigurationOption progConf showOrParseArgs get set =
      case showOrParseArgs of
        -- we don't want a verbose help text list so we just show a generic one:
        ShowArgs  -> [programOption "PROG"]
        ParseArgs -> map (programOption  . programName . fst)
                     (knownPrograms progConf)
      where
        programOption prog =
          option "" [prog ++ "-option"]
            ("give an extra option to " ++ prog ++
             " (no need to quote options containing spaces)")
            get set
            (reqArg' "OPT" (\arg -> [(prog, [arg])])
               (\progArgs -> concat [ args
                                    | (prog', args) <- progArgs, prog==prog' ]))

    -- | For each known program @PROG@ in 'progConf', produce a @PROG-options@
    -- 'OptionField'.
    programConfigurationOptions
      :: ProgramConfiguration
      -> ShowOrParseArgs
      -> (flags -> [(String, [String])])
      -> ([(String, [String])] -> (flags -> flags))
      -> [OptionField flags]
    programConfigurationOptions progConf showOrParseArgs get set =
      case showOrParseArgs of
        -- we don't want a verbose help text list so we just show a generic one:
        ShowArgs  -> [programOptions  "PROG"]
        ParseArgs -> map (programOptions . programName . fst)
                     (knownPrograms progConf)
      where
        programOptions prog =
          option "" [prog ++ "-options"]
            ("give extra options to " ++ prog)
            get set
            (reqArg' "OPTS" (\args -> [(prog, splitArgs args)]) (const []))

    -- ------------------------------------------------------------
    -- * GetOpt Utils
    -- ------------------------------------------------------------

    boolOpt :: SFlags -> SFlags
               -> MkOptDescr (a -> Flag Bool) (Flag Bool -> a -> a) a
    boolOpt  = Command.boolOpt  flagToMaybe Flag

    boolOpt' :: OptFlags -> OptFlags
                -> MkOptDescr (a -> Flag Bool) (Flag Bool -> a -> a) a
    boolOpt' = Command.boolOpt' flagToMaybe Flag

    trueArg, falseArg :: MkOptDescr (a -> Flag Bool) (Flag Bool -> a -> a) a
    trueArg  sfT lfT = boolOpt' (sfT, lfT) ([], [])   sfT lfT
    falseArg sfF lfF = boolOpt' ([],  [])  (sfF, lfF) sfF lfF

    reqArgFlag :: ArgPlaceHolder -> SFlags -> LFlags -> Description ->
                  (b -> Flag String) -> (Flag String -> b -> b) -> OptDescr b
    reqArgFlag ad = reqArg ad (succeedReadE Flag) flagToList

    optionDistPref :: (flags -> Flag FilePath)
                   -> (Flag FilePath -> flags -> flags)
                   -> ShowOrParseArgs
                   -> OptionField flags
    optionDistPref get set = \showOrParseArgs ->
      option "" (distPrefFlagName showOrParseArgs)
        (   "The directory where Cabal puts generated build files "
         ++ "(default " ++ defaultDistPref ++ ")")
        get set
        (reqArgFlag "DIR")
      where
        distPrefFlagName ShowArgs  = ["builddir"]
        distPrefFlagName ParseArgs = ["builddir", "distdir", "distpref"]

    optionVerbosity :: (flags -> Flag Verbosity)
                    -> (Flag Verbosity -> flags -> flags)
                    -> OptionField flags
    optionVerbosity get set =
      option "v" ["verbose"]
        "Control verbosity (n is 0--3, default verbosity level is 1)"
        get set
        (optArg "n" (fmap Flag flagToVerbosity)
                    (Flag verbose) -- default Value if no n is given
                    (fmap (Just . showForCabal) . flagToList))

    optionNumJobs :: (flags -> Flag (Maybe Int))
                  -> (Flag (Maybe Int) -> flags -> flags)
                  -> OptionField flags
    optionNumJobs get set =
      option "j" ["jobs"]
        "Run NUM jobs simultaneously (or '$ncpus' if no NUM is given)."
        get set
        (optArg "NUM" (fmap Flag numJobsParser)
                      (Flag Nothing)
                      (map (Just . maybe "$ncpus" show) . flagToList))
      where
        numJobsParser :: ReadE (Maybe Int)
        numJobsParser = ReadE $ \s ->
          case s of
            "$ncpus" -> Right Nothing
            _        -> case reads s of
              [(n, "")]
                | n < 1     -> Left "The number of jobs should be 1 or more."
                | n > 64    -> Left "You probably don't want that many jobs."
                | otherwise -> Right (Just n)
              _             -> Left "The jobs value should be a number or '$ncpus'"

    -- ------------------------------------------------------------
    -- * Other Utils
    -- ------------------------------------------------------------

    -- | Arguments to pass to a @configure@ script, e.g. generated by
    -- @autoconf@.
    configureArgs :: Bool -> ConfigFlags -> [String]
    configureArgs bcHack flags
      = hc_flag
     ++ optFlag  "with-hc-pkg" configHcPkg
     ++ optFlag' "prefix"      prefix
     ++ optFlag' "bindir"      bindir
     ++ optFlag' "libdir"      libdir
     ++ optFlag' "libexecdir"  libexecdir
     ++ optFlag' "datadir"     datadir
     ++ optFlag' "sysconfdir"  sysconfdir
     ++ configConfigureArgs flags
      where
            hc_flag = case (configHcFlavor flags, configHcPath flags) of
                            (_, Flag hc_path) -> [hc_flag_name ++ hc_path]
                            (Flag hc, NoFlag) -> [hc_flag_name ++ display hc]
                            (NoFlag,NoFlag)   -> []
            hc_flag_name
                --TODO kill off thic bc hack when defaultUserHooks is removed.
                | bcHack    = "--with-hc="
                | otherwise = "--with-compiler="
            optFlag name config_field = case config_field flags of
                            Flag p -> ["--" ++ name ++ "=" ++ p]
                            NoFlag -> []
            optFlag' name config_field = optFlag name (fmap fromPathTemplate
                                                     . config_field
                                                     . configInstallDirs)

    configureCCompiler :: Verbosity -> ProgramConfiguration
                          -> IO (FilePath, [String])
    configureCCompiler verbosity lbi = configureProg verbosity lbi gccProgram

    configureLinker :: Verbosity -> ProgramConfiguration -> IO (FilePath, [String])
    configureLinker verbosity lbi = configureProg verbosity lbi ldProgram

    configureProg :: Verbosity -> ProgramConfiguration -> Program
                     -> IO (FilePath, [String])
    configureProg verbosity programConfig prog = do
        (p, _) <- requireProgram verbosity prog programConfig
        let pInv = programInvocation p []
        return (progInvokePath pInv, progInvokeArgs pInv)

    -- | Helper function to split a string into a list of arguments.
    -- It's supposed to handle quoted things sensibly, eg:
    --
    -- > splitArgs "--foo=\"C:\Program Files\Bar\" --baz"
    -- >   = ["--foo=C:\Program Files\Bar", "--baz"]
    --
    splitArgs :: String -> [String]
    splitArgs  = space []
      where
        space :: String -> String -> [String]
        space w []      = word w []
        space w ( c :s)
            | isSpace c = word w (space [] s)
        space w ('"':s) = string w s
        space w s       = nonstring w s

        string :: String -> String -> [String]
        string w []      = word w []
        string w ('"':s) = space w s
        string w ( c :s) = string (c:w) s

        nonstring :: String -> String -> [String]
        nonstring w  []      = word w []
        nonstring w  ('"':s) = string w s
        nonstring w  ( c :s) = space (c:w) s

        word [] s = s
        word w  s = reverse w : s

    -- The test cases kinda have to be rewritten from the ground up... :/
    --hunitTests :: [Test]
    --hunitTests =
    --    let m = [("ghc", GHC), ("nhc98", NHC), ("hugs", Hugs)]
    --        (flags, commands', unkFlags, ers)
    --               = getOpt Permute options ["configure", "foobar", "--prefix=/foo", "--ghc", "--nhc98", "--hugs", "--with-compiler=/comp", "--unknown1", "--unknown2", "--install-prefix=/foo", "--user", "--global"]
    --       in  [TestLabel "very basic option parsing" $ TestList [
    --                 "getOpt flags" ~: "failed" ~:
    --                 [Prefix "/foo", GhcFlag, NhcFlag, HugsFlag,
    --                  WithCompiler "/comp", InstPrefix "/foo", UserFlag, GlobalFlag]
    --                 ~=? flags,
    --                 "getOpt commands" ~: "failed" ~: ["configure", "foobar"] ~=? commands',
    --                 "getOpt unknown opts" ~: "failed" ~:
    --                      ["--unknown1", "--unknown2"] ~=? unkFlags,
    --                 "getOpt errors" ~: "failed" ~: [] ~=? ers],
    --
    --               TestLabel "test location of various compilers" $ TestList
    --               ["configure parsing for prefix and compiler flag" ~: "failed" ~:
    --                    (Right (ConfigCmd (Just comp, Nothing, Just "/usr/local"), []))
    --                   ~=? (parseArgs ["--prefix=/usr/local", "--"++name, "configure"])
    --                   | (name, comp) <- m],
    --
    --               TestLabel "find the package tool" $ TestList
    --               ["configure parsing for prefix comp flag, withcompiler" ~: "failed" ~:
    --                    (Right (ConfigCmd (Just comp, Just "/foo/comp", Just "/usr/local"), []))
    --                   ~=? (parseArgs ["--prefix=/usr/local", "--"++name,
    --                                   "--with-compiler=/foo/comp", "configure"])
    --                   | (name, comp) <- m],
    --
    --               TestLabel "simpler commands" $ TestList
    --               [flag ~: "failed" ~: (Right (flagCmd, [])) ~=? (parseArgs [flag])
    --                   | (flag, flagCmd) <- [("build", BuildCmd),
    --                                         ("install", InstallCmd Nothing False),
    --                                         ("sdist", SDistCmd),
    --                                         ("register", RegisterCmd False)]
    --                  ]
    --               ]

    {- Testing ideas:
       * IO to look for hugs and hugs-pkg (which hugs, etc)
       * quickCheck to test permutations of arguments
       * what other options can we over-ride with a command-line flag?
    -}