Skip to content

Commit 77d1d3f

Browse files
committed
Add POC for HLS loading multiple components on startup
Powered by hie-bios.
1 parent 2e0f6cd commit 77d1d3f

File tree

3 files changed

+93
-66
lines changed

3 files changed

+93
-66
lines changed

cabal.project

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ source-repository-package
3737
location: https://github.com/hsyl20/ghc-api-compat
3838
tag: 8fee87eac97a538dbe81ff1ab18cff10f2f9fa15
3939

40+
source-repository-package
41+
type: git
42+
location: https://github.com/fendor/hie-bios
43+
tag: fe823adfa0e82aa76e098a57cc424c92902e1db8
44+
4045
write-ghc-environment-files: never
4146

4247
index-state: 2021-08-12T12:00:38Z

ghcide/ghcide.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ library
100100
ghc-paths,
101101
ghc-api-compat,
102102
cryptohash-sha1 >=0.11.100 && <0.12,
103-
hie-bios >= 0.7.1 && < 0.8.0,
103+
hie-bios >= 0.8.0 && < 0.9.0,
104104
implicit-hie-cradle >= 0.3.0.5 && < 0.4,
105105
base16-bytestring >=0.1.1 && <1.1
106106
if os(windows)

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

Lines changed: 87 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ import Database.SQLite.Simple
8585
import HieDb.Create
8686
import HieDb.Types
8787
import HieDb.Utils
88+
import Ide.Types (dynFlagsModifyGlobal)
89+
import Data.List.NonEmpty (NonEmpty)
90+
import qualified Data.List.NonEmpty as NE
8891

8992
-- | Bump this version number when making changes to the format of the data stored in hiedb
9093
hiedbDataVersion :: String
@@ -230,7 +233,9 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
230233
filesMap <- newVar HM.empty :: IO (Var FilesMap)
231234
-- Version of the mappings above
232235
version <- newVar 0
233-
let returnWithVersion fun = IdeGhcSession fun <$> liftIO (readVar version)
236+
let returnWithVersion :: (FilePath -> IO (IdeResult HscEnvEq, [FilePath]))
237+
-> Action IdeGhcSession
238+
returnWithVersion fun = (IdeGhcSession fun) <$> liftIO (readVar version)
234239
-- This caches the mapping from Mod.hs -> hie.yaml
235240
cradleLoc <- liftIO $ memoIO $ \v -> do
236241
res <- findCradle v
@@ -277,9 +282,9 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
277282
-- If the hieYaml file already has an HscEnv, the new component is
278283
-- combined with the components in the old HscEnv into a new HscEnv
279284
-- which contains the union.
280-
let packageSetup :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)
281-
-> IO (HscEnv, ComponentInfo, [ComponentInfo])
282-
packageSetup (hieYaml, cfp, opts, libDir) = do
285+
let packageSetup :: (Maybe FilePath, NormalizedFilePath, NonEmpty ComponentOptions, FilePath)
286+
-> IO [(HscEnv, ComponentInfo, [ComponentInfo])]
287+
packageSetup (hieYaml, cfp, allOpts, libDir) = concatForM (NE.toList allOpts) $ \opts -> do
283288
-- Parse DynFlags for the newly discovered component
284289
hscEnv <- emptyHscEnv ideNc libDir
285290
(df, targets) <- evalGhcEnv hscEnv $ setOptions opts (hsc_dflags hscEnv)
@@ -289,7 +294,7 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
289294
-- or making a new one. The lookup returns the HscEnv and a list of
290295
-- information about other components loaded into the HscEnv
291296
-- (unitId, DynFlag, Targets)
292-
modifyVar hscEnvs $ \m -> do
297+
r <- modifyVar hscEnvs $ \m -> do
293298
-- Just deps if there's already an HscEnv
294299
-- Nothing is it's the first time we are making an HscEnv
295300
let oldDeps = Map.lookup hieYaml m
@@ -343,12 +348,13 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
343348
-- . The modified information (without -inplace flags) for
344349
-- existing packages
345350
pure (Map.insert hieYaml (newHscEnv, new_deps) m, (newHscEnv, head new_deps', tail new_deps'))
351+
pure [r]
346352

347353

348-
let session :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)
354+
let session :: (Maybe FilePath, NormalizedFilePath, NonEmpty ComponentOptions, FilePath)
349355
-> IO (IdeResult HscEnvEq,[FilePath])
350-
session args@(hieYaml, _cfp, _opts, _libDir) = do
351-
(hscEnv, new, old_deps) <- packageSetup args
356+
session args@(hieYaml, cfp, _opts, _libDir) = do
357+
setupInfos <- packageSetup args -- (hscEnvs, new, old_deps)
352358

353359
-- Whenever we spin up a session on Linux, dynamically load libm.so.6
354360
-- in. We need this in case the binary is statically linked, in which
@@ -358,58 +364,73 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
358364
-- some code. If the binary is dynamically linked, then this will have
359365
-- no effect.
360366
-- See https://github.com/haskell/haskell-language-server/issues/221
361-
when (os == "linux") $ do
362-
initObjLinker hscEnv
363-
res <- loadDLL hscEnv "libm.so.6"
364-
case res of
365-
Nothing -> pure ()
366-
Just err -> hPutStrLn stderr $
367-
"Error dynamically loading libm.so.6:\n" <> err
368-
369-
-- Make a map from unit-id to DynFlags, this is used when trying to
370-
-- resolve imports. (especially PackageImports)
371-
let uids = map (\ci -> (componentUnitId ci, componentDynFlags ci)) (new : old_deps)
372-
373-
-- For each component, now make a new HscEnvEq which contains the
374-
-- HscEnv for the hie.yaml file but the DynFlags for that component
375-
376-
-- New HscEnv for the component in question, returns the new HscEnvEq and
377-
-- a mapping from FilePath to the newly created HscEnvEq.
378-
let new_cache = newComponentCache logger optExtensions hieYaml _cfp hscEnv uids
379-
(cs, res) <- new_cache new
380-
-- Modified cache targets for everything else in the hie.yaml file
381-
-- which now uses the same EPS and so on
382-
cached_targets <- concatMapM (fmap fst . new_cache) old_deps
383-
384-
let all_targets = cs ++ cached_targets
385-
386-
void $ modifyVar' fileToFlags $
387-
Map.insert hieYaml (HM.fromList (concatMap toFlagsMap all_targets))
388-
void $ modifyVar' filesMap $
389-
flip HM.union (HM.fromList (zip (map fst $ concatMap toFlagsMap all_targets) (repeat hieYaml)))
390-
391-
void $ extendKnownTargets all_targets
392-
393-
-- Invalidate all the existing GhcSession build nodes by restarting the Shake session
394-
invalidateShakeCache
395-
restartShakeSession []
396-
397-
-- Typecheck all files in the project on startup
398-
checkProject <- getCheckProject
399-
unless (null cs || not checkProject) $ do
400-
cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations cs)
401-
void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do
402-
mmt <- uses GetModificationTime cfps'
403-
let cs_exist = catMaybes (zipWith (<$) cfps' mmt)
404-
modIfaces <- uses GetModIface cs_exist
405-
-- update exports map
406-
extras <- getShakeExtras
407-
let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces
408-
liftIO $ modifyVar_ (exportsMap extras) $ evaluate . (exportsMap' <>)
409-
410-
return (second Map.keys res)
411-
412-
let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])
367+
forM_ setupInfos $ \(hscEnv, new, old_deps) -> do
368+
when (os == "linux") $ do
369+
initObjLinker hscEnv
370+
res <- loadDLL hscEnv "libm.so.6"
371+
case res of
372+
Nothing -> pure ()
373+
Just err -> hPutStrLn stderr $
374+
"Error dynamically loading libm.so.6:\n" <> err
375+
376+
-- Make a map from unit-id to DynFlags, this is used when trying to
377+
-- resolve imports. (especially PackageImports)
378+
let uids = map (\ci -> (componentUnitId ci, componentDynFlags ci)) (new : old_deps)
379+
380+
-- For each component, now make a new HscEnvEq which contains the
381+
-- HscEnv for the hie.yaml file but the DynFlags for that component
382+
383+
-- New HscEnv for the component in question, returns the new HscEnvEq and
384+
-- a mapping from FilePath to the newly created HscEnvEq.
385+
let new_cache = newComponentCache logger optExtensions hieYaml cfp hscEnv uids
386+
(cs, _res) <- new_cache new
387+
-- Modified cache targets for everything else in the hie.yaml file
388+
-- which now uses the same EPS and so on
389+
cached_targets <- concatMapM (fmap fst . new_cache) old_deps
390+
391+
let all_targets = cs ++ cached_targets
392+
393+
void $ modifyVar' fileToFlags $
394+
Map.insert hieYaml (HM.fromList (concatMap toFlagsMap all_targets))
395+
void $ modifyVar' filesMap $
396+
flip HM.union (HM.fromList (zip (map fst $ concatMap toFlagsMap all_targets) (repeat hieYaml)))
397+
398+
void $ extendKnownTargets all_targets
399+
400+
-- Invalidate all the existing GhcSession build nodes by restarting the Shake session
401+
invalidateShakeCache
402+
restartShakeSession []
403+
404+
-- Typecheck all files in the project on startup
405+
checkProject <- getCheckProject
406+
unless (null cs || not checkProject) $ do
407+
cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations cs)
408+
void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do
409+
mmt <- uses GetModificationTime cfps'
410+
let cs_exist = catMaybes (zipWith (<$) cfps' mmt)
411+
modIfaces <- uses GetModIface cs_exist
412+
-- update exports map
413+
extras <- getShakeExtras
414+
let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces
415+
liftIO $ modifyVar_ (exportsMap extras) $ evaluate . (exportsMap' <>)
416+
417+
yamlMap <- readVar fileToFlags
418+
let (hscEnvEq, depInfo) = (yamlMap Map.! hieYaml) HM.! cfp
419+
pure (hscEnvEq, Map.keys depInfo)
420+
421+
-- Let's be insanely hacky!
422+
-- Go through all new components and find the one that the given fp most likely
423+
-- belongs to.
424+
-- Since all ComponentInfo's carry a proof for which file caused their creation,
425+
-- and add this file to their targets, we need to do some post-processing.
426+
-- It is an implementation detail that the first known target is the normalized
427+
-- filepath of the proof file.
428+
-- Let's strip it away and let's see whether the targets still contain 'cfp'!
429+
-- If they do, it must be the home component of cfp.
430+
-- If we find it, remove it as a target from all other 'HscEnvEq' to avoid recompilation
431+
-- issues.
432+
433+
let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq,[FilePath])
413434
consultCradle hieYaml cfp = do
414435
lfp <- flip makeRelative cfp <$> getCurrentDirectory
415436
logInfo logger $ T.pack ("Consulting the cradle for " <> show lfp)
@@ -441,7 +462,7 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
441462
InstallationMismatch{..} ->
442463
return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[])
443464
InstallationChecked _compileTime _ghcLibCheck ->
444-
session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
465+
session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
445466
-- Failure case, either a cradle error or the none cradle
446467
Left err -> do
447468
dep_info <- getDependencyInfo (maybeToList hieYaml)
@@ -498,7 +519,7 @@ loadSessionWithOptions SessionLoadingOptions{..} dir = do
498519
-- GHC options/dynflags needed for the session and the GHC library directory
499520

500521
cradleToOptsAndLibDir :: Show a => Cradle a -> FilePath
501-
-> IO (Either [CradleError] (ComponentOptions, FilePath))
522+
-> IO (Either [CradleError] (NonEmpty ComponentOptions, FilePath))
502523
cradleToOptsAndLibDir cradle file = do
503524
-- Start off by getting the session options
504525
hPutStrLn stderr $ "Output from setting up the cradle " <> show cradle
@@ -574,7 +595,7 @@ newComponentCache
574595
-> [(InstalledUnitId, DynFlags)]
575596
-> ComponentInfo
576597
-> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))
577-
newComponentCache logger exts cradlePath cfp hsc_env uids ci = do
598+
newComponentCache logger exts cradlePath _cfp hsc_env uids ci = do
578599
let df = componentDynFlags ci
579600
let hscEnv' = hsc_env { hsc_dflags = df
580601
, hsc_IC = (hsc_IC hsc_env) { ic_dflags = df } }
@@ -594,8 +615,9 @@ newComponentCache logger exts cradlePath cfp hsc_env uids ci = do
594615
-- the component, in which case things will be horribly broken anyway.
595616
-- Otherwise, we will immediately attempt to reload this module which
596617
-- causes an infinite loop and high CPU usage.
597-
let special_target = TargetDetails (TargetFile cfp) targetEnv targetDepends [componentFP ci]
598-
return (special_target:ctargets, res)
618+
-- let special_target = TargetDetails (TargetFile cfp) targetEnv targetDepends [componentFP ci]
619+
-- return (special_target:ctargets, res)
620+
return (ctargets, res)
599621

600622
{- Note [Avoiding bad interface files]
601623
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

0 commit comments

Comments
 (0)