Skip to content

Commit 0da4168

Browse files
fendormergify[bot]
andauthored
Unify session loading using implicit-hie (#1783)
* Unify session loading using implicit-hie Make Wrapper and Session loader use the same logic to avoid loading logic divergence. Cleans up existing usages to use infrastructure in place. * Add major CLI mode for printing the cradle type Adds test-case for proving that wrapper and hls report the same cradle type for a project. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 8f089d4 commit 0da4168

File tree

10 files changed

+140
-50
lines changed

10 files changed

+140
-50
lines changed

exe/Wrapper.hs

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import Data.Default
88
import Data.Foldable
99
import Data.List
1010
import Data.Void
11-
import Development.IDE.Session (findCradle)
12-
import HIE.Bios hiding (findCradle)
13-
import HIE.Bios.Environment
11+
import qualified Development.IDE.Session as Session
12+
import qualified HIE.Bios.Environment as HieBios
1413
import HIE.Bios.Types
1514
import Ide.Arguments
1615
import Ide.Version
@@ -44,6 +43,9 @@ main = do
4443
VersionMode PrintNumericVersion ->
4544
putStrLn haskellLanguageServerNumericVersion
4645

46+
BiosMode PrintCradleType ->
47+
print =<< findProjectCradle
48+
4749
_ -> launchHaskellLanguageServer args
4850

4951
launchHaskellLanguageServer :: Arguments -> IO ()
@@ -53,9 +55,11 @@ launchHaskellLanguageServer parsedArgs = do
5355
_ -> pure ()
5456

5557
d <- getCurrentDirectory
58+
59+
-- search for the project cradle type
60+
cradle <- findProjectCradle
5661

57-
-- Get the cabal directory from the cradle
58-
cradle <- findLocalCradle (d </> "a")
62+
-- Get the root directory from the cradle
5963
setCurrentDirectory $ cradleRootDir cradle
6064

6165
case parsedArgs of
@@ -114,7 +118,7 @@ getRuntimeGhcVersion' cradle = do
114118
Direct -> checkToolExists "ghc"
115119
_ -> pure ()
116120

117-
ghcVersionRes <- getRuntimeGhcVersion cradle
121+
ghcVersionRes <- HieBios.getRuntimeGhcVersion cradle
118122
case ghcVersionRes of
119123
CradleSuccess ver -> do
120124
return ver
@@ -129,23 +133,16 @@ getRuntimeGhcVersion' cradle = do
129133
die $ "Cradle requires " ++ exe ++ " but couldn't find it" ++ "\n"
130134
++ show cradle
131135

132-
-- | Find the cradle that the given File belongs to.
133-
--
134-
-- First looks for a "hie.yaml" file in the directory of the file
135-
-- or one of its parents. If this file is found, the cradle
136-
-- is read from the config. If this config does not comply to the "hie.yaml"
137-
-- specification, an error is raised.
138-
--
139-
-- If no "hie.yaml" can be found, the implicit config is used.
140-
-- The implicit config uses different heuristics to determine the type
141-
-- of the project that may or may not be accurate.
142-
findLocalCradle :: FilePath -> IO (Cradle Void)
143-
findLocalCradle fp = do
144-
cradleConf <- findCradle def fp
145-
crdl <- case cradleConf of
146-
Just yaml -> do
147-
hPutStrLn stderr $ "Found \"" ++ yaml ++ "\" for \"" ++ fp ++ "\""
148-
loadCradle yaml
149-
Nothing -> loadImplicitCradle fp
150-
hPutStrLn stderr $ "Module \"" ++ fp ++ "\" is loaded by Cradle: " ++ show crdl
151-
return crdl
136+
findProjectCradle :: IO (Cradle Void)
137+
findProjectCradle = do
138+
d <- getCurrentDirectory
139+
140+
let initialFp = (d </> "a")
141+
hieYaml <- Session.findCradle def initialFp
142+
143+
-- Some log messages
144+
case hieYaml of
145+
Just yaml -> hPutStrLn stderr $ "Found \"" ++ yaml ++ "\" for \"" ++ initialFp ++ "\""
146+
Nothing -> hPutStrLn stderr "No 'hie.yaml' found. Try to discover the project type!"
147+
148+
Session.loadCradle def hieYaml d

ghcide/session-loader/Development/IDE/Session.hs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import Development.IDE.GHC.Compat hiding (Target,
4747
TargetFile, TargetModule)
4848
import qualified Development.IDE.GHC.Compat as GHC
4949
import Development.IDE.GHC.Util
50+
import Development.IDE.Graph (Action)
5051
import Development.IDE.Session.VersionCheck
5152
import Development.IDE.Types.Diagnostics
5253
import Development.IDE.Types.Exports
@@ -55,7 +56,6 @@ import Development.IDE.Types.HscEnvEq (HscEnvEq, newHscEnvEq,
5556
import Development.IDE.Types.Location
5657
import Development.IDE.Types.Logger
5758
import Development.IDE.Types.Options
58-
import Development.IDE.Graph (Action)
5959
import GHC.Check
6060
import qualified HIE.Bios as HieBios
6161
import HIE.Bios.Environment hiding (getCacheDir)
@@ -84,12 +84,10 @@ import Control.Concurrent.STM (atomically)
8484
import Control.Concurrent.STM.TQueue
8585
import qualified Data.HashSet as Set
8686
import Database.SQLite.Simple
87-
import HIE.Bios.Cradle (yamlConfig)
87+
import GHC.LanguageExtensions (Extension (EmptyCase))
8888
import HieDb.Create
8989
import HieDb.Types
9090
import HieDb.Utils
91-
import Maybes (MaybeT (runMaybeT))
92-
import GHC.LanguageExtensions (Extension(EmptyCase))
9391

9492
-- | Bump this version number when making changes to the format of the data stored in hiedb
9593
hiedbDataVersion :: String
@@ -99,15 +97,18 @@ data CacheDirs = CacheDirs
9997
{ hiCacheDir, hieCacheDir, oCacheDir :: Maybe FilePath}
10098

10199
data SessionLoadingOptions = SessionLoadingOptions
102-
{ findCradle :: FilePath -> IO (Maybe FilePath)
103-
, loadCradle :: FilePath -> IO (HieBios.Cradle Void)
100+
{ findCradle :: FilePath -> IO (Maybe FilePath)
101+
-- | Load the cradle with an optional 'hie.yaml' location.
102+
-- If a 'hie.yaml' is given, use it to load the cradle.
103+
-- Otherwise, use the provided project root directory to determine the cradle type.
104+
, loadCradle :: Maybe FilePath -> FilePath -> IO (HieBios.Cradle Void)
104105
-- | Given the project name and a set of command line flags,
105106
-- return the path for storing generated GHC artifacts,
106107
-- or 'Nothing' to respect the cradle setting
107-
, getCacheDirs :: String -> [String] -> IO CacheDirs
108+
, getCacheDirs :: String -> [String] -> IO CacheDirs
108109
-- | Return the GHC lib dir to use for the 'unsafeGlobalDynFlags'
109-
, getInitialGhcLibDir :: IO (Maybe LibDir)
110-
, fakeUid :: InstalledUnitId
110+
, getInitialGhcLibDir :: IO (Maybe LibDir)
111+
, fakeUid :: InstalledUnitId
111112
-- ^ unit id used to tag the internal component built by ghcide
112113
-- To reuse external interface files the unit ids must match,
113114
-- thus make sure to build them with `--this-unit-id` set to the
@@ -117,17 +118,39 @@ data SessionLoadingOptions = SessionLoadingOptions
117118
instance Default SessionLoadingOptions where
118119
def = SessionLoadingOptions
119120
{findCradle = HieBios.findCradle
120-
,loadCradle = HieBios.loadCradle
121+
,loadCradle = loadWithImplicitCradle
121122
,getCacheDirs = getCacheDirsDefault
122123
,getInitialGhcLibDir = getInitialGhcLibDirDefault
123124
,fakeUid = toInstalledUnitId (stringToUnitId "main")
124125
}
125126

127+
-- | Find the cradle for a given 'hie.yaml' configuration.
128+
--
129+
-- If a 'hie.yaml' is given, the cradle is read from the config.
130+
-- If this config does not comply to the "hie.yaml"
131+
-- specification, an error is raised.
132+
--
133+
-- If no location for "hie.yaml" is provided, the implicit config is used
134+
-- using the provided root directory for discovering the project.
135+
-- The implicit config uses different heuristics to determine the type
136+
-- of the project that may or may not be accurate.
137+
loadWithImplicitCradle :: Maybe FilePath
138+
-- ^ Optional 'hie.yaml' location. Will be used if given.
139+
-> FilePath
140+
-- ^ Root directory of the project. Required as a fallback
141+
-- if no 'hie.yaml' location is given.
142+
-> IO (HieBios.Cradle Void)
143+
loadWithImplicitCradle mHieYaml rootDir = do
144+
crdl <- case mHieYaml of
145+
Just yaml -> HieBios.loadCradle yaml
146+
Nothing -> loadImplicitHieCradle $ addTrailingPathSeparator rootDir
147+
return crdl
148+
126149
getInitialGhcLibDirDefault :: IO (Maybe LibDir)
127150
getInitialGhcLibDirDefault = do
128151
dir <- IO.getCurrentDirectory
129-
hieYaml <- runMaybeT $ yamlConfig dir
130-
cradle <- maybe (loadImplicitHieCradle $ addTrailingPathSeparator dir) HieBios.loadCradle hieYaml
152+
hieYaml <- findCradle def dir
153+
cradle <- loadCradle def hieYaml dir
131154
hPutStrLn stderr $ "setInitialDynFlags cradle: " ++ show cradle
132155
libDirRes <- getRuntimeGhcLibDir cradle
133156
case libDirRes of
@@ -399,7 +422,7 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
399422
when (isNothing hieYaml) $
400423
logWarning logger $ implicitCradleWarning lfp
401424

402-
cradle <- maybe (loadImplicitHieCradle $ addTrailingPathSeparator dir) loadCradle hieYaml
425+
cradle <- loadCradle hieYaml dir
403426

404427
when optTesting $ mRunLspT lspEnv $
405428
sendNotification (SCustomMethod "ghcide/cradle/loaded") (toJSON cfp)

haskell-language-server.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,8 @@ test-suite func-test
439439
test-suite wrapper-test
440440
type: exitcode-stdio-1.0
441441
build-tool-depends:
442-
haskell-language-server:haskell-language-server-wrapper -any
442+
haskell-language-server:haskell-language-server-wrapper -any,
443+
haskell-language-server:haskell-language-server -any
443444

444445
default-language: Haskell2010
445446
build-depends:

hls-test-utils/src/Test/Hls/Util.hs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module Test.Hls.Util
2929
, waitForDiagnosticsFromSource
3030
, waitForDiagnosticsFromSourceWithTimeout
3131
, withCurrentDirectoryInTmp
32+
, withCurrentDirectoryInTmp'
3233
)
3334
where
3435

@@ -269,30 +270,54 @@ flushStackEnvironment = do
269270

270271
-- | Like 'withCurrentDirectory', but will copy the directory over to the system
271272
-- temporary directory first to avoid haskell-language-server's source tree from
272-
-- interfering with the cradle
273+
-- interfering with the cradle.
274+
--
275+
-- Ignores directories containing build artefacts to avoid interference and
276+
-- provide reproducible test-behaviour.
273277
withCurrentDirectoryInTmp :: FilePath -> IO a -> IO a
274278
withCurrentDirectoryInTmp dir f =
275-
withTempCopy dir $ \newDir ->
279+
withTempCopy ignored dir $ \newDir ->
280+
withCurrentDirectory newDir f
281+
where
282+
ignored = ["dist", "dist-newstyle", ".stack-work"]
283+
284+
285+
-- | Like 'withCurrentDirectory', but will copy the directory over to the system
286+
-- temporary directory first to avoid haskell-language-server's source tree from
287+
-- interfering with the cradle.
288+
--
289+
-- You may specify directories to ignore, but should be careful to maintain reproducibility.
290+
withCurrentDirectoryInTmp' :: [FilePath] -> FilePath -> IO a -> IO a
291+
withCurrentDirectoryInTmp' ignored dir f =
292+
withTempCopy ignored dir $ \newDir ->
276293
withCurrentDirectory newDir f
277294

278-
withTempCopy :: FilePath -> (FilePath -> IO a) -> IO a
279-
withTempCopy srcDir f = do
295+
-- | Example call: @withTempCopy ignored src f@
296+
--
297+
-- Copy directory 'src' to into a temporary directory ignoring any directories
298+
-- (and files) that are listed in 'ignored'. Pass the temporary directory
299+
-- containing the copied sources to the continuation.
300+
withTempCopy :: [FilePath] -> FilePath -> (FilePath -> IO a) -> IO a
301+
withTempCopy ignored srcDir f = do
280302
withSystemTempDirectory "hls-test" $ \newDir -> do
281-
copyDir srcDir newDir
303+
copyDir ignored srcDir newDir
282304
f newDir
283305

284-
copyDir :: FilePath -> FilePath -> IO ()
285-
copyDir src dst = do
306+
-- | Example call: @copyDir ignored src dst@
307+
--
308+
-- Copy directory 'src' to 'dst' ignoring any directories (and files)
309+
-- that are listed in 'ignored'.
310+
copyDir :: [FilePath] -> FilePath -> FilePath -> IO ()
311+
copyDir ignored src dst = do
286312
cnts <- listDirectory src
287313
forM_ cnts $ \file -> do
288314
unless (file `elem` ignored) $ do
289315
let srcFp = src </> file
290316
dstFp = dst </> file
291317
isDir <- doesDirectoryExist srcFp
292318
if isDir
293-
then createDirectory dstFp >> copyDir srcFp dstFp
319+
then createDirectory dstFp >> copyDir ignored srcFp dstFp
294320
else copyFile srcFp dstFp
295-
where ignored = ["dist", "dist-newstyle", ".stack-work"]
296321

297322
fromAction :: (Command |? CodeAction) -> CodeAction
298323
fromAction (InR action) = action

src/Ide/Arguments.hs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Ide.Arguments
1010
( Arguments(..)
1111
, GhcideArguments(..)
1212
, PrintVersion(..)
13+
, BiosAction(..)
1314
, getArguments
1415
, haskellLanguageServerVersion
1516
, haskellLanguageServerNumericVersion
@@ -27,6 +28,7 @@ import System.Environment
2728
data Arguments
2829
= VersionMode PrintVersion
2930
| ProbeToolsMode
31+
| BiosMode BiosAction
3032
| Ghcide GhcideArguments
3133
| VSCodeExtensionSchemaMode
3234
| DefaultConfigurationMode
@@ -50,12 +52,17 @@ data PrintVersion
5052
| PrintNumericVersion
5153
deriving (Show, Eq, Ord)
5254

55+
data BiosAction
56+
= PrintCradleType
57+
deriving (Show, Eq, Ord)
58+
5359
getArguments :: String -> IO Arguments
5460
getArguments exeName = execParser opts
5561
where
5662
opts = info ((
5763
VersionMode <$> printVersionParser exeName
5864
<|> probeToolsParser exeName
65+
<|> BiosMode <$> biosParser
5966
<|> Ghcide <$> arguments
6067
<|> vsCodeExtensionSchemaModeParser
6168
<|> defaultConfigurationModeParser)
@@ -72,6 +79,11 @@ printVersionParser exeName =
7279
flag' PrintNumericVersion
7380
(long "numeric-version" <> help ("Show numeric version of " ++ exeName))
7481

82+
biosParser :: Parser BiosAction
83+
biosParser =
84+
flag' PrintCradleType
85+
(long "print-cradle" <> help "Print the project cradle type")
86+
7587
probeToolsParser :: String -> Parser Arguments
7688
probeToolsParser exeName =
7789
flag' ProbeToolsMode

src/Ide/Main.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import qualified Data.Text as T
1717
import Development.IDE.Core.Rules
1818
import Development.IDE.Main (isLSP)
1919
import qualified Development.IDE.Main as Main
20+
import qualified Development.IDE.Session as Session
2021
import Development.IDE.Types.Logger as G
2122
import qualified Development.IDE.Types.Options as Ghcide
2223
import Development.IDE.Graph (ShakeOptions (shakeThreads))
@@ -50,6 +51,12 @@ defaultMain args idePlugins = do
5051
VersionMode PrintNumericVersion ->
5152
putStrLn haskellLanguageServerNumericVersion
5253

54+
BiosMode PrintCradleType -> do
55+
dir <- IO.getCurrentDirectory
56+
hieYaml <- Session.findCradle def dir
57+
cradle <- Session.loadCradle def hieYaml dir
58+
print cradle
59+
5360
Ghcide ghcideArgs -> do
5461
{- see WARNING above -}
5562
hPutStrLn stderr hlsVer

test/wrapper/Main.hs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Data.List.Extra (trimEnd)
1+
import Data.List.Extra (trimEnd, isInfixOf)
22
import Data.Maybe
33
import System.Environment
44
import System.Process
@@ -18,6 +18,11 @@ projectGhcVersionTests = testGroup "--project-ghc-version"
1818
, testCase "cabal with global ghc" $ do
1919
ghcVer <- trimEnd <$> readProcess "ghc" ["--numeric-version"] ""
2020
testDir "test/wrapper/testdata/cabal-cur-ver" ghcVer
21+
, testCase "stack with existing cabal build artifact" $ do
22+
-- Should report cabal as existing build artifacts are more important than
23+
-- the existence of 'stack.yaml'
24+
testProjectType "test/wrapper/testdata/stack-with-dist-newstyle"
25+
("cradleOptsProg = CradleAction: Cabal" `isInfixOf`)
2126
]
2227

2328
testDir :: FilePath -> String -> Assertion
@@ -27,3 +32,15 @@ testDir dir expectedVer =
2732
<$> lookupEnv "HLS_WRAPPER_TEST_EXE"
2833
actualVer <- trimEnd <$> readProcess testExe ["--project-ghc-version"] ""
2934
actualVer @?= expectedVer
35+
36+
testProjectType :: FilePath -> (String -> Bool) -> Assertion
37+
testProjectType dir matcher =
38+
withCurrentDirectoryInTmp' [".stack-work", "dist"] dir $ do
39+
wrapperTestExe <- fromMaybe "haskell-language-server-wrapper"
40+
<$> lookupEnv "HLS_WRAPPER_TEST_EXE"
41+
hlsTestExe <- fromMaybe "haskell-language-server"
42+
<$> lookupEnv "HLS_TEST_EXE"
43+
actualWrapperCradle <- trimEnd <$> readProcess wrapperTestExe ["--print-cradle"] ""
44+
actualHlsCradle <- trimEnd <$> readProcess hlsTestExe ["--print-cradle"] ""
45+
matcher actualWrapperCradle @? "Wrapper reported wrong project type: " ++ actualWrapperCradle
46+
matcher actualHlsCradle @? "HLS reported wrong project type: " ++ actualHlsCradle

test/wrapper/testdata/stack-with-dist-newstyle/dist-newstyle/.gitkeep

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cabal-version: 2.4
2+
name: stack-with-dist-newstyle
3+
version: 0.1.0.0
4+
5+
library
6+
default-language: Haskell2010
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# specific version does not matter
2+
resolver: ghc-8.10.4

0 commit comments

Comments
 (0)