Skip to content

Commit dbda632

Browse files
committed
Index dependency hie files
1 parent f39c4da commit dbda632

File tree

10 files changed

+323
-55
lines changed

10 files changed

+323
-55
lines changed

cabal.project

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ packages:
3737
./plugins/hls-refactor-plugin
3838
./plugins/hls-overloaded-record-dot-plugin
3939

40+
source-repository-package
41+
type:git
42+
location: https://github.com/nlander/HieDb.git
43+
tag: f10051a6dc1b809d5f40a45beab92205d1829736
44+
4045
-- Standard location for temporary packages needed for particular environments
4146
-- For example it is used in the project gitlab mirror to help in the MAcOS M1 build script
4247
-- See https://github.com/haskell/haskell-language-server/blob/master/.gitlab-ci.yml

ghcide/ghcide.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ library
6868
hls-plugin-api == 2.1.0.0,
6969
lens,
7070
list-t,
71-
hiedb == 0.4.3.*,
71+
hiedb ^>= 0.4.3.0,
7272
lsp-types ^>= 2.0.1.0,
7373
lsp ^>= 2.1.0.0 ,
7474
mtl,
@@ -150,6 +150,7 @@ library
150150
Development.IDE.Core.Actions
151151
Development.IDE.Main.HeapStats
152152
Development.IDE.Core.Debouncer
153+
Development.IDE.Core.Dependencies
153154
Development.IDE.Core.FileStore
154155
Development.IDE.Core.FileUtils
155156
Development.IDE.Core.IdeConfiguration

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import qualified Data.Text as T
4848
import Data.Time.Clock
4949
import Data.Version
5050
import Development.IDE.Core.RuleTypes
51+
import qualified Development.IDE.Core.Rules as Rules
5152
import Development.IDE.Core.Shake hiding (Log, Priority,
5253
withHieDb)
5354
import qualified Development.IDE.GHC.Compat as Compat
@@ -127,6 +128,7 @@ data Log
127128
| LogNoneCradleFound FilePath
128129
| LogNewComponentCache !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo)
129130
| LogHieBios HieBios.Log
131+
| LogRules Rules.Log
130132
deriving instance Show Log
131133

132134
instance Pretty Log where
@@ -197,6 +199,7 @@ instance Pretty Log where
197199
LogNewComponentCache componentCache ->
198200
"New component cache HscEnvEq:" <+> viaShow componentCache
199201
LogHieBios log -> pretty log
202+
LogRules log -> pretty log
200203

201204
-- | Bump this version number when making changes to the format of the data stored in hiedb
202205
hiedbDataVersion :: String
@@ -604,7 +607,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do
604607

605608
-- New HscEnv for the component in question, returns the new HscEnvEq and
606609
-- a mapping from FilePath to the newly created HscEnvEq.
607-
let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv uids
610+
let new_cache = newComponentCache recorder extras optExtensions hieYaml _cfp hscEnv uids
608611
(cs, res) <- new_cache new
609612
-- Modified cache targets for everything else in the hie.yaml file
610613
-- which now uses the same EPS and so on
@@ -812,14 +815,15 @@ setNameCache nc hsc = hsc { hsc_NC = nc }
812815
-- | Create a mapping from FilePaths to HscEnvEqs
813816
newComponentCache
814817
:: Recorder (WithPriority Log)
818+
-> ShakeExtras
815819
-> [String] -- File extensions to consider
816820
-> Maybe FilePath -- Path to cradle
817821
-> NormalizedFilePath -- Path to file that caused the creation of this component
818822
-> HscEnv
819823
-> [(UnitId, DynFlags)]
820824
-> ComponentInfo
821825
-> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))
822-
newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do
826+
newComponentCache recorder extras exts cradlePath cfp hsc_env uids ci = do
823827
let df = componentDynFlags ci
824828
hscEnv' <-
825829
#if MIN_VERSION_ghc(9,3,0)
@@ -842,7 +846,7 @@ newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do
842846
#endif
843847

844848
let newFunc = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath
845-
henv <- newFunc hscEnv' uids
849+
henv <- newFunc (cmapWithPrio LogRules recorder) extras hscEnv' uids
846850
let targetEnv = ([], Just henv)
847851
targetDepends = componentDependencyInfo ci
848852
res = (targetEnv, targetDepends)

ghcide/src/Development/IDE/Core/Compile.hs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -895,34 +895,32 @@ spliceExpressions Splices{..} =
895895
-- TVar to 0 in order to set it up for a fresh indexing session. Otherwise, we
896896
-- can just increment the 'indexCompleted' TVar and exit.
897897
--
898-
indexHieFile :: ShakeExtras -> ModSummary -> NormalizedFilePath -> Util.Fingerprint -> Compat.HieFile -> IO ()
899-
indexHieFile se mod_summary srcPath !hash hf = do
898+
indexHieFile :: ShakeExtras -> NormalizedFilePath -> HieDb.SourceFile -> Util.Fingerprint -> Compat.HieFile -> IO ()
899+
indexHieFile se hiePath sourceFile !hash hf = do
900900
IdeOptions{optProgressStyle} <- getIdeOptionsIO se
901901
atomically $ do
902902
pending <- readTVar indexPending
903-
case HashMap.lookup srcPath pending of
903+
case HashMap.lookup hiePath pending of
904904
Just pendingHash | pendingHash == hash -> pure () -- An index is already scheduled
905905
_ -> do
906906
-- hiedb doesn't use the Haskell src, so we clear it to avoid unnecessarily keeping it around
907907
let !hf' = hf{hie_hs_src = mempty}
908-
modifyTVar' indexPending $ HashMap.insert srcPath hash
908+
modifyTVar' indexPending $ HashMap.insert hiePath hash
909909
writeTQueue indexQueue $ \withHieDb -> do
910910
-- We are now in the worker thread
911911
-- Check if a newer index of this file has been scheduled, and if so skip this one
912912
newerScheduled <- atomically $ do
913913
pending <- readTVar indexPending
914-
pure $ case HashMap.lookup srcPath pending of
914+
pure $ case HashMap.lookup hiePath pending of
915915
Nothing -> False
916916
-- If the hash in the pending list doesn't match the current hash, then skip
917917
Just pendingHash -> pendingHash /= hash
918918
unless newerScheduled $ do
919919
-- Using bracket, so even if an exception happen during withHieDb call,
920920
-- the `post` (which clean the progress indicator) will still be called.
921921
bracket_ (pre optProgressStyle) post $
922-
withHieDb (\db -> HieDb.addRefsFromLoaded db targetPath (HieDb.RealFile $ fromNormalizedFilePath srcPath) hash hf')
922+
withHieDb (\db -> HieDb.addRefsFromLoaded db (fromNormalizedFilePath hiePath) sourceFile hash hf')
923923
where
924-
mod_location = ms_location mod_summary
925-
targetPath = Compat.ml_hie_file mod_location
926924
HieDbWriter{..} = hiedbWriter se
927925

928926
-- Get a progress token to report progress and update it for the current file
@@ -986,15 +984,17 @@ indexHieFile se mod_summary srcPath !hash hf = do
986984
mdone <- atomically $ do
987985
-- Remove current element from pending
988986
pending <- stateTVar indexPending $
989-
dupe . HashMap.update (\pendingHash -> guard (pendingHash /= hash) $> pendingHash) srcPath
987+
dupe . HashMap.update (\pendingHash -> guard (pendingHash /= hash) $> pendingHash) hiePath
990988
modifyTVar' indexCompleted (+1)
991989
-- If we are done, report and reset completed
992990
whenMaybe (HashMap.null pending) $
993991
swapTVar indexCompleted 0
994992
whenJust (lspEnv se) $ \env -> LSP.runLspT env $
995993
when (coerce $ ideTesting se) $
996994
LSP.sendNotification (LSP.SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $
997-
toJSON $ fromNormalizedFilePath srcPath
995+
toJSON $ case sourceFile of
996+
HieDb.RealFile sourceFilePath -> sourceFilePath
997+
HieDb.FakeFile _ -> fromNormalizedFilePath hiePath
998998
whenJust mdone $ \done ->
999999
modifyVar_ indexProgressToken $ \tok -> do
10001000
whenJust (lspEnv se) $ \env -> LSP.runLspT env $
@@ -1015,7 +1015,7 @@ writeAndIndexHieFile hscEnv se mod_summary srcPath exports ast source =
10151015
GHC.mkHieFile' mod_summary exports ast source
10161016
atomicFileWrite se targetPath $ flip GHC.writeHieFile hf
10171017
hash <- Util.getFileHash targetPath
1018-
indexHieFile se mod_summary srcPath hash hf
1018+
indexHieFile se (toNormalizedFilePath' targetPath) (HieDb.RealFile $ fromNormalizedFilePath srcPath) hash hf
10191019
where
10201020
dflags = hsc_dflags hscEnv
10211021
mod_location = ms_location mod_summary
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
module Development.IDE.Core.Dependencies
2+
( indexDependencyHieFiles
3+
) where
4+
5+
import Control.Concurrent.STM (atomically)
6+
import Control.Concurrent.STM.TQueue (writeTQueue)
7+
import Control.Monad (unless, void)
8+
import Data.Foldable (traverse_)
9+
import qualified Data.Map as Map
10+
import Data.Maybe (isNothing)
11+
import Data.Set (Set)
12+
import qualified Data.Set as Set
13+
import Development.IDE.Core.Compile (indexHieFile)
14+
import Development.IDE.Core.Rules (HieFileCheck(..), Log, checkHieFile)
15+
import Development.IDE.Core.Shake (HieDbWriter(indexQueue), ShakeExtras(hiedbWriter, lspEnv, withHieDb))
16+
import qualified Development.IDE.GHC.Compat as GHC
17+
import Development.IDE.Types.Location (NormalizedFilePath, toNormalizedFilePath')
18+
import Ide.Logger (Recorder, WithPriority)
19+
import HieDb (SourceFile(FakeFile), lookupPackage, removeDependencySrcFiles)
20+
import Language.LSP.Server (resRootPath)
21+
import System.Directory (doesDirectoryExist)
22+
import System.FilePath ((</>), (<.>))
23+
24+
newtype Package = Package GHC.UnitInfo deriving Eq
25+
instance Ord Package where
26+
compare (Package u1) (Package u2) = compare (GHC.unitId u1) (GHC.unitId u2)
27+
28+
-- indexDependencyHieFiles gets all of the direct and transitive dependencies
29+
-- from the HscEnv and indexes their HIE files in the HieDb.
30+
indexDependencyHieFiles :: Recorder (WithPriority Log) -> ShakeExtras -> GHC.HscEnv -> IO ()
31+
indexDependencyHieFiles recorder se hscEnv = do
32+
-- Check whether the .hls directory exists.
33+
dotHlsDirExists <- maybe (pure False) doesDirectoryExist mHlsDir
34+
-- If the .hls directory does not exits, it may have been deleted.
35+
-- In this case, delete the indexed source files for all
36+
-- dependencies that are already indexed.
37+
unless dotHlsDirExists deleteMissingDependencySources
38+
-- Index all dependency HIE files in the HieDb database.
39+
void $ Map.traverseWithKey indexPackageHieFiles packagesWithModules
40+
where
41+
mHlsDir :: Maybe FilePath
42+
mHlsDir = do
43+
projectDir <- resRootPath =<< lspEnv se
44+
pure $ projectDir </> ".hls"
45+
-- Add the deletion of dependency source files from the
46+
-- HieDb database to the database write queue.
47+
deleteMissingDependencySources :: IO ()
48+
deleteMissingDependencySources =
49+
atomically $ writeTQueue (indexQueue $ hiedbWriter se) $
50+
\withHieDb ->
51+
withHieDb $ \db ->
52+
removeDependencySrcFiles db
53+
-- Index all of the modules in a package (a Unit).
54+
indexPackageHieFiles :: Package -> [GHC.Module] -> IO ()
55+
indexPackageHieFiles (Package package) modules = do
56+
let pkgLibDir :: FilePath
57+
pkgLibDir = case GHC.unitLibraryDirs package of
58+
[] -> ""
59+
(libraryDir : _) -> libraryDir
60+
-- Cabal puts the HIE files for a package in the
61+
-- extra-compilation-artifacts directory, provided
62+
-- it is compiled with the -fwrite-ide-info ghc option.
63+
hieDir :: FilePath
64+
hieDir = pkgLibDir </> "extra-compilation-artifacts"
65+
unit :: GHC.Unit
66+
unit = GHC.RealUnit $ GHC.Definite $ GHC.unitId package
67+
-- Check if we have already indexed this package.
68+
moduleRows <- withHieDb se $ \db ->
69+
lookupPackage db unit
70+
case moduleRows of
71+
-- There are no modules from this package in the database,
72+
-- so go ahead and index all the modules.
73+
[] -> traverse_ (indexModuleHieFile hieDir) modules
74+
-- There are modules from this package in the database,
75+
-- so assume all the modules have already been indexed
76+
-- and do nothing.
77+
_ -> return ()
78+
indexModuleHieFile :: FilePath -> GHC.Module -> IO ()
79+
indexModuleHieFile hieDir m = do
80+
let hiePath :: NormalizedFilePath
81+
hiePath = toNormalizedFilePath' $
82+
hieDir </> GHC.moduleNameSlashes (GHC.moduleName m) <.> "hie"
83+
-- Check that the module HIE file has correctly loaded. If there
84+
-- was some problem loading it, or if it has already been indexed
85+
-- (which shouldn't happen because we check whether each package
86+
-- has been indexed), then do nothing. Otherwise, call the
87+
-- indexHieFile function from Core.Compile.
88+
hieCheck <- checkHieFile recorder se "newHscEnvEqWithImportPaths" hiePath
89+
case hieCheck of
90+
HieFileMissing -> return ()
91+
HieAlreadyIndexed -> return ()
92+
CouldNotLoadHie _e -> return ()
93+
DoIndexing hash hie ->
94+
-- At this point there is no source file for the HIE file,
95+
-- so the HieDb.SourceFile we give is FakeFile Nothing.
96+
indexHieFile se hiePath (FakeFile Nothing) hash hie
97+
packagesWithModules :: Map.Map Package [GHC.Module]
98+
packagesWithModules = Map.fromSet getModulesForPackage packages
99+
packages :: Set Package
100+
packages = Set.fromList
101+
$ map Package
102+
$ Map.elems
103+
-- Take only the packages in the unitInfoMap that are direct
104+
-- or transitive dependencies.
105+
$ Map.filterWithKey (\uid _ -> uid `Set.member` dependencyIds) unitInfoMap
106+
where
107+
unitInfoMap :: GHC.UnitInfoMap
108+
unitInfoMap = GHC.getUnitInfoMap hscEnv
109+
dependencyIds :: Set GHC.UnitId
110+
dependencyIds =
111+
calculateTransitiveDependencies unitInfoMap directDependencyIds directDependencyIds
112+
directDependencyIds :: Set GHC.UnitId
113+
directDependencyIds = Set.fromList
114+
$ map GHC.toUnitId
115+
$ GHC.explicitUnits
116+
$ GHC.unitState hscEnv
117+
118+
-- calculateTransitiveDependencies finds the UnitId keys in the UnitInfoMap
119+
-- that are dependencies or transitive dependencies.
120+
calculateTransitiveDependencies :: GHC.UnitInfoMap -> Set GHC.UnitId -> Set GHC.UnitId -> Set GHC.UnitId
121+
calculateTransitiveDependencies unitInfoMap allDependencies newDepencencies
122+
-- If there are no new dependencies, we have found them all,
123+
-- so return allDependencies
124+
| Set.null newDepencencies = allDependencies
125+
-- Otherwise recursively add any dependencies of the newDepencencies
126+
-- that are not in allDependencies already.
127+
| otherwise = calculateTransitiveDependencies unitInfoMap nextAll nextNew
128+
where
129+
nextAll :: Set GHC.UnitId
130+
nextAll = Set.union allDependencies nextNew
131+
-- Get the dependencies of the newDependencies. Then the nextNew depencencies
132+
-- will be the set difference of the dependencies we have so far (allDependencies),
133+
-- and the dependencies of the newDepencencies.
134+
nextNew :: Set GHC.UnitId
135+
nextNew = flip Set.difference allDependencies
136+
$ Set.unions
137+
$ map (Set.fromList . GHC.unitDepends)
138+
$ Map.elems
139+
$ Map.filterWithKey (\uid _ -> uid `Set.member` newDepencencies) unitInfoMap
140+
141+
getModulesForPackage :: Package -> [GHC.Module]
142+
getModulesForPackage (Package package) =
143+
map makeModule allModules
144+
where
145+
allModules :: [GHC.ModuleName]
146+
allModules = map fst
147+
-- The modules with a Just value in the tuple
148+
-- are from other packages. These won't have
149+
-- an HIE file in this package, and should be
150+
-- covered by the transitive dependencies.
151+
( filter (isNothing . snd)
152+
$ GHC.unitExposedModules package
153+
)
154+
++ GHC.unitHiddenModules package
155+
makeModule :: GHC.ModuleName
156+
-> GHC.Module
157+
makeModule = GHC.mkModule (GHC.unitInfoId package)

ghcide/src/Development/IDE/Core/RuleTypes.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import Development.IDE.GHC.CoreFile
3131
import Development.IDE.GHC.Util
3232
import Development.IDE.Graph
3333
import Development.IDE.Import.DependencyInformation
34-
import Development.IDE.Types.HscEnvEq (HscEnvEq)
34+
import {-# SOURCE #-} Development.IDE.Types.HscEnvEq (HscEnvEq)
3535
import Development.IDE.Types.KnownTargets
3636
import GHC.Generics (Generic)
3737

0 commit comments

Comments
 (0)