forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCLI.hs
More file actions
700 lines (644 loc) · 20.9 KB
/
CLI.hs
File metadata and controls
700 lines (644 loc) · 20.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
{-# LANGUAGE NoImplicitPrelude #-}
module Stack.CLI
( commandLineHandler
) where
import Data.Attoparsec.Interpreter ( getInterpreterArgs )
import Data.Char ( toLower )
import qualified Data.List as L
import Options.Applicative
( Parser, ParserFailure, ParserHelp, ParserResult (..), flag, switch
, handleParseResult, help, helpError, idm, long, metavar
, overFailure, renderFailure, strArgument, switch )
import Options.Applicative.Help ( errorHelp, stringChunk, vcatChunks )
import Options.Applicative.Builder.Extra
( boolFlags, extraHelpOption, textOption )
import Options.Applicative.Complicated
( addCommand, addSubCommands, complicatedOptions )
import qualified RIO.Process ( exec )
import RIO.Process ( withProcessContextNoLogging )
import Stack.Build ( buildCmd )
import Stack.BuildInfo ( hpackVersion, versionString' )
import Stack.Clean ( CleanCommand (..), cleanCmd )
import Stack.ConfigCmd as ConfigCmd
import Stack.Constants ( globalFooter, osIsWindows, stackProgName )
import Stack.Coverage ( hpcReportCmd )
import Stack.Docker
( dockerCmdName, dockerHelpOptName, dockerPullCmdName )
import Stack.DockerCmd ( dockerPullCmd, dockerResetCmd )
import qualified Stack.Dot ( dot )
import Stack.Exec ( SpecialExecCmd (..), execCmd )
import Stack.Eval ( evalCmd )
import Stack.Ghci ( ghciCmd )
import Stack.Hoogle ( hoogleCmd )
import Stack.IDE
( ListPackagesCmd (..), OutputStream (..), idePackagesCmd
, ideTargetsCmd
)
import Stack.Init ( initCmd )
import Stack.List ( listCmd )
import Stack.Ls ( lsCmd )
import Stack.New ( newCmd )
import qualified Stack.Nix as Nix
import Stack.Options.BuildParser ( buildOptsParser )
import Stack.Options.CleanParser ( cleanOptsParser )
import Stack.Options.DotParser ( dotOptsParser )
import Stack.Options.EvalParser ( evalOptsParser )
import Stack.Options.ExecParser ( execOptsParser )
import Stack.Options.GhciParser ( ghciOptsParser )
import Stack.Options.GlobalParser ( globalOptsParser )
import Stack.Options.HpcReportParser ( hpcReportOptsParser )
import Stack.Options.InitParser ( initOptsParser )
import Stack.Options.LsParser ( lsOptsParser )
import Stack.Options.NewParser ( newOptsParser )
import Stack.Options.PathParser ( pathParser )
import Stack.Options.SDistParser ( sdistOptsParser )
import Stack.Options.ScriptParser ( scriptOptsParser )
import Stack.Options.SetupParser ( setupOptsParser )
import Stack.Options.UpgradeParser ( upgradeOptsParser )
import Stack.Options.UploadParser ( uploadOptsParser )
import Stack.Options.Utils ( GlobalOptsContext (..) )
import qualified Stack.Path ( path )
import Stack.Prelude
import Stack.Query ( queryCmd )
import Stack.Runners
( ShouldReexec (..), withConfig, withDefaultEnvConfig )
import Stack.SDist ( sdistCmd )
import Stack.Script ( ScriptOpts (..), scriptCmd )
import Stack.SetupCmd ( setupCmd )
import Stack.Templates ( templatesCmd )
import Stack.Types.AddCommand ( AddCommand )
import Stack.Types.BuildOpts ( BuildCommand (..) )
import Stack.Types.GlobalOptsMonoid ( GlobalOptsMonoid (..) )
import Stack.Types.Runner ( Runner )
import Stack.Types.Version ( stackVersion )
import Stack.Uninstall ( uninstallCmd )
import Stack.Unpack ( unpackCmd )
import Stack.Update ( updateCmd )
import Stack.Upgrade ( upgradeCmd )
import Stack.Upload ( uploadCmd )
import qualified System.Directory as D
import System.Environment ( getProgName, withArgs )
import System.FilePath ( pathSeparator, takeDirectory )
-- | Stack's command line handler.
commandLineHandler ::
FilePath
-> String
-> Bool
-> IO (GlobalOptsMonoid, RIO Runner ())
commandLineHandler currentDir progName isInterpreter =
-- Append the relevant default (potentially affecting the LogLevel) *after*
-- appending the global options of the `stack` command to the global options
-- of the subcommand - see #5326.
first (<> defaultGlobalOpts) <$> complicatedOptions
stackVersion
(Just versionString')
hpackVersion
"stack - The Haskell Tool Stack"
""
("Stack's documentation is available at https://docs.haskellstack.org/. \
\Command '" <> progName <> " COMMAND --help' for help about a Stack command. Stack also \
\supports the Haskell Error Index at https://errors.haskell.org/.")
(globalOpts OuterGlobalOpts)
(Just failureCallback)
addCommands
where
defaultGlobalOpts = if isInterpreter
then
-- Silent except when errors occur - see #2879
mempty { globalMonoidLogLevel = First (Just LevelError) }
else mempty
failureCallback f args =
case L.stripPrefix "Invalid argument" (fst (renderFailure f "")) of
Just _ -> if isInterpreter
then parseResultHandler args f
else secondaryCommandHandler args f
>>= interpreterHandler currentDir args
Nothing -> parseResultHandler args f
parseResultHandler args f =
if isInterpreter
then do
let hlp = errorHelp $ stringChunk
(unwords ["Error executing interpreter command:"
, progName
, unwords args])
handleParseResult (overFailure (vcatErrorHelp hlp) (Failure f))
else handleParseResult (Failure f)
-- The order of commands below determines the order in which they are listed
-- in `stack --help`.
addCommands = do
unless isInterpreter $ do
build
install
uninstall
test
bench
haddock
new
templates
init
setup
path
ls
unpack
update
upgrade
upload
sdist
dot
ghc
hoogle
-- These are the only commands allowed in interpreter mode as well
exec
run
ghci
repl
runghc
runhaskell
script
unless isInterpreter $ do
eval
clean
purge
query
list
ide
docker
config
hpc
-- Stack's subcommands are listed below in alphabetical order
bench = addBuildCommand'
"bench"
"Shortcut for 'build --bench'."
buildCmd
(buildOptsParser Bench)
build = addBuildCommand'
"build"
"Build the package(s) in this directory/configuration."
buildCmd
(buildOptsParser Build)
clean = addCommand'
"clean"
"Delete build artefacts for the project packages."
cleanCmd
(cleanOptsParser Clean)
config = addSubCommands'
ConfigCmd.cfgCmdName
"Subcommands for accessing and modifying configuration values."
( do
addCommand'
ConfigCmd.cfgCmdSetName
"Sets a key in YAML configuration file to value."
(withConfig NoReexec . cfgCmdSet)
configCmdSetParser
addCommand'
ConfigCmd.cfgCmdEnvName
"Print environment variables for use in a shell."
(withConfig YesReexec . withDefaultEnvConfig . cfgCmdEnv)
configCmdEnvParser
)
docker = addSubCommands'
dockerCmdName
"Subcommands specific to Docker use."
( do
addCommand'
dockerPullCmdName
"Pull latest version of Docker image from registry."
dockerPullCmd
(pure ())
addCommand'
"reset"
"Reset the Docker sandbox."
dockerResetCmd
( switch
( long "keep-home"
<> help "Do not delete sandbox's home directory."
)
)
)
dot = addCommand'
"dot"
"Visualize your project's dependency graph using Graphviz dot."
Stack.Dot.dot
(dotOptsParser False) -- Default for --external is False.
eval = addCommand'
"eval"
"Evaluate some Haskell code inline. Shortcut for \
\'stack exec ghc -- -e CODE'."
evalCmd
(evalOptsParser "CODE")
exec = addCommand'
"exec"
"Execute a command. If the command is absent, the first of any arguments \
\is taken as the command."
execCmd
(execOptsParser Nothing)
ghc = addCommand'
"ghc"
"Run ghc."
execCmd
(execOptsParser $ Just ExecGhc)
ghci = addGhciCommand'
"ghci"
"Run ghci in the context of package(s)."
ghciCmd
ghciOptsParser
haddock = addBuildCommand'
"haddock"
"Shortcut for 'build --haddock'."
buildCmd
(buildOptsParser Haddock)
hoogle = addCommand'
"hoogle"
"Run hoogle, the Haskell API search engine. Use the '-- ARGUMENT(S)' \
\syntax to pass Hoogle arguments, e.g. 'stack hoogle -- --count=20', \
\or 'stack hoogle -- server --local'."
hoogleCmd
( (,,,)
<$> many (strArgument
( metavar "-- ARGUMENT(S) (e.g. 'stack hoogle -- server --local')"
))
<*> boolFlags
True
"setup"
"If needed: install Hoogle, build Haddock documentation and \
\generate a Hoogle database."
idm
<*> switch
( long "rebuild"
<> help "Rebuild the Hoogle database."
)
<*> switch
( long "server"
<> help "Start local Hoogle server."
)
)
hpc = addSubCommands'
"hpc"
"Subcommands specific to Haskell Program Coverage."
( addCommand'
"report"
"Generate unified HPC coverage report from tix files and project \
\targets."
hpcReportCmd
hpcReportOptsParser
)
ide = addSubCommands'
"ide"
"IDE-specific commands."
( let outputFlag = flag
OutputLogInfo
OutputStdout
( long "stdout"
<> help "Send output to the standard output stream instead of the \
\default, the standard error stream."
)
cabalFileFlag = flag
ListPackageNames
ListPackageCabalFiles
( long "cabal-files"
<> help "Print paths to package Cabal files instead of package \
\names."
)
exeFlag = switch
( long "exes"
<> help "Include executables."
)
testFlag = switch
( long "tests"
<> help "Include test suites."
)
benchFlag = switch
( long "benchmarks"
<> help "Include benchmarks."
)
in do
addCommand'
"packages"
"List all available local loadable packages."
idePackagesCmd
((,) <$> outputFlag <*> cabalFileFlag)
addCommand'
"targets"
"List all targets or pick component types to list."
ideTargetsCmd
( (,)
<$> ((,,) <$> exeFlag <*> testFlag <*> benchFlag)
<*> outputFlag
)
)
init = addCommand'
"init"
"Create Stack project configuration from Cabal or Hpack package \
\specifications."
initCmd
initOptsParser
install = addBuildCommand'
"install"
"Shortcut for 'build --copy-bins'."
buildCmd
(buildOptsParser Install)
list = addCommand'
"list"
"List package id's in snapshot (experimental)."
listCmd
(many $ strArgument $ metavar "PACKAGE")
ls = addCommand'
"ls"
"List command. (Supports snapshots, dependencies, Stack's styles and \
\installed tools.)"
lsCmd
lsOptsParser
new = addCommand'
"new"
"Create a new project from a template. Run 'stack templates' to see \
\available templates. Will also initialise if there is no stack.yaml \
\file. Note: you can also specify a local file or a remote URL as a \
\template; or force an initialisation."
newCmd
newOptsParser
path = addCommand'
"path"
"Print out handy path information."
Stack.Path.path
pathParser
purge = addCommand'
"purge"
"Delete the project Stack working directories (.stack-work by \
\default). Shortcut for 'stack clean --full'."
cleanCmd
(cleanOptsParser Purge)
query = addCommand'
"query"
"Query general build information (experimental)."
queryCmd
(many $ strArgument $ metavar "SELECTOR...")
repl = addGhciCommand'
"repl"
"Run ghci in the context of package(s) (alias for 'ghci')."
ghciCmd
ghciOptsParser
run = addCommand'
"run"
"Build and run an executable. Defaults to the first available \
\executable if none is provided as the first argument."
execCmd
(execOptsParser $ Just ExecRun)
runghc = addCommand'
"runghc"
"Run runghc."
execCmd
(execOptsParser $ Just ExecRunGhc)
runhaskell = addCommand'
"runhaskell"
"Run runghc (alias for 'runghc')."
execCmd
(execOptsParser $ Just ExecRunGhc)
script = addCommand
"script"
"Run a Stack script."
globalFooter
scriptCmd
( \so gom ->
gom
{ globalMonoidResolverRoot =
First $ Just $ takeDirectory $ soFile so
}
)
(globalOpts OtherCmdGlobalOpts)
scriptOptsParser
sdist = addCommand'
"sdist"
"Create source distribution tarballs."
sdistCmd
sdistOptsParser
setup = addCommand'
"setup"
"Get the appropriate GHC for your project."
setupCmd
setupOptsParser
templates = addCommand'
"templates"
"Show how to find templates available for 'stack new'. 'stack new' \
\can accept a template from a remote repository (default: github), \
\local file or remote URL. Note: this downloads the help file."
templatesCmd
(pure ())
test = addBuildCommand'
"test"
"Shortcut for 'build --test'."
buildCmd
(buildOptsParser Test)
uninstall = addCommand'
"uninstall"
"Show how to uninstall Stack or a Stack-supplied tool. This command does \
\not itself uninstall Stack or a Stack-supplied tool."
uninstallCmd
(pure ())
unpack = addCommand'
"unpack"
"Unpack one or more packages locally."
unpackCmd
( (,)
<$> some (strArgument $ metavar "PACKAGE")
<*> optional (textOption
( long "to"
<> help "Optional path to unpack the package into (will \
\unpack into subdirectory)."
))
)
update = addCommand'
"update"
"Update the package index."
updateCmd
(pure ())
upgrade = addCommand''
"upgrade"
"Upgrade Stack, installing to Stack's local-bin directory and, if \
\different and permitted, the directory of the current Stack \
\executable."
upgradeCmd
"Warning: if you use GHCup to install Stack, use only GHCup to \
\upgrade Stack."
(upgradeOptsParser onlyLocalBins)
where
onlyLocalBins =
(lowercase progName /= lowercase stackProgName)
&& not ( osIsWindows
&& lowercase progName == lowercase (stackProgName <> ".EXE")
)
lowercase = map toLower
upload = addCommand'
"upload"
"Upload a package to Hackage."
uploadCmd
uploadOptsParser
-- addCommand hiding global options
addCommand' ::
String
-> String
-> (a -> RIO Runner ())
-> Parser a
-> AddCommand
addCommand' cmd title constr =
addCommand
cmd
title
globalFooter
constr
(\_ gom -> gom)
(globalOpts OtherCmdGlobalOpts)
-- addCommand with custom footer hiding global options
addCommand'' ::
String
-> String
-> (a -> RIO Runner ())
-> String
-> Parser a
-> AddCommand
addCommand'' cmd title constr cmdFooter =
addCommand
cmd
title
(globalFooter <> " " <> cmdFooter)
constr
(\_ gom -> gom)
(globalOpts OtherCmdGlobalOpts)
addSubCommands' ::
String
-> String
-> AddCommand
-> AddCommand
addSubCommands' cmd title =
addSubCommands
cmd
title
globalFooter
(globalOpts OtherCmdGlobalOpts)
-- Additional helper that hides global options and shows build options
addBuildCommand' ::
String
-> String
-> (a -> RIO Runner ())
-> Parser a
-> AddCommand
addBuildCommand' cmd title constr =
addCommand
cmd
title
globalFooter
constr
(\_ gom -> gom)
(globalOpts BuildCmdGlobalOpts)
-- Additional helper that hides global options and shows some ghci options
addGhciCommand' ::
String
-> String
-> (a -> RIO Runner ())
-> Parser a
-> AddCommand
addGhciCommand' cmd title constr =
addCommand
cmd
title
globalFooter
constr
(\_ gom -> gom)
(globalOpts GhciCmdGlobalOpts)
globalOpts :: GlobalOptsContext -> Parser GlobalOptsMonoid
globalOpts kind =
extraHelpOption
hide
progName
(dockerCmdName ++ "*")
dockerHelpOptName
<*> extraHelpOption
hide
progName
(Nix.nixCmdName ++ "*")
Nix.nixHelpOptName
<*> globalOptsParser currentDir kind
where
hide = kind /= OuterGlobalOpts
-- | fall-through to external executables in `git` style if they exist
-- (i.e. `stack something` looks for `stack-something` before
-- failing with "Invalid argument `something'")
secondaryCommandHandler ::
[String]
-> ParserFailure ParserHelp
-> IO (ParserFailure ParserHelp)
secondaryCommandHandler args f =
-- don't even try when the argument looks like a path or flag
if elem pathSeparator cmd || "-" `L.isPrefixOf` L.head args
then pure f
else do
mExternalExec <- D.findExecutable cmd
case mExternalExec of
Just ex -> withProcessContextNoLogging $ do
-- TODO show the command in verbose mode
-- hPutStrLn stderr $ unwords $
-- ["Running", "[" ++ ex, unwords (tail args) ++ "]"]
_ <- RIO.Process.exec ex (L.tail args)
pure f
Nothing -> pure $ fmap (vcatErrorHelp (noSuchCmd cmd)) f
where
-- FIXME this is broken when any options are specified before the command
-- e.g. stack --verbosity silent cmd
cmd = stackProgName ++ "-" ++ L.head args
noSuchCmd name = errorHelp $ stringChunk
("Auxiliary command not found in path '" ++ name ++ "'.")
interpreterHandler ::
Monoid t
=> FilePath
-> [String]
-> ParserFailure ParserHelp
-> IO (GlobalOptsMonoid, (RIO Runner (), t))
interpreterHandler currentDir args f = do
-- args can include top-level config such as --extra-lib-dirs=... (set by
-- nix-shell) - we need to find the first argument which is a file, everything
-- afterwards is an argument to the script, everything before is an argument
-- to Stack
(stackArgs, fileArgs) <- spanM (fmap not . D.doesFileExist) args
case fileArgs of
(file:fileArgs') -> runInterpreterCommand file stackArgs fileArgs'
[] -> parseResultHandler (errorCombine (noSuchFile firstArg))
where
firstArg = L.head args
spanM _ [] = pure ([], [])
spanM p xs@(x:xs') = do
r <- p x
if r
then do
(ys, zs) <- spanM p xs'
pure (x:ys, zs)
else
pure ([], xs)
-- if the first argument contains a path separator then it might be a file,
-- or a Stack option referencing a file. In that case we only show the
-- interpreter error message and exclude the command related error messages.
errorCombine =
if pathSeparator `elem` firstArg
then overrideErrorHelp
else vcatErrorHelp
overrideErrorHelp h1 h2 = h2 { helpError = helpError h1 }
parseResultHandler fn = handleParseResult (overFailure fn (Failure f))
noSuchFile name = errorHelp $ stringChunk
("File does not exist or is not a regular file '" ++ name ++ "'.")
runInterpreterCommand path stackArgs fileArgs = do
progName <- getProgName
iargs <- getInterpreterArgs path
let parseCmdLine = commandLineHandler currentDir progName True
-- Implicit file arguments are put before other arguments that
-- occur after "--". See #3658
cmdArgs = stackArgs ++ case break (== "--") iargs of
(beforeSep, []) -> beforeSep ++ ["--"] ++ [path] ++ fileArgs
(beforeSep, optSep : afterSep) ->
beforeSep ++ [optSep] ++ [path] ++ fileArgs ++ afterSep
-- TODO show the command in verbose mode
-- hPutStrLn stderr $ unwords $
-- ["Running", "[" ++ progName, unwords cmdArgs ++ "]"]
(a,b) <- withArgs cmdArgs parseCmdLine
pure (a,(b,mempty))
-- Vertically combine only the error component of the first argument with the
-- error component of the second.
vcatErrorHelp :: ParserHelp -> ParserHelp -> ParserHelp
vcatErrorHelp h1 h2 = h2 { helpError = vcatChunks [helpError h2, helpError h1] }