Clean up the IDE (#961)

* Split the Options into a separate module

* Make the Logger handle live in IO

* Reduce the amount of IDE logging to just two

* Rename CompileOpts to IdeOptions

* Rename PackageState to PackageDynFlags

* Clean up setting the PackageDynFlags

* Stop hiding PackageState, we no longer clash on it

* Introduce a helper for collecting the package flags

* Move the StringBuffer conversion to its only use
This commit is contained in:
Neil Mitchell 2019-05-07 10:19:12 +01:00 committed by GitHub
parent c0e9c90429
commit 1c27ffe760
9 changed files with 116 additions and 109 deletions

View File

@ -8,9 +8,7 @@
-- | Based on https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/API.
-- Given a list of paths to find libraries, and a file to compile, produce a list of 'CoreModule' values.
module Development.IDE.Functions.Compile
( CompileOpts(..)
, PackageState(..)
, GhcModule(..)
( GhcModule(..)
, TcModuleResult(..)
, LoadPackageResult(..)
, getGhcDynFlags
@ -29,6 +27,7 @@ import qualified Development.IDE.Functions.FindImports as FindImports
import Development.IDE.Functions.GHCError
import Development.IDE.Functions.SpanInfo
import Development.IDE.UtilGHC
import Development.IDE.Types.Options
import GHC hiding (parseModule, typecheckModule)
import qualified Parser
@ -38,7 +37,7 @@ import Bag
import qualified GHC
import Panic
import GhcMonad
import GhcPlugins as GHC hiding (PackageState, fst3, (<>))
import GhcPlugins as GHC hiding (fst3, (<>))
import qualified HeaderInfo as Hdr
import MkIface
import NameCache
@ -61,24 +60,6 @@ import Development.IDE.Types.SpanInfo
import GHC.Generics (Generic)
import System.FilePath
-- TODO (MK) Move to a separate Options module
data CompileOpts = CompileOpts
{ optPreprocessor :: GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource)
, optRunGhcSession :: forall a. Maybe ParsedModule -> PackageState -> Ghc a -> IO a
-- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
-- the import path should be setup for that module.
, optWriteIface :: Bool
, optMbPackageName :: Maybe String
, optPackageDbs :: [FilePath]
, optHideAllPkgs :: Bool
, optPackageImports :: [(String, ModRenaming)]
, optThreads :: Int
, optShakeProfiling :: Maybe FilePath
}
-- | 'CoreModule' together with some additional information required for the
-- conversion to DAML-LF.
data GhcModule = GhcModule
@ -106,9 +87,9 @@ data LoadPackageResult = LoadPackageResult
-- | Get source span info, used for e.g. AtPoint and Goto Definition.
getSrcSpanInfos
:: CompileOpts
:: IdeOptions
-> ParsedModule
-> PackageState
-> PackageDynFlags
-> [(Located ModuleName, Maybe FilePath)]
-> TcModuleResult
-> IO [SpanInfo]
@ -120,18 +101,18 @@ getSrcSpanInfos opt mod packageState imports tc =
-- | Given a string buffer, return a pre-processed @ParsedModule@.
parseModule
:: CompileOpts
-> PackageState
:: IdeOptions
-> PackageDynFlags
-> FilePath
-> (UTCTime, SB.StringBuffer)
-> IO ([FileDiagnostic], Maybe ParsedModule)
parseModule opt@CompileOpts{..} packageState file =
parseModule opt@IdeOptions{..} packageState file =
fmap (either (, Nothing) (second Just)) . Ex.runExceptT .
-- We need packages since imports fail to resolve otherwise.
runGhcSessionExcept opt Nothing packageState . parseFileContents optPreprocessor file
computePackageDeps ::
CompileOpts -> PackageState -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
IdeOptions -> PackageDynFlags -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
computePackageDeps opts packageState iuid =
Ex.runExceptT $
runGhcSessionExcept opts Nothing packageState $
@ -149,9 +130,9 @@ getPackage dflags p =
-- | Typecheck a single module using the supplied dependencies and packages.
typecheckModule
:: CompileOpts
:: IdeOptions
-> ParsedModule
-> PackageState
-> PackageDynFlags
-> UniqSupply
-> [TcModuleResult]
-> [LoadPackageResult]
@ -169,8 +150,8 @@ typecheckModule opt mod packageState uniqSupply deps pkgs pm =
-- | Load a pkg and populate the name cache and external package state.
loadPackage ::
CompileOpts
-> PackageState
IdeOptions
-> PackageDynFlags
-> UniqSupply
-> [LoadPackageResult]
-> InstalledUnitId
@ -196,9 +177,9 @@ loadPackage opt packageState us lps p =
-- | Compile a single type-checked module to a 'CoreModule' value, or
-- provide errors.
compileModule
:: CompileOpts
:: IdeOptions
-> ParsedModule
-> PackageState
-> PackageDynFlags
-> UniqSupply
-> [TcModuleResult]
-> [LoadPackageResult]
@ -233,27 +214,27 @@ compileModule opt mod packageState uniqSupply deps pkgs tmr =
-- | Evaluate a GHC session using a new environment constructed with
-- the supplied options.
runGhcSessionExcept
:: CompileOpts
:: IdeOptions
-> Maybe ParsedModule
-> PackageState
-> PackageDynFlags
-> Ex.ExceptT e Ghc a
-> Ex.ExceptT e IO a
runGhcSessionExcept opts mbMod pkg m =
Ex.ExceptT $ runGhcSession opts mbMod pkg $ Ex.runExceptT m
getGhcDynFlags :: CompileOpts -> ParsedModule -> PackageState -> IO DynFlags
getGhcDynFlags :: IdeOptions -> ParsedModule -> PackageDynFlags -> IO DynFlags
getGhcDynFlags opts mod pkg = runGhcSession opts (Just mod) pkg getSessionDynFlags
-- | Evaluate a GHC session using a new environment constructed with
-- the supplied options.
runGhcSession
:: CompileOpts
:: IdeOptions
-> Maybe ParsedModule
-> PackageState
-> PackageDynFlags
-> Ghc a
-> IO a
runGhcSession CompileOpts{..} = optRunGhcSession
runGhcSession IdeOptions{..} = optRunGhcSession
-- When we make a fresh GHC environment, the OrigNameCache comes already partially
-- populated. So to be safe, we simply extend this one.
@ -496,11 +477,11 @@ parsePragmasIntoDynFlags fp contents = catchSrcErrors $ do
(dflags, _, _) <- parseDynamicFilePragma dflags0 opts
return dflags
generatePackageState :: [FilePath] -> Bool -> [(String, ModRenaming)] -> IO PackageState
generatePackageState :: [FilePath] -> Bool -> [(String, ModRenaming)] -> IO PackageDynFlags
generatePackageState paths hideAllPkgs pkgImports = do
let dflags = setPackageImports hideAllPkgs pkgImports $ setPackageDbs paths (defaultDynFlags fakeSettings fakeLlvmConfig)
(newDynFlags, _) <- initPackages dflags
pure $ PackageState (pkgDatabase newDynFlags) (pkgState newDynFlags) (thisUnitIdInsts_ newDynFlags)
pure $ getPackageDynFlags newDynFlags
-- | Run something in a Ghc monad and catch the errors (SourceErrors and
-- compiler-internal exceptions like Panic or InstallationError).

View File

@ -13,13 +13,11 @@ module Development.IDE.Logger
import qualified Data.Text as T
import GHC.Stack
data Handle m = Handle {
logError :: HasCallStack => T.Text -> m ()
, logWarning :: HasCallStack => T.Text -> m ()
, logInfo :: HasCallStack => T.Text -> m ()
, logDebug :: HasCallStack => T.Text -> m ()
data Handle = Handle {
logSeriousError :: HasCallStack => T.Text -> IO ()
, logDebug :: HasCallStack => T.Text -> IO ()
}
makeNopHandle :: Monad m => Handle m
makeNopHandle = Handle e e e e where
makeNopHandle :: Handle
makeNopHandle = Handle e e where
e _ = pure ()

View File

@ -12,6 +12,7 @@ module Development.IDE.State.FileStore(
import StringBuffer
import Development.IDE.UtilGHC()
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
@ -21,12 +22,12 @@ import qualified System.Directory as Dir
import Development.Shake
import Development.Shake.Classes
import Development.IDE.State.Shake
import Development.IDE.UtilGHC
import Control.Concurrent.Extra
import Control.Exception
import GHC.Generics
import System.IO.Error
import qualified Data.ByteString.Char8 as BS
import qualified StringBuffer as SB
import Development.IDE.Types.Diagnostics
import Data.Time
@ -141,7 +142,13 @@ setBufferModified state absFile (mcontents, !time) = do
-- update vars synchronously
modifyVar_ envDirtyFiles $ evaluate . case mcontents of
Nothing -> Map.delete absFile
Just contents -> Map.insert absFile $ strictPair time (textToStringBuffer contents)
Just contents -> Map.insert absFile $ strictPair time $ textToStringBuffer contents
-- run shake to update results regarding the files of interest
void $ shakeRun state []
-- would be nice to do this more efficiently...
textToStringBuffer :: T.Text -> SB.StringBuffer
-- would be nice to do this more efficiently...
textToStringBuffer = SB.stringToStringBuffer . T.unpack

View File

@ -15,6 +15,7 @@ module Development.IDE.State.RuleTypes(
import Control.DeepSeq
import Development.IDE.Functions.Compile (TcModuleResult, GhcModule, LoadPackageResult(..))
import qualified Development.IDE.Functions.Compile as Compile
import qualified Development.IDE.UtilGHC as Compile
import Development.IDE.Functions.FindImports (Import(..))
import Development.IDE.Functions.DependencyInformation
import Data.Hashable
@ -61,7 +62,7 @@ type instance RuleResult GenerateCore = GhcModule
-- | We capture the subset of `DynFlags` that is computed by package initialization in a rule to
-- make session initialization cheaper by reusing it.
type instance RuleResult LoadPackageState = Compile.PackageState
type instance RuleResult LoadPackageState = Compile.PackageDynFlags
-- | Resolve the imports in a module to the list of either external packages or absolute file paths
-- for modules in the same package.

View File

@ -28,6 +28,7 @@ import Control.Exception (evaluate)
import Control.Monad.Except
import Control.Monad.Extra (whenJust)
import qualified Development.IDE.Functions.Compile as Compile
import qualified Development.IDE.Types.Options as Compile
import Development.IDE.Functions.DependencyInformation
import Development.IDE.Functions.FindImports
import Development.IDE.State.FileStore
@ -152,7 +153,7 @@ getDefinitionForFile file pos = do
spans <- useE GetSpanInfo file
return $ AtPoint.gotoDefinition spans pos
getOpts :: Action Compile.CompileOpts
getOpts :: Action Compile.IdeOptions
getOpts = envOptions <$> getServiceEnv
------------------------------------------------------------

View File

@ -16,18 +16,17 @@ module Development.IDE.State.Service(
setFilesOfInterest,
writeProfile,
getDiagnostics, unsafeClearDiagnostics,
logDebug, logInfo, logWarning, logError
logDebug, logSeriousError
) where
import Control.Concurrent.Extra
import Control.Monad.Except
import Development.IDE.Functions.Compile (CompileOpts(..))
import Development.IDE.Types.Options (IdeOptions(..))
import Development.IDE.State.FileStore
import qualified Development.IDE.Logger as Logger
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text as T
import Development.IDE.Functions.GHCError
import Development.Shake hiding (Diagnostic, Env, newCache)
import Development.IDE.Types.LSP as Compiler
@ -39,7 +38,7 @@ import Development.IDE.State.Shake
-- | Environment threaded through the Shake actions.
data Env = Env
{ envOptions :: CompileOpts
{ envOptions :: IdeOptions
-- ^ Compiler options.
, envOfInterestVar :: Var (Set FilePath)
-- ^ The files of interest.
@ -49,7 +48,7 @@ data Env = Env
instance IsIdeGlobal Env
mkEnv :: CompileOpts -> IO Env
mkEnv :: IdeOptions -> IO Env
mkEnv options = do
ofInterestVar <- newVar Set.empty
uniqSupplyVar <- mkSplitUniqSupply 'a' >>= newVar
@ -72,8 +71,8 @@ unsafeClearDiagnostics = unsafeClearAllDiagnostics
-- | Initialise the Compiler Service.
initialise :: Rules ()
-> Maybe (Event -> IO ())
-> Logger.Handle IO
-> CompileOpts
-> Logger.Handle
-> IdeOptions
-> IO IdeState
initialise mainRule toDiags logger options =
shakeOpen
@ -90,7 +89,7 @@ initialise mainRule toDiags logger options =
writeProfile :: IdeState -> FilePath -> IO ()
writeProfile = shakeProfile
setProfiling :: CompileOpts -> ShakeOptions -> ShakeOptions
setProfiling :: IdeOptions -> ShakeOptions -> ShakeOptions
setProfiling opts shakeOpts =
maybe shakeOpts (\p -> shakeOpts { shakeReport = [p], shakeTimings = True }) (optShakeProfiling opts)
@ -119,9 +118,3 @@ setFilesOfInterest state files = do
getServiceEnv :: Action Env
getServiceEnv = getIdeGlobalAction
logDebug, logInfo, logWarning, logError :: IdeState -> T.Text -> IO ()
logDebug = shakeLogDebug
logInfo = shakeLogInfo
logWarning = shakeLogWarning
logError = shakeLogError

View File

@ -38,10 +38,8 @@ module Development.IDE.State.Shake(
garbageCollect,
setPriority,
sendEvent,
shakeLogDebug,
shakeLogInfo,
shakeLogWarning,
shakeLogError,
Development.IDE.State.Shake.logDebug,
Development.IDE.State.Shake.logSeriousError,
) where
import Development.Shake
@ -78,7 +76,7 @@ import Numeric.Extra
-- information we stash inside the shakeExtra field
data ShakeExtras = ShakeExtras
{eventer :: Event -> IO ()
,logger :: Logger.Handle IO
,logger :: Logger.Handle
,globals :: Var (Map.HashMap TypeRep Dynamic)
,state :: Var Values
}
@ -221,7 +219,7 @@ getValues state key file = do
-- | Open a 'IdeState', should be shut using 'shakeShut'.
shakeOpen :: (Event -> IO ()) -- ^ diagnostic handler
-> Logger.Handle IO
-> Logger.Handle
-> ShakeOptions
-> Rules ()
-> IO IdeState
@ -245,13 +243,13 @@ shakeRun :: IdeState -> [Action a] -> IO (IO [a])
-- not even start, which would make issues with async exceptions less problematic.
shakeRun IdeState{shakeExtras=ShakeExtras{..}, ..} acts = modifyVar shakeAbort $ \stop -> do
(stopTime,_) <- duration stop
Logger.logInfo logger $ T.pack $ "Starting shakeRun (aborting the previous one took " ++ showDuration stopTime ++ ")"
Logger.logDebug logger $ T.pack $ "Starting shakeRun (aborting the previous one took " ++ showDuration stopTime ++ ")"
bar <- newBarrier
start <- offsetTime
thread <- forkFinally (shakeRunDatabaseProfile shakeDb acts) $ \res -> do
signalBarrier bar res
runTime <- start
Logger.logInfo logger $ T.pack $
Logger.logDebug logger $ T.pack $
"Finishing shakeRun (took " ++ showDuration runTime ++ ", " ++ (if isLeft res then "exception" else "completed") ++ ")"
-- important: we send an async exception to the thread, then wait for it to die, before continuing
return (do killThread thread; void $ waitBarrier bar, either throwIO return =<< waitBarrier bar)
@ -302,12 +300,12 @@ uses_ key files = do
reportSeriousError :: String -> Action ()
reportSeriousError t = do
ShakeExtras{logger} <- getShakeExtras
liftIO $ Logger.logError logger $ T.pack t
liftIO $ Logger.logSeriousError logger $ T.pack t
reportSeriousErrorDie :: String -> Action a
reportSeriousErrorDie t = do
ShakeExtras{logger} <- getShakeExtras
liftIO $ Logger.logError logger $ T.pack t
liftIO $ Logger.logSeriousError logger $ T.pack t
fail t
@ -419,12 +417,10 @@ sendEvent e = do
liftIO $ eventer e
-- | bit of an odd signature because we're trying to remove priority
sl :: (Handle IO -> T.Text -> IO ()) -> IdeState -> T.Text -> IO ()
sl :: (Handle -> T.Text -> IO ()) -> IdeState -> T.Text -> IO ()
sl f IdeState{shakeExtras=ShakeExtras{logger}} p = f logger p
shakeLogDebug, shakeLogInfo, shakeLogWarning, shakeLogError
logDebug, logSeriousError
:: IdeState -> T.Text -> IO ()
shakeLogDebug = sl logDebug
shakeLogInfo = sl logInfo
shakeLogWarning = sl logWarning
shakeLogError = sl logError
logDebug = sl Logger.logDebug
logSeriousError = sl Logger.logSeriousError

View File

@ -0,0 +1,31 @@
-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE RankNTypes #-}
-- | Options
module Development.IDE.Types.Options
( IdeOptions(..)
) where
import Development.IDE.UtilGHC
import GHC hiding (parseModule, typecheckModule)
import GhcPlugins as GHC hiding (fst3, (<>))
data IdeOptions = IdeOptions
{ optPreprocessor :: GHC.ParsedSource -> ([(GHC.SrcSpan, String)], GHC.ParsedSource)
, optRunGhcSession :: forall a. Maybe ParsedModule -> PackageDynFlags -> Ghc a -> IO a
-- ^ Setup a GHC session using a given package state. If a `ParsedModule` is supplied,
-- the import path should be setup for that module.
, optWriteIface :: Bool
, optMbPackageName :: Maybe String
, optPackageDbs :: [FilePath]
, optHideAllPkgs :: Bool
, optPackageImports :: [(String, ModRenaming)]
, optThreads :: Int
, optShakeProfiling :: Maybe FilePath
}

View File

@ -12,9 +12,8 @@
--
-- * Call setSessionDynFlags, use modifyDynFlags instead. It's faster and avoids loading packages.
module Development.IDE.UtilGHC(
PackageState(..),
PackageDynFlags(..), setPackageDynFlags, getPackageDynFlags,
modifyDynFlags,
textToStringBuffer,
removeTypeableInfo,
setPackageImports,
setPackageDbs,
@ -25,7 +24,6 @@ module Development.IDE.UtilGHC(
mkImport,
runGhcFast,
setImports,
setPackageState,
setThisInstalledUnitId,
modIsInternal
) where
@ -34,18 +32,17 @@ import Config
import Fingerprint
import GHC hiding (convertLit)
import GhcMonad
import GhcPlugins as GHC hiding (PackageState, fst3, (<>))
import GhcPlugins as GHC hiding (fst3, (<>))
import HscMain
import qualified Packages
import Platform
import qualified StringBuffer as SB
import qualified EnumSet
import Control.DeepSeq
import Data.IORef
import Data.List
import qualified Data.Text as T
import GHC.Generics (Generic)
import qualified StringBuffer as SB
----------------------------------------------------------------------
-- GHC setup
@ -82,15 +79,29 @@ modifyDynFlags f = do
modifySession $ \h ->
h { hsc_dflags = newFlags, hsc_IC = (hsc_IC h) {ic_dflags = newFlags} }
-- | This is the subset of `DynFlags` that is computed by package initialization.
data PackageState = PackageState
{ pkgStateDb :: !(Maybe [(FilePath, [Packages.PackageConfig])])
, pkgStateState :: !Packages.PackageState
, pkgThisUnitIdInsts :: !(Maybe [(ModuleName, Module)])
} deriving (Generic, Show)
-- | The subset of @DynFlags@ computed by package initialization.
data PackageDynFlags = PackageDynFlags
{ pdfPkgDatabase :: !(Maybe [(FilePath, [Packages.PackageConfig])])
, pdfPkgState :: !Packages.PackageState
, pdfThisUnitIdInsts :: !(Maybe [(ModuleName, Module)])
} deriving (Generic, Show)
instance NFData PackageState where
rnf (PackageState db state insts) = db `seq` state `seq` rnf insts
instance NFData PackageDynFlags where
rnf (PackageDynFlags db state insts) = db `seq` state `seq` rnf insts
setPackageDynFlags :: PackageDynFlags -> DynFlags -> DynFlags
setPackageDynFlags PackageDynFlags{..} dflags = dflags
{ pkgDatabase = pdfPkgDatabase
, pkgState = pdfPkgState
, thisUnitIdInsts_ = pdfThisUnitIdInsts
}
getPackageDynFlags :: DynFlags -> PackageDynFlags
getPackageDynFlags DynFlags{..} = PackageDynFlags
{ pdfPkgDatabase = pkgDatabase
, pdfPkgState = pkgState
, pdfThisUnitIdInsts = thisUnitIdInsts_
}
-- | A version of `showSDoc` that uses default flags (to avoid uses of
@ -102,10 +113,6 @@ showSDocDefault = showSDoc dynFlags
prettyPrint :: Outputable a => a -> String
prettyPrint = showSDocDefault . ppr
textToStringBuffer :: T.Text -> SB.StringBuffer
-- would be nice to do this more efficiently...
textToStringBuffer = SB.stringToStringBuffer . T.unpack
-- FIXME(#1203): This must move out of `haskell-ide-core` and into `damlc`.
internalModules :: [String]
internalModules =
@ -177,14 +184,6 @@ setThisInstalledUnitId unitId dflags =
setImports :: [FilePath] -> DynFlags -> DynFlags
setImports paths dflags = dflags { importPaths = paths }
setPackageState :: PackageState -> DynFlags -> DynFlags
setPackageState state dflags =
dflags
{ pkgDatabase = pkgStateDb state
, pkgState = pkgStateState state
, thisUnitIdInsts_ = pkgThisUnitIdInsts state
}
-- Orphan instances for types from the GHC API.