Skip to content

Commit e54869d

Browse files
committed
Improve docs for Cabal.hs
1 parent b0b8437 commit e54869d

File tree

1 file changed

+78
-17
lines changed

1 file changed

+78
-17
lines changed

src/HIE/Bios/Cradle/Cabal.hs

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,27 +51,84 @@ import HIE.Bios.Process
5151
import GHC.Fingerprint (fingerprintString)
5252
import GHC.ResponseFile (escapeArgs)
5353

54+
{- Note [Finding ghc-options with cabal]
55+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56+
We want to know how to compile a cabal component with GHC.
57+
There are two main ways to obtain the ghc-options:
58+
59+
1. `cabal --with-ghc <ghc-shim>` (for exe:cabal <3.15 or lib:Cabal <3.15)
60+
61+
In this approach, we generate a <ghc-shim> which is passed to the exe:cabal process.
62+
If a package needs to be compiled, we compile the package with the same GHC process that
63+
exe:cabal would have used.
64+
65+
If the first argument is `--interactive`, then we do not launch the GHCi process,
66+
but record all the arguments for later processing.
67+
68+
2. `cabal --with-repl <repl-shim>` (for exe:cabal >=3.15 and lib:Cabal >=3.15)
69+
70+
The <repl-shim> is notably simpler than the <ghc-shim>, as `--with-repl` invokes
71+
<repl-shim> *only* as the final GHCi process, not for compiling dependencies or
72+
executing preprocessors.
73+
74+
Thus, <repl-shim> merely needs to log all arguments that are passed to <repl-shim>.
75+
76+
This is the simpler, more maintainable approach, with fewer unintended side-effects.
77+
78+
=== Finding the GHC process cabal uses to compile a project with
79+
80+
We want HLS and hie-bios to honour the `with-compiler` field in `cabal.project` files.
81+
Again, we identify two ways to find the exact GHC program that is going to be invoked by cabal.
82+
83+
1. `cabal exec -- ghc --interactive -e System.Environment.getExecutablePath` (for exe:cabal <3.14)
84+
85+
Ignoring a couple of details, we can get the path to the raw executable by asking
86+
the GHCi process for its executable path.
87+
The issue is that on linux, the executable path is insufficient, the GHC executable
88+
invoked by the user is "wrapped" in a shim that specifies the libdir location, e.g.:
89+
90+
> cat /home/hugin/.ghcup/bin/ghc
91+
#!/bin/sh
92+
exedir="/home/hugin/.ghcup/ghc/9.6.7/lib/ghc-9.6.7/bin"
93+
exeprog="./ghc-9.6.7"
94+
executablename="/home/hugin/.ghcup/ghc/9.6.7/lib/ghc-9.6.7/bin/./ghc-9.6.7"
95+
bindir="/home/hugin/.ghcup/ghc/9.6.7/bin"
96+
libdir="/home/hugin/.ghcup/ghc/9.6.7/lib/ghc-9.6.7/lib"
97+
docdir="/home/hugin/.ghcup/ghc/9.6.7/share/doc/ghc-9.6.7"
98+
includedir="/home/hugin/.ghcup/ghc/9.6.7/include"
99+
100+
exec "$executablename" -B"$libdir" ${1+"$@"}
101+
102+
We find the libdir by asking GHC via `cabal exec -- ghc --print-libdir`.
103+
Once we have these two paths, we also need to find the `ghc-pkg` location,
104+
otherwise cabal will use the `ghc-pkg` that is found on PATH, which is not correct
105+
if the user overwrites the compiler field via `with-compiler`.
106+
107+
To find `ghc-pkg`, we assume it is going to be located next to the `libdir`, and then
108+
reconstruct the wrapper shim for `ghc-pkg`.
109+
110+
Then we reconstructed both the ghc and ghc-pkg program that is going to be used by cabal
111+
and can use it in the <ghc-shim> and `cabal repl --with-compiler <ghc-shim> --with-hc-pkg <hc-pkg-shim>`.
112+
113+
Calling `cabal exec` can be very slow on a large codebase, over 1 second per invocation.
114+
115+
2. `cabal path` (for exe:cabal >= 3.14)
116+
117+
We can skip the reconstruction of the GHC shim by using the output of `cabal path --compiler-info`.
118+
This gives us the location of the GHC executable shim, so we don't need to reconstruct any shims.
119+
120+
However, we still have to reconstruct the ghc-pkg shim when using `cabal repl --with-compiler`.
121+
122+
`cabal path` is incredibly fast to invoke, as it circumvents running the cabal solver.
123+
It is easier to maintain as well.
124+
-}
125+
54126
-- | Main entry point into the cabal cradle invocation.
55127
--
56128
-- This function does a lot of work, supporting multiple cabal-install versions and
57129
-- different ways of obtaining the component options.
58130
--
59-
-- Generally, there are three different, supported ways to obtain the ghc-options:
60-
--
61-
-- * For cabal <3.14.
62-
-- The oldest supported way, we are doing roughly the following:
63-
-- 1. Find the 'ghc' version used by the cabal project (might be overwritten by a @cabal.project@ file)
64-
-- 2. Guess the libdir location and ghc-pkg version
65-
-- 3. Create wrapper shims for ghc-pkg (<ghc-pkg-shim>) and ghc (<ghc-shim>).
66-
-- 4. call @cabal repl --with-compiler <ghc-shim> --with-hc-pkg <ghc-pkg-shim>@ and log the ghc-options
67-
-- * For cabal >=3.14, we can use the new @cabal path@ command to speed up step 1. and 2. of the previous approach.
68-
-- * For cabal >=3.15, cabal supports the @--with-repl@ flag.
69-
-- This allows us to skip step 1-2 entirely, and we simply need to create a shell program
70-
-- which can be passed to @cabal repl --with-repl@, which records the final ghc-options.
71-
--
72-
-- However, this last approach doesn't work, if the user uses a custom-setup forcing a @lib:Cabal <3.15@.
73-
-- If this is the case, we fall back to @cabal path@ and creating wrapper shims.
74-
--
131+
-- See Note [Finding ghc-options with cabal] for a detailed elaboration.
75132
cabalAction ::
76133
ResolvedCradles a ->
77134
FilePath ->
@@ -149,7 +206,7 @@ cabalAction cradles workDir mc l projectFile fp loadStyle = do
149206
where
150207
-- | Run the given cabal process to obtain ghc options.
151208
-- In the special case of 'cabal >= 3.15' but 'lib:Cabal <3.15' (via custom-setups),
152-
-- we gracefully fall back to the given action to create an alterantive cabal process which
209+
-- we gracefully fall back to the given action to create an alternative cabal process which
153210
-- we use to find the ghc options.
154211
runCabalToGetGhcOptions ::
155212
Process.CreateProcess ->
@@ -434,6 +491,10 @@ withGhcWrapperTool l mkGhcCall wdir = do
434491

435492
-- | Generate a script/binary that can be passed to cabal's '--with-repl'.
436493
-- On windows, this compiles a Haskell file, while on other systems, we persist
494+
-- a haskell source file and ad-hoc compile it with 'GhcProc'.
495+
--
496+
-- 'GhcProc' is unused on other platforms.
497+
--
437498
withReplWrapperTool :: LogAction IO (WithSeverity Log) -> GhcProc -> FilePath -> IO FilePath
438499
withReplWrapperTool l mkGhcCall wdir =
439500
withWrapperTool l mkGhcCall wdir "repl-wrapper" cabalWithReplWrapperHs cabalWithReplWrapper

0 commit comments

Comments
 (0)