halive-0.1.1: src/Halive/SubHalive.hs
{-# LANGUAGE OverloadedStrings, LambdaCase, ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
module Halive.SubHalive (
module Halive.SubHalive
#if __GLASGOW_HASKELL__ >= 800
, module GHC.LanguageExtensions
#else
, ExtensionFlag(..)
#endif
) where
import GHC
#if __GLASGOW_HASKELL__ >= 800
import GHC.LanguageExtensions
#else
import Module
#endif
import DynFlags
import Exception
import ErrUtils
import HscTypes
import GHC.Paths
import Outputable
import StringBuffer
--import Packages
import Linker
import Control.Monad
import Control.Monad.IO.Class
import Data.IORef
import Data.Time
import Halive.FindPackageDBs
import Control.Concurrent
import System.Signal
import Data.Dynamic
data FixDebounce = DebounceFix | NoDebounceFix deriving Eq
data CompliationMode = Interpreted | Compiled deriving Eq
data GHCSessionConfig = GHCSessionConfig
{ gscFixDebounce :: FixDebounce
, gscImportPaths :: [FilePath]
, gscPackageDBs :: [FilePath]
, gscLibDir :: FilePath
#if __GLASGOW_HASKELL__ >= 800
, gscLanguageExtensions :: [Extension]
#else
, gscLanguageExtensions :: [ExtensionFlag]
#endif
, gscCompilationMode :: CompliationMode
, gscStartupFile :: Maybe (FilePath, String)
-- ^ Allow API users to block until a given file is compiled,
-- to work around a bug where the GHC API crashes while loading libraries
-- if the main thread is doing work (possibly due to accessing said libraries in some way)
, gscVerbosity :: Int
}
defaultGHCSessionConfig :: GHCSessionConfig
defaultGHCSessionConfig = GHCSessionConfig
{ gscFixDebounce = DebounceFix
, gscImportPaths = []
, gscPackageDBs = []
, gscLanguageExtensions = []
, gscLibDir = libdir
, gscCompilationMode = Interpreted
, gscStartupFile = Nothing
, gscVerbosity = 0
}
--pkgConfRefToString = \case
-- GlobalPkgConf -> "GlobalPkgConf"
-- UserPkgConf -> "UserPkgConf"
-- PkgConfFile file -> "PkgConfFile " ++ show file
--extraPkgConfsToString dflags = show $ map pkgConfRefToString $ extraPkgConfs dflags $ []
logIO :: MonadIO m => String -> m ()
logIO = liftIO . putStrLn
-- Starts up a GHC session and then runs the given action within it
withGHCSession :: ThreadId -> GHCSessionConfig -> Ghc a -> IO a
withGHCSession mainThreadID GHCSessionConfig{..} action = do
-- Work around https://ghc.haskell.org/trac/ghc/ticket/4162
let restoreControlC f = do
liftIO $ installHandler sigINT (\_signal -> killThread mainThreadID)
f
-- defaultErrorHandler defaultFatalMessager defaultFlushOut $ runGhc (Just libdir) $ do
runGhc (Just gscLibDir) . restoreControlC $ do
-- Get the default dynFlags
dflags0 <- getSessionDynFlags
-- Add passed-in package DBs
let dflags1 = addExtraPkgConfs dflags0 gscPackageDBs
-- If there's a sandbox, add its package DB
dflags2 <- updateDynFlagsWithCabalSandbox dflags1
-- If this is a stack project, add its package DBs
dflags3 <- updateDynFlagsWithStackDB dflags2
-- Make sure we're configured for live-reload
let dflags4 = dflags3 { hscTarget = if gscCompilationMode == Compiled then HscAsm else HscInterpreted
, optLevel = if gscCompilationMode == Compiled then 2 else 0
, ghcLink = LinkInMemory
, ghcMode = CompManager
, importPaths = gscImportPaths
, objectDir = Just ".halive"
, hiDir = Just ".halive"
, stubDir = Just ".halive"
, dumpDir = Just ".halive"
, verbosity = gscVerbosity
}
-- turn off the GHCi sandbox
-- since it breaks OpenGL/GUI usage
`gopt_unset` Opt_GhciSandbox
-- GHC seems to try to "debounce" compilations within
-- about a half second (i.e., it won't recompile)
-- This fixes that, but probably isn't quite what we want
-- since it will cause extra files to be recompiled...
dflags5 = if gscFixDebounce == DebounceFix
then dflags4 `gopt_set` Opt_ForceRecomp
else dflags4
dflags6 = foldl xopt_set dflags5 gscLanguageExtensions
-- We must call setSessionDynFlags before calling initPackages or any other GHC API
packageIDs <- setSessionDynFlags dflags6
-- Works around a yet-unidentified segfault when loading
-- 5/1/2016: I've implemented this in a different way,
-- (by just passing in a file to compile that will trigger
-- loads of all its dependencies)
-- but this is still a viable approach... not quite as convenient though!
--let gscPreloadPackagesForModules = ["Sound.Pd"]
--preloadPackageKeys <- forM gscPreloadPackagesForModules $ \modName ->
-- modulePackageKey <$> findModule (mkModuleName modName) Nothing
--let finalPackageIDs = preloadPackageKeys ++ packageIDs
let finalPackageIDs = packageIDs
--logIO $ "linkPackages: " ++ show (map packageKeyString finalPackageIDs)
-- Initialize the package database and dynamic linker.
-- Explicitly calling these avoids crashes on some of my machines.
#if __GLASGOW_HASKELL__ >= 800
hscEnv1 <- getSession
liftIO $ linkPackages hscEnv1 finalPackageIDs
hscEnv2 <- getSession
liftIO (initDynLinker hscEnv2)
#else
dflags7 <- getSessionDynFlags
liftIO $ linkPackages dflags7 finalPackageIDs
dflags8 <- getSessionDynFlags
liftIO (initDynLinker dflags8)
#endif
action
-- See note below - this isn't actually called right now
gatherErrors :: GhcMonad m => SourceError -> m [String]
gatherErrors sourceError = do
printException sourceError
dflags <- getSessionDynFlags
let errorSDocs = pprErrMsgBagWithLoc (srcErrorMessages sourceError)
errorStrings = map (showSDoc dflags) errorSDocs
return errorStrings
--newtype CompiledValue = CompiledValue HValue
newtype CompiledValue = CompiledValue Dynamic deriving Show
--getCompiledValue :: CompiledValue -> a
--getCompiledValue (CompiledValue r) = unsafeCoerce r
getCompiledValue :: Typeable a => CompiledValue -> Maybe a
getCompiledValue (CompiledValue r) = fromDynamic r
fileContentsStringToBuffer :: (MonadIO m) => Maybe String -> m (Maybe (StringBuffer, UTCTime))
fileContentsStringToBuffer mFileContents = forM mFileContents $ \fileContents -> do
now <- liftIO getCurrentTime
return (stringToStringBuffer fileContents, now)
-- | We return the uncoerced HValue, which lets us send polymorphic values back through channels
recompileExpressionInFile :: FilePath -> Maybe String -> String -> Ghc (Either [String] CompiledValue)
recompileExpressionInFile fileName mFileContents expression =
-- NOTE: handleSourceError doesn't actually seem to do anything, and we use
-- the IORef + log_action solution instead. The API docs claim 'load' should
-- throw SourceErrors but it doesn't afaict.
catchExceptions . handleSourceError (fmap Left . gatherErrors) $ do
--logIO $ "Recompiling " ++ show (fileName, expression)
-- Prepend a '*' to prevent GHC from trying to load from any previously compiled object files
-- see http://stackoverflow.com/questions/12790341/haskell-ghc-dynamic-compliation-only-works-on-first-compile
--logIO "guessTarget"
target <- guessTarget ('*':fileName) Nothing
mFileContentsBuffer <- fileContentsStringToBuffer mFileContents
--logIO "setTargets"
setTargets [target { targetContents = mFileContentsBuffer }]
errorsRef <- liftIO (newIORef "")
dflags <- getSessionDynFlags
--logIO "setSessionDynFlags"
_ <- setSessionDynFlags dflags { log_action = logHandler errorsRef }
-- Get the dependencies of the main target
--logIO "depanal"
graph <- depanal [] False
--logIO $ "Loading " ++ show (fileName, expression)
-- Reload the main target
--logIO "load LoadAllTargets"
loadSuccess <- load LoadAllTargets
--logIO $ "Done loading " ++ show (fileName, expression)
if failed loadSuccess
then do
errors <- liftIO (readIORef errorsRef)
return (Left [errors])
else do
--logIO "typecheckModule"
-- We must parse and typecheck modules before they'll be available for usage
forM_ graph (typecheckModule <=< parseModule)
-- Load the dependencies of the main target
-- This brings all top-level definitions into scope (whether exported or not),
-- but only works on interpreted modules
--setContext (IIModule . ms_mod_name <$> graph)
setContext (IIDecl . simpleImportDecl . ms_mod_name <$> graph)
--logIO $ "Compiling " ++ show (fileName, expression)
--result <- compileExpr expression
--logIO "dynCompileExpr"
result <- dynCompileExpr expression
--logIO $ "Done compiling " ++ show (fileName, expression)
return (Right (CompiledValue result))
catchExceptions :: ExceptionMonad m => m (Either [String] a) -> m (Either [String] a)
catchExceptions a = gcatch a
(\(_x :: SomeException) -> do
liftIO (putStrLn ("Caught exception during recompileExpressionInFile: " ++ show _x))
return (Left [show _x]))
-- A helper from interactive-diagrams to print out GHC API values,
-- useful while debugging the API.
-- | Outputs any value that can be pretty-printed using the default style
output :: (GhcMonad m, Outputable a) => a -> m ()
output a = do
dfs <- getSessionDynFlags
let style = defaultUserStyle
let cntx = initSDocContext dfs style
liftIO $ print $ runSDoc (ppr a) cntx
logHandler :: IORef String -> LogAction
#if __GLASGOW_HASKELL__ >= 800
logHandler ref dflags _warnReason severity srcSpan style msg =
#else
logHandler ref dflags severity srcSpan style msg =
#endif
case severity of
SevError -> modifyIORef' ref (++ ('\n':messageWithLocation))
SevFatal -> modifyIORef' ref (++ ('\n':messageWithLocation))
SevWarning -> modifyIORef' ref (++ ('\n':messageWithLocation))
_ -> do
putStr messageOther
return () -- ignore the rest
where cntx = initSDocContext dflags style
locMsg = mkLocMessage severity srcSpan msg
messageWithLocation = show (runSDoc locMsg cntx)
messageOther = show (runSDoc msg cntx)