1
1
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
2
2
-- SPDX-License-Identifier: Apache-2.0
3
3
{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above
4
+ {-# LANGUAGE CPP #-} -- To get precise GHC version
5
+ {-# LANGUAGE TemplateHaskell #-}
4
6
{-# LANGUAGE DeriveGeneric #-}
5
7
{-# LANGUAGE OverloadedStrings #-}
6
8
{-# LANGUAGE RecordWildCards #-}
@@ -16,7 +18,10 @@ import Control.Exception
16
18
import Control.Monad.Extra
17
19
import Control.Monad.IO.Class
18
20
import Data.Default
21
+ import Data.Either
22
+ import qualified Data.HashMap.Strict as HM
19
23
import qualified Data.HashSet as HashSet
24
+ import Data.IORef
20
25
import Data.List.Extra
21
26
import qualified Data.Map.Strict as Map
22
27
import Data.Maybe
@@ -39,23 +44,40 @@ import Development.IDE.Types.Diagnostics
39
44
import Development.IDE.Types.Location
40
45
import Development.IDE.Types.Logger
41
46
import Development.IDE.Types.Options
42
- import Development.Shake (Action , Rules , action )
43
- import HIE.Bios
47
+ import Development.Shake (Action , action )
48
+ import HIE.Bios.Environment ( addCmdOpts )
44
49
import qualified Language.Haskell.LSP.Core as LSP
45
50
import Ide.Logger
46
51
import Ide.Plugin
47
52
import Ide.Plugin.Config
48
53
import Language.Haskell.LSP.Messages
49
54
import Language.Haskell.LSP.Types (LspId (IdInt ))
50
- import RuleTypes
51
- import Rules
55
+ import Linker (initDynLinker )
56
+ import Module
57
+ import NameCache
58
+ import Packages
59
+ -- import Paths_ghcide
52
60
import qualified System.Directory.Extra as IO
53
61
-- import System.Environment
54
62
import System.Exit
55
63
import System.FilePath
56
64
import System.IO
57
65
import System.Log.Logger as L
58
66
import System.Time.Extra
67
+ -- import Outputable (pprTraceM, ppr, text)
68
+ import qualified Crypto.Hash.SHA1 as H
69
+ import qualified Data.ByteString.Char8 as B
70
+ import Data.ByteString.Base16 (encode )
71
+ import DynFlags (gopt_set , gopt_unset ,
72
+ updOptLevel )
73
+ import GhcMonad
74
+ import HscTypes (HscEnv (.. ), ic_dflags )
75
+ import DynFlags (PackageFlag (.. ), PackageArg (.. ))
76
+ import GHC hiding (def )
77
+ import qualified GHC.Paths
78
+ import HIE.Bios.Cradle
79
+ import HIE.Bios.Types
80
+ import System.Directory
59
81
60
82
-- ---------------------------------------------------------------------
61
83
@@ -161,9 +183,10 @@ main = do
161
183
{ optReportProgress = clientSupportsProgress caps
162
184
, optShakeProfiling = argsShakeProfiling
163
185
, optTesting = argsTesting
186
+ , optInterfaceLoadingDiagnostics = argsTesting
164
187
}
165
188
debouncer <- newAsyncDebouncer
166
- initialise caps (cradleRules >> mainRule >> pluginRules plugins >> action kick)
189
+ initialise caps (mainRule >> pluginRules plugins >> action kick)
167
190
getLspId event hlsLogger debouncer options vfs
168
191
else do
169
192
-- GHC produces messages with UTF8 in them, so make sure the terminal doesn't error
@@ -184,44 +207,21 @@ main = do
184
207
let ucradles = nubOrd cradles
185
208
let n = length ucradles
186
209
putStrLn $ " Found " ++ show n ++ " cradle" ++ [' s' | n /= 1 ]
187
- sessions <- forM (zipFrom (1 :: Int ) ucradles) $ \ (i, x) -> do
188
- let msg = maybe (" Implicit cradle for " ++ dir) (" Loading " ++ ) x
189
- putStrLn $ " \n Step 3/6, Cradle " ++ show i ++ " /" ++ show n ++ " : " ++ msg
190
- cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle x
191
- when (isNothing x) $ print cradle
192
- putStrLn $ " \n Step 4/6, Cradle " ++ show i ++ " /" ++ show n ++ " : Loading GHC Session"
193
- opts <- getComponentOptions cradle
194
- createSession opts
195
-
196
- putStrLn " \n Step 5/6: Initializing the IDE"
210
+ putStrLn " \n Step 3/6: Initializing the IDE"
197
211
vfs <- makeVFSHandle
198
- let cradlesToSessions = Map. fromList $ zip ucradles sessions
199
- let filesToCradles = Map. fromList $ zip files cradles
200
- let grab file = fromMaybe (head sessions) $ do
201
- cradle <- Map. lookup file filesToCradles
202
- Map. lookup cradle cradlesToSessions
203
-
204
- let options =
205
- (defaultIdeOptions $ return $ return . grab)
206
- { optShakeProfiling = argsShakeProfiling }
207
- ide <- initialise def (cradleRules >> mainRule) (pure $ IdInt 0 ) (showEvent lock) (logger Info ) noopDebouncer options vfs
208
-
209
- putStrLn " \n Step 6/6: Type checking the files"
212
+ debouncer <- newAsyncDebouncer
213
+ -- let options =
214
+ -- (defaultIdeOptions $ return $ return . grab)
215
+ -- { optShakeProfiling = argsShakeProfiling }
216
+ let options = defaultIdeOptions $ loadSession dir
217
+ ide <- initialise def mainRule (pure $ IdInt 0 ) (showEvent lock) (logger Info ) debouncer options vfs
218
+
219
+ putStrLn " \n Step 4/6: Type checking the files"
210
220
setFilesOfInterest ide $ HashSet. fromList $ map toNormalizedFilePath files
211
- results <- runActionSync ide $ uses TypeCheck $ map toNormalizedFilePath files
212
- let (worked, failed) = partition fst $ zip (map isJust results) files
213
- when (failed /= [] ) $
214
- putStr $ unlines $ " Files that failed:" : map ((++) " * " . snd ) failed
215
-
216
- let files xs = let n = length xs in if n == 1 then " 1 file" else show n ++ " files"
217
- putStrLn $ " \n Completed (" ++ files worked ++ " worked, " ++ files failed ++ " failed)"
218
-
219
- unless (null failed) exitFailure
220
-
221
- cradleRules :: Rules ()
222
- cradleRules = do
223
- loadGhcSession
224
- cradleToSession
221
+ _ <- runActionSync ide $ uses TypeCheck (map toNormalizedFilePath files)
222
+ -- results <- runActionSync ide $ use TypeCheck $ toNormalizedFilePath "src/Development/IDE/Core/Rules.hs"
223
+ -- results <- runActionSync ide $ use TypeCheck $ toNormalizedFilePath "exe/Main.hs"
224
+ return ()
225
225
226
226
expandFiles :: [FilePath ] -> IO [FilePath ]
227
227
expandFiles = concatMapM $ \ x -> do
@@ -248,22 +248,189 @@ showEvent lock (EventFileDiagnostics (toNormalizedFilePath -> file) diags) =
248
248
withLock lock $ T. putStrLn $ showDiagnosticsColored $ map (file,ShowDiag ,) diags
249
249
showEvent lock e = withLock lock $ print e
250
250
251
+
252
+ cradleToSessionOpts :: Lock -> Cradle a -> FilePath -> IO ComponentOptions
253
+ cradleToSessionOpts lock cradle file = do
254
+ let showLine s = putStrLn (" > " ++ s)
255
+ cradleRes <- withLock lock $ mask $ \ _ -> runCradle (cradleOptsProg cradle) showLine file
256
+ opts <- case cradleRes of
257
+ CradleSuccess r -> pure r
258
+ CradleFail err -> throwIO err
259
+ -- TODO Rather than failing here, we should ignore any files that use this cradle.
260
+ -- That will require some more changes.
261
+ CradleNone -> fail " 'none' cradle is not yet supported"
262
+ pure opts
263
+
264
+ emptyHscEnv :: IO HscEnv
265
+ emptyHscEnv = do
266
+ libdir <- getLibdir
267
+ env <- runGhc (Just libdir) getSession
268
+ initDynLinker env
269
+ pure env
270
+
271
+ -- Convert a target to a list of potential absolute paths.
272
+ -- A TargetModule can be anywhere listed by the supplied include
273
+ -- directories
274
+ -- A target file is a relative path but with a specific prefix so just need
275
+ -- to canonicalise it.
276
+ targetToFile :: [FilePath ] -> TargetId -> IO [NormalizedFilePath ]
277
+ targetToFile is (TargetModule mod ) = do
278
+ let fps = [i </> (moduleNameSlashes mod ) -<.> ext | ext <- exts, i <- is ]
279
+ exts = [" hs" , " hs-boot" , " lhs" ]
280
+ mapM (fmap (toNormalizedFilePath) . canonicalizePath) fps
281
+ targetToFile _ (TargetFile f _) = do
282
+ f' <- canonicalizePath f
283
+ return [(toNormalizedFilePath f')]
284
+
285
+ setNameCache :: IORef NameCache -> HscEnv -> HscEnv
286
+ setNameCache nc hsc = hsc { hsc_NC = nc }
287
+
288
+ -- This is the key function which implements multi-component support. All
289
+ -- components mapping to the same hie,yaml file are mapped to the same
290
+ -- HscEnv which is updated as new components are discovered.
251
291
loadSession :: FilePath -> Action (FilePath -> Action HscEnvEq )
252
292
loadSession dir = liftIO $ do
293
+ -- Mapping from hie.yaml file to HscEnv, one per hie.yaml file
294
+ hscEnvs <- newVar Map. empty
295
+ -- Mapping from a filepath to HscEnv
296
+ fileToFlags <- newVar Map. empty
297
+ -- This caches the mapping from Mod.hs -> hie.yaml
253
298
cradleLoc <- memoIO $ \ v -> do
254
299
res <- findCradle v
255
300
-- Sometimes we get C:, sometimes we get c:, and sometimes we get a relative path
256
301
-- try and normalise that
257
302
-- e.g. see https://github.com/digital-asset/ghcide/issues/126
258
303
res' <- traverse IO. makeAbsolute res
259
304
return $ normalise <$> res'
260
- let session :: Maybe FilePath -> Action HscEnvEq
261
- session file = do
262
- -- In the absence of a cradle file, just pass the directory from where to calculate an implicit cradle
263
- let cradle = toNormalizedFilePath $ fromMaybe dir file
264
- use_ LoadCradle cradle
265
- return $ \ file -> session =<< liftIO (cradleLoc file)
266
305
306
+ -- Create a new HscEnv from a hieYaml root and a set of options
307
+ -- If the hieYaml file already has an HscEnv, the new component is
308
+ -- combined with the components in the old HscEnv into a new HscEnv
309
+ -- which contains both.
310
+ packageSetup <- return $ \ (hieYaml, opts) -> do
311
+ -- Parse DynFlags for the newly discovered component
312
+ hscEnv <- emptyHscEnv
313
+ (df, targets) <- evalGhcEnv hscEnv $ do
314
+ setOptions opts (hsc_dflags hscEnv)
315
+ -- Now lookup to see whether we are combining with an exisiting HscEnv
316
+ -- or making a new one. The lookup returns the HscEnv and a list of
317
+ -- information about other components loaded into the HscEnv
318
+ -- (unitId, DynFlag, Targets)
319
+ modifyVar hscEnvs $ \ m -> do
320
+ -- Just deps if there's already an HscEnv
321
+ -- Nothing is it's the first time we are making an HscEnv
322
+ let oldDeps = Map. lookup hieYaml m
323
+ let -- Add the raw information about this component to the list
324
+ -- We will modify the unitId and DynFlags used for
325
+ -- compilation but these are the true source of
326
+ -- information.
327
+ new_deps = (thisInstalledUnitId df, df, targets) : maybe [] snd oldDeps
328
+ -- Get all the unit-ids for things in this component
329
+ inplace = map (\ (a, _, _) -> a) new_deps
330
+ -- Remove all inplace dependencies from package flags for
331
+ -- components in this HscEnv
332
+ rearrange (uid, (df, uids), ts) = (uid, (df, uids, ts))
333
+ do_one (uid,df, ts) = rearrange (uid, removeInplacePackages inplace df, ts)
334
+ -- All deps, but without any packages which are also loaded
335
+ -- into memory
336
+ new_deps' = map do_one new_deps
337
+ -- Make a new HscEnv, we have to recompile everything from
338
+ -- scratch again (for now)
339
+ -- It's important to keep the same NameCache though for reasons
340
+ -- that I do not fully understand
341
+ print (" Making new HscEnv" ++ (show inplace))
342
+ hscEnv <- case oldDeps of
343
+ Nothing -> emptyHscEnv
344
+ Just (old_hsc, _) -> setNameCache (hsc_NC old_hsc) <$> emptyHscEnv
345
+ newHscEnv <-
346
+ -- Add the options for the current component to the HscEnv
347
+ evalGhcEnv hscEnv $ do
348
+ _ <- setSessionDynFlags df
349
+ getSession
350
+ -- Modify the map so the hieYaml now maps to the newly created
351
+ -- HscEnv
352
+ -- Returns
353
+ -- * the new HscEnv so it can be used to modify the
354
+ -- FilePath -> HscEnv map
355
+ -- * The information for the new component which caused this cache miss
356
+ -- * The modified information (without -inplace flags) for
357
+ -- existing packages
358
+ pure (Map. insert hieYaml (newHscEnv, new_deps) m, (newHscEnv, head new_deps', tail new_deps'))
359
+
360
+
361
+ session <- return $ \ (hieYaml, opts) -> do
362
+ (hscEnv, new, old_deps) <- packageSetup (hieYaml, opts)
363
+ -- TODO Handle the case where there is no hie.yaml
364
+ -- Make a map from unit-id to DynFlags, this is used when trying to
365
+ -- resolve imports.
366
+ let uids = map (\ (iuid, (df, _uis, _targets)) -> (iuid, df)) (new : old_deps)
367
+
368
+ -- For each component, now make a new HscEnvEq which contains the
369
+ -- HscEnv for the hie.yaml file but the DynFlags for that component
370
+ --
371
+ -- Then look at the targets for each component and create a map
372
+ -- from FilePath to the HscEnv
373
+ let new_cache (_iuid, (df, _uis, targets)) = do
374
+ let hscEnv' = hscEnv { hsc_dflags = df
375
+ , hsc_IC = (hsc_IC hscEnv) { ic_dflags = df } }
376
+
377
+ res <- newHscEnvEq hscEnv' uids
378
+
379
+ let is = importPaths df
380
+ ctargets <- concatMapM (targetToFile is . targetId) targets
381
+ -- pprTraceM "TARGETS" (ppr (map (text . show) ctargets))
382
+ let xs = map (,res) ctargets
383
+ return (xs, res)
384
+
385
+ -- New HscEnv for the component in question
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
+ modifyVar_ fileToFlags $ \ var -> do
391
+ pure $ Map. insert hieYaml (HM. fromList (cs ++ cached_targets))var
392
+ return res
393
+
394
+ lock <- newLock
395
+ cradle_lock <- newLock
396
+
397
+ -- This caches the mapping from hie.yaml + Mod.hs -> [String]
398
+ sessionOpts <- return $ \ (hieYaml, file) -> do
399
+ fm <- readVar fileToFlags
400
+ let mv = Map. lookup hieYaml fm
401
+ let v = fromMaybe HM. empty mv
402
+ cfp <- liftIO $ canonicalizePath file
403
+ -- We sort so exact matches come first.
404
+ case HM. lookup (toNormalizedFilePath cfp) v of
405
+ Just opts -> do
406
+ -- putStrLn $ "Cached component of " <> show file
407
+ pure opts
408
+ Nothing -> do
409
+ putStrLn $ " Shelling out to cabal " <> show file
410
+ cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle hieYaml
411
+ opts <- cradleToSessionOpts cradle_lock cradle file
412
+ print opts
413
+ session (hieYaml, opts)
414
+ return $ \ file -> liftIO $ withLock lock $ do
415
+ hieYaml <- cradleLoc file
416
+ sessionOpts (hieYaml, file)
417
+
418
+ -- This function removes all the -package flags which refer to packages we
419
+ -- are going to deal with ourselves. For example, if a executable depends
420
+ -- on a library component, then this function will remove the library flag
421
+ -- from the package flags for the executable
422
+ --
423
+ -- There are several places in GHC (for example the call to hptInstances in
424
+ -- tcRnImports) which assume that all modules in the HPT have the same unit
425
+ -- ID. Therefore we create a fake one and give them all the same unit id.
426
+ removeInplacePackages :: [InstalledUnitId ] -> DynFlags -> (DynFlags , [InstalledUnitId ])
427
+ removeInplacePackages us df = (df { packageFlags = ps
428
+ , thisInstalledUnitId = fake_uid }, uids)
429
+ where
430
+ (uids, ps) = partitionEithers (map go (packageFlags df))
431
+ fake_uid = toInstalledUnitId (stringToUnitId " fake_uid" )
432
+ go p@ (ExposePackage _ (UnitIdArg u) _) = if (toInstalledUnitId u `elem` us) then Left (toInstalledUnitId u) else Right p
433
+ go p = Right p
267
434
268
435
-- | Memoize an IO function, with the characteristics:
269
436
--
@@ -281,3 +448,58 @@ memoIO op = do
281
448
res <- onceFork $ op k
282
449
return (Map. insert k res mp, res)
283
450
Just res -> return (mp, res)
451
+
452
+ setOptions :: GhcMonad m => ComponentOptions -> DynFlags -> m (DynFlags , [Target ])
453
+ setOptions (ComponentOptions theOpts _) dflags = do
454
+ cacheDir <- liftIO $ getCacheDir theOpts
455
+ (dflags', targets) <- addCmdOpts theOpts dflags
456
+ let dflags'' =
457
+ -- disabled, generated directly by ghcide instead
458
+ flip gopt_unset Opt_WriteInterface $
459
+ -- disabled, generated directly by ghcide instead
460
+ -- also, it can confuse the interface stale check
461
+ dontWriteHieFiles $
462
+ setHiDir cacheDir $
463
+ setDefaultHieDir cacheDir $
464
+ setIgnoreInterfacePragmas $
465
+ setLinkerOptions $
466
+ disableOptimisation dflags'
467
+ -- initPackages parses the -package flags and
468
+ -- sets up the visibility for each component.
469
+ (final_df, _) <- liftIO $ initPackages dflags''
470
+ -- let df'' = gopt_unset df' Opt_WarnIsError
471
+ return (final_df, targets)
472
+
473
+
474
+ -- we don't want to generate object code so we compile to bytecode
475
+ -- (HscInterpreted) which implies LinkInMemory
476
+ -- HscInterpreted
477
+ setLinkerOptions :: DynFlags -> DynFlags
478
+ setLinkerOptions df = df {
479
+ ghcLink = LinkInMemory
480
+ , hscTarget = HscNothing
481
+ , ghcMode = CompManager
482
+ }
483
+
484
+ setIgnoreInterfacePragmas :: DynFlags -> DynFlags
485
+ setIgnoreInterfacePragmas df =
486
+ gopt_set (gopt_set df Opt_IgnoreInterfacePragmas ) Opt_IgnoreOptimChanges
487
+
488
+ disableOptimisation :: DynFlags -> DynFlags
489
+ disableOptimisation df = updOptLevel 0 df
490
+
491
+ setHiDir :: FilePath -> DynFlags -> DynFlags
492
+ setHiDir f d =
493
+ -- override user settings to avoid conflicts leading to recompilation
494
+ d { hiDir = Just f}
495
+
496
+ getCacheDir :: [String ] -> IO FilePath
497
+ getCacheDir opts = IO. getXdgDirectory IO. XdgCache (cacheDir </> opts_hash)
498
+ where
499
+ -- Create a unique folder per set of different GHC options, assuming that each different set of
500
+ -- GHC options will create incompatible interface files.
501
+ opts_hash = B. unpack $ encode $ H. finalize $ H. updates H. init (map B. pack opts)
502
+
503
+ -- Prefix for the cache path
504
+ cacheDir :: String
505
+ cacheDir = " ghcide"
0 commit comments