Skip to content

Commit 30435ac

Browse files
committed
Add --with-repl flag to modify program the "repl" starts with
Programs like doctest and hie-bios want to use `cabal-install` in order to discover the correct options to start a GHC session. Previously they used the `--with-compiler` option, but this led to complications since the wrapper was called for compiling all dependencies and so on, the shim had to be more complicated and forward arguments onto the user's version of GHC. The `--with-repl` command allows you to pass a program which is used instead of GHC at the final invocation of the repl. Therefore the wrappers don't have to deal with being a complete shim but can concentrate on intercepting the arguments at the end. This commit removes the special hack to not use response files with --interactive mode. Tools are expected to deal with them appropiately, which is much easier now only one invocation is passed to the wrapper. Fixes #9115
1 parent bc7fc99 commit 30435ac

File tree

18 files changed

+169
-40
lines changed

18 files changed

+169
-40
lines changed

Cabal/src/Distribution/Simple/GHC/Build/Link.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -751,10 +751,10 @@ runReplOrWriteFlags ghcProg lbi rflags ghcOpts pkg_name target =
751751
verbosity = fromFlag $ setupVerbosity common
752752
tempFileOptions = commonSetupTempFileOptions common
753753
in case replOptionsFlagOutput (replReplOptions rflags) of
754-
NoFlag ->
755-
runGHCWithResponseFile
756-
"ghc.rsp"
757-
Nothing
754+
NoFlag -> do
755+
-- If a specific GHC implementation is specified, use it
756+
runReplProgram
757+
(flagToMaybe $ replWithRepl (replReplOptions rflags))
758758
tempFileOptions
759759
verbosity
760760
ghcProg

Cabal/src/Distribution/Simple/Program/GHC.hs

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Distribution.Simple.Program.GHC
1616
, renderGhcOptions
1717
, runGHC
1818
, runGHCWithResponseFile
19+
, runReplProgram
1920
, packageDbArgsDb
2021
, normaliseGhcArgs
2122
) where
@@ -668,37 +669,7 @@ runGHCWithResponseFile fileNameTemplate encoding tempFileOptions verbosity ghcPr
668669

669670
args = progInvokeArgs invocation
670671

671-
-- Don't use response files if the first argument is `--interactive`, for
672-
-- two related reasons.
673-
--
674-
-- `hie-bios` relies on a hack to intercept the command-line that `Cabal`
675-
-- supplies to `ghc`. Specifically, `hie-bios` creates a script around
676-
-- `ghc` that detects if the first option is `--interactive` and if so then
677-
-- instead of running `ghc` it prints the command-line that `ghc` was given
678-
-- instead of running the command:
679-
--
680-
-- https://github.com/haskell/hie-bios/blob/ce863dba7b57ded20160b4f11a487e4ff8372c08/wrappers/cabal#L7
681-
--
682-
-- … so we can't store that flag in the response file, otherwise that will
683-
-- break. However, even if we were to add a special-case to keep that flag
684-
-- out of the response file things would still break because `hie-bios`
685-
-- stores the arguments to `ghc` that the wrapper script outputs and reuses
686-
-- them later. That breaks if you use a response file because it will
687-
-- store an argument like `@…/ghc36000-0.rsp` which is a temporary path
688-
-- that no longer exists after the wrapper script completes.
689-
--
690-
-- The work-around here is that we don't use a response file at all if the
691-
-- first argument (and only the first argument) to `ghc` is
692-
-- `--interactive`. This ensures that `hie-bios` and all downstream
693-
-- utilities (e.g. `haskell-language-server`) continue working.
694-
--
695-
--
696-
useResponseFile =
697-
case args of
698-
"--interactive" : _ -> False
699-
_ -> compilerSupportsResponseFiles
700-
701-
if not useResponseFile
672+
if not compilerSupportsResponseFiles
702673
then runProgramInvocation verbosity invocation
703674
else do
704675
let (rtsArgs, otherArgs) = splitRTSArgs args
@@ -721,6 +692,24 @@ runGHCWithResponseFile fileNameTemplate encoding tempFileOptions verbosity ghcPr
721692

722693
runProgramInvocation verbosity newInvocation
723694

695+
-- Either run GHC to start the repl or the argument to --with-repl flag.
696+
runReplProgram
697+
:: Maybe FilePath
698+
-- ^ --with-repl argument
699+
-> TempFileOptions
700+
-> Verbosity
701+
-> ConfiguredProgram
702+
-> Compiler
703+
-> Platform
704+
-> Maybe (SymbolicPath CWD (Dir Pkg))
705+
-> GhcOptions
706+
-> IO ()
707+
runReplProgram withReplProg tempFileOptions verbosity ghcProg comp platform mbWorkDir ghcOpts =
708+
let replProg = case withReplProg of
709+
Just path -> ghcProg{programLocation = FoundOnSystem path}
710+
Nothing -> ghcProg
711+
in runGHCWithResponseFile "ghci.rsp" Nothing tempFileOptions verbosity replProg comp platform mbWorkDir ghcOpts
712+
724713
ghcInvocation
725714
:: Verbosity
726715
-> ConfiguredProgram

Cabal/src/Distribution/Simple/Setup/Repl.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ data ReplOptions = ReplOptions
5454
{ replOptionsFlags :: [String]
5555
, replOptionsNoLoad :: Flag Bool
5656
, replOptionsFlagOutput :: Flag FilePath
57+
, replWithRepl :: Flag FilePath
5758
}
5859
deriving (Show, Generic)
5960

@@ -85,7 +86,7 @@ instance Binary ReplOptions
8586
instance Structured ReplOptions
8687

8788
instance Monoid ReplOptions where
88-
mempty = ReplOptions mempty (Flag False) NoFlag
89+
mempty = ReplOptions mempty (Flag False) NoFlag NoFlag
8990
mappend = (<>)
9091

9192
instance Semigroup ReplOptions where
@@ -229,4 +230,11 @@ replOptions _ =
229230
replOptionsFlagOutput
230231
(\p flags -> flags{replOptionsFlagOutput = p})
231232
(reqArg "DIR" (succeedReadE Flag) flagToList)
233+
, option
234+
[]
235+
["with-repl"]
236+
"give the path to a particular GHC implementation to use for REPL"
237+
replWithRepl
238+
(\v flags -> flags{replWithRepl = v})
239+
(reqArgFlag "PATH")
232240
]

cabal-install/src/Distribution/Client/CmdRepl.hs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,10 @@ import Distribution.Client.ReplFlags
180180
, topReplOptions
181181
)
182182
import Distribution.Compat.Binary (decode)
183-
import Distribution.Simple.Flag (fromFlagOrDefault, pattern Flag)
183+
import Distribution.Simple.Flag (flagToMaybe, fromFlagOrDefault, pattern Flag)
184184
import Distribution.Simple.Program.Builtin (ghcProgram)
185185
import Distribution.Simple.Program.Db (requireProgram)
186186
import Distribution.Simple.Program.Types
187-
( ConfiguredProgram (programOverrideEnv)
188-
)
189187
import System.Directory
190188
( doesFileExist
191189
, getCurrentDirectory
@@ -481,7 +479,7 @@ replAction flags@NixStyleFlags{extraFlags = r@ReplFlags{..}, ..} targetStrings g
481479
}
482480

483481
-- run ghc --interactive with
484-
runGHCWithResponseFile "ghci_multi.rsp" Nothing tempFileOptions verbosity ghcProg' compiler platform Nothing ghc_opts
482+
runReplProgram (flagToMaybe $ replWithRepl replOpts') tempFileOptions verbosity ghcProg' compiler platform Nothing ghc_opts
485483
else do
486484
-- single target repl
487485
replOpts'' <- case targetCtx of
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module ModuleA where
2+
3+
x :: Int
4+
x = 42
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cabal-version: 2.4
2+
name: cabal-with-repl
3+
version: 0.1.0.0
4+
build-type: Simple
5+
6+
library
7+
exposed-modules: ModuleA
8+
build-depends: base
9+
default-language: Haskell2010
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
packages: .
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Test.Cabal.Prelude
2+
import System.Directory (getCurrentDirectory)
3+
import System.FilePath ((</>))
4+
5+
main = do
6+
-- Test that --with-repl works with a valid GHC path
7+
cabalTest' "with-repl-valid-path" $ do
8+
cabal' "clean" []
9+
-- Get the path to the system GHC
10+
ghc_prog <- requireProgramM ghcProgram
11+
res <- cabalWithStdin "v2-repl" ["--with-repl=" ++ programPath ghc_prog] ""
12+
assertOutputContains "Ok, one module loaded." res
13+
assertOutputContains "GHCi, version" res
14+
15+
-- Test that --with-repl fails with an invalid path
16+
cabalTest' "with-repl-invalid-path" $ do
17+
cabal' "clean" []
18+
res <- fails $ cabalWithStdin "v2-repl" ["--with-repl=/nonexistent/path/to/ghc"] ""
19+
assertOutputContains "does not exist" res
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# cabal clean
2+
# cabal v2-repl
3+
Configuration is affected by the following files:
4+
- cabal.project
5+
Resolving dependencies...
6+
Build profile: -w ghc-<GHCVER> -O1
7+
In order, the following will be built:
8+
- cabal-with-repl-0.1.0.0 (interactive) (lib) (first run)
9+
Configuring library for cabal-with-repl-0.1.0.0...
10+
Preprocessing library for cabal-with-repl-0.1.0.0...
11+
Error: [Cabal-7125]
12+
repl failed for cabal-with-repl-0.1.0.0-inplace.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# cabal clean
2+
# cabal v2-repl
3+
Configuration is affected by the following files:
4+
- cabal.project
5+
Resolving dependencies...
6+
Build profile: -w ghc-<GHCVER> -O1
7+
In order, the following will be built:
8+
- cabal-with-repl-0.1.0.0 (interactive) (lib) (first run)
9+
Configuring library for cabal-with-repl-0.1.0.0...
10+
Preprocessing library for cabal-with-repl-0.1.0.0...
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import ModuleA
2+
3+
main :: IO ()
4+
main = print $ "My specific executable"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Main where
2+
3+
main = print "My other executable"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module ModuleA where
2+
3+
add :: Num a => a -> a -> a
4+
add x y = x + y
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cabal-version: 2.4
2+
name: cabal-with-repl-exe
3+
version: 0.1.0.0
4+
build-type: Simple
5+
6+
executable test-exe
7+
main-is: Main.hs
8+
build-depends: base
9+
default-language: Haskell2010
10+
11+
executable test-exe2
12+
main-is: Main2.hs
13+
build-depends: base
14+
default-language: Haskell2010
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# cabal v2-build
2+
Configuration is affected by the following files:
3+
- cabal.project
4+
Resolving dependencies...
5+
Build profile: -w ghc-<GHCVER> -O1
6+
In order, the following will be built:
7+
- cabal-with-repl-exe-0.1.0.0 (exe:test-exe) (first run)
8+
Configuring executable 'test-exe' for cabal-with-repl-exe-0.1.0.0...
9+
Preprocessing executable 'test-exe' for cabal-with-repl-exe-0.1.0.0...
10+
Building executable 'test-exe' for cabal-with-repl-exe-0.1.0.0...
11+
# cabal v2-repl
12+
Configuration is affected by the following files:
13+
- cabal.project
14+
Resolving dependencies...
15+
Build profile: -w ghc-<GHCVER> -O1
16+
In order, the following will be built:
17+
- cabal-with-repl-exe-0.1.0.0 (interactive) (exe:test-exe) (configuration changed)
18+
- cabal-with-repl-exe-0.1.0.0 (interactive) (exe:test-exe2) (first run)
19+
Configuring executable 'test-exe' for cabal-with-repl-exe-0.1.0.0...
20+
Preprocessing executable 'test-exe' for cabal-with-repl-exe-0.1.0.0...
21+
Configuring executable 'test-exe2' for cabal-with-repl-exe-0.1.0.0...
22+
Preprocessing executable 'test-exe2' for cabal-with-repl-exe-0.1.0.0...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
packages: .
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
import Distribution.Simple.Program
3+
import Distribution.Simple.Program.GHC
4+
import Distribution.Simple.Utils
5+
import Test.Cabal.Prelude
6+
7+
main = do
8+
mkTest $ \exePath -> do
9+
-- Try using the executable with --with-repl
10+
res <- cabalWithStdin "v2-repl" ["--with-repl=" ++ exePath, "test-exe"] ""
11+
assertOutputContains "My specific executable" res
12+
mkTest $ \exePath -> do
13+
requireGhcSupportsMultiRepl
14+
res <- cabalWithStdin "v2-repl" ["--enable-multi-repl", "--with-repl=" ++ exePath, "all"] ""
15+
assertOutputContains "My specific executable" res
16+
17+
18+
mkTest act = cabalTest $ do
19+
-- Build the executable
20+
cabal' "v2-build" ["test-exe"]
21+
-- Get the path to the built executable
22+
withPlan $ do
23+
exePath <- planExePath "cabal-with-repl-exe" "test-exe"
24+
act exePath
25+
26+
27+

cabal-testsuite/src/Test/Cabal/Prelude.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,10 @@ skipUnlessJavaScript = skipUnlessIO "needs the JavaScript backend" isJavaScript
10551055
skipIfJavaScript :: IO ()
10561056
skipIfJavaScript = skipIfIO "incompatible with the JavaScript backend" isJavaScript
10571057

1058+
requireGhcSupportsMultiRepl :: TestM ()
1059+
requireGhcSupportsMultiRepl =
1060+
skipUnlessGhcVersion ">= 9.4"
1061+
10581062
isWindows :: Bool
10591063
isWindows = buildOS == Windows
10601064

0 commit comments

Comments
 (0)