Skip to content

Commit ae03fca

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 d8c21ab commit ae03fca

File tree

19 files changed

+230
-54
lines changed

19 files changed

+230
-54
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+
-- Start the repl. Either use `ghc`, or the program specified by the --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 program to use for REPL"
237+
replWithRepl
238+
(\v flags -> flags{replWithRepl = v})
239+
(reqArgFlag "PATH")
232240
]

cabal-install-solver/src/Distribution/Solver/Types/ConstraintSource.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ data ConstraintSource =
4242
-- from Cabal >= 3.11
4343
| ConstraintSourceMultiRepl
4444

45+
-- | Constraint introduced by --with-repl, which requires features
46+
-- from Cabal >= 3.15
47+
| ConstraintSourceWithRepl
48+
4549
-- | Constraint introduced by --enable-profiling-shared, which requires features
4650
-- from Cabal >= 3.13
4751
| ConstraintSourceProfiledDynamic
@@ -81,6 +85,8 @@ instance Pretty ConstraintSource where
8185
text "config file, command line flag, or user target"
8286
ConstraintSourceMultiRepl ->
8387
text "--enable-multi-repl"
88+
ConstraintSourceWithRepl ->
89+
text "--with-repl"
8490
ConstraintSourceProfiledDynamic ->
8591
text "--enable-profiling-shared"
8692
ConstraintSourceUnknown -> text "unknown source"

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

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ import Distribution.Simple.Utils
116116
, wrapText
117117
)
118118
import Distribution.Solver.Types.ConstraintSource
119-
( ConstraintSource (ConstraintSourceMultiRepl)
119+
( ConstraintSource (ConstraintSourceMultiRepl, ConstraintSourceWithRepl)
120120
)
121121
import Distribution.Solver.Types.PackageConstraint
122122
( PackageProperty (PackagePropertyVersion)
@@ -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
@@ -325,15 +323,34 @@ replAction flags@NixStyleFlags{extraFlags = r@ReplFlags{..}, ..} targetStrings g
325323
-- We need to do this before solving, but the compiler version is only known
326324
-- after solving (phaseConfigureCompiler), so instead of using
327325
-- multiReplDecision we just check the flag.
328-
let baseCtx' =
329-
if fromFlagOrDefault False $
326+
let multiReplEnabled =
327+
fromFlagOrDefault False $
330328
projectConfigMultiRepl (projectConfigShared $ projectConfig baseCtx)
331329
<> replUseMulti
330+
331+
withReplEnabled =
332+
isJust $ flagToMaybe $ replWithRepl configureReplOptions
333+
334+
addConstraintWhen cond constraint base_ctx =
335+
if cond
332336
then
333-
baseCtx
337+
base_ctx
334338
& lProjectConfig . lProjectConfigShared . lProjectConfigConstraints
335-
%~ (multiReplCabalConstraint :)
336-
else baseCtx
339+
%~ (constraint :)
340+
else base_ctx
341+
342+
-- This is the constraint setup.Cabal>=3.11. 3.11 is when Cabal options
343+
-- used for multi-repl were introduced.
344+
-- Idelly we'd apply this constraint only on the closure of repl targets,
345+
-- but that would require another solver run for marginal advantages that
346+
-- will further shrink as 3.11 is adopted.
347+
addMultiReplConstraint = addConstraintWhen multiReplEnabled $ requireCabal [3,11] ConstraintSourceMultiRepl
348+
349+
-- Similarly, if you use `--with-repl` then your version of `Cabal` needs to
350+
-- support the `--with-repl` flag.
351+
addWithReplConstraint = addConstraintWhen withReplEnabled $ requireCabal [3,15] ConstraintSourceWithRepl
352+
353+
baseCtx' = addMultiReplConstraint $ addWithReplConstraint baseCtx
337354

338355
(originalComponent, baseCtx'') <-
339356
if null (envPackages replEnvFlags)
@@ -481,7 +498,7 @@ replAction flags@NixStyleFlags{extraFlags = r@ReplFlags{..}, ..} targetStrings g
481498
}
482499

483500
-- run ghc --interactive with
484-
runGHCWithResponseFile "ghci_multi.rsp" Nothing tempFileOptions verbosity ghcProg' compiler platform Nothing ghc_opts
501+
runReplProgram (flagToMaybe $ replWithRepl replOpts') tempFileOptions verbosity ghcProg' compiler platform Nothing ghc_opts
485502
else do
486503
-- single target repl
487504
replOpts'' <- case targetCtx of
@@ -526,16 +543,16 @@ replAction flags@NixStyleFlags{extraFlags = r@ReplFlags{..}, ..} targetStrings g
526543

527544
return targets
528545

529-
-- This is the constraint setup.Cabal>=3.11. 3.11 is when Cabal options
530-
-- used for multi-repl were introduced.
531-
-- Idelly we'd apply this constraint only on the closure of repl targets,
532-
-- but that would require another solver run for marginal advantages that
533-
-- will further shrink as 3.11 is adopted.
534-
multiReplCabalConstraint =
546+
547+
-- | Create a constraint which requires a later version of Cabal.
548+
-- This is used for commands which require a specific feature from the Cabal library
549+
-- such as multi-repl or the --with-repl flag.
550+
requireCabal :: [Int] -> ConstraintSource -> (UserConstraint, ConstraintSource)
551+
requireCabal version source =
535552
( UserConstraint
536553
(UserAnySetupQualifier (mkPackageName "Cabal"))
537-
(PackagePropertyVersion $ orLaterVersion $ mkVersion [3, 11])
538-
, ConstraintSourceMultiRepl
554+
(PackagePropertyVersion $ orLaterVersion $ mkVersion version)
555+
, source
539556
)
540557

541558
-- | First version of GHC which supports multiple home packages
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: 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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# cabal clean
2+
# cabal v2-repl
3+
Resolving dependencies...
4+
Build profile: -w ghc-<GHCVER> -O1
5+
In order, the following will be built:
6+
- cabal-with-repl-0.1.0.0 (interactive) (lib) (first run)
7+
Configuring library for cabal-with-repl-0.1.0.0...
8+
Preprocessing library for cabal-with-repl-0.1.0.0...
9+
Error: [Cabal-7125]
10+
repl failed for cabal-with-repl-0.1.0.0-inplace.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# cabal clean
2+
# cabal v2-repl
3+
Resolving dependencies...
4+
Build profile: -w ghc-<GHCVER> -O1
5+
In order, the following will be built:
6+
- cabal-with-repl-0.1.0.0 (interactive) (lib) (first run)
7+
Configuring library for cabal-with-repl-0.1.0.0...
8+
Preprocessing library for cabal-with-repl-0.1.0.0...

0 commit comments

Comments
 (0)