@@ -42,12 +42,15 @@ import Language.Haskell.Extension
42import Network.Socket hiding (Debug)
42import Network.Socket hiding (Debug)
43import System.Directory
43import System.Directory
44import System.Environment (getExecutablePath)
44import System.Environment (getExecutablePath)
45+import System.Exit (ExitCode (ExitSuccess, ExitFailure))
45import System.FilePath
46import System.FilePath
46import qualified System.Info
47import qualified System.Info
48+import System.IO (hPutStr, hFlush, hClose)
47−import System.IO.Temp (withSystemTempDirectory)
49+import System.IO.Temp (withSystemTempDirectory, withSystemTempFile)
50+import System.Process (waitForProcess)
49import Obelisk.App (MonadObelisk)
52import Obelisk.App (MonadObelisk)
50−import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode)
53+import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, setCwd, setDelegateCtlc, createProcess_)
51import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, findProjectAssets)
54import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, findProjectAssets)
52import Obelisk.Command.Thunk (attrCacheFileName)
55import Obelisk.Command.Thunk (attrCacheFileName)
53import Obelisk.Command.Utils (findExePath, ghcidExePath)
56import Obelisk.Command.Utils (findExePath, ghcidExePath)
@@ -71,20 +74,67 @@ data CabalPackageInfo = CabalPackageInfo
71preprocessorIdentifier :: String
74preprocessorIdentifier :: String
72preprocessorIdentifier = "__preprocessor-apply-packages"
75preprocessorIdentifier = "__preprocessor-apply-packages"
77+-- | Imports needed to use Obelisk.Run
78+obRunImports :: [String]
79+obRunImports = [ "import qualified Obelisk.Run"
80+ , "import qualified Frontend"
81+ , "import qualified Backend" ]
74−run :: MonadObelisk m => m ()
84+ :: MonadObelisk m
86+ -- ^ Whether to enable profiling in the current project
75−run = withProjectRoot "." $ \root -> do
88+run profiled = withProjectRoot "." $ \root -> do
76 pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
89 pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root
77− withGhciScript pkgs root $ \dotGhciPath -> do
78− freePort <- getFreePort
90+ freePort <- getFreePort
79− assets <- findProjectAssets root
91+ assets <- findProjectAssets root
80− putLog Debug $ "Assets impurely loaded from: " <> assets
92+ putLog Debug $ "Assets impurely loaded from: " <> assets
81− runGhcid root True dotGhciPath pkgs $ Just $ unwords
93+ let obRunExpr = unwords
82− [ "Obelisk.Run.run"
94+ [ "Obelisk.Run.run"
83− , show freePort
95+ , show freePort
84− , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
96+ , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
85− , "Backend.backend"
97+ , "Backend.backend"
86− , "Frontend.frontend"
98+ , "Frontend.frontend"
102+ putLog Debug "Using profiled build of project."
105+ [ "module Main where"
106+ , "import Control.Concurrent"
107+ , "import Control.Monad.Fix"
108+ , "import Reflex.Profiled" ]
112+ , " forkIO $ fix $ \\rec -> do"
113+ -- TODO: make this filename customizable
114+ , " writeProfilingData \"ob-run.rfprof\""
115+ -- write every .1 seconds.
116+ -- TODO: perhaps it should only write once at the end of ecah run
117+ , " threadDelay 10000"
120+ -- sane flags to enable by default, enable time profiling + closure heap profiling
121+ rtsFlags = [ "+RTS", "-p", "-hc", "-RTS" ]
122+ withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do
123+ liftIO $ hPutStr hsHandle exeSource
124+ liftIO $ hFlush hsHandle
125+ (_, _, _, ph1) <- createProcess_ "nixGhcWithProfiling" $ setCwd (Just root) $ proc "nix-shell" [ "-p", "((import ./. {}).profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend]))", "--run", unwords [ "ghc", "-x", "hs", "-prof", "-fprof-auto", hsFname, "-o", exeFname ] ]
126+ code <- liftIO $ waitForProcess ph1
129+ liftIO $ hClose exeHandle
130+ (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags
131+ _ <- liftIO $ waitForProcess ph2
136+ withGhciScript pkgs root $ \dotGhciPath -> do
137+ runGhcid root True dotGhciPath pkgs $ Just obRunExpr
89runRepl :: MonadObelisk m => m ()
139runRepl :: MonadObelisk m => m ()
90runRepl = withProjectRoot "." $ \root -> do
140runRepl = withProjectRoot "." $ \root -> do
@@ -256,15 +306,12 @@ withGhciScript packageInfos pathBase f = do
256 , [ "Backend" | "backend" `Set.member` packageNames ]
306 , [ "Backend" | "backend" `Set.member` packageNames ]
257 , [ "Frontend" | "frontend" `Set.member` packageNames ]
307 , [ "Frontend" | "frontend" `Set.member` packageNames ]
259− dotGhci = unlines
309+ dotGhci = unlines $
260 -- TODO: Shell escape
310 -- TODO: Shell escape
261 [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos)
311 [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos)
262 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
312 , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
263 , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
313 , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
264− , "import qualified Obelisk.Run"
314+ ] <> obRunImports
265− , "import qualified Frontend"
266− , "import qualified Backend"
268 withSystemTempDirectory "ob-ghci" $ \fp -> do
315 withSystemTempDirectory "ob-ghci" $ \fp -> do
269 let dotGhciPath = fp </> ".ghci"
316 let dotGhciPath = fp </> ".ghci"
270 liftIO $ writeFile dotGhciPath dotGhci
317 liftIO $ writeFile dotGhciPath dotGhci