Skip to content

Allow users to specify whether to use cabal's multi-repl feature #4179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ packages:
./hls-plugin-api
./hls-test-utils

index-state: 2024-03-09T08:17:00Z
index-state: 2024-04-23T12:00:00Z

tests: True
test-show-details: direct
Expand Down
2 changes: 1 addition & 1 deletion ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ library
, Glob
, haddock-library >=1.8 && <1.12
, hashable
, hie-bios ==0.13.1
, hie-bios ^>=0.14.0
, hie-compat ^>=0.3.0.0
, hiedb ^>= 0.6.0.0
, hls-graph == 2.7.0.0
Expand Down
60 changes: 51 additions & 9 deletions ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import Control.Concurrent.Strict
import Control.Exception.Safe as Safe
import Control.Monad
import Control.Monad.Extra
import Control.Monad.Extra as Extra
import Control.Monad.IO.Class
import qualified Crypto.Hash.SHA1 as H
import Data.Aeson hiding (Error)
Expand All @@ -52,13 +52,13 @@
import Development.IDE.Core.Shake hiding (Log, Priority,
knownTargets, withHieDb)
import qualified Development.IDE.GHC.Compat as Compat
import qualified Development.IDE.GHC.Compat.Util as Compat
import Development.IDE.GHC.Compat.Core hiding (Target,
TargetFile, TargetModule,
Var, Warning, getOptions)
import qualified Development.IDE.GHC.Compat.Core as GHC
import Development.IDE.GHC.Compat.Env hiding (Logger)
import Development.IDE.GHC.Compat.Units (UnitId)
import qualified Development.IDE.GHC.Compat.Util as Compat
import Development.IDE.GHC.Util
import Development.IDE.Graph (Action)
import Development.IDE.Session.VersionCheck
Expand All @@ -70,6 +70,7 @@
import Development.IDE.Types.Options
import GHC.Check
import qualified HIE.Bios as HieBios
import qualified HIE.Bios.Cradle as HieBios
import HIE.Bios.Environment hiding (getCacheDir)
import HIE.Bios.Types hiding (Log)
import qualified HIE.Bios.Types as HieBios
Expand All @@ -80,6 +81,8 @@
nest,
toCologActionWithPrio,
vcat, viaShow, (<+>))
import Ide.Types (SessionLoadingPreferenceConfig (..),
sessionLoading)
import Language.LSP.Protocol.Message
import Language.LSP.Server
import System.Directory
Expand Down Expand Up @@ -123,7 +126,8 @@
import GHC.Driver.Env (hsc_all_home_unit_ids)
import GHC.Driver.Errors.Types
import GHC.Driver.Make (checkHomeUnitsClosed)
import GHC.Types.Error (errMsgDiagnostic, singleMessage)
import GHC.Types.Error (errMsgDiagnostic,
singleMessage)
import GHC.Unit.State
#endif

Expand All @@ -149,6 +153,7 @@
| LogNoneCradleFound FilePath
| LogNewComponentCache !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo)
| LogHieBios HieBios.Log
| LogSessionLoadingChanged
deriving instance Show Log

instance Pretty Log where
Expand Down Expand Up @@ -219,6 +224,8 @@
LogNewComponentCache componentCache ->
"New component cache HscEnvEq:" <+> viaShow componentCache
LogHieBios msg -> pretty msg
LogSessionLoadingChanged ->
"Session Loading config changed, reloading the full session."

-- | Bump this version number when making changes to the format of the data stored in hiedb
hiedbDataVersion :: String
Expand Down Expand Up @@ -449,6 +456,7 @@
filesMap <- newVar HM.empty :: IO (Var FilesMap)
-- Version of the mappings above
version <- newVar 0
biosSessionLoadingVar <- newVar Nothing :: IO (Var (Maybe SessionLoadingPreferenceConfig))
let returnWithVersion fun = IdeGhcSession fun <$> liftIO (readVar version)
-- This caches the mapping from Mod.hs -> hie.yaml
cradleLoc <- liftIO $ memoIO $ \v -> do
Expand All @@ -463,6 +471,7 @@
runningCradle <- newVar dummyAs :: IO (Var (Async (IdeResult HscEnvEq,[FilePath])))

return $ do
clientConfig <- getClientConfigAction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this actually works? This will get the client config at this point, but we consult it inside consultCradle which is an IO function that maybe runs later? So might we just always see a stale version of the config? I think we might need to get the config in IO inside consultCradle.

Copy link
Collaborator Author

@fendor fendor Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does work, in combination with invalidating the GhcSessionIO rule.

This new dependency seems to be working: https://github.com/haskell/haskell-language-server/pull/4179/files#diff-65a56362c2b3593800cd8369e6cfab1e92c347a9be97d1f0b8793f0e5dd9d524R711

And it doesn't invalidate the whole session on config changes.

extras@ShakeExtras{restartShakeSession, ideNc, knownTargetsVar, lspEnv
} <- getShakeExtras
let invalidateShakeCache :: IO ()
Expand Down Expand Up @@ -653,7 +662,7 @@
withTrace "Load cradle" $ \addTag -> do
addTag "file" lfp
old_files <- readIORef cradle_files
res <- cradleToOptsAndLibDir recorder cradle cfp old_files
res <- cradleToOptsAndLibDir recorder (sessionLoading clientConfig) cradle cfp old_files
addTag "result" (show res)
return res

Expand All @@ -669,7 +678,7 @@
InstallationMismatch{..} ->
return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[])
InstallationChecked _compileTime _ghcLibCheck -> do
atomicModifyIORef' cradle_files (\xs -> (cfp:xs,()))

Check warning on line 681 in ghcide/session-loader/Development/IDE/Session.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Warning in loadSessionWithOptions in module Development.IDE.Session: Use atomicModifyIORef'_ ▫︎ Found: "atomicModifyIORef' cradle_files (\\ xs -> (cfp : xs, ()))" ▫︎ Perhaps: "atomicModifyIORef'_ cradle_files ((:) cfp)"
session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
-- Failure case, either a cradle error or the none cradle
Left err -> do
Expand All @@ -681,11 +690,38 @@
void $ modifyVar' filesMap $ HM.insert ncfp hieYaml
return (res, maybe [] pure hieYaml ++ concatMap cradleErrorDependencies err)

let
-- | We allow users to specify a loading strategy.
-- Check whether this config was changed since the last time we have loaded
-- a session.
--
-- If the loading configuration changed, we likely should restart the session
-- in its entirety.
didSessionLoadingPreferenceConfigChange :: IO Bool
didSessionLoadingPreferenceConfigChange = do
mLoadingConfig <- readVar biosSessionLoadingVar
case mLoadingConfig of
Nothing -> do
writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))
pure False
Just loadingConfig -> do
writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))
pure (loadingConfig /= sessionLoading clientConfig)

-- This caches the mapping from hie.yaml + Mod.hs -> [String]
-- Returns the Ghc session and the cradle dependencies
let sessionOpts :: (Maybe FilePath, FilePath)
-> IO (IdeResult HscEnvEq, [FilePath])
sessionOpts (hieYaml, file) = do
Extra.whenM didSessionLoadingPreferenceConfigChange $ do
logWith recorder Info LogSessionLoadingChanged
-- If the dependencies are out of date then clear both caches and start
-- again.
modifyVar_ fileToFlags (const (return Map.empty))
modifyVar_ filesMap (const (return HM.empty))
-- Don't even keep the name cache, we start from scratch here!
modifyVar_ hscEnvs (const (return Map.empty))

v <- Map.findWithDefault HM.empty hieYaml <$> readVar fileToFlags
cfp <- makeAbsolute file
case HM.lookup (toNormalizedFilePath' cfp) v of
Expand All @@ -696,6 +732,7 @@
-- If the dependencies are out of date then clear both caches and start
-- again.
modifyVar_ fileToFlags (const (return Map.empty))
modifyVar_ filesMap (const (return HM.empty))
-- Keep the same name cache
modifyVar_ hscEnvs (return . Map.adjust (const []) hieYaml )
consultCradle hieYaml cfp
Expand All @@ -715,7 +752,7 @@
return (([renderPackageSetupException file e], Nothing), maybe [] pure hieYaml)

returnWithVersion $ \file -> do
opts <- liftIO $ join $ mask_ $ modifyVar runningCradle $ \as -> do
opts <- join $ mask_ $ modifyVar runningCradle $ \as -> do
-- If the cradle is not finished, then wait for it to finish.
void $ wait as
asyncRes <- async $ getOptions file
Expand All @@ -725,14 +762,14 @@
-- | Run the specific cradle on a specific FilePath via hie-bios.
-- This then builds dependencies or whatever based on the cradle, gets the
-- GHC options/dynflags needed for the session and the GHC library directory
cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> Cradle Void -> FilePath -> [FilePath]
cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> SessionLoadingPreferenceConfig -> Cradle Void -> FilePath -> [FilePath]
-> IO (Either [CradleError] (ComponentOptions, FilePath))
cradleToOptsAndLibDir recorder cradle file old_files = do
cradleToOptsAndLibDir recorder loadConfig cradle file old_fps = do
-- let noneCradleFoundMessage :: FilePath -> T.Text
-- noneCradleFoundMessage f = T.pack $ "none cradle found for " <> f <> ", ignoring the file"
-- Start off by getting the session options
logWith recorder Debug $ LogCradle cradle
cradleRes <- HieBios.getCompilerOptions file old_files cradle
cradleRes <- HieBios.getCompilerOptions file loadStyle cradle
case cradleRes of
CradleSuccess r -> do
-- Now get the GHC lib dir
Expand All @@ -750,6 +787,11 @@
logWith recorder Info $ LogNoneCradleFound file
return (Left [])

where
loadStyle = case loadConfig of
PreferSingleComponentLoading -> LoadFile
PreferMultiComponentLoading -> LoadWithContext old_fps

#if MIN_VERSION_ghc(9,3,0)
emptyHscEnv :: NameCache -> FilePath -> IO HscEnv
#else
Expand Down Expand Up @@ -1150,7 +1192,7 @@
-- component to be created. In case the cradle doesn't list all the targets for
-- the component, in which case things will be horribly broken anyway.
--
-- When we have a single component that is caused to be loaded due to a
-- When we have a singleComponent that is caused to be loaded due to a
-- file, we assume the file is part of that component. This is useful
-- for bare GHC sessions, such as many of the ones used in the testsuite
--
Expand Down
13 changes: 12 additions & 1 deletion ghcide/src/Development/IDE/Core/Rules.hs
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,20 @@
defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GhcSessionIO -> do
alwaysRerun
opts <- getIdeOptions
config <- getClientConfigAction
res <- optGhcSession opts

let fingerprint = LBS.toStrict $ B.encode $ hash (sessionVersion res)
let fingerprint = LBS.toStrict $ LBS.concat
[ B.encode (hash (sessionVersion res))
-- When the session version changes, reload all session
-- hsc env sessions
, B.encode (show (sessionLoading config))
-- The loading config affects session loading.
-- Invalidate all build nodes.
-- Changing the session loading config will increment
-- the 'sessionVersion', thus we don't generate the same fingerprint
-- twice by accident.
]
return (fingerprint, res)

defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GhcSession file -> do
Expand Down Expand Up @@ -812,7 +823,7 @@
{ source_version = ver
, old_value = m_old
, get_file_version = use GetModificationTime_{missingFileDiagnostics = False}
, get_linkable_hashes = \fs -> map (snd . fromJust . hirCoreFp) <$> uses_ GetModIface fs

Check warning on line 826 in ghcide/src/Development/IDE/Core/Rules.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Suggestion in getModIfaceFromDiskRule in module Development.IDE.Core.Rules: Use fmap ▫︎ Found: "\\ fs -> map (snd . fromJust . hirCoreFp) <$> uses_ GetModIface fs" ▫︎ Perhaps: "fmap (map (snd . fromJust . hirCoreFp)) . uses_ GetModIface"
, regenerate = regenerateHiFile session f ms
}
r <- loadInterface (hscEnv session) ms linkableType recompInfo
Expand Down Expand Up @@ -1084,7 +1095,7 @@
-- thus bump its modification time, forcing this rule to be rerun every time.
exists <- liftIO $ doesFileExist obj_file
mobj_time <- liftIO $
if exists

Check warning on line 1098 in ghcide/src/Development/IDE/Core/Rules.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Warning in getLinkableRule in module Development.IDE.Core.Rules: Use whenMaybe ▫︎ Found: "if exists then Just <$> getModTime obj_file else pure Nothing" ▫︎ Perhaps: "whenMaybe exists (getModTime obj_file)"
then Just <$> getModTime obj_file
else pure Nothing
case mobj_time of
Expand Down
2 changes: 1 addition & 1 deletion haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ test-suite hls-stan-plugin-tests
, lens
, lsp-types
, text
default-extensions:
default-extensions:
OverloadedStrings

-----------------------------
Expand Down
1 change: 1 addition & 0 deletions hls-plugin-api/src/Ide/Plugin/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ parseConfig idePlugins defValue = A.withObject "settings" $ \o ->
<*> o .:? "formattingProvider" .!= formattingProvider defValue
<*> o .:? "cabalFormattingProvider" .!= cabalFormattingProvider defValue
<*> o .:? "maxCompletions" .!= maxCompletions defValue
<*> o .:? "sessionLoading" .!= sessionLoading defValue
<*> A.explicitParseFieldMaybe (parsePlugins idePlugins) o "plugin" .!= plugins defValue

-- | Parse the 'PluginConfig'.
Expand Down
39 changes: 38 additions & 1 deletion hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module Ide.Types
, IdeNotification(..)
, IdePlugins(IdePlugins, ipMap)
, DynFlagsModifications(..)
, Config(..), PluginConfig(..), CheckParents(..)
, Config(..), PluginConfig(..), CheckParents(..), SessionLoadingPreferenceConfig(..)
, ConfigDescriptor(..), defaultConfigDescriptor, configForPlugin
, CustomConfig(..), mkCustomConfig
, FallbackCodeActionParams(..)
Expand Down Expand Up @@ -65,6 +65,7 @@ import Control.Monad.Error.Class (MonadError (throwError))
import Control.Monad.Trans.Class (MonadTrans (lift))
import Control.Monad.Trans.Except (ExceptT, runExceptT)
import Data.Aeson hiding (Null, defaultOptions)
import qualified Data.Aeson.Types as A
import Data.Default
import Data.Dependent.Map (DMap)
import qualified Data.Dependent.Map as DMap
Expand Down Expand Up @@ -170,6 +171,7 @@ data Config =
, formattingProvider :: !T.Text
, cabalFormattingProvider :: !T.Text
, maxCompletions :: !Int
, sessionLoading :: !SessionLoadingPreferenceConfig
, plugins :: !(Map.Map PluginId PluginConfig)
} deriving (Show,Eq)

Expand All @@ -180,6 +182,7 @@ instance ToJSON Config where
, "formattingProvider" .= formattingProvider
, "cabalFormattingProvider" .= cabalFormattingProvider
, "maxCompletions" .= maxCompletions
, "sessionLoading" .= sessionLoading
, "plugin" .= Map.mapKeysMonotonic (\(PluginId p) -> p) plugins
]

Expand All @@ -194,6 +197,7 @@ instance Default Config where
-- , cabalFormattingProvider = "cabal-fmt"
-- this string value needs to kept in sync with the value provided in HlsPlugins
, maxCompletions = 40
, sessionLoading = PreferSingleComponentLoading
, plugins = mempty
}

Expand All @@ -206,6 +210,39 @@ data CheckParents
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (FromJSON, ToJSON)


data SessionLoadingPreferenceConfig
= PreferSingleComponentLoading
-- ^ Always load only a singleComponent when a new component
-- is discovered.
| PreferMultiComponentLoading
-- ^ Always prefer loading multiple components in the cradle
-- at once. This might not be always possible, if the tool doesn't
-- support multiple components loading.
--
-- The cradle can decide how to handle these situations, and whether
-- to honour the preference at all.
deriving stock (Eq, Ord, Show, Generic)

instance Pretty SessionLoadingPreferenceConfig where
pretty PreferSingleComponentLoading = "Prefer Single Component Loading"
pretty PreferMultiComponentLoading = "Prefer Multiple Components Loading"

instance ToJSON SessionLoadingPreferenceConfig where
toJSON PreferSingleComponentLoading =
String "singleComponent"
toJSON PreferMultiComponentLoading =
String "multipleComponents"

instance FromJSON SessionLoadingPreferenceConfig where
parseJSON (String val) = case val of
"singleComponent" -> pure PreferSingleComponentLoading
"multipleComponents" -> pure PreferMultiComponentLoading
_ -> A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.parseFail $ "Expected one of \"singleComponent\" or \"multipleComponents\" but got " <> T.unpack val )
parseJSON o = A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.typeMismatch "String" o)

-- | A PluginConfig is a generic configuration for a given HLS plugin. It
-- provides a "big switch" to turn it on or off as a whole, as well as small
-- switches per feature, and a slot for custom config.
Expand Down
2 changes: 1 addition & 1 deletion stack-lts21.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ allow-newer: true
extra-deps:
- floskell-0.11.1
- hiedb-0.6.0.0
- hie-bios-0.13.1
- hie-bios-0.14.0
- implicit-hie-0.1.4.0
- monad-dijkstra-0.1.1.3
- retrie-1.2.2
Expand Down
1 change: 1 addition & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extra-deps:
- floskell-0.11.1
- retrie-1.2.2
- hiedb-0.6.0.0
- hie-bios-0.14.0
- implicit-hie-0.1.4.0
- lsp-2.4.0.0
- lsp-test-0.17.0.0
Expand Down
3 changes: 2 additions & 1 deletion test/testdata/schema/ghc92/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,6 @@
"splice": {
"globalOn": true
}
}
},
"sessionLoading": "singleComponent"
}
3 changes: 2 additions & 1 deletion test/testdata/schema/ghc94/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,6 @@
"stan": {
"globalOn": false
}
}
},
"sessionLoading": "singleComponent"
}
3 changes: 2 additions & 1 deletion test/testdata/schema/ghc96/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,6 @@
"stan": {
"globalOn": false
}
}
},
"sessionLoading": "singleComponent"
}
3 changes: 2 additions & 1 deletion test/testdata/schema/ghc98/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,6 @@
"stan": {
"globalOn": false
}
}
},
"sessionLoading": "singleComponent"
}
Loading