module HsDev.Tools.Ghc.Worker (
GhcM(..), runGhcM, liftGhc,
ghcWorker, ghciWorker,
ghcRun,
withFlags, modifyFlags, addCmdOpts, setCmdOpts,
importModules, preludeModules,
evaluate,
clearTargets, makeTarget, loadTargets,
listPackages, spanRegion,
withCurrentDirectory,
logToChan, logToNull,
Ghc,
module Control.Concurrent.Worker
) where
import Control.Monad
import Control.Monad.Catch
import Control.Monad.CatchIO
import Control.Monad.Except
import Control.Monad.Reader
import Data.Dynamic
import Data.Maybe
import Data.Time.Clock (getCurrentTime)
import Data.Version (showVersion)
import Packages
import StringBuffer
import System.Directory (getCurrentDirectory, setCurrentDirectory)
import qualified System.Log.Simple as Log
import System.Log.Simple.Monad (MonadLog(..))
import Text.Read (readMaybe)
import Text.Format
import Exception (ExceptionMonad(..))
import GHC hiding (Warning, Module, moduleName)
import GhcMonad (Ghc(..))
import GHC.Paths
import DynFlags (HasDynFlags(..))
import Outputable
import qualified ErrUtils as E
import FastString (unpackFS)
import Control.Concurrent.FiniteChan
import Control.Concurrent.Worker
import System.Directory.Paths
import HsDev.Symbols.Location (Position(..), Region(..), region, ModulePackage, ModuleLocation(..))
import HsDev.Tools.Types
instance MonadThrow Ghc where
throwM = liftIO . throwM
instance MonadCatch Ghc where
catch = gcatch
instance MonadCatchIO Ghc where
catch = gcatch
block act = Ghc $ block . unGhc act
unblock act = Ghc $ unblock . unGhc act
instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where
gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (flip runReaderT v . onError)
gmask f = ReaderT $ \v -> gmask (\h -> flip runReaderT v (f $ \act -> ReaderT (\v' -> h (runReaderT act v'))))
instance (Monad m, HasDynFlags m) => HasDynFlags (ReaderT r m) where
getDynFlags = lift getDynFlags
instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where
getSession = lift getSession
setSession = lift . setSession
newtype GhcM a = GhcM { unGhcM :: ReaderT Log.Log Ghc a }
deriving (Functor, Applicative, Monad, MonadIO, MonadCatchIO, MonadThrow, MonadCatch, ExceptionMonad, HasDynFlags, MonadLog, GhcMonad)
runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a
runGhcM dir act = do
l <- askLog
liftIO $ runGhc dir (runReaderT (unGhcM act) l)
liftGhc :: Ghc a -> GhcM a
liftGhc = GhcM . lift
ghcWorker :: MonadLog m => [String] -> GhcM () -> m (Worker GhcM)
ghcWorker opts initialize = do
l <- askLog
liftIO $ startWorker (flip runReaderT l . runGhcM (Just libdir)) (ghcRun opts . (initialize >>)) (Log.scope "ghc")
ghciWorker :: MonadLog m => m (Worker GhcM)
ghciWorker = do
l <- askLog
liftIO $ startWorker (flip runReaderT l . runGhcM (Just libdir)) (ghcRun [] . (importModules preludeModules >>)) (Log.scope "ghc")
ghcRun :: GhcMonad m => [String] -> m a -> m a
ghcRun opts f = do
fs <- getSessionDynFlags
defaultCleanupHandler fs $ do
(fs', _, _) <- parseDynamicFlags fs (map noLoc opts)
let fs'' = fs' {
ghcMode = CompManager,
ghcLink = LinkInMemory,
hscTarget = HscInterpreted }
void $ setSessionDynFlags fs''
f
withFlags :: GhcMonad m => m a -> m a
withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const
modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m ()
modifyFlags f = do
fs <- getSessionDynFlags
let
fs' = f fs
_ <- setSessionDynFlags fs'
_ <- liftIO $ initPackages fs'
return ()
addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()
addCmdOpts opts = do
Log.log Log.Trace $ "setting ghc options: {}" ~~ unwords opts
fs <- getSessionDynFlags
(fs', _, _) <- parseDynamicFlags fs (map noLoc opts)
let fs'' = fs' {
ghcMode = CompManager,
ghcLink = LinkInMemory,
hscTarget = HscInterpreted }
void $ setSessionDynFlags fs''
setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()
setCmdOpts opts = do
Log.log Log.Trace $ "restarting ghc session with: {}" ~~ unwords opts
initGhcMonad (Just libdir)
addCmdOpts opts
modifyFlags (\fs -> fs { log_action = logToNull })
importModules :: GhcMonad m => [String] -> m ()
importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl
preludeModules :: [String]
preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"]
evaluate :: GhcMonad m => String -> m String
evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>=
maybe (fail "evaluate fail") return
clearTargets :: GhcMonad m => m ()
clearTargets = loadTargets []
makeTarget :: GhcMonad m => String -> Maybe String -> m Target
makeTarget name Nothing = guessTarget name Nothing
makeTarget name (Just cts) = do
t <- guessTarget name Nothing
tm <- liftIO getCurrentTime
return t { targetContents = Just (stringToStringBuffer cts, tm) }
loadTargets :: GhcMonad m => [Target] -> m ()
loadTargets ts = setTargets ts >> load LoadAllTargets >> return ()
listPackages :: GhcMonad m => m [ModulePackage]
listPackages = liftM (mapMaybe readPackage . fromMaybe [] . pkgDatabase) getSessionDynFlags
readPackage :: PackageConfig -> Maybe ModulePackage
readPackage pc = readMaybe $ packageNameString pc ++ "-" ++ showVersion (packageVersion pc)
spanRegion :: SrcSpan -> Region
spanRegion (RealSrcSpan s) = Position (srcSpanStartLine s) (srcSpanStartCol s) `region` Position (srcSpanEndLine s) (srcSpanEndCol s)
spanRegion _ = Position 0 0 `region` Position 0 0
withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a
withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $
const (liftIO (setCurrentDirectory dir) >> act)
logToChan :: Chan (Note OutputMessage) -> DynFlags -> E.Severity -> SrcSpan -> PprStyle -> SDoc -> IO ()
logToChan ch fs sev src _ msg
| Just sev' <- checkSev sev = do
src' <- canonicalize srcMod
putChan ch $ Note {
_noteSource = src',
_noteRegion = spanRegion src,
_noteLevel = Just sev',
_note = OutputMessage {
_message = showSDoc fs msg,
_messageSuggestion = Nothing } }
| otherwise = return ()
where
checkSev SevWarning = Just Warning
checkSev SevError = Just Error
checkSev SevFatal = Just Error
checkSev _ = Nothing
srcMod = case src of
RealSrcSpan s' -> FileModule (unpackFS $ srcSpanFile s') Nothing
_ -> ModuleSource Nothing
logToNull :: DynFlags -> E.Severity -> SrcSpan -> PprStyle -> SDoc -> IO ()
logToNull _ _ _ _ _ = return ()