1 -- | Parse CLI arguments and resolve defaults from the environment.
7 , VersionParseException
(..)
11 import Control
.Applicative
(Alternative
(many
, (<|
>)), (<**>))
12 import Control
.Exception
(Exception
(displayException
), throw
)
13 import Control
.Monad
(forM_
, when)
14 import Data
.Data
(Typeable
)
15 import Data
.Maybe (listToMaybe)
16 import qualified Data
.Text
as T
17 import qualified Data
.Text
.Lazy
as T
(toStrict
)
18 import qualified Data
.Text
.Lazy
.Encoding
as T
(decodeUtf8
)
19 import Data
.Version
(Version
, parseVersion
)
20 import GHC
.Conc
(getNumCapabilities
)
21 import Options
.Applicative
44 import qualified Options
.Applicative
as Opt
45 import System
.Directory
(getCurrentDirectory)
46 import System
.Exit
(exitSuccess
)
47 import System
.Info
(arch
, os
)
48 import System
.Process
.Typed
(proc
, readProcessStdout_
)
49 import Text
.ParserCombinators
.ReadP
(readP_to_S
)
51 import ClockUtil
(AbsoluteTime
, getAbsoluteTime
)
52 import Step
(Step
(..), displayStep
, parseStep
)
54 -- | Command-line options, resolved with context from the environment.
57 -- ^ Whether to display build and test output.
59 -- ^ How many jobs to use when running tests.
61 -- Defaults to the number of physical cores.
63 -- ^ Current working directory when @cabal-validate@ was started.
64 , startTime
:: AbsoluteTime
65 -- ^ System time when @cabal-validate@ was started.
67 -- Used to determine the total test duration so far.
68 , compiler
:: Compiler
69 -- ^ Compiler to build Cabal with.
72 , extraCompilers
:: [FilePath]
73 -- ^ Extra compilers to run @cabal-testsuite@ with.
75 -- ^ @cabal-install@ to build Cabal with.
77 -- Defaults to @cabal@.
78 , hackageTests
:: HackageTests
79 -- ^ Whether to run tests on Hackage data, and if so how much.
81 -- Defaults to `NoHackageTests`.
82 , archPath
:: FilePath
83 -- ^ The path for this system's architecture within the build directory.
85 -- Like @x86_64-windows@ or @aarch64-osx@ or @arm-linux@.
86 , projectFile
:: FilePath
87 -- ^ Path to the @cabal.project@ file to use for running tests.
88 , tastyArgs
:: [String]
89 -- ^ Extra arguments to pass to @tasty@ test suites.
91 -- This defaults to @--hide-successes@ (which cannot yet be changed) and
92 -- includes the @--pattern@ argument if one is given.
94 -- ^ Targets to build.
100 -- | Whether to run tests on Hackage data, and if so how much.
102 = -- | Run tests on complete Hackage data.
104 |
-- | Run tests on partial Hackage data.
106 |
-- | Do not run tests on Hackage data.
110 -- | A compiler executable and version number.
111 data Compiler
= Compiler
112 { compilerExecutable
:: FilePath
113 -- ^ The compiler's executable.
114 , compilerVersion
:: Version
115 -- ^ The compiler's version number.
119 -- | An `Exception` thrown when parsing @--numeric-version@ output from a compiler.
120 data VersionParseException
= VersionParseException
121 { versionInput
:: String
122 -- ^ The string we attempted to parse.
123 , versionExecutable
:: FilePath
124 -- ^ The compiler which produced the string.
126 deriving (Typeable
, Show)
128 instance Exception VersionParseException
where
129 displayException exception
=
131 <> versionExecutable exception
132 <> " --numeric-version` output: "
133 <> show (versionInput exception
)
135 -- | Runs @ghc --numeric-version@ for the given executable to construct a
137 makeCompiler
:: FilePath -> IO Compiler
138 makeCompiler
executable = do
141 proc
executable ["--numeric-version"]
142 let version
= T
.unpack
$ T
.strip
$ T
.toStrict
$ T
.decodeUtf8
stdout
143 parsedVersions
= readP_to_S parseVersion version
144 -- Who needs error messages? Those aren't in the API.
148 |
(parsed
, []) <- parsedVersions
150 parsedVersion
= case maybeParsedVersion
of
151 Just parsedVersion
' -> parsedVersion
'
154 VersionParseException
155 { versionInput
= version
156 , versionExecutable
= executable
161 { compilerExecutable
= executable
162 , compilerVersion
= parsedVersion
165 -- | Resolve options and default values from the environment.
167 -- This makes the `Opts` type much nicer to deal with than `RawOpts`.
168 resolveOpts
:: RawOpts
-> IO Opts
169 resolveOpts opts
= do
170 let optionals
:: Bool -> [a
] -> [a
]
171 optionals
True items
= items
172 optionals
False _
= []
174 optional
:: Bool -> a
-> [a
]
175 optional keep
item = optionals keep
[item]
178 if not (null (rawSteps opts
))
187 , optional
(rawDoctest opts
) Doctest
188 , optional
(rawRunLibTests opts
) LibTests
189 , optional
(rawRunLibSuite opts
) LibSuite
190 , optional
(rawRunLibSuite opts
&& not (null (rawExtraCompilers opts
))) LibSuiteExtras
191 , optional
(rawRunCliTests opts
&& not (rawLibOnly opts
)) CliTests
192 , optional
(rawRunCliSuite opts
&& not (rawLibOnly opts
)) CliSuite
193 , optionals
(rawSolverBenchmarks opts
) [SolverBenchmarksTests
, SolverBenchmarksRun
]
209 (not (rawLibOnly opts
))
211 , "cabal-install-solver"
215 (rawSolverBenchmarks opts
)
216 [ "solver-benchmarks"
217 , "solver-benchmarks:tests"
226 "mingw32" -> "windows"
227 _
-> os
-- TODO: Warning?
228 in arch
<> "-" <> osPath
232 then "cabal.validate-libonly.project"
233 else "cabal.validate.project"
239 (\tastyPattern
-> ["--pattern", tastyPattern
])
240 (rawTastyPattern opts
)
243 when (rawListSteps opts
) $ do
244 -- TODO: This should probably list _all_ available steps, not just the selected ones!
246 forM_ targets
' $ \target
-> do
247 putStrLn $ " " <> target
249 forM_ steps
' $ \step
-> do
250 putStrLn $ " " <> displayStep step
253 startTime
' <- getAbsoluteTime
254 jobs
' <- maybe getNumCapabilities pure
(rawJobs opts
)
255 cwd
' <- getCurrentDirectory
256 compiler
' <- makeCompiler
(rawCompiler opts
)
260 { verbose
= rawVerbose opts
263 , startTime
= startTime
'
264 , compiler
= compiler
'
265 , extraCompilers
= rawExtraCompilers opts
266 , cabal
= rawCabal opts
267 , archPath
= archPath
'
268 , projectFile
= projectFile
'
269 , hackageTests
= rawHackageTests opts
270 , tastyArgs
= tastyArgs
'
275 -- | Literate command-line options as supplied by the user, before resolving
276 -- defaults and other values from the environment.
277 data RawOpts
= RawOpts
279 , rawJobs
:: Maybe Int
280 , rawCompiler
:: FilePath
281 , rawCabal
:: FilePath
282 , rawExtraCompilers
:: [FilePath]
283 , rawTastyPattern
:: Maybe String
284 , rawTastyArgs
:: [String]
287 , rawListSteps
:: Bool
289 , rawRunLibTests
:: Bool
290 , rawRunCliTests
:: Bool
291 , rawRunLibSuite
:: Bool
292 , rawRunCliSuite
:: Bool
293 , rawSolverBenchmarks
:: Bool
294 , rawHackageTests
:: HackageTests
298 -- | `Parser` for `RawOpts`.
300 -- See: `fullRawOptsParser`
301 rawOptsParser
:: Parser RawOpts
308 <> help
"Always display build and test output"
315 <> help
"Silence build and test output"
322 <> help
"Passed to `cabal build --jobs`"
327 <> long
"with-compiler"
328 <> help
"Build Cabal with the given compiler instead of `ghc`"
333 <> help
"Test the given `cabal-install` (the `cabal` on your `$PATH` is used for builds)"
339 <> help
"Extra compilers to run the test suites against"
346 <> help
"Pattern to filter tests by"
352 <> help
"Extra arguments to pass to Tasty test suites"
358 ( help
"Run doctest on the `Cabal` library"
362 (maybeReader parseStep
)
365 <> help
"Run only a specific step (can be specified multiple times)"
370 <> help
"List the available steps and exit"
375 <> help
"Test only `Cabal` (the library)"
381 <> help
"Test `cabal-install` (the executable) in addition to `Cabal` (the library)"
387 ( help
"Run tests for the `Cabal` library"
392 ( help
"Run client tests for the `cabal-install` executable"
397 ( help
"Run `cabal-testsuite` with the `Cabal` library"
402 ( help
"Run `cabal-testsuite` with the `cabal-install` executable"
407 ( help
"Build and trial run `solver-benchmarks`"
411 ( long
"complete-hackage-tests"
412 <> help
"Run `hackage-tests` on complete Hackage data"
417 ( long
"partial-hackage-tests"
418 <> help
"Run `hackage-tests` on parts of Hackage data"
422 -- | Parse a boolean switch with separate names for the true and false options.
423 boolOption
' :: Bool -> String -> String -> Mod FlagFields
Bool -> Parser
Bool
424 boolOption
' defaultValue trueName falseName modifiers
=
425 flag
' True (modifiers
<> long trueName
)
426 <|
> flag defaultValue
False (modifiers
<> hidden
<> long falseName
)
428 -- | Parse a boolean switch with a @--no-*@ flag for setting the option to false.
429 boolOption
:: Bool -> String -> Mod FlagFields
Bool -> Parser
Bool
430 boolOption defaultValue trueName
=
431 boolOption
' defaultValue trueName
("no-" <> trueName
)
433 -- | Full `Parser` for `RawOpts`, which includes a @--help@ argument and
434 -- information about the program.
435 fullRawOptsParser
:: ParserInfo RawOpts
438 (rawOptsParser
<**> helper
)
440 <> progDesc
"Test suite runner for `Cabal` and `cabal-install` developers"
443 -- | Parse command-line arguments and resolve defaults from the environment,
446 parseOpts
= execParser fullRawOptsParser
>>= resolveOpts