Skip to content

Commit bdc9bdb

Browse files
authored
Use hie-bios to generalize build command (#37)
* Use hie-bios to generalize build command Currently, HDA determines how to run ghci for any given project by getting runCmd from the client. Even though the VSCode extension and others set a default scaffolding for either stack or cabal projects, this approach has a few shortcomings: 1. Newish Haskell users are not well versed in the build systems so they can't work around failures to invoke the commands as specified 2. Other build systems or even simple Haskell programs that are not a package at all cannot benefit from HDA. - For example, GHC uses the hadrian build system. - Users new to Haskell don't use cabal nor stack straight away, but could benefit from step by step execution of their program Luckily, the hie-bios project aims to solve exactly this problem of determining how to invoke GHC for any given Haskell project and supports a wide range of different tools. It also allows users to be very precise about their own build set up by reading a hie.yaml configuration. haskell-language-server already uses this approach: call functions from hie-bios to determine how to invoke ghc. This commit does the same for HDA, to make it more robust across different Haskell projects. Manually tested on: - GHC (custom hie.yaml) - fast-tags (Cabal package) - Simple Main.hs file with 1 dependency * Only enable hie-bios when cmd=ghci-dap Re-introduce stack and cabal based debugging sessions. Now, hie-bios will only be used if the ghciCmd in launch.json is exactly "ghci-dap" This allows hie-bios workflows to co-exist with the remaining ones, serving as a safety net if something goes wrong.
1 parent 7b346ca commit bdc9bdb

File tree

4 files changed

+78
-29
lines changed

4 files changed

+78
-29
lines changed

haskell-debug-adapter.cabal

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
cabal-version: 1.12
22

3-
-- This file has been generated from package.yaml by hpack version 0.35.2.
3+
-- This file has been generated from package.yaml by hpack version 0.37.0.
44
--
55
-- see: https://github.com/sol/hpack
6-
--
7-
-- hash: 31df6f8a3983d139dd38dfb23e6a70668fe0e158220e5b7a993db5050d161c30
86

97
name: haskell-debug-adapter
108
version: 0.0.41.0
@@ -55,7 +53,7 @@ library
5553
Haskell.Debug.Adapter.TH.Utility
5654
Haskell.Debug.Adapter.Type
5755
Haskell.Debug.Adapter.Utility
58-
-- Haskell.Debug.Adapter.Watch
56+
Haskell.Debug.Adapter.Watch
5957
Paths_haskell_debug_adapter
6058
hs-source-dirs:
6159
src
@@ -111,9 +109,10 @@ library
111109
, data-default
112110
, directory
113111
, filepath
114-
-- , fsnotify
112+
, fsnotify
115113
, ghci-dap >=0.0.23.0
116114
, haskell-dap >=0.0.16.0
115+
, hie-bios >=0.13
117116
, hslogger
118117
, lens
119118
, mtl
@@ -185,10 +184,11 @@ executable haskell-debug-adapter
185184
, data-default
186185
, directory
187186
, filepath
188-
-- , fsnotify
187+
, fsnotify
189188
, ghci-dap >=0.0.23.0
190189
, haskell-dap >=0.0.16.0
191190
, haskell-debug-adapter
191+
, hie-bios >=0.13
192192
, hslogger
193193
, lens
194194
, mtl
@@ -263,10 +263,11 @@ test-suite haskell-debug-adapter-test
263263
, data-default
264264
, directory
265265
, filepath
266-
-- , fsnotify
266+
, fsnotify
267267
, ghci-dap >=0.0.23.0
268268
, haskell-dap >=0.0.16.0
269269
, haskell-debug-adapter
270+
, hie-bios >=0.13
270271
, hslogger
271272
, hspec
272273
, lens

package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ dependencies:
9292
- optparse-applicative
9393
- haskell-dap >=0.0.16.0
9494
- ghci-dap >=0.0.23.0
95+
- hie-bios >=0.13
9596

9697
library:
9798
source-dirs: src

src/Haskell/Debug/Adapter/State/Init/Launch.hs

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE LambdaCase #-}
13
{-# LANGUAGE MultiParamTypeClasses #-}
24
{-# OPTIONS_GHC -fno-warn-orphans #-}
35

46
module Haskell.Debug.Adapter.State.Init.Launch where
57

8+
import Control.Monad
69
import Control.Monad.IO.Class
710
import Control.Monad.Except
811
import Control.Monad.State
@@ -14,6 +17,7 @@ import qualified System.Log.Logger as L
1417
import qualified Data.ByteString.Lazy as LB
1518
import qualified Data.List as L
1619
import qualified Data.Version as V
20+
import qualified System.Directory as D
1721

1822
import qualified Haskell.DAP as DAP
1923
import qualified Haskell.Debug.Adapter.Utility as U
@@ -23,6 +27,9 @@ import Haskell.Debug.Adapter.Constant
2327
import qualified Haskell.Debug.Adapter.Logger as L
2428
import qualified Haskell.Debug.Adapter.GHCi as P
2529

30+
import qualified HIE.Bios as HIE
31+
import qualified HIE.Bios.Types as HIE
32+
import qualified HIE.Bios.Environment as HIE
2633

2734
-- |
2835
-- Any errors should be critical. don't catch anything here.
@@ -48,11 +55,11 @@ app req = flip catchError errHdl $ do
4855

4956
-- must start here. can not start in the entry of GHCiRun State.
5057
-- because there is a transition from DebugRun to GHCiRun.
51-
startGHCi req
58+
flags <- startGHCi req
5259
setPrompt
5360
launchCmd req
5461
setMainArgs
55-
loadStarupFile
62+
loadStarupFile flags
5663

5764
-- dont send launch response here.
5865
-- it must send after configuration done response.
@@ -127,38 +134,75 @@ setUpLogger req = do
127134
liftIO $ L.setUpLogger (DAP.logFileLaunchRequestArguments args) logPR
128135

129136

130-
-- |
131-
--
132-
startGHCi :: DAP.LaunchRequest -> AppContext ()
137+
-- | Starts GHCi and returns the list of arguments it passed to invoke it.
138+
startGHCi :: DAP.LaunchRequest -> AppContext [String]
133139
startGHCi req = do
134140
let args = DAP.argumentsLaunchRequest req
135141
initPmpt = maybe _GHCI_PROMPT id (DAP.ghciInitialPromptLaunchRequestArguments args)
136142
envs = DAP.ghciEnvLaunchRequestArguments args
137-
cmdStr = DAP.ghciCmdLaunchRequestArguments args
138-
cmdList = filter (not.null) $ U.split " " cmdStr
139-
cmd = head cmdList
140143

141-
U.debugEV _LOG_APP $ show cmdList
144+
-- Ignore ghciCmd LaunchRequestArguments
145+
-- Instead, use `hie-bios` to do the Right Thing across projects without complicated user input.
146+
-- Eventually, get rid of this option from haskell-dap.
147+
cmdStr = DAP.ghciCmdLaunchRequestArguments args
148+
(cmd:cmdOpts) = filter (not.null) $ U.split " " cmdStr
142149

143-
opts <- addWithGHC (tail cmdList)
150+
startup_file = DAP.startupLaunchRequestArguments args
144151

145152
appStores <- get
146153
cwd <- U.liftIOE $ readMVar $ appStores^.workspaceAppStores
147154

155+
-- Use hie-bios when Cmd is exactly "ghci-dap"
156+
flags <- if cmdStr /= "ghci-dap" then addWithGHC cmdOpts else do
157+
explicitCradle <- U.liftIOE $ HIE.findCradle startup_file
158+
cradle <- U.liftIOE $ maybe (HIE.loadImplicitCradle mempty startup_file)
159+
(HIE.loadCradle mempty) explicitCradle
160+
161+
libdir <- U.liftIOE (HIE.getRuntimeGhcLibDir cradle) >>= unwrapCradleResult "Failed to get runtime GHC libdir"
162+
163+
-- getCompilerOptions depends on CWD being the proper root dir.
164+
let compilerOpts = D.withCurrentDirectory cwd $
165+
#if MIN_VERSION_hie_bios(0,14,0)
166+
HIE.getCompilerOptions startup_file HIE.LoadFile cradle
167+
#else
168+
HIE.getCompilerOptions startup_file [] cradle
169+
#endif
170+
HIE.ComponentOptions {HIE.componentOptions = flags} <- U.liftIOE compilerOpts >>= unwrapCradleResult "Failed to get compiler options using hie-bios cradle"
171+
172+
return $
173+
#if __GLASGOW_HASKELL__ >= 913
174+
-- fwrite-if-simplified-core requires a recent bug fix regarding GHCi loading
175+
["-fwrite-if-simplified-core"] ++
176+
#endif
177+
["--interactive", "-B"++libdir] ++ flags
178+
179+
U.debugEV _LOG_APP $ show flags
180+
148181
U.liftIOE $ L.debugM _LOG_APP $ "ghci initial prompt [" ++ initPmpt ++ "]."
149182

150183
U.sendConsoleEventLF $ "CWD: " ++ cwd
151-
U.sendConsoleEventLF $ "CMD: " ++ L.intercalate " " (cmd : opts)
184+
U.sendConsoleEventLF $ "CMD: " ++ L.intercalate " " (cmd:flags)
152185
U.sendConsoleEventLF ""
153186

154-
P.startGHCi cmd opts cwd envs
187+
P.startGHCi cmd flags cwd envs
188+
155189
U.sendErrorEventLF $ "Now, waiting for an initial prompt(\""++initPmpt++"\")" ++ " from ghci."
156190
U.sendConsoleEventLF ""
157191
res <- P.expectInitPmpt initPmpt
158192

159193
updateGHCiVersion res
160194

195+
return flags
196+
161197
where
198+
unwrapCradleResult m = \case
199+
HIE.CradleNone -> panic (error m) "HIE.CradleNone"
200+
HIE.CradleFail err -> panic (error m) (unlines $ HIE.cradleErrorStderr err)
201+
HIE.CradleSuccess x -> return x
202+
203+
panic exit m = do
204+
U.sendErrorEvent m
205+
exit
162206

163207
updateGHCiVersion acc = case parse verParser "getGHCiVersion" (unlines acc) of
164208
Right v -> do
@@ -226,12 +270,16 @@ setMainArgs = view mainArgsAppStores <$> get >>= \case
226270
return ()
227271

228272

229-
-- |
230-
--
231-
loadStarupFile :: AppContext ()
232-
loadStarupFile = do
273+
-- | Takes as an argument the list of flags used to invoke GHCi to determine
274+
-- if the main module has already been loaded. If it hasn't, loads the main file.
275+
loadStarupFile :: [String] -> AppContext ()
276+
loadStarupFile flags = do
233277
file <- view startupAppStores <$> get
234-
SU.loadHsFile file
278+
when (not $ any (\lf -> lf `L.isSuffixOf` file) flags) $
279+
-- We only load the file if it hasn't already been given as an argument;
280+
-- Otherwise, we'll force loading the main module and all of its dependencies a second time.
281+
-- That is incredibly painful in large projects (like GHC).
282+
SU.loadHsFile file
235283

236284
let cmd = ":dap-context-modules "
237285

@@ -240,9 +288,6 @@ loadStarupFile = do
240288

241289
return ()
242290

243-
244-
-- |
245-
--
246291
addWithGHC :: [String] -> AppContext [String]
247292
addWithGHC [] = return []
248293
addWithGHC cmds
@@ -260,7 +305,6 @@ addWithGHC cmds
260305
| L.isPrefixOf "--with-ghc=" x = True
261306
| otherwise = withGhciExists xs
262307

263-
264308
-- |
265309
--
266310
_TASKS_JSON_FILE_CONTENTS :: LB.ByteString

stack.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
# resolver: ./custom-snapshot.yaml
1919
# resolver: https://example.com/snapshots/2018-01-01.yaml
2020

21+
# for ghc-9.10
22+
resolver: nightly-2025-02-28
23+
2124
# for ghc-9.0.1
22-
resolver: nightly-2021-07-02
25+
# resolver: nightly-2021-07-02
2326

2427
# for ghc-8.10.3
2528
# resolver: lts-17.0

0 commit comments

Comments
 (0)