{-# LANGUAGE OverloadedStrings, LambdaCase #-} module Halive where import GHC import Linker import Packages import DynFlags import GHC.Paths import Outputable import Data.IORef import Control.Monad import Control.Concurrent import Control.Monad.IO.Class import System.FSNotify import System.FilePath import SandboxPath directoryWatcher :: IO (Chan Event) directoryWatcher = do let predicate event = case event of Modified path _ -> takeExtension path `elem` [".hs", ".vert", ".frag", ".pd"] _ -> False eventChan <- newChan _ <- forkIO $ withManager $ \manager -> do -- start a watching job (in the background) let watchDirectory = "." _stopListening <- watchTreeChan manager watchDirectory predicate eventChan -- Keep the watcher alive forever forever $ threadDelay 10000000 return eventChan recompiler :: FilePath -> [FilePath] -> IO () recompiler mainFileName importPaths' = withGHCSession mainFileName importPaths' $ do mainThreadId <- liftIO myThreadId {- Watcher: Tell the main thread to recompile. If the main thread isn't done yet, kill it. Compiler: Wait for the signal to recompile. Before recompiling & running, mark that we've started, and after we're done running, mark that we're done. -} mainDone <- liftIO $ newIORef False -- Start with a full MVar so we recompile right away. recompile <- liftIO $ newMVar () -- Watch for changes and recompile whenever they occur watcher <- liftIO directoryWatcher _ <- liftIO . forkIO . forever $ do _ <- readChan watcher putMVar recompile () mainIsDone <- readIORef mainDone unless mainIsDone $ killThread mainThreadId -- Start up the app forever $ do _ <- liftIO $ takeMVar recompile liftIO $ writeIORef mainDone False recompileTargets liftIO $ writeIORef mainDone True withGHCSession :: FilePath -> [FilePath] -> Ghc () -> IO () withGHCSession mainFileName importPaths' action = do defaultErrorHandler defaultFatalMessager defaultFlushOut $ runGhc (Just libdir) $ do -- Add the main file's path to the import path list let mainFilePath = dropFileName mainFileName importPaths'' = mainFilePath:importPaths' -- Get the default dynFlags dflags0 <- getSessionDynFlags -- If there's a sandbox, add its package DB dflags1 <- liftIO getSandboxDb >>= \case Nothing -> return dflags0 Just sandboxDB -> do let pkgs = map PkgConfFile [sandboxDB] return dflags0 { extraPkgConfs = (pkgs ++) . extraPkgConfs dflags0 } -- Make sure we're configured for live-reload, and turn off the GHCi sandbox -- since it breaks OpenGL/GUI usage let dflags2 = dflags1 { hscTarget = HscInterpreted , ghcLink = LinkInMemory , ghcMode = CompManager , importPaths = importPaths'' } `gopt_unset` Opt_GhciSandbox -- We must set dynflags before calling initPackages or any other GHC API _ <- setSessionDynFlags dflags2 -- Initialize the package database (dflags3, _) <- liftIO $ initPackages dflags2 -- Initialize the dynamic linker liftIO $ initDynLinker dflags3 -- Set the given filename as a compilation target setTargets =<< sequence [guessTarget mainFileName Nothing] action -- Recompiles the current targets recompileTargets :: Ghc () recompileTargets = handleSourceError printException $ do -- Get the dependencies of the main target graph <- depanal [] False -- Reload the main target loadSuccess <- load LoadAllTargets unless (failed loadSuccess) $ do -- We must parse and typecheck modules before they'll be available for usage forM_ graph (typecheckModule <=< parseModule) -- Load the dependencies of the main target setContext $ map (IIModule . ms_mod_name) graph -- Run the target file's "main" function rr <- runStmt "main" RunToCompletion case rr of RunOk _ -> liftIO $ putStrLn "OK" RunException exception -> liftIO $ print exception RunBreak _ _ _ -> liftIO $ putStrLn "Breakpoint" -- 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, MonadIO m) => Outputable a => a -> m () output a = do dfs <- getSessionDynFlags let style = defaultUserStyle let cntx = initSDocContext dfs style liftIO $ print $ runSDoc (ppr a) cntx