diff --git a/cabal-install/src/Distribution/Client/CmdRun.hs b/cabal-install/src/Distribution/Client/CmdRun.hs index 80d7ff48a6c..6de25f0d2e9 100644 --- a/cabal-install/src/Distribution/Client/CmdRun.hs +++ b/cabal-install/src/Distribution/Client/CmdRun.hs @@ -199,7 +199,9 @@ runCommand = -- For more details on how this works, see the module -- "Distribution.Client.ProjectOrchestration" runAction :: NixStyleFlags () -> [String] -> GlobalFlags -> IO () -runAction flags targetAndArgs globalFlags = +runAction flags targetAndArgs globalFlags = do + fullArgs <- getFullArgs + let (targetStr, args) = splitTargetAndArgs fullArgs targetAndArgs withContextAndSelectors (cfgVerbosity normal flags) RejectNoTargets (Just ExeKind) flags targetStr globalFlags OtherCommand $ \targetCtx ctx targetSelectors -> do (baseCtx, defaultVerbosity) <- case targetCtx of ProjectContext -> return (ctx, normal) @@ -213,7 +215,6 @@ runAction flags targetAndArgs globalFlags = when (buildSettingOnlyDeps (buildSettings baseCtx)) $ dieWithException verbosity NoSupportForRunCommand - fullArgs <- getFullArgs when (occursOnlyOrBefore fullArgs "+RTS" "--") $ warn verbosity $ giveRTSWarning "run" @@ -353,8 +354,62 @@ runAction flags targetAndArgs globalFlags = (distDirLayout baseCtx) elaboratedPlan } - where - (targetStr, args) = splitAt 1 targetAndArgs + +-- | Split @cabal run@ arguments (@exe cmd@ arguments in the examples) into +-- target selectors and target executable arguments. +-- +-- When a target is given it appears in both lists: +-- +-- >>> splitTargetAndArgs ["exe", "cmd", "target"] ["target"] +-- (["target"],[]) +-- +-- The @+RTS@ argument is passed to the executable so only appears in the first +-- list: +-- +-- >>> splitTargetAndArgs ["exe", "cmd", "target", "+RTS"] ["target"] +-- (["target"],[]) +-- +-- The @--@ follows the @+RTS@ argument, so @+RTS@ is passed to the executable +-- and only appears in the first list: +-- +-- >>> splitTargetAndArgs ["exe", "cmd", "target", "+RTS", "--"] ["target"] +-- (["target"],[]) +-- +-- The @--@ precedes the @+RTS@ argument, so @+RTS@ is included in the +-- 'targetAndArgs' list as well: +-- +-- >>> splitTargetAndArgs ["exe", "cmd", "target", "--", "+RTS"] ["target", "+RTS"] +-- (["target"],["+RTS"]) +-- +-- Same examples as above but when no target is given: +-- +-- >>> splitTargetAndArgs ["exe", "cmd"] [] +-- ([],[]) +-- >>> splitTargetAndArgs ["exe", "cmd", "+RTS"] [] +-- ([],[]) +-- >>> splitTargetAndArgs ["exe", "cmd", "+RTS", "--"] [] +-- ([],[]) +-- >>> splitTargetAndArgs ["exe", "cmd", "--", "+RTS"] ["+RTS"] +-- ([],["+RTS"]) +splitTargetAndArgs + :: [String] + -- ^ Full command line arguments, the original command line from + -- 'getFullArgs', which is only used to detect whether a @--@ separator was + -- present so that @cabal run -- ...@ keeps the target empty. + -> [String] + -- ^ The second argument is the parser-produced list that combines targets and + -- their arguments. These arguments do not include those passed to @cabal@ + -- such as @+RTS@ preceding the @--@ separator. + -> ([String], [String]) +splitTargetAndArgs fullArgs targetAndArgs = case dropWhile (/= "--") fullArgs of + ("--" : exeArgs) -> + -- targetAndArgs contains targets (>=0) and args; exeArgs contains only args; so + -- the difference (>=0) is the number of targets + let numTargets = length targetAndArgs - length exeArgs + in splitAt numTargets targetAndArgs + _ -> + -- No '--': first element (if any) is the target. + splitAt 1 targetAndArgs -- | Used by the main CLI parser as heuristic to decide whether @cabal@ was -- invoked as a script interpreter, i.e. via diff --git a/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.no-target.out b/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.no-target.out new file mode 100644 index 00000000000..a7f6033bb1d --- /dev/null +++ b/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.no-target.out @@ -0,0 +1,12 @@ +# cabal run +Resolving dependencies... +Warning: Your RTS options are applied to cabal, not the executable. Use '--' to separate cabal options from your executable options. For example, use 'cabal run -- +RTS -N to pass the '-N' RTS option to your executable. +Build profile: -w ghc- -O1 +In order, the following will be built: + - WarningRTS-1.0 (exe:foo) (first run) +Configuring executable 'foo' for WarningRTS-1.0... +Preprocessing executable 'foo' for WarningRTS-1.0... +Building executable 'foo' for WarningRTS-1.0... +# cabal run +Warning: Your RTS options are applied to cabal, not the executable. Use '--' to separate cabal options from your executable options. For example, use 'cabal run -- +RTS -N to pass the '-N' RTS option to your executable. +# cabal run diff --git a/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.test.hs b/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.test.hs index 99b9f2008a2..39994589144 100644 --- a/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.test.hs +++ b/cabal-testsuite/PackageTests/NewBuild/CmdRun/WarningRTS/cabal.test.hs @@ -1,6 +1,7 @@ import Test.Cabal.Prelude -main = cabalTest $ do +main = do + cabalTest $ do res <- cabal' "run" ["foo", "+RTS"] assertOutputContains "Warning: Your RTS options" res @@ -9,3 +10,16 @@ main = cabalTest $ do res <- cabal' "run" ["foo", "--", "+RTS"] assertOutputDoesNotContain "Warning: Your RTS options" res + + -- Regression tests for https://github.com/haskell/cabal/issues/10487: + -- 'cabal run -- +RTS' should not fail with "Unrecognised target '+RTS'" + cabalTest' "no-target" $ do + res <- cabal' "run" ["+RTS"] + assertOutputContains "Warning: Your RTS options" res + + res <- cabal' "run" ["+RTS", "--"] + assertOutputContains "Warning: Your RTS options" res + + res <- cabal' "run" ["--", "+RTS"] + assertOutputDoesNotContain "Warning: Your RTS options" res + assertOutputDoesNotContain "Unrecognised target" res diff --git a/changelog.d/pr-10487.md b/changelog.d/pr-10487.md new file mode 100644 index 00000000000..ea0e3064bb9 --- /dev/null +++ b/changelog.d/pr-10487.md @@ -0,0 +1,18 @@ +--- +synopsis: Fix `cabal run` handling of `--` with empty targets +packages: [cabal-install] +prs: 10487 +issues: 10487 +--- + +Previously, when using `--` to separate cabal options from executable +arguments, without an explicit target, the first argument after `--` was +incorrectly treated as a target selector. + +For example, the following now works as expected while it failed before +thinking that `+RTS` is a target: + +``` +$ cabal run -- +RTS -s +$ cabal run -w /path/to/ghc -- +RTS -s +``` diff --git a/doc/cabal-commands.rst b/doc/cabal-commands.rst index 06a1e082373..ca05df1b42b 100644 --- a/doc/cabal-commands.rst +++ b/doc/cabal-commands.rst @@ -1212,8 +1212,9 @@ When ``TARGET`` is one of the following: - Empty target: Same as package target, implicitly using the package from the current working directory. -Except in the case of the empty target, the strings after it will be -passed to the executable as arguments. +With a non-empty target, the strings after it are passed to the +executable as arguments. With an empty target you must use ``--`` to +separate executable arguments from cabal flags. If one of the arguments starts with ``-`` it will be interpreted as a cabal flag, so if you need to pass flags to the executable you @@ -1222,6 +1223,10 @@ have to separate them with ``--``. :: $ cabal run target -- -a -bcd --argument + $ cabal run -- +RTS -s -RTS + +The second form (empty target with ``--``) runs the single executable +in the current package and passes the RTS options to it. ``run`` supports running script files that use a certain format. Scripts look like: