1 -- | Parse CLI arguments and resolve defaults from the environment.
7 , VersionParseException
(..)
13 import Control
.Applicative
(Alternative
(many
, (<|
>)), (<**>))
14 import Control
.Exception
(Exception
(displayException
), throw
)
15 import Control
.Monad
(forM_
, when)
16 import Data
.Data
(Typeable
)
17 import Data
.Maybe (listToMaybe)
18 import qualified Data
.Text
as T
19 import qualified Data
.Text
.Lazy
as T
(toStrict
)
20 import qualified Data
.Text
.Lazy
.Encoding
as T
(decodeUtf8
)
21 import Data
.Version
(Version
, parseVersion
)
22 import GHC
.Conc
(getNumCapabilities
)
23 import Options
.Applicative
46 import qualified Options
.Applicative
as Opt
47 import System
.Directory
(getCurrentDirectory)
48 import System
.Exit
(exitSuccess
)
49 import System
.Info
(arch
, os
)
50 import System
.Process
.Typed
(proc
, readProcessStdout_
)
51 import Text
.ParserCombinators
.ReadP
(readP_to_S
)
53 import ClockUtil
(AbsoluteTime
, getAbsoluteTime
)
54 import Step
(Step
(..), displayStep
, parseStep
)
56 -- | Command-line options, resolved with context from the environment.
58 { verbosity
:: Verbosity
59 -- ^ Whether to display build and test output.
61 -- ^ How many jobs to use when running tests.
63 -- Defaults to the number of physical cores.
65 -- ^ Current working directory when @cabal-validate@ was started.
66 , startTime
:: AbsoluteTime
67 -- ^ System time when @cabal-validate@ was started.
69 -- Used to determine the total test duration so far.
70 , compiler
:: Compiler
71 -- ^ Compiler to build Cabal with.
74 , extraCompilers
:: [FilePath]
75 -- ^ Extra compilers to run @cabal-testsuite@ with.
77 -- ^ @cabal-install@ to build Cabal with.
79 -- Defaults to @cabal@.
80 , hackageTests
:: HackageTests
81 -- ^ Whether to run tests on Hackage data, and if so how much.
83 -- Defaults to `NoHackageTests`.
84 , archPath
:: FilePath
85 -- ^ The path for this system's architecture within the build directory.
87 -- Like @x86_64-windows@ or @aarch64-osx@ or @arm-linux@.
88 , projectFile
:: FilePath
89 -- ^ Path to the @cabal.project@ file to use for running tests.
90 , tastyArgs
:: [String]
91 -- ^ Extra arguments to pass to @tasty@ test suites.
93 -- This defaults to @--hide-successes@ (which cannot yet be changed) and
94 -- includes the @--pattern@ argument if one is given.
96 -- ^ Targets to build.
102 -- | Whether to run tests on Hackage data, and if so how much.
104 = -- | Run tests on complete Hackage data.
106 |
-- | Run tests on partial Hackage data.
108 |
-- | Do not run tests on Hackage data.
112 -- | A compiler executable and version number.
113 data Compiler
= Compiler
114 { compilerExecutable
:: FilePath
115 -- ^ The compiler's executable.
116 , compilerVersion
:: Version
117 -- ^ The compiler's version number.
121 -- | A verbosity level, for log output.
126 deriving (Show, Eq
, Ord
)
128 -- | Run an action only if the `verbosity` is `Verbose` or higher.
129 whenVerbose
:: Applicative f
=> Opts
-> f
() -> f
()
130 whenVerbose opts action
= when (verbosity opts
>= Verbose
) action
132 -- | An `Exception` thrown when parsing @--numeric-version@ output from a compiler.
133 data VersionParseException
= VersionParseException
134 { versionInput
:: String
135 -- ^ The string we attempted to parse.
136 , versionExecutable
:: FilePath
137 -- ^ The compiler which produced the string.
139 deriving (Typeable
, Show)
141 instance Exception VersionParseException
where
142 displayException exception
=
144 <> versionExecutable exception
145 <> " --numeric-version` output: "
146 <> show (versionInput exception
)
148 -- | Runs @ghc --numeric-version@ for the given executable to construct a
150 makeCompiler
:: FilePath -> IO Compiler
151 makeCompiler
executable = do
154 proc
executable ["--numeric-version"]
155 let version
= T
.unpack
$ T
.strip
$ T
.toStrict
$ T
.decodeUtf8
stdout
156 parsedVersions
= readP_to_S parseVersion version
157 -- Who needs error messages? Those aren't in the API.
161 |
(parsed
, []) <- parsedVersions
163 parsedVersion
= case maybeParsedVersion
of
164 Just parsedVersion
' -> parsedVersion
'
167 VersionParseException
168 { versionInput
= version
169 , versionExecutable
= executable
174 { compilerExecutable
= executable
175 , compilerVersion
= parsedVersion
178 -- | Resolve options and default values from the environment.
180 -- This makes the `Opts` type much nicer to deal with than `RawOpts`.
181 resolveOpts
:: RawOpts
-> IO Opts
182 resolveOpts opts
= do
183 let optionals
:: Bool -> [a
] -> [a
]
184 optionals
True items
= items
185 optionals
False _
= []
187 optional
:: Bool -> a
-> [a
]
188 optional keep
item = optionals keep
[item]
191 if not (null (rawSteps opts
))
196 , optional
(rawDoctest opts
) Doctest
197 , optional
(rawRunLibTests opts
) LibTests
198 , optional
(rawRunLibSuite opts
) LibSuite
199 , optional
(rawRunLibSuite opts
&& not (null (rawExtraCompilers opts
))) LibSuiteExtras
200 , optional
(rawRunCliTests opts
&& not (rawLibOnly opts
)) CliTests
201 , optional
(rawRunCliSuite opts
&& not (rawLibOnly opts
)) CliSuite
202 , optionals
(rawSolverBenchmarks opts
) [SolverBenchmarksTests
, SolverBenchmarksRun
]
217 (not (rawLibOnly opts
))
219 , "cabal-install-solver"
223 (rawSolverBenchmarks opts
)
224 [ "solver-benchmarks"
225 , "solver-benchmarks:tests"
234 "mingw32" -> "windows"
235 _
-> os
-- TODO: Warning?
236 in arch
<> "-" <> osPath
240 then "cabal.validate-libonly.project"
241 else "cabal.validate.project"
245 -- If neither `--hide-successes` or `--no-hide-successes` was given, then
246 -- only `--hide-successes` if `--quiet` is given.
247 (optional
(rawVerbosity opts
<= Quiet
) "--hide-successes")
248 (\hideSuccesses
-> optional hideSuccesses
"--hide-successes")
249 (rawTastyHideSuccesses opts
)
252 (\tastyPattern
-> ["--pattern", tastyPattern
])
253 (rawTastyPattern opts
)
256 when (rawListSteps opts
) $ do
257 -- TODO: This should probably list _all_ available steps, not just the selected ones!
259 forM_ targets
' $ \target
-> do
260 putStrLn $ " " <> target
262 forM_ steps
' $ \step
-> do
263 putStrLn $ " " <> displayStep step
266 startTime
' <- getAbsoluteTime
267 jobs
' <- maybe getNumCapabilities pure
(rawJobs opts
)
268 cwd
' <- getCurrentDirectory
269 compiler
' <- makeCompiler
(rawCompiler opts
)
273 { verbosity
= rawVerbosity opts
276 , startTime
= startTime
'
277 , compiler
= compiler
'
278 , extraCompilers
= rawExtraCompilers opts
279 , cabal
= rawCabal opts
280 , archPath
= archPath
'
281 , projectFile
= projectFile
'
282 , hackageTests
= rawHackageTests opts
283 , tastyArgs
= tastyArgs
'
288 -- | Literate command-line options as supplied by the user, before resolving
289 -- defaults and other values from the environment.
290 data RawOpts
= RawOpts
291 { rawVerbosity
:: Verbosity
292 , rawJobs
:: Maybe Int
293 , rawCompiler
:: FilePath
294 , rawCabal
:: FilePath
295 , rawExtraCompilers
:: [FilePath]
296 , rawTastyPattern
:: Maybe String
297 , rawTastyArgs
:: [String]
298 , rawTastyHideSuccesses
:: Maybe Bool
301 , rawListSteps
:: Bool
303 , rawRunLibTests
:: Bool
304 , rawRunCliTests
:: Bool
305 , rawRunLibSuite
:: Bool
306 , rawRunCliSuite
:: Bool
307 , rawSolverBenchmarks
:: Bool
308 , rawHackageTests
:: HackageTests
312 -- | `Parser` for `RawOpts`.
314 -- See: `fullRawOptsParser`
315 rawOptsParser
:: Parser RawOpts
322 <> help
"Always display build and test output"
329 <> help
"Silence build and test output"
336 <> help
"Passed to `cabal build --jobs`"
341 <> long
"with-compiler"
342 <> help
"Build Cabal with the given compiler instead of `ghc`"
347 <> help
"Test the given `cabal-install` (the `cabal` on your `$PATH` is used for builds)"
353 <> help
"Extra compilers to run the test suites against"
360 <> help
"Pattern to filter tests by"
366 <> help
"Extra arguments to pass to Tasty test suites"
371 ( help
"Do not print tests that passed successfully"
376 ( help
"Run doctest on the `Cabal` library"
380 (maybeReader parseStep
)
383 <> help
"Run only a specific step (can be specified multiple times)"
388 <> help
"List the available steps and exit"
393 <> help
"Test only `Cabal` (the library)"
399 <> help
"Test `cabal-install` (the executable) in addition to `Cabal` (the library)"
405 ( help
"Run tests for the `Cabal` library"
410 ( help
"Run client tests for the `cabal-install` executable"
415 ( help
"Run `cabal-testsuite` with the `Cabal` library"
420 ( help
"Run `cabal-testsuite` with the `cabal-install` executable"
425 ( help
"Build and trial run `solver-benchmarks`"
429 ( long
"complete-hackage-tests"
430 <> help
"Run `hackage-tests` on complete Hackage data"
435 ( long
"partial-hackage-tests"
436 <> help
"Run `hackage-tests` on parts of Hackage data"
440 -- | Parse a boolean switch with separate names for the true and false options.
441 boolOption
' :: Bool -> String -> String -> Mod FlagFields
Bool -> Parser
Bool
442 boolOption
' defaultValue trueName falseName modifiers
=
443 flag
' True (modifiers
<> long trueName
)
444 <|
> flag defaultValue
False (modifiers
<> hidden
<> long falseName
)
446 -- | Parse a boolean switch with a @--no-*@ flag for setting the option to false.
447 boolOption
:: Bool -> String -> Mod FlagFields
Bool -> Parser
Bool
448 boolOption defaultValue trueName
=
449 boolOption
' defaultValue trueName
("no-" <> trueName
)
451 -- | Like `boolOption`, but can tell if an option was passed or not.
452 maybeBoolOption
:: String -> Mod FlagFields
(Maybe Bool) -> Parser
(Maybe Bool)
453 maybeBoolOption trueName modifiers
=
454 flag
' (Just
True) (modifiers
<> long trueName
)
455 <|
> flag Nothing
(Just
False) (modifiers
<> hidden
<> long
("no-" <> trueName
))
457 -- | Full `Parser` for `RawOpts`, which includes a @--help@ argument and
458 -- information about the program.
459 fullRawOptsParser
:: ParserInfo RawOpts
462 (rawOptsParser
<**> helper
)
464 <> progDesc
"Test suite runner for `Cabal` and `cabal-install` developers"
467 -- | Parse command-line arguments and resolve defaults from the environment,
470 parseOpts
= execParser fullRawOptsParser
>>= resolveOpts