From eea6d5b9ce79348dd87b85de012da8e0135c995f Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 10 Feb 2020 15:06:01 -0500 Subject: [PATCH 01/39] Move findProjectAssets to Obelisk.Command.Project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it easier to add ‘Ob run’-like commands by factoring out important bits. --- lib/command/src/Obelisk/Command/Project.hs | 26 +++++++++++++++++++- lib/command/src/Obelisk/Command/Run.hs | 28 ++++------------------ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index 81ae6ec37..854feaf98 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -5,6 +5,7 @@ module Obelisk.Command.Project ( InitSource (..) , findProjectObeliskCommand , findProjectRoot + , findProjectAssets , initProject , nixShellRunConfig , nixShellRunProc @@ -50,7 +51,7 @@ import Obelisk.App (MonadObelisk) import Obelisk.CliApp import Obelisk.Command.Nix import Obelisk.Command.Thunk -import Obelisk.Command.Utils (nixExePath) +import Obelisk.Command.Utils (nixExePath, nixBuildExePath) --TODO: Make this module resilient to random exceptions @@ -307,3 +308,26 @@ projectShell root isPure shellName command = do & nixShellConfig_common . nixCmdConfig_target . target_path ?~ "default.nix" & nixShellConfig_common . nixCmdConfig_target . target_attr ?~ ("shells." <> shellName) void $ liftIO $ waitForProcess ph + +findProjectAssets :: MonadObelisk m => FilePath -> m Text +findProjectAssets root = do + let importableRoot = toNixPath root + isDerivation <- readProcessAndLogStderr Debug $ + proc nixExePath + [ "eval" + , "-f" + , root + , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))" + , "--raw" + -- `--raw` is not available with old nix-instantiate. It drops quotation + -- marks and trailing newline, so is very convenient for shelling out. + ] + -- Check whether the impure static files are a derivation (and so must be built) + if isDerivation == "1" + then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option + proc nixBuildExePath + [ "--no-out-link" + , "-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure" + ] + else readProcessAndLogStderr Debug $ + proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"] diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index eaf51f7af..df1a752e0 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -47,10 +47,10 @@ import qualified System.Info import System.IO.Temp (withSystemTempDirectory) import Obelisk.App (MonadObelisk) -import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, readProcessAndLogStderr) -import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, toNixPath) +import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode) +import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, findProjectAssets) import Obelisk.Command.Thunk (attrCacheFileName) -import Obelisk.Command.Utils (findExePath, ghcidExePath, nixBuildExePath, nixExePath) +import Obelisk.Command.Utils (findExePath, ghcidExePath) data CabalPackageInfo = CabalPackageInfo { _cabalPackageInfo_packageFile :: FilePath @@ -76,27 +76,7 @@ run = withProjectRoot "." $ \root -> do pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root withGhciScript pkgs root $ \dotGhciPath -> do freePort <- getFreePort - assets <- do - let importableRoot = toNixPath root - isDerivation <- readProcessAndLogStderr Debug $ - proc nixExePath - [ "eval" - , "-f" - , root - , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))" - , "--raw" - -- `--raw` is not available with old nix-instantiate. It drops quotation - -- marks and trailing newline, so is very convenient for shelling out. - ] - -- Check whether the impure static files are a derivation (and so must be built) - if isDerivation == "1" - then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option - proc nixBuildExePath - [ "--no-out-link" - , "-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure" - ] - else readProcessAndLogStderr Debug $ - proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"] + assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets runGhcid root True dotGhciPath pkgs $ Just $ unwords [ "Obelisk.Run.run" From 383781d72697108e8b7018d302cda4370d81a327 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 10 Feb 2020 16:26:16 -0500 Subject: [PATCH 02/39] Expose profiled in obelisk This adds profiled attr to set enableLibraryProfiling to true. If not set, it defaults to profiling arg. --- default.nix | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/default.nix b/default.nix index 4484817ba..4181fb760 100644 --- a/default.nix +++ b/default.nix @@ -17,10 +17,8 @@ let openssh ]; - getReflexPlatform = sys: reflex-platform-func { - inherit iosSdkVersion config; - system = sys; - enableLibraryProfiling = profiling; + getReflexPlatform = { system, enableLibraryProfiling ? profiling }: reflex-platform-func { + inherit iosSdkVersion config system enableLibraryProfiling; nixpkgsOverlays = [ (self: super: { @@ -102,7 +100,7 @@ let ]; }; - reflex-platform = getReflexPlatform system; + reflex-platform = getReflexPlatform { inherit system; }; inherit (reflex-platform) hackGet nixpkgs; pkgs = nixpkgs; @@ -261,7 +259,7 @@ in rec { # An Obelisk project is a reflex-platform project with a predefined layout and role for each component project = base': projectDefinition: let - projectOut = sys: let reflexPlatformProject = (getReflexPlatform sys).project; in reflexPlatformProject (args@{ nixpkgs, ... }: + projectOut = { system, enableLibraryProfiling ? profiling }: let reflexPlatformProject = (getReflexPlatform { inherit system enableLibraryProfiling; }).project; in reflexPlatformProject (args@{ nixpkgs, ... }: let inherit (lib.strings) hasPrefix; mkProject = @@ -366,7 +364,7 @@ in rec { }); in allConfig; in (mkProject (projectDefinition args)).projectConfig); - mainProjectOut = projectOut system; + mainProjectOut = projectOut { inherit system; }; serverOn = projectInst: version: serverExe projectInst.ghc.backend mainProjectOut.ghcjs.frontend @@ -376,6 +374,7 @@ in rec { linuxExe = serverOn (projectOut "x86_64-linux"); dummyVersion = "Version number is only available for deployments"; in mainProjectOut // { + profiled = projectOut { inherit system; enableLibraryProfiling = true; }; linuxExeConfigurable = linuxExe; linuxExe = linuxExe dummyVersion; exe = serverOn mainProjectOut dummyVersion; From 53d3dffd22b5a6fd4ff5ec858f7f6c926ead8818 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 11:47:49 -0500 Subject: [PATCH 03/39] Add ob profile command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This dumps some profiling information that can be viewed and used to debug Obelisk projects. ‘ob profile’ works like ob run, but instead of using ghci, it builds an executable that is built with profiling enabled. --- ChangeLog.md | 3 + lib/command/src/Obelisk/Command.hs | 5 +- lib/command/src/Obelisk/Command/Run.hs | 87 ++++++++++++++++++++------ 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c536ead67..c21f8918d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,9 @@ This project's release branch is `master`. This log is written from the perspect * Add local unpacked packages to the `ob run`, `ob watch`, and `ob repl` sessions. Any `.cabal` or hpack package inside the current obelisk project will be loaded into the session. For `ob run`/`ob watch` this means the session will automatically reload when you save a source file in any of those packages. For `ob repl` it means that `:r` will reload changes to any of those packages. There are some edge cases where this integration is still rough. Report any issues you encounter. ([#489](https://github.com/obsidiansystems/obelisk/pull/489)) * Add `ob hoogle` command to start a local [Hoogle](https://hoogle.haskell.org/) server for the project. ([#628](https://github.com/obsidiansystems/obelisk/pull/628)) * `ob thunk pack` will now attempt to automatically detect if the thunk is a private or public repo. To avoid this detection, specify `--private` or `--public` manually. ([#607](https://github.com/obsidiansystems/obelisk/pull/607)) +* Add `ob profile` command to run Obelisk project with profiling. `ob +profile` works like ob run, but instead of using ghci, it builds an +executable that is built with profiling enabled. ## v0.4.0.0 - 2020-01-10 diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 11bb03bd5..e2814c32d 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -87,6 +87,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run + | ObCommand_Profile | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -108,6 +109,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" + , command "profile" $ info (pure ObCommand_Profile) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -365,7 +367,8 @@ ob = \case Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs - ObCommand_Run -> run + ObCommand_Run -> run False + ObCommand_Profile -> run True ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index df1a752e0..604592eac 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -42,12 +42,15 @@ import Language.Haskell.Extension import Network.Socket hiding (Debug) import System.Directory import System.Environment (getExecutablePath) +import System.Exit (ExitCode (ExitSuccess, ExitFailure)) import System.FilePath import qualified System.Info -import System.IO.Temp (withSystemTempDirectory) +import System.IO (hPutStr, hFlush, hClose) +import System.IO.Temp (withSystemTempDirectory, withSystemTempFile) +import System.Process (waitForProcess) import Obelisk.App (MonadObelisk) -import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode) +import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, setCwd, setDelegateCtlc, createProcess_) import Obelisk.Command.Project (obeliskDirName, toObeliskDir, withProjectRoot, nixShellWithPkgs, findProjectAssets) import Obelisk.Command.Thunk (attrCacheFileName) import Obelisk.Command.Utils (findExePath, ghcidExePath) @@ -71,20 +74,67 @@ data CabalPackageInfo = CabalPackageInfo preprocessorIdentifier :: String preprocessorIdentifier = "__preprocessor-apply-packages" -run :: MonadObelisk m => m () -run = withProjectRoot "." $ \root -> do +-- | Imports needed to use Obelisk.Run +obRunImports :: [String] +obRunImports = [ "import qualified Obelisk.Run" + , "import qualified Frontend" + , "import qualified Backend" ] + +run + :: MonadObelisk m + => Bool + -- ^ Whether to enable profiling in the current project + -> m () +run profiled = withProjectRoot "." $ \root -> do pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root - withGhciScript pkgs root $ \dotGhciPath -> do - freePort <- getFreePort - assets <- findProjectAssets root - putLog Debug $ "Assets impurely loaded from: " <> assets - runGhcid root True dotGhciPath pkgs $ Just $ unwords - [ "Obelisk.Run.run" - , show freePort - , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" - , "Backend.backend" - , "Frontend.frontend" - ] + freePort <- getFreePort + assets <- findProjectAssets root + putLog Debug $ "Assets impurely loaded from: " <> assets + let obRunExpr = unwords + [ "Obelisk.Run.run" + , show freePort + , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" + , "Backend.backend" + , "Frontend.frontend" + ] + case profiled of + True -> do + putLog Debug "Using profiled build of project." + let exeSource = + unlines $ + [ "module Main where" + , "import Control.Concurrent" + , "import Control.Monad.Fix" + , "import Reflex.Profiled" ] + <> obRunImports <> + [ "main :: IO ()" + , "main = do" + , " forkIO $ fix $ \\rec -> do" + -- TODO: make this filename customizable + , " writeProfilingData \"ob-run.rfprof\"" + -- write every .1 seconds. + -- TODO: perhaps it should only write once at the end of ecah run + , " threadDelay 10000" + , " rec" + , " " <> obRunExpr] + -- sane flags to enable by default, enable time profiling + closure heap profiling + rtsFlags = [ "+RTS", "-p", "-hc", "-RTS" ] + withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do + liftIO $ hPutStr hsHandle exeSource + liftIO $ hFlush hsHandle + (_, _, _, 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 ] ] + code <- liftIO $ waitForProcess ph1 + case code of + ExitSuccess -> do + liftIO $ hClose exeHandle + (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags + _ <- liftIO $ waitForProcess ph2 + pure () + ExitFailure _ -> do + pure () + False -> + withGhciScript pkgs root $ \dotGhciPath -> do + runGhcid root True dotGhciPath pkgs $ Just obRunExpr runRepl :: MonadObelisk m => m () runRepl = withProjectRoot "." $ \root -> do @@ -256,15 +306,12 @@ withGhciScript packageInfos pathBase f = do , [ "Backend" | "backend" `Set.member` packageNames ] , [ "Frontend" | "frontend" `Set.member` packageNames ] ] - dotGhci = unlines + dotGhci = unlines $ -- TODO: Shell escape [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos) , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs) , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad - , "import qualified Obelisk.Run" - , "import qualified Frontend" - , "import qualified Backend" - ] + ] <> obRunImports withSystemTempDirectory "ob-ghci" $ \fp -> do let dotGhciPath = fp ".ghci" liftIO $ writeFile dotGhciPath dotGhci From 0be7a17bd75067ec8d53f0464beb3c82d260b92d Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 12:06:11 -0500 Subject: [PATCH 04/39] Store ob profile data in profile/ directory This avoids polluting the root directory and gives some ability to reference multiple profiles. --- lib/command/src/Obelisk/Command/Run.hs | 13 ++++++++++--- skeleton/.gitignore | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 604592eac..4636a4fbb 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -23,6 +23,8 @@ import Data.Maybe import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T +import Data.Time (getCurrentTime) +import Data.Time.Format (formatTime, defaultTimeLocale) import Data.Traversable (for) import Debug.Trace (trace) import Distribution.Compiler (CompilerFlavor(..)) @@ -100,6 +102,7 @@ run profiled = withProjectRoot "." $ \root -> do case profiled of True -> do putLog Debug "Using profiled build of project." + time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime let exeSource = unlines $ [ "module Main where" @@ -111,14 +114,18 @@ run profiled = withProjectRoot "." $ \root -> do , "main = do" , " forkIO $ fix $ \\rec -> do" -- TODO: make this filename customizable - , " writeProfilingData \"ob-run.rfprof\"" + , " writeProfilingData \"" <> profileBaseName <> ".rprof\"" -- write every .1 seconds. -- TODO: perhaps it should only write once at the end of ecah run , " threadDelay 10000" , " rec" , " " <> obRunExpr] - -- sane flags to enable by default, enable time profiling + closure heap profiling - rtsFlags = [ "+RTS", "-p", "-hc", "-RTS" ] + -- Sane flags to enable by default, enable time profiling + + -- closure heap profiling. + rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] + profileDirectory = root "profile" + profileBaseName = profileDirectory time + liftIO $ createDirectoryIfMissing False profileDirectory withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do liftIO $ hPutStr hsHandle exeSource liftIO $ hFlush hsHandle diff --git a/skeleton/.gitignore b/skeleton/.gitignore index dbed775e1..2dae72cbb 100644 --- a/skeleton/.gitignore +++ b/skeleton/.gitignore @@ -6,3 +6,4 @@ result-ios result-exe .attr-cache ghcid-output.txt +profile/ From 78d05a2d6d4e832e8f3c47e8cedb4e5551605673 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 12:38:01 -0500 Subject: [PATCH 05/39] ob profile: Use finally to write profiling data This avoids a call every .1 seconds. --- lib/command/src/Obelisk/Command/Run.hs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 790c6134b..bae0063de 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -104,20 +104,11 @@ run profiled = withProjectRoot "." $ \root -> do let exeSource = unlines $ [ "module Main where" - , "import Control.Concurrent" - , "import Control.Monad.Fix" + , "import Control.Exception" , "import Reflex.Profiled" ] <> obRunImports <> [ "main :: IO ()" - , "main = do" - , " forkIO $ fix $ \\rec -> do" - -- TODO: make this filename customizable - , " writeProfilingData \"" <> profileBaseName <> ".rprof\"" - -- write every .1 seconds. - -- TODO: perhaps it should only write once at the end of ecah run - , " threadDelay 10000" - , " rec" - , " " <> obRunExpr] + , "main = " <> obRunExpr <> " `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] From c0618d59c1163b6a2f44492a80d8a09b7bc86ff5 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 12:41:47 -0500 Subject: [PATCH 06/39] =?UTF-8?q?Don=E2=80=99t=20auto=20profile=20Main=20m?= =?UTF-8?q?odule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This module will always be there, and no need to add annotations. backend & frontend still get their annotations. --- lib/command/src/Obelisk/Command/Run.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index bae0063de..824dc5de3 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -118,7 +118,7 @@ run profiled = withProjectRoot "." $ \root -> do withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do liftIO $ hPutStr hsHandle exeSource liftIO $ hFlush hsHandle - (_, _, _, 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 ] ] + (_, _, _, 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", "-fno-prof-auto", hsFname, "-o", exeFname ] ] code <- liftIO $ waitForProcess ph1 case code of ExitSuccess -> do From 419f5e49d37a37d0c7ca57c97c5febef31cce88f Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 14:59:31 -0500 Subject: [PATCH 07/39] =?UTF-8?q?Don=E2=80=99t=20pass=20-f=20to=20nix=20wh?= =?UTF-8?q?en=20running=20findProjectAssets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is unnecessary as we import immediately in the expression. --- lib/command/src/Obelisk/Command/Project.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index 854feaf98..26f79f647 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -315,8 +315,6 @@ findProjectAssets root = do isDerivation <- readProcessAndLogStderr Debug $ proc nixExePath [ "eval" - , "-f" - , root , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))" , "--raw" -- `--raw` is not available with old nix-instantiate. It drops quotation From b32206b656672bbdc9620ed1177ee41872111e56 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 15:44:30 -0500 Subject: [PATCH 08/39] Wrap obRunExpr in parens This avoids an issue with precedence in case of obRunExpr using $. --- lib/command/src/Obelisk/Command/Run.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 824dc5de3..7d4358c82 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -108,7 +108,7 @@ run profiled = withProjectRoot "." $ \root -> do , "import Reflex.Profiled" ] <> obRunImports <> [ "main :: IO ()" - , "main = " <> obRunExpr <> " `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] + , "main = (" <> obRunExpr <> ") `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] From 7a58b16b9e467fb87de62eefeedeac5e7172791e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 16:04:08 -0500 Subject: [PATCH 09/39] Move ob profile to own command This avoids having to mix up options for ob run and ob profile. Makes the way for adding a -o option to ob profile. --- lib/command/src/Obelisk/Command.hs | 4 +- lib/command/src/Obelisk/Command/Run.hs | 86 ++++++++++++++------------ 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index e2814c32d..81c225a38 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -367,8 +367,8 @@ ob = \case Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs - ObCommand_Run -> run False - ObCommand_Profile -> run True + ObCommand_Run -> run + ObCommand_Profile -> profile ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 7d4358c82..d72dd35dd 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -80,12 +80,54 @@ obRunImports = [ "import qualified Obelisk.Run" , "import qualified Frontend" , "import qualified Backend" ] +profile + :: MonadObelisk m + => m () +profile = withProjectRoot "." $ \root -> do + freePort <- getFreePort + assets <- findProjectAssets root + putLog Debug $ "Assets impurely loaded from: " <> assets + let obRunExpr = unwords + [ "Obelisk.Run.run" + , show freePort + , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" + , "Backend.backend" + , "Frontend.frontend" + ] + putLog Debug "Using profiled build of project." + time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime + let exeSource = + unlines $ + [ "module Main where" + , "import Control.Exception" + , "import Reflex.Profiled" ] + <> obRunImports <> + [ "main :: IO ()" + , "main = (" <> obRunExpr <> ") `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] + -- Sane flags to enable by default, enable time profiling + + -- closure heap profiling. + rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] + profileDirectory = root "profile" + profileBaseName = profileDirectory time + liftIO $ createDirectoryIfMissing False profileDirectory + withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do + liftIO $ hPutStr hsHandle exeSource + liftIO $ hFlush hsHandle + (_, _, _, 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", "-fno-prof-auto", hsFname, "-o", exeFname ] ] + code <- liftIO $ waitForProcess ph1 + case code of + ExitSuccess -> do + liftIO $ hClose exeHandle + (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags + _ <- liftIO $ waitForProcess ph2 + pure () + ExitFailure _ -> do + pure () + run :: MonadObelisk m - => Bool - -- ^ Whether to enable profiling in the current project - -> m () -run profiled = withProjectRoot "." $ \root -> do + => m () +run = withProjectRoot "." $ \root -> do pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root freePort <- getFreePort assets <- findProjectAssets root @@ -97,40 +139,8 @@ run profiled = withProjectRoot "." $ \root -> do , "Backend.backend" , "Frontend.frontend" ] - case profiled of - True -> do - putLog Debug "Using profiled build of project." - time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime - let exeSource = - unlines $ - [ "module Main where" - , "import Control.Exception" - , "import Reflex.Profiled" ] - <> obRunImports <> - [ "main :: IO ()" - , "main = (" <> obRunExpr <> ") `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] - -- Sane flags to enable by default, enable time profiling + - -- closure heap profiling. - rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] - profileDirectory = root "profile" - profileBaseName = profileDirectory time - liftIO $ createDirectoryIfMissing False profileDirectory - withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do - liftIO $ hPutStr hsHandle exeSource - liftIO $ hFlush hsHandle - (_, _, _, 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", "-fno-prof-auto", hsFname, "-o", exeFname ] ] - code <- liftIO $ waitForProcess ph1 - case code of - ExitSuccess -> do - liftIO $ hClose exeHandle - (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags - _ <- liftIO $ waitForProcess ph2 - pure () - ExitFailure _ -> do - pure () - False -> - withGhciScript pkgs root $ \dotGhciPath -> do - runGhcid root True dotGhciPath pkgs $ Just obRunExpr + withGhciScript pkgs root $ \dotGhciPath -> do + runGhcid root True dotGhciPath pkgs $ Just obRunExpr runRepl :: MonadObelisk m => m () runRepl = withProjectRoot "." $ \root -> do From 5396971babb1f1a17255d3748c9d686fcd25beb4 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 16:50:56 -0500 Subject: [PATCH 10/39] Add option to set profile output path --- lib/command/src/Obelisk/Command.hs | 6 +++--- lib/command/src/Obelisk/Command/Run.hs | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 81c225a38..7cde61a8d 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -87,7 +87,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile + | ObCommand_Profile (Maybe FilePath) | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -109,7 +109,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (pure ObCommand_Profile) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> optional (strOption ( long "output" <> short 'o' <> metavar "BASEPATH" ))) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -368,7 +368,7 @@ ob = \case DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs ObCommand_Run -> run - ObCommand_Profile -> profile + ObCommand_Profile basePath -> profile basePath ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index d72dd35dd..8b3615c8f 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -82,8 +82,9 @@ obRunImports = [ "import qualified Obelisk.Run" profile :: MonadObelisk m - => m () -profile = withProjectRoot "." $ \root -> do + => Maybe FilePath + -> m () +profile mProfileBaseName = withProjectRoot "." $ \root -> do freePort <- getFreePort assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets @@ -108,7 +109,7 @@ profile = withProjectRoot "." $ \root -> do -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] profileDirectory = root "profile" - profileBaseName = profileDirectory time + profileBaseName = fromMaybe (profileDirectory time) mProfileBaseName liftIO $ createDirectoryIfMissing False profileDirectory withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do liftIO $ hPutStr hsHandle exeSource From 63b9ff58d9b757db5baf39d14155831ddbd0faec Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 16:55:13 -0500 Subject: [PATCH 11/39] Use setCwd and relative paths in findProjectAssets --- lib/command/src/Obelisk/Command/Project.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index 26f79f647..85ecd007b 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -311,11 +311,10 @@ projectShell root isPure shellName command = do findProjectAssets :: MonadObelisk m => FilePath -> m Text findProjectAssets root = do - let importableRoot = toNixPath root - isDerivation <- readProcessAndLogStderr Debug $ + isDerivation <- readProcessAndLogStderr Debug $ setCwd (Just root) $ proc nixExePath [ "eval" - , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))" + , "(let a = import ./. {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))" , "--raw" -- `--raw` is not available with old nix-instantiate. It drops quotation -- marks and trailing newline, so is very convenient for shelling out. @@ -323,9 +322,9 @@ findProjectAssets root = do -- Check whether the impure static files are a derivation (and so must be built) if isDerivation == "1" then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option - proc nixBuildExePath + setCwd (Just root) $ proc nixBuildExePath [ "--no-out-link" - , "-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure" + , "-E", "(import ./. {}).passthru.staticFilesImpure" ] else readProcessAndLogStderr Debug $ proc nixExePath ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"] From 1243cc809a5780f9761f973225ebf17148733f50 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 17:12:34 -0500 Subject: [PATCH 12/39] Pass port and assets to ob profile as arguments This avoids embedding this information into the binary. --- lib/command/src/Obelisk/Command/Run.hs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 8b3615c8f..e20d2bc71 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -88,23 +88,17 @@ profile mProfileBaseName = withProjectRoot "." $ \root -> do freePort <- getFreePort assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets - let obRunExpr = unwords - [ "Obelisk.Run.run" - , show freePort - , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" - , "Backend.backend" - , "Frontend.frontend" - ] putLog Debug "Using profiled build of project." time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime let exeSource = unlines $ [ "module Main where" , "import Control.Exception" - , "import Reflex.Profiled" ] + , "import Reflex.Profiled" + , "import System.Environment" ] <> obRunImports <> [ "main :: IO ()" - , "main = (" <> obRunExpr <> ") `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] + , "main = getArgs >>= \\args -> (Obelisk.Run.run (read (args !! 0)) (Obelisk.Run.runServeAsset (args !! 1)) Backend.backend Frontend.frontend) `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] @@ -119,7 +113,7 @@ profile mProfileBaseName = withProjectRoot "." $ \root -> do case code of ExitSuccess -> do liftIO $ hClose exeHandle - (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname rtsFlags + (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname ([show freePort, T.unpack assets] <> rtsFlags) _ <- liftIO $ waitForProcess ph2 pure () ExitFailure _ -> do From f882d6317296ed28f0eec7268a7844122f0ae01a Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 17:38:50 -0500 Subject: [PATCH 13/39] Use nix-build to make Ob Profile binary This allow caching between ob profile runs. --- lib/command/src/Obelisk/Command/Run.hs | 64 ++++++++++++++------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index e20d2bc71..a7a75745d 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -8,11 +8,13 @@ module Obelisk.Command.Run where import Control.Arrow ((&&&)) import Control.Exception (Exception, bracket) +import qualified Control.Lens as Lens import Control.Monad (filterM, unless) import Control.Monad.Except (runExceptT, throwError) import Control.Monad.IO.Class (liftIO) import Control.Monad.Reader (MonadIO) import Data.Coerce (coerce) +import Data.Default (def) import Data.Either import Data.Foldable (for_, toList) import qualified Data.List.NonEmpty as NE @@ -43,15 +45,14 @@ import Language.Haskell.Extension import Network.Socket hiding (Debug) import System.Directory import System.Environment (getExecutablePath) -import System.Exit (ExitCode (ExitSuccess, ExitFailure)) import System.FilePath import qualified System.Info -import System.IO (hPutStr, hFlush, hClose) -import System.IO.Temp (withSystemTempDirectory, withSystemTempFile) +import System.IO.Temp (withSystemTempDirectory) import System.Process (waitForProcess) import Obelisk.App (MonadObelisk) import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, setCwd, setDelegateCtlc, createProcess_) +import Obelisk.Command.Nix import Obelisk.Command.Project (withProjectRoot, nixShellWithPkgs, findProjectAssets) import Obelisk.Command.Utils (findExePath, ghcidExePath) @@ -90,34 +91,39 @@ profile mProfileBaseName = withProjectRoot "." $ \root -> do putLog Debug $ "Assets impurely loaded from: " <> assets putLog Debug "Using profiled build of project." time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime - let exeSource = - unlines $ - [ "module Main where" - , "import Control.Exception" - , "import Reflex.Profiled" - , "import System.Environment" ] - <> obRunImports <> - [ "main :: IO ()" - , "main = getArgs >>= \\args -> (Obelisk.Run.run (read (args !! 0)) (Obelisk.Run.runServeAsset (args !! 1)) Backend.backend Frontend.frontend) `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" ] - -- Sane flags to enable by default, enable time profiling + + profileBaseName <- case mProfileBaseName of + Just baseName -> pure baseName + Nothing -> do + let profileDirectory = root "profile" + liftIO $ createDirectoryIfMissing False profileDirectory + pure $ profileDirectory time + let -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] - profileDirectory = root "profile" - profileBaseName = fromMaybe (profileDirectory time) mProfileBaseName - liftIO $ createDirectoryIfMissing False profileDirectory - withSystemTempFile "ob-run-profiled.hs" $ \hsFname hsHandle -> withSystemTempFile "ob-run" $ \exeFname exeHandle -> do - liftIO $ hPutStr hsHandle exeSource - liftIO $ hFlush hsHandle - (_, _, _, 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", "-fno-prof-auto", hsFname, "-o", exeFname ] ] - code <- liftIO $ waitForProcess ph1 - case code of - ExitSuccess -> do - liftIO $ hClose exeHandle - (_, _, _, ph2) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exeFname ([show freePort, T.unpack assets] <> rtsFlags) - _ <- liftIO $ waitForProcess ph2 - pure () - ExitFailure _ -> do - pure () + nixBuildExpr = unlines $ + [ "(with (import ./. {});" + , "let" + , " exeSource = obelisk.nixpkgs.writeText \"ob-run\" ''" + , "module Main where" + , "import Control.Exception" + , "import Reflex.Profiled" + , "import System.Environment" ] + <> obRunImports <> + [ "main :: IO ()" + , "main = getArgs >>= \\args -> (Obelisk.Run.run (read (args !! 0)) (Obelisk.Run.runServeAsset (args !! 1)) Backend.backend Frontend.frontend) `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" + , " '';" + , "in obelisk.nixpkgs.runCommand \"ob-run\" {" + , " buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ];" + , "} \"ghc -x hs -prof -fno-prof-auto ${exeSource} -o $out\")" ] + exePath <- nixCmd $ NixCmd_Build $ def + Lens.& nixBuildConfig_outLink Lens..~ OutLink_None + Lens.& nixCmdConfig_target Lens..~ Target + { _target_path = Nothing + , _target_attr = Nothing + , _target_expr = Just nixBuildExpr } + (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath ([show freePort, T.unpack assets] <> rtsFlags) + _ <- liftIO $ waitForProcess ph + pure () run :: MonadObelisk m From 9ec5d2b6ba40033e1c63d61811f8362e63be862f Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Tue, 11 Feb 2020 17:53:10 -0500 Subject: [PATCH 14/39] Avoid using maybe for filepath in ob profile --- lib/command/src/Obelisk/Command.hs | 28 ++++++++++++++++---------- lib/command/src/Obelisk/Command/Run.hs | 14 +++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 7cde61a8d..32759cfa2 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -13,9 +13,12 @@ import qualified Data.ByteString.Base16 as Base16 import qualified Data.ByteString.Lazy as LBS import Data.Foldable (for_) import Data.List +import Data.Maybe import qualified Data.Text as T import Data.Text.Encoding import Data.Text.Encoding.Error (lenientDecode) +import Data.Time.Clock +import Data.Time.Format (formatTime, defaultTimeLocale) import GHC.StaticPtr import Options.Applicative import Options.Applicative.Help.Pretty (text, (<$$>)) @@ -50,8 +53,8 @@ newtype ArgsConfig = ArgsConfig { _argsConfig_enableVmBuilderByDefault :: Bool } -args :: ArgsConfig -> Parser Args -args cfg = Args <$> noHandoff <*> verbose <*> obCommand cfg +args :: ArgsConfig -> UTCTime -> Maybe FilePath -> Parser Args +args cfg time root = Args <$> noHandoff <*> verbose <*> obCommand cfg time root noHandoff :: Parser Bool noHandoff = flag False True $ mconcat @@ -67,8 +70,8 @@ verbose = flag False True $ mconcat , help "Be more verbose" ] -argsInfo :: ArgsConfig -> ParserInfo Args -argsInfo cfg = info (args cfg <**> helper) $ mconcat +argsInfo :: ArgsConfig -> UTCTime -> Maybe FilePath -> ParserInfo Args +argsInfo cfg time root = info (args cfg time root <**> helper) $ mconcat [ fullDesc , progDesc "Manage Obelisk projects" ] @@ -87,7 +90,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile (Maybe FilePath) + | ObCommand_Profile FilePath | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -103,13 +106,13 @@ data ObInternal = ObInternal_ApplyPackages String String String [String] deriving Show -obCommand :: ArgsConfig -> Parser ObCommand -obCommand cfg = hsubparser +obCommand :: ArgsConfig -> UTCTime -> Maybe FilePath -> Parser ObCommand +obCommand cfg time root = hsubparser (mconcat [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> optional (strOption ( long "output" <> short 'o' <> metavar "BASEPATH" ))) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type." <> showDefault <> value ((fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time) <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -250,8 +253,8 @@ parserPrefs = defaultPrefs { prefShowHelpOnEmpty = True } -parseCLIArgs :: ArgsConfig -> [String] -> IO Args -parseCLIArgs cfg = handleParseResult . execParserPure parserPrefs (argsInfo cfg) +parseCLIArgs :: ArgsConfig -> UTCTime -> Maybe FilePath -> [String] -> IO Args +parseCLIArgs cfg time root = handleParseResult . execParserPure parserPrefs (argsInfo cfg time root) -- | Create an Obelisk config for the current process. mkObeliskConfig :: IO Obelisk @@ -306,12 +309,15 @@ main' argsCfg = do , "logging-level=" <> show logLevel ] + time <- liftIO $ getCurrentTime + root <- findProjectRoot "." + --TODO: We'd like to actually use the parser to determine whether to hand off, --but in the case where this implementation of 'ob' doesn't support all --arguments being passed along, this could fail. For now, we don't bother --with optparse-applicative until we've done the handoff. let go as = do - args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg) as) + args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg time root) as) case _args_noHandOffPassed args' of False -> return () True -> putLog Warning "--no-handoff should only be passed once and as the first argument; ignoring" diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index a7a75745d..25d853dc9 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -24,8 +24,6 @@ import Data.Maybe import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T -import Data.Time (getCurrentTime) -import Data.Time.Format (formatTime, defaultTimeLocale) import Data.Traversable (for) import Debug.Trace (trace) import Distribution.Compiler (CompilerFlavor(..)) @@ -83,20 +81,14 @@ obRunImports = [ "import qualified Obelisk.Run" profile :: MonadObelisk m - => Maybe FilePath + => FilePath -> m () -profile mProfileBaseName = withProjectRoot "." $ \root -> do +profile profileBaseName = withProjectRoot "." $ \root -> do freePort <- getFreePort assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets putLog Debug "Using profiled build of project." - time <- liftIO $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" <$> getCurrentTime - profileBaseName <- case mProfileBaseName of - Just baseName -> pure baseName - Nothing -> do - let profileDirectory = root "profile" - liftIO $ createDirectoryIfMissing False profileDirectory - pure $ profileDirectory time + liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName let -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] From 396cdadb61f95e82a4109ee2c3adaa99613b460e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 11:16:54 -0500 Subject: [PATCH 15/39] Document default value in ob profile command --- lib/command/src/Obelisk/Command.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 32759cfa2..3138b404a 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -112,7 +112,7 @@ obCommand cfg time root = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type." <> showDefault <> value ((fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time) <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> showDefault <> value ((fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time) <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" From 30183ed6aaa6cb8dc3f9b1b356bce32385e250da Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 13:33:50 -0500 Subject: [PATCH 16/39] Get project root & current time for ob profile later on Avoid showing it in the help documentation. Just do it when it is needed. --- lib/command/src/Obelisk/Command.hs | 34 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 3138b404a..dfbd00172 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -53,8 +53,8 @@ newtype ArgsConfig = ArgsConfig { _argsConfig_enableVmBuilderByDefault :: Bool } -args :: ArgsConfig -> UTCTime -> Maybe FilePath -> Parser Args -args cfg time root = Args <$> noHandoff <*> verbose <*> obCommand cfg time root +args :: ArgsConfig -> Parser Args +args cfg = Args <$> noHandoff <*> verbose <*> obCommand cfg noHandoff :: Parser Bool noHandoff = flag False True $ mconcat @@ -70,8 +70,8 @@ verbose = flag False True $ mconcat , help "Be more verbose" ] -argsInfo :: ArgsConfig -> UTCTime -> Maybe FilePath -> ParserInfo Args -argsInfo cfg time root = info (args cfg time root <**> helper) $ mconcat +argsInfo :: ArgsConfig -> ParserInfo Args +argsInfo cfg = info (args cfg <**> helper) $ mconcat [ fullDesc , progDesc "Manage Obelisk projects" ] @@ -90,7 +90,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile FilePath + | ObCommand_Profile (Maybe FilePath) | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -106,13 +106,13 @@ data ObInternal = ObInternal_ApplyPackages String String String [String] deriving Show -obCommand :: ArgsConfig -> UTCTime -> Maybe FilePath -> Parser ObCommand -obCommand cfg time root = hsubparser +obCommand :: ArgsConfig -> Parser ObCommand +obCommand cfg = hsubparser (mconcat [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> showDefault <> value ((fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time) <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -253,8 +253,8 @@ parserPrefs = defaultPrefs { prefShowHelpOnEmpty = True } -parseCLIArgs :: ArgsConfig -> UTCTime -> Maybe FilePath -> [String] -> IO Args -parseCLIArgs cfg time root = handleParseResult . execParserPure parserPrefs (argsInfo cfg time root) +parseCLIArgs :: ArgsConfig -> [String] -> IO Args +parseCLIArgs cfg = handleParseResult . execParserPure parserPrefs (argsInfo cfg) -- | Create an Obelisk config for the current process. mkObeliskConfig :: IO Obelisk @@ -309,15 +309,12 @@ main' argsCfg = do , "logging-level=" <> show logLevel ] - time <- liftIO $ getCurrentTime - root <- findProjectRoot "." - --TODO: We'd like to actually use the parser to determine whether to hand off, --but in the case where this implementation of 'ob' doesn't support all --arguments being passed along, this could fail. For now, we don't bother --with optparse-applicative until we've done the handoff. let go as = do - args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg time root) as) + args' <- liftIO $ handleParseResult (execParserPure parserPrefs (argsInfo argsCfg) as) case _args_noHandOffPassed args' of False -> return () True -> putLog Warning "--no-handoff should only be passed once and as the first argument; ignoring" @@ -374,7 +371,14 @@ ob = \case DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs ObCommand_Run -> run - ObCommand_Profile basePath -> profile basePath + ObCommand_Profile mBasePath -> do + basePath <- case mBasePath of + Just path -> pure path + Nothing -> do + time <- liftIO $ getCurrentTime + root <- findProjectRoot "." + pure $ (fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time + profile basePath ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk From 0d71538a07124d61d1d0b188e2822c0c55f47037 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 14:18:23 -0500 Subject: [PATCH 17/39] Fix ob profile to correctly take command arguments This makes the -o option optional again. Also makes it parse correctly. --- lib/command/src/Obelisk/Command.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index dfbd00172..94184fb66 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -112,7 +112,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> option auto (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH")) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" From 673c796ac7e2f98ada98b5bd39da9586b180f0dd Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 14:32:34 -0500 Subject: [PATCH 18/39] Avoid qualifying lens operators in Obelisk.Command.Run --- lib/command/src/Obelisk/Command/Run.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index fad170672..0ed573168 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -8,8 +8,7 @@ module Obelisk.Command.Run where import Control.Arrow ((&&&)) import Control.Exception (Exception, bracket) -import qualified Control.Lens as Lens -import Control.Lens (ifor) +import Control.Lens (ifor, (.~), (&)) import Control.Monad (filterM, unless) import Control.Monad.Except (runExceptT, throwError) import Control.Monad.IO.Class (liftIO) @@ -109,8 +108,8 @@ profile profileBaseName = withProjectRoot "." $ \root -> do , " buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ];" , "} \"ghc -x hs -prof -fno-prof-auto ${exeSource} -o $out\")" ] exePath <- nixCmd $ NixCmd_Build $ def - Lens.& nixBuildConfig_outLink Lens..~ OutLink_None - Lens.& nixCmdConfig_target Lens..~ Target + & nixBuildConfig_outLink .~ OutLink_None + & nixCmdConfig_target .~ Target { _target_path = Nothing , _target_attr = Nothing , _target_expr = Just nixBuildExpr } From 491697105f936d47fbeecb82cdc203a2c5ee911a Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 14:33:01 -0500 Subject: [PATCH 19/39] Delay getting port or assets until right before the In Obelisk.Command.Run, we need to get port and assets to start the server. But these values could change so we want to delay getting them until right before we actually run the server, potentially avoiding race conditions. --- lib/command/src/Obelisk/Command/Run.hs | 27 +++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 0ed573168..098858d4b 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -84,9 +84,6 @@ profile => FilePath -> m () profile profileBaseName = withProjectRoot "." $ \root -> do - freePort <- getFreePort - assets <- findProjectAssets root - putLog Debug $ "Assets impurely loaded from: " <> assets putLog Debug "Using profiled build of project." liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName let -- Sane flags to enable by default, enable time profiling + @@ -113,6 +110,9 @@ profile profileBaseName = withProjectRoot "." $ \root -> do { _target_path = Nothing , _target_attr = Nothing , _target_expr = Just nixBuildExpr } + assets <- findProjectAssets root + putLog Debug $ "Assets impurely loaded from: " <> assets + freePort <- getFreePort (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath ([show freePort, T.unpack assets] <> rtsFlags) _ <- liftIO $ waitForProcess ph pure () @@ -122,18 +122,17 @@ run => m () run = withProjectRoot "." $ \root -> do pkgs <- fmap toList . parsePackagesOrFail =<< getLocalPkgs root - freePort <- getFreePort - assets <- findProjectAssets root - putLog Debug $ "Assets impurely loaded from: " <> assets - let obRunExpr = unwords - [ "Obelisk.Run.run" - , show freePort - , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" - , "Backend.backend" - , "Frontend.frontend" - ] withGhciScript pkgs root $ \dotGhciPath -> do - runGhcid root True dotGhciPath pkgs $ Just obRunExpr + freePort <- getFreePort + assets <- findProjectAssets root + putLog Debug $ "Assets impurely loaded from: " <> assets + runGhcid root True dotGhciPath pkgs $ Just $ unwords + [ "Obelisk.Run.run" + , show freePort + , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")" + , "Backend.backend" + , "Frontend.frontend" + ] runRepl :: MonadObelisk m => m () runRepl = withProjectRoot "." $ \root -> do From 085461ba433b7673619ebf0103515b61d0f99475 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 14:34:33 -0500 Subject: [PATCH 20/39] =?UTF-8?q?Don=E2=80=99t=20rely=20on=20root=20for=20?= =?UTF-8?q?profile=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is easier to handle since we always know the executable will run in the project root. --- lib/command/src/Obelisk/Command.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 94184fb66..759d34037 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -13,7 +13,6 @@ import qualified Data.ByteString.Base16 as Base16 import qualified Data.ByteString.Lazy as LBS import Data.Foldable (for_) import Data.List -import Data.Maybe import qualified Data.Text as T import Data.Text.Encoding import Data.Text.Encoding.Error (lenientDecode) @@ -376,8 +375,7 @@ ob = \case Just path -> pure path Nothing -> do time <- liftIO $ getCurrentTime - root <- findProjectRoot "." - pure $ (fromMaybe "" root) "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time + pure $ "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time profile basePath ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) From f14f29033c85abbd33c359695c1fcb866fe5476c Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 14:51:51 -0500 Subject: [PATCH 21/39] Use quasiquotes for ob profile command This makes things much more readable --- lib/command/src/Obelisk/Command/Run.hs | 55 +++++++++++++++----------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 098858d4b..56b47670e 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -4,6 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE QuasiQuotes #-} module Obelisk.Command.Run where import Control.Arrow ((&&&)) @@ -22,6 +23,7 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set +import Data.String.Here (hereLit) import Data.Text (Text) import qualified Data.Text as T import Data.Traversable (for) @@ -73,12 +75,6 @@ data CabalPackageInfo = CabalPackageInfo preprocessorIdentifier :: String preprocessorIdentifier = "__preprocessor-apply-packages" --- | Imports needed to use Obelisk.Run -obRunImports :: [String] -obRunImports = [ "import qualified Obelisk.Run" - , "import qualified Frontend" - , "import qualified Backend" ] - profile :: MonadObelisk m => FilePath @@ -89,21 +85,32 @@ profile profileBaseName = withProjectRoot "." $ \root -> do let -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] - nixBuildExpr = unlines $ - [ "(with (import ./. {});" - , "let" - , " exeSource = obelisk.nixpkgs.writeText \"ob-run\" ''" - , "module Main where" - , "import Control.Exception" - , "import Reflex.Profiled" - , "import System.Environment" ] - <> obRunImports <> - [ "main :: IO ()" - , "main = getArgs >>= \\args -> (Obelisk.Run.run (read (args !! 0)) (Obelisk.Run.runServeAsset (args !! 1)) Backend.backend Frontend.frontend) `finally` writeProfilingData \"" <> profileBaseName <> ".rprof\"" - , " '';" - , "in obelisk.nixpkgs.runCommand \"ob-run\" {" - , " buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ];" - , "} \"ghc -x hs -prof -fno-prof-auto ${exeSource} -o $out\")" ] + nixBuildExpr = [hereLit| +with (import ./. {}); +let + exeSource = obelisk.nixpkgs.writeText "ob-run" '' +module Main where + +import Control.Exception +import Reflex.Profiled +import System.Environment + +import qualified Obelisk.Run +import qualified Frontend +import qualified Backend + +main :: IO () +main = do + args <- getArgs + let port = read $ args !! 0 + assets = args !! 1 + profileFile = (args !! 2) <> ".rprof" + Obelisk.Run.run port (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend `finally` writeProfilingData profileFile + ''; +in obelisk.nixpkgs.runCommand "ob-run" { + buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; +} "ghc -x hs -prof -fno-prof-auto ${exeSource} -o $out" +|] exePath <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_outLink .~ OutLink_None & nixCmdConfig_target .~ Target @@ -113,7 +120,7 @@ profile profileBaseName = withProjectRoot "." $ \root -> do assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets freePort <- getFreePort - (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath ([show freePort, T.unpack assets] <> rtsFlags) + (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath ([show freePort, T.unpack assets, profileBaseName] <> rtsFlags) _ <- liftIO $ waitForProcess ph pure () @@ -318,7 +325,9 @@ withGhciScript packageInfos pathBase f = do [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos) , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs) , if null modulesToLoad then "" else ":load " <> unwords modulesToLoad - ] <> obRunImports + , "import qualified Obelisk.Run" + , "import qualified Frontend" + , "import qualified Backend" ] withSystemTempDirectory "ob-ghci" $ \fp -> do let dotGhciPath = fp ".ghci" liftIO $ writeFile dotGhciPath dotGhci From ba85ebf82fb61754785d103b56152874f4a58613 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 12 Feb 2020 17:19:28 -0500 Subject: [PATCH 22/39] Use fixed reflex for profiling --- dep/reflex-platform/github.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dep/reflex-platform/github.json b/dep/reflex-platform/github.json index b5bd8a0f6..0e22ff515 100644 --- a/dep/reflex-platform/github.json +++ b/dep/reflex-platform/github.json @@ -1,8 +1,8 @@ { "owner": "reflex-frp", "repo": "reflex-platform", - "branch": "master", + "branch": "bump-reflex-0-6-4-1", "private": false, - "rev": "5429278830e1555a577f2550e045ce7f7164aa65", - "sha256": "1lp86cgccmim573rarsjny5vh0ygkfp5afq7006li0k9w2sw2d4c" + "rev": "38060894cb377160d1217305334f7f11202042d4", + "sha256": "0n47fcwj9szy3aqip0smmxdsp9fr78985rdslpmx2zdlkswmm5r1" } From 7ce5d5bf64b905b30b094e1cd14e66895048b5a0 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 13 Feb 2020 18:56:38 -0500 Subject: [PATCH 23/39] Set -threaded in ob profile --- lib/command/src/Obelisk/Command/Run.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 56b47670e..0b3ecfd3e 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -109,7 +109,7 @@ main = do ''; in obelisk.nixpkgs.runCommand "ob-run" { buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; -} "ghc -x hs -prof -fno-prof-auto ${exeSource} -o $out" +} "ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out" |] exePath <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_outLink .~ OutLink_None From 0743c6da65f3e55596113ec4524eccdb6b3bccb6 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 19 Feb 2020 11:37:15 -0500 Subject: [PATCH 24/39] Allow passing custom RTS flags to ob profile This allows custom flags to be provided by the user. For instance, there are multiple types of heap profiles that can be used to get different information from ob profile. These can be added with the --rts-flags option. --- lib/command/src/Obelisk/Command.hs | 8 ++++---- lib/command/src/Obelisk/Command/Run.hs | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 759d34037..aae5da9d3 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -89,7 +89,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile (Maybe FilePath) + | ObCommand_Profile (Maybe FilePath) (Maybe String) | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -111,7 +111,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> (optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) <*> (optional (strOption (long "rts-flags" <> help "RTS Flags to pass to the executable" <> metavar "FLAGS")))) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -370,13 +370,13 @@ ob = \case DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs ObCommand_Run -> run - ObCommand_Profile mBasePath -> do + ObCommand_Profile mBasePath rtsFlags -> do basePath <- case mBasePath of Just path -> pure path Nothing -> do time <- liftIO $ getCurrentTime pure $ "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time - profile basePath + profile basePath rtsFlags ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 0b3ecfd3e..2f98b4589 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -78,13 +78,15 @@ preprocessorIdentifier = "__preprocessor-apply-packages" profile :: MonadObelisk m => FilePath + -> Maybe String -> m () -profile profileBaseName = withProjectRoot "." $ \root -> do +profile profileBaseName mRtsFlags = withProjectRoot "." $ \root -> do putLog Debug "Using profiled build of project." liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName let -- Sane flags to enable by default, enable time profiling + -- closure heap profiling. - rtsFlags = [ "+RTS", "-p", "-po" <> profileBaseName, "-hc", "-RTS" ] + defaultRtsFlags = [ "-p", "-hc" ] + rtsFlags = maybe defaultRtsFlags words mRtsFlags nixBuildExpr = [hereLit| with (import ./. {}); let @@ -120,7 +122,14 @@ in obelisk.nixpkgs.runCommand "ob-run" { assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets freePort <- getFreePort - (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath ([show freePort, T.unpack assets, profileBaseName] <> rtsFlags) + (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath $ + [ show freePort + , T.unpack assets + , profileBaseName + , "+RTS" + , "-po" <> profileBaseName ] + <> rtsFlags + <> ["-RTS"] _ <- liftIO $ waitForProcess ph pure () From 42524b06975a66d985f489a57423ef6d8c4efdb5 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 19 Feb 2020 14:23:41 -0500 Subject: [PATCH 25/39] Avoid maybe in ob profile rts-flags This makes the default more obvious in the command line options. --- lib/command/src/Obelisk/Command.hs | 4 ++-- lib/command/src/Obelisk/Command/Run.hs | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index aae5da9d3..42627a308 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -89,7 +89,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile (Maybe FilePath) (Maybe String) + | ObCommand_Profile (Maybe FilePath) String | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -111,7 +111,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> (optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) <*> (optional (strOption (long "rts-flags" <> help "RTS Flags to pass to the executable" <> metavar "FLAGS")))) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> (optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) <*> (strOption (long "rts-flags" <> help "RTS Flags to pass to the executable." <> value "-p -hc" <> metavar "FLAGS" <> showDefault))) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 2f98b4589..72b70d573 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -78,16 +78,12 @@ preprocessorIdentifier = "__preprocessor-apply-packages" profile :: MonadObelisk m => FilePath - -> Maybe String + -> String -> m () -profile profileBaseName mRtsFlags = withProjectRoot "." $ \root -> do +profile profileBaseName rtsFlags = withProjectRoot "." $ \root -> do putLog Debug "Using profiled build of project." liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName - let -- Sane flags to enable by default, enable time profiling + - -- closure heap profiling. - defaultRtsFlags = [ "-p", "-hc" ] - rtsFlags = maybe defaultRtsFlags words mRtsFlags - nixBuildExpr = [hereLit| + let nixBuildExpr = [hereLit| with (import ./. {}); let exeSource = obelisk.nixpkgs.writeText "ob-run" '' @@ -127,9 +123,9 @@ in obelisk.nixpkgs.runCommand "ob-run" { , T.unpack assets , profileBaseName , "+RTS" - , "-po" <> profileBaseName ] - <> rtsFlags - <> ["-RTS"] + , "-po" <> profileBaseName + ] <> words rtsFlags + <> [ "-RTS" ] _ <- liftIO $ waitForProcess ph pure () From 38e68c0fbd33805dc238af21d0f72f000b7df4ea Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 19 Feb 2020 14:34:11 -0500 Subject: [PATCH 26/39] Make commmand line argument handling more clear for ob profile - Use list of strings for rts args - Move time handling to the profile function, out of argument handling --- lib/command/src/Obelisk/Command.hs | 14 +++----------- lib/command/src/Obelisk/Command/Run.hs | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 42627a308..138490e7f 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -16,8 +16,6 @@ import Data.List import qualified Data.Text as T import Data.Text.Encoding import Data.Text.Encoding.Error (lenientDecode) -import Data.Time.Clock -import Data.Time.Format (formatTime, defaultTimeLocale) import GHC.StaticPtr import Options.Applicative import Options.Applicative.Help.Pretty (text, (<$$>)) @@ -89,7 +87,7 @@ data ObCommand = ObCommand_Init InitSource Bool | ObCommand_Deploy DeployCommand | ObCommand_Run - | ObCommand_Profile (Maybe FilePath) String + | ObCommand_Profile String [String] | ObCommand_Thunk ThunkCommand | ObCommand_Repl | ObCommand_Watch @@ -111,7 +109,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> (optional (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH"))) <*> (strOption (long "rts-flags" <> help "RTS Flags to pass to the executable." <> value "-p -hc" <> metavar "FLAGS" <> showDefault))) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH" <> value "profile/%Y-%m-%dT%H:%M:%S" <> showDefault)) <*> (words <$> strOption (long "rts-flags" <> help "RTS Flags to pass to the executable." <> value "-p -hc" <> metavar "FLAGS" <> showDefault))) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -370,13 +368,7 @@ ob = \case DeployCommand_Update -> deployUpdate "." DeployCommand_Test (platform, extraArgs) -> deployMobile platform extraArgs ObCommand_Run -> run - ObCommand_Profile mBasePath rtsFlags -> do - basePath <- case mBasePath of - Just path -> pure path - Nothing -> do - time <- liftIO $ getCurrentTime - pure $ "profile" formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S" time - profile basePath rtsFlags + ObCommand_Profile basePath rtsFlags -> profile basePath rtsFlags ObCommand_Thunk tc -> case tc of ThunkCommand_Update thunks config -> for_ thunks (updateThunkToLatest config) ThunkCommand_Unpack thunks -> for_ thunks unpackThunk diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 72b70d573..a89d1f4ab 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -26,6 +26,8 @@ import qualified Data.Set as Set import Data.String.Here (hereLit) import Data.Text (Text) import qualified Data.Text as T +import Data.Time.Clock +import Data.Time.Format (formatTime, defaultTimeLocale) import Data.Traversable (for) import Debug.Trace (trace) import Distribution.Compiler (CompilerFlavor(..)) @@ -77,12 +79,19 @@ preprocessorIdentifier = "__preprocessor-apply-packages" profile :: MonadObelisk m - => FilePath - -> String + => String + -> [String] -> m () -profile profileBaseName rtsFlags = withProjectRoot "." $ \root -> do +profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do putLog Debug "Using profiled build of project." + + time <- liftIO $ getCurrentTime + let profileBaseName = formatTime defaultTimeLocale profileBasePattern time + + putLog Debug $ T.pack $ "Storing profiled data under base name of " <> profileBaseName + liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName + let nixBuildExpr = [hereLit| with (import ./. {}); let @@ -124,7 +133,7 @@ in obelisk.nixpkgs.runCommand "ob-run" { , profileBaseName , "+RTS" , "-po" <> profileBaseName - ] <> words rtsFlags + ] <> rtsFlags <> [ "-RTS" ] _ <- liftIO $ waitForProcess ph pure () From 92c1a4aebdbe486980fb1c0be534cde0de70edb9 Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Thu, 20 Feb 2020 11:59:55 -0500 Subject: [PATCH 27/39] Wrap 'waitForProcess' to avoid System.Process imports --- lib/cliapp/src/Obelisk/CliApp.hs | 1 + lib/cliapp/src/Obelisk/CliApp/Process.hs | 11 ++++++++--- lib/command/src/Obelisk/Command/Project.hs | 7 +++---- lib/command/src/Obelisk/Command/Run.hs | 7 ++++--- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/cliapp/src/Obelisk/CliApp.hs b/lib/cliapp/src/Obelisk/CliApp.hs index 0f6792e97..b7c6e37ae 100644 --- a/lib/cliapp/src/Obelisk/CliApp.hs +++ b/lib/cliapp/src/Obelisk/CliApp.hs @@ -51,6 +51,7 @@ module Obelisk.CliApp , setDelegateCtlc , setEnvOverride , shell + , waitForProcess ) where import Control.Monad.Log (Severity (..)) diff --git a/lib/cliapp/src/Obelisk/CliApp/Process.hs b/lib/cliapp/src/Obelisk/CliApp/Process.hs index 2f1843f2c..24bc0e5a1 100644 --- a/lib/cliapp/src/Obelisk/CliApp/Process.hs +++ b/lib/cliapp/src/Obelisk/CliApp/Process.hs @@ -28,6 +28,7 @@ module Obelisk.CliApp.Process , setDelegateCtlc , setEnvOverride , shell + , waitForProcess ) where import Control.Monad ((<=<), join, void) @@ -51,7 +52,7 @@ import System.IO (Handle) import System.IO.Streams (InputStream, handleToInputStream) import qualified System.IO.Streams as Streams import System.IO.Streams.Concurrent (concurrentMerge) -import System.Process (CreateProcess, ProcessHandle, StdStream (CreatePipe), std_err, std_out, waitForProcess) +import System.Process (CreateProcess, ProcessHandle, StdStream (CreatePipe), std_err, std_out) import qualified System.Process as Process import qualified Data.Aeson as Aeson @@ -143,7 +144,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do outText <- liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out putLogRaw sev_out outText - liftIO (waitForProcess p) >>= \case + waitForProcess p >>= \case ExitSuccess -> pure outText ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code @@ -218,7 +219,7 @@ withProcess process f = do -- TODO: Use bracket. f out err -- Pass the handles to the passed function - liftIO (waitForProcess p) >>= \case + waitForProcess p >>= \case ExitSuccess -> return (out, err) ExitFailure code -> throwError $ review asProcessFailure $ ProcessFailure (Process.cmdspec $ _processSpec_createProcess process) code @@ -235,6 +236,10 @@ streamToLog stream = fix $ \loop -> do Nothing -> return () Just (sev, line) -> putLogRaw sev (T.decodeUtf8With lenientDecode line) >> loop +-- | Wrapper around `System.Process.waitForProcess` +waitForProcess :: MonadIO m => ProcessHandle -> m ExitCode +waitForProcess = liftIO . Process.waitForProcess + -- | Pretty print a 'CmdSpec' reconstructCommand :: Process.CmdSpec -> Text reconstructCommand p = case p of diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index 65078370f..346b2fabd 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -42,7 +42,6 @@ import System.IO.Temp import System.IO.Unsafe (unsafePerformIO) import System.Posix (FileStatus, FileMode, CMode (..), UserID, deviceID, fileID, fileMode, fileOwner, getFileStatus, getRealUserID) import System.Posix.Files -import System.Process (waitForProcess) import GitHub.Data.GitData (Branch) import GitHub.Data.Name (Name) @@ -288,7 +287,7 @@ nixShellWithPkgs root isPure chdirToRoot packageNamesAndPaths command = do [ rawArg "root" $ toNixPath $ if chdirToRoot then "." else root , strArg "pkgs" (T.unpack $ decodeUtf8 $ BSL.toStrict $ Json.encode packageNamesAndAbsPaths) ] - void $ liftIO $ waitForProcess ph + void $ waitForProcess ph nixShellWithHoogle :: MonadObelisk m => FilePath -> Bool -> String -> Maybe String -> m () nixShellWithHoogle root isPure shell' command = do @@ -299,7 +298,7 @@ nixShellWithHoogle root isPure shell' command = do \userSettings = super.userSettings // { withHoogle = true; };\ \})).project.shells.${shell}" & nixShellConfig_common . nixCmdConfig_args .~ [ strArg "shell" shell' ] - void $ liftIO $ waitForProcess ph + void $ waitForProcess ph projectShell :: MonadObelisk m => FilePath -> Bool -> String -> Maybe String -> m () projectShell root isPure shellName command = do @@ -307,7 +306,7 @@ projectShell root isPure shellName command = do (_, _, _, ph) <- createProcess_ "runNixShellAttr" $ setCwd (Just root) $ nixShellRunProc $ defShellConfig & nixShellConfig_common . nixCmdConfig_target . target_path ?~ "default.nix" & nixShellConfig_common . nixCmdConfig_target . target_attr ?~ ("shells." <> shellName) - void $ liftIO $ waitForProcess ph + void $ waitForProcess ph findProjectAssets :: MonadObelisk m => FilePath -> m Text findProjectAssets root = do diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 0485989b7..8a4f620df 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -50,10 +50,11 @@ import System.Environment (getExecutablePath) import System.FilePath import qualified System.Info import System.IO.Temp (withSystemTempDirectory) -import System.Process (waitForProcess) import Obelisk.App (MonadObelisk) -import Obelisk.CliApp (Severity (..) , failWith, putLog, proc, readCreateProcessWithExitCode, setCwd, setDelegateCtlc, createProcess_) +import Obelisk.CliApp (Severity (..), + failWith, putLog, proc, readCreateProcessWithExitCode, + setCwd, setDelegateCtlc, createProcess_, waitForProcess) import Obelisk.Command.Nix import Obelisk.Command.Project (nixShellWithPkgs, toImplDir, withProjectRoot, findProjectAssets) import Obelisk.Command.Thunk (attrCacheFileName) @@ -136,7 +137,7 @@ in obelisk.nixpkgs.runCommand "ob-run" { , "-po" <> profileBaseName ] <> rtsFlags <> [ "-RTS" ] - _ <- liftIO $ waitForProcess ph + _ <- waitForProcess ph pure () run From a505c203b873883e0c16d4da4d1607b203904767 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 20 Feb 2020 12:12:25 -0500 Subject: [PATCH 28/39] Move profile flags top level adds: - profileRtsFlag - profileOutputFlag to avoid massively long lines. --- lib/command/src/Obelisk/Command.hs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 138490e7f..3e0bcaf3a 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -109,7 +109,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> (strOption (long "output" <> short 'o' <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." <> metavar "PATH" <> value "profile/%Y-%m-%dT%H:%M:%S" <> showDefault)) <*> (words <$> strOption (long "rts-flags" <> help "RTS Flags to pass to the executable." <> value "-p -hc" <> metavar "FLAGS" <> showDefault))) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (ObCommand_Profile <$> profileOutputFlag <*> profileRtsFlag) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -224,6 +224,21 @@ thunkCommand = hsubparser $ mconcat , command "pack" $ info (ThunkCommand_Pack <$> some thunkDirectoryParser <*> thunkPackConfig) $ progDesc "Pack git checkout into thunk that points at the current branch's upstream" ] +profileRtsFlag :: Parser [String] +profileRtsFlag = words <$> strOption ( long "rts-flags" + <> help "RTS Flags to pass to the executable." + <> value "-p -hc" + <> metavar "FLAGS" + <> showDefault) + +profileOutputFlag :: Parser String +profileOutputFlag = strOption ( long "output" + <> short 'o' + <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." + <> metavar "PATH" + <> value "profile/%Y-%m-%dT%H:%M:%S" + <> showDefault) + data ShellOpts = ShellOpts { _shellOpts_shell :: String From 876957b4e7f0f8dedb27e8b0cdce4db3208e7cb0 Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Thu, 20 Feb 2020 12:16:32 -0500 Subject: [PATCH 29/39] Clean lint and style --- lib/command/src/Obelisk/Command.hs | 38 +++++++++++--------- lib/command/src/Obelisk/Command/Run.hs | 48 +++++++++++++------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/lib/command/src/Obelisk/Command.hs b/lib/command/src/Obelisk/Command.hs index 3e0bcaf3a..ef5e43e00 100644 --- a/lib/command/src/Obelisk/Command.hs +++ b/lib/command/src/Obelisk/Command.hs @@ -109,7 +109,7 @@ obCommand cfg = hsubparser [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project" , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project" , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode" - , command "profile" $ info (ObCommand_Profile <$> profileOutputFlag <*> profileRtsFlag) $ progDesc "Run current project with profiling enabled" + , command "profile" $ info (uncurry ObCommand_Profile <$> profileCommand) $ progDesc "Run current project with profiling enabled" , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories" , command "repl" $ info (pure ObCommand_Repl) $ progDesc "Open an interactive interpreter" , command "watch" $ info (pure ObCommand_Watch) $ progDesc "Watch current project for errors and warnings" @@ -135,7 +135,7 @@ deployCommand cfg = hsubparser $ mconcat where platformP = hsubparser $ mconcat [ command "android" $ info (pure (Android, [])) mempty - , command "ios" $ info ((,) <$> pure IOS <*> (fmap pure $ strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal"))) mempty + , command "ios" $ info ((,) <$> pure IOS <*> fmap pure (strArgument (metavar "TEAMID" <> help "Your Team ID - found in the Apple developer portal"))) mempty ] remoteBuilderParser :: Parser (Maybe RemoteBuilder) @@ -185,6 +185,25 @@ data DeployInitOpts = DeployInitOpts } deriving Show +profileCommand :: Parser (String, [String]) +profileCommand = (,) + <$> strOption + ( long "output" + <> short 'o' + <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." + <> metavar "PATH" + <> value "profile/%Y-%m-%dT%H:%M:%S" + <> showDefault + ) + <*> (words <$> strOption + ( long "rts-flags" + <> help "RTS Flags to pass to the executable." + <> value "-p -hc" + <> metavar "FLAGS" + <> showDefault + )) + + --TODO: Result should provide normalised path and also original user input for error reporting. thunkDirectoryParser :: Parser FilePath thunkDirectoryParser = fmap (dropTrailingPathSeparator . normalise) . strArgument $ mconcat @@ -224,21 +243,6 @@ thunkCommand = hsubparser $ mconcat , command "pack" $ info (ThunkCommand_Pack <$> some thunkDirectoryParser <*> thunkPackConfig) $ progDesc "Pack git checkout into thunk that points at the current branch's upstream" ] -profileRtsFlag :: Parser [String] -profileRtsFlag = words <$> strOption ( long "rts-flags" - <> help "RTS Flags to pass to the executable." - <> value "-p -hc" - <> metavar "FLAGS" - <> showDefault) - -profileOutputFlag :: Parser String -profileOutputFlag = strOption ( long "output" - <> short 'o' - <> help "Base output to use for profiling output. Suffixes are added to this based on the profiling type. Defaults to a timestamped path in the profile/ directory in the project's root." - <> metavar "PATH" - <> value "profile/%Y-%m-%dT%H:%M:%S" - <> showDefault) - data ShellOpts = ShellOpts { _shellOpts_shell :: String diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 8a4f620df..9491c844f 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -10,7 +10,7 @@ module Obelisk.Command.Run where import Control.Arrow ((&&&)) import Control.Exception (Exception, bracket) import Control.Lens (ifor, (.~), (&)) -import Control.Monad (filterM, unless) +import Control.Monad (filterM, unless, void) import Control.Monad.Except (runExceptT, throwError) import Control.Monad.IO.Class (liftIO) import Control.Monad.Reader (MonadIO) @@ -26,7 +26,7 @@ import qualified Data.Set as Set import Data.String.Here (hereLit) import Data.Text (Text) import qualified Data.Text as T -import Data.Time.Clock +import Data.Time.Clock (getCurrentTime) import Data.Time.Format (formatTime, defaultTimeLocale) import Data.Traversable (for) import Debug.Trace (trace) @@ -87,14 +87,33 @@ profile profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do putLog Debug "Using profiled build of project." - time <- liftIO $ getCurrentTime + time <- liftIO getCurrentTime let profileBaseName = formatTime defaultTimeLocale profileBasePattern time putLog Debug $ T.pack $ "Storing profiled data under base name of " <> profileBaseName liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName - let nixBuildExpr = [hereLit| + exePath <- nixCmd $ NixCmd_Build $ def + & nixBuildConfig_outLink .~ OutLink_None + & nixCmdConfig_target .~ Target + { _target_path = Nothing + , _target_attr = Nothing + , _target_expr = Just nixBuildExpr } + assets <- findProjectAssets root + putLog Debug $ "Assets impurely loaded from: " <> assets + freePort <- getFreePort + (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath $ + [ show freePort + , T.unpack assets + , profileBaseName + , "+RTS" + , "-po" <> profileBaseName + ] <> rtsFlags + <> [ "-RTS" ] + void $ waitForProcess ph + where + nixBuildExpr = [hereLit| with (import ./. {}); let exeSource = obelisk.nixpkgs.writeText "ob-run" '' @@ -120,25 +139,6 @@ in obelisk.nixpkgs.runCommand "ob-run" { buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; } "ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out" |] - exePath <- nixCmd $ NixCmd_Build $ def - & nixBuildConfig_outLink .~ OutLink_None - & nixCmdConfig_target .~ Target - { _target_path = Nothing - , _target_attr = Nothing - , _target_expr = Just nixBuildExpr } - assets <- findProjectAssets root - putLog Debug $ "Assets impurely loaded from: " <> assets - freePort <- getFreePort - (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath $ - [ show freePort - , T.unpack assets - , profileBaseName - , "+RTS" - , "-po" <> profileBaseName - ] <> rtsFlags - <> [ "-RTS" ] - _ <- waitForProcess ph - pure () run :: MonadObelisk m @@ -336,7 +336,7 @@ withGhciScript packageInfos pathBase f = do , [ "Backend" | "backend" `Set.member` packageNames ] , [ "Frontend" | "frontend" `Set.member` packageNames ] ] - dotGhci = unlines $ + dotGhci = unlines -- TODO: Shell escape [ ":set -F -pgmF " <> selfExe <> " -optF " <> preprocessorIdentifier <> " " <> unwords (map (("-optF " <>) . makeRelative pathBase . _cabalPackageInfo_packageFile) packageInfos) , ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs) From 8a2e1ab5a63ea1f63964a496fdf3329101997cf4 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Thu, 20 Feb 2020 14:02:43 -0500 Subject: [PATCH 30/39] Move profiledObRun script to default.nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file is just a static nix script. We can avoid hereDoc and just hit the attr directly. In addition, add the skeleton’s profiledObRun to the release.nix. --- all-builds.nix | 1 + default.nix | 27 +++++++++++++++++++- lib/command/src/Obelisk/Command/Run.hs | 34 +++----------------------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/all-builds.nix b/all-builds.nix index 293ecc7a3..2c40386b8 100644 --- a/all-builds.nix +++ b/all-builds.nix @@ -68,6 +68,7 @@ let iosSkeleton = (import ./skeleton { inherit obelisk; }).ios.frontend; nameSuffix = if profiling then "profiled" else "unprofiled"; packages = { + skeletonProfiledObRun = skeleton.profiledObRun; inherit command serverSkeletonShell diff --git a/default.nix b/default.nix index 4181fb760..81e3b2180 100644 --- a/default.nix +++ b/default.nix @@ -374,7 +374,32 @@ in rec { linuxExe = serverOn (projectOut "x86_64-linux"); dummyVersion = "Version number is only available for deployments"; in mainProjectOut // { - profiled = projectOut { inherit system; enableLibraryProfiling = true; }; + + profiledObRun = let + profiled = projectOut { inherit system; enableLibraryProfiling = true; }; + exeSource = builtins.toFile "ob-run.hs" '' + module Main where + + import Control.Exception + import Reflex.Profiled + import System.Environment + + import qualified Obelisk.Run + import qualified Frontend + import qualified Backend + + main :: IO () + main = do + args <- getArgs + let port = read $ args !! 0 + assets = args !! 1 + profileFile = (args !! 2) <> ".rprof" + Obelisk.Run.run port (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend `finally` writeProfilingData profileFile + ''; + in nixpkgs.runCommand "ob-run" { + buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; + } "ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out"; + linuxExeConfigurable = linuxExe; linuxExe = linuxExe dummyVersion; exe = serverOn mainProjectOut dummyVersion; diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 9491c844f..f055fdd41 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -23,7 +23,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import qualified Data.Set as Set -import Data.String.Here (hereLit) import Data.Text (Text) import qualified Data.Text as T import Data.Time.Clock (getCurrentTime) @@ -97,9 +96,9 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do exePath <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_outLink .~ OutLink_None & nixCmdConfig_target .~ Target - { _target_path = Nothing - , _target_attr = Nothing - , _target_expr = Just nixBuildExpr } + { _target_path = Just "." + , _target_attr = Just "profiledObRun" + , _target_expr = Nothing } assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets freePort <- getFreePort @@ -112,33 +111,6 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do ] <> rtsFlags <> [ "-RTS" ] void $ waitForProcess ph - where - nixBuildExpr = [hereLit| -with (import ./. {}); -let - exeSource = obelisk.nixpkgs.writeText "ob-run" '' -module Main where - -import Control.Exception -import Reflex.Profiled -import System.Environment - -import qualified Obelisk.Run -import qualified Frontend -import qualified Backend - -main :: IO () -main = do - args <- getArgs - let port = read $ args !! 0 - assets = args !! 1 - profileFile = (args !! 2) <> ".rprof" - Obelisk.Run.run port (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend `finally` writeProfilingData profileFile - ''; -in obelisk.nixpkgs.runCommand "ob-run" { - buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; -} "ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out" -|] run :: MonadObelisk m From 46accb430cac13da47cbef59255546b9399c936c Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Fri, 21 Feb 2020 15:44:40 -0500 Subject: [PATCH 31/39] Move profiledObRun to __unstable__ attr --- all-builds.nix | 2 +- default.nix | 2 +- lib/command/src/Obelisk/Command/Run.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/all-builds.nix b/all-builds.nix index 2c40386b8..9b1a59245 100644 --- a/all-builds.nix +++ b/all-builds.nix @@ -68,7 +68,7 @@ let iosSkeleton = (import ./skeleton { inherit obelisk; }).ios.frontend; nameSuffix = if profiling then "profiled" else "unprofiled"; packages = { - skeletonProfiledObRun = skeleton.profiledObRun; + skeletonProfiledObRun = skeleton.__unstable__.profiledObRun; inherit command serverSkeletonShell diff --git a/default.nix b/default.nix index 81e3b2180..7d99207dd 100644 --- a/default.nix +++ b/default.nix @@ -375,7 +375,7 @@ in rec { dummyVersion = "Version number is only available for deployments"; in mainProjectOut // { - profiledObRun = let + __unstable__.profiledObRun = let profiled = projectOut { inherit system; enableLibraryProfiling = true; }; exeSource = builtins.toFile "ob-run.hs" '' module Main where diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index f055fdd41..55efafc0f 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -97,7 +97,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do & nixBuildConfig_outLink .~ OutLink_None & nixCmdConfig_target .~ Target { _target_path = Just "." - , _target_attr = Just "profiledObRun" + , _target_attr = Just "__unstable__.profiledObRun" , _target_expr = Nothing } assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets From 772b42edb3efe9ef53dc6572064c32b5aae7399c Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 24 Feb 2020 11:26:28 -0500 Subject: [PATCH 32/39] Fix CI by removing skeletonProfiledObRun from pinBuildInputs --- all-builds.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/all-builds.nix b/all-builds.nix index 9b1a59245..d698e799e 100644 --- a/all-builds.nix +++ b/all-builds.nix @@ -87,7 +87,8 @@ let in packages // { cache = reflex-platform.pinBuildInputs "obelisk-${system}-${nameSuffix}" - (collect packages); + # skeletonProfiledObRun is a binary, so can’t be used in pinBuildInputs + (collect (builtins.removeAttrs ["skeletonProfiledObRun"] packages)); }; perProfiling = { From 9fb928968ac370c56bc30a437e124044bfd5265e Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 24 Feb 2020 11:37:03 -0500 Subject: [PATCH 33/39] Make profileObRun a directory instead of a binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pinBuildInputs tries to run binaries when they exist. We don’t want that, so we need to wrap it in a directory with $out/bin/ob-run containing the actual binary. --- all-builds.nix | 3 +-- default.nix | 5 ++++- lib/command/src/Obelisk/Command/Run.hs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/all-builds.nix b/all-builds.nix index d698e799e..9b1a59245 100644 --- a/all-builds.nix +++ b/all-builds.nix @@ -87,8 +87,7 @@ let in packages // { cache = reflex-platform.pinBuildInputs "obelisk-${system}-${nameSuffix}" - # skeletonProfiledObRun is a binary, so can’t be used in pinBuildInputs - (collect (builtins.removeAttrs ["skeletonProfiledObRun"] packages)); + (collect packages); }; perProfiling = { diff --git a/default.nix b/default.nix index 7d99207dd..69fbd56e8 100644 --- a/default.nix +++ b/default.nix @@ -398,7 +398,10 @@ in rec { ''; in nixpkgs.runCommand "ob-run" { buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; - } "ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out"; + } '' + mkdir -p $out/bin/ob-run + ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out/bin/ob-run + ''; linuxExeConfigurable = linuxExe; linuxExe = linuxExe dummyVersion; diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 55efafc0f..29413a49e 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -93,7 +93,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName - exePath <- nixCmd $ NixCmd_Build $ def + outPath <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_outLink .~ OutLink_None & nixCmdConfig_target .~ Target { _target_path = Just "." @@ -102,7 +102,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets freePort <- getFreePort - (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc exePath $ + (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath "bin" "ob-run") $ [ show freePort , T.unpack assets , profileBaseName From 3c804683176407cfa830db48b886183bf6da4371 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 24 Feb 2020 11:43:50 -0500 Subject: [PATCH 34/39] Only make $out/bin/ dir for profiledObRun $out/bin/ob-run was a mistake --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 69fbd56e8..d70fb6525 100644 --- a/default.nix +++ b/default.nix @@ -399,7 +399,7 @@ in rec { in nixpkgs.runCommand "ob-run" { buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ]; } '' - mkdir -p $out/bin/ob-run + mkdir -p $out/bin/ ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out/bin/ob-run ''; From ee5b42e75ffdb166d32ecdc74fe81a0b51d319fc Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Mon, 24 Feb 2020 13:06:26 -0500 Subject: [PATCH 35/39] Create parent directory as well in ob profile templates --- lib/command/src/Obelisk/Command/Run.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 29413a49e..611278bd7 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -91,7 +91,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do putLog Debug $ T.pack $ "Storing profiled data under base name of " <> profileBaseName - liftIO $ createDirectoryIfMissing False $ takeDirectory profileBaseName + liftIO $ createDirectoryIfMissing True $ takeDirectory profileBaseName outPath <- nixCmd $ NixCmd_Build $ def & nixBuildConfig_outLink .~ OutLink_None From 795fd9d3b0949e2f55531d22205cb480aaf512f9 Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Mon, 24 Feb 2020 13:07:47 -0500 Subject: [PATCH 36/39] Make ob profile work in subdirectories --- lib/command/src/Obelisk/Command/Nix.hs | 32 +++++++++++++++++--------- lib/command/src/Obelisk/Command/Run.hs | 31 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Nix.hs b/lib/command/src/Obelisk/Command/Nix.hs index 6198e6d8b..b7b097534 100644 --- a/lib/command/src/Obelisk/Command/Nix.hs +++ b/lib/command/src/Obelisk/Command/Nix.hs @@ -32,6 +32,8 @@ module Obelisk.Command.Nix , boolArg , nixCmd + , nixCmdProc + , nixCmdProc' , rawArg , runNixShellConfig , strArg @@ -188,27 +190,35 @@ runNixShellConfig cfg = mconcat ["--run", run] | run <- maybeToList $ cfg ^. nixShellConfig_run ] -nixCmd :: MonadObelisk m => NixCmd -> m FilePath -nixCmd cmdCfg = withSpinner' ("Running " <> cmd <> desc) (Just $ const $ "Built " <> desc) $ do - output <- readProcessAndLogStderr Debug $ proc (T.unpack cmd) options - -- Remove final newline that Nix appends - Just (outPath, '\n') <- pure $ T.unsnoc output - pure $ T.unpack outPath +nixCmdProc :: NixCmd -> ProcessSpec +nixCmdProc = fst . nixCmdProc' + +nixCmdProc' :: NixCmd -> (ProcessSpec, T.Text) +nixCmdProc' cmdCfg = (proc (T.unpack cmd) options, cmd) where - (cmd, options, commonCfg) = case cmdCfg of + (cmd, options) = case cmdCfg of NixCmd_Build cfg' -> ( "nix-build" , runNixBuildConfig cfg' - , cfg' ^. nixCommonConfig ) NixCmd_Instantiate cfg' -> ( "nix-instantiate" , runNixInstantiateConfig cfg' - , cfg' ^. nixCommonConfig ) + +nixCmd :: MonadObelisk m => NixCmd -> m FilePath +nixCmd cmdCfg = withSpinner' ("Running " <> cmd <> desc) (Just $ const $ "Built " <> desc) $ do + output <- readProcessAndLogStderr Debug cmdProc + -- Remove final newline that Nix appends + Just (outPath, '\n') <- pure $ T.unsnoc output + pure $ T.unpack outPath + where + (cmdProc, cmd) = nixCmdProc' cmdCfg + commonCfg = case cmdCfg of + NixCmd_Build cfg' -> cfg' ^. nixCommonConfig + NixCmd_Instantiate cfg' -> cfg' ^. nixCommonConfig path = commonCfg ^. nixCmdConfig_target . target_path desc = T.pack $ mconcat $ catMaybes - [ (" on " <>) <$> path + [ ("on " <>) <$> path , (\a -> " [" <> a <> "]") <$> (commonCfg ^. nixCmdConfig_target . target_attr) ] - diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 611278bd7..3d4518595 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -51,9 +51,19 @@ import qualified System.Info import System.IO.Temp (withSystemTempDirectory) import Obelisk.App (MonadObelisk) -import Obelisk.CliApp (Severity (..), - failWith, putLog, proc, readCreateProcessWithExitCode, - setCwd, setDelegateCtlc, createProcess_, waitForProcess) +import Obelisk.CliApp ( + Severity (..), + createProcess_, + failWith, + proc, + putLog, + readCreateProcessWithExitCode, + readProcessAndLogStderr, + setCwd, + setDelegateCtlc, + waitForProcess, + withSpinner, + ) import Obelisk.Command.Nix import Obelisk.Command.Project (nixShellWithPkgs, toImplDir, withProjectRoot, findProjectAssets) import Obelisk.Command.Thunk (attrCacheFileName) @@ -93,12 +103,15 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do liftIO $ createDirectoryIfMissing True $ takeDirectory profileBaseName - outPath <- nixCmd $ NixCmd_Build $ def - & nixBuildConfig_outLink .~ OutLink_None - & nixCmdConfig_target .~ Target - { _target_path = Just "." - , _target_attr = Just "__unstable__.profiledObRun" - , _target_expr = Nothing } + outPath <- withSpinner "Building profiled executable" $ + fmap (T.unpack . T.strip) $ readProcessAndLogStderr Debug $ setCwd (Just root) $ nixCmdProc $ + NixCmd_Build $ def + & nixBuildConfig_outLink .~ OutLink_None + & nixCmdConfig_target .~ Target + { _target_path = Just "." + , _target_attr = Just "__unstable__.profiledObRun" + , _target_expr = Nothing + } assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets freePort <- getFreePort From 6b15a33987f905475161c62ad6fde21dda57410a Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Mon, 24 Feb 2020 14:05:13 -0500 Subject: [PATCH 37/39] Fix ob profile from subdirectory --- lib/command/src/Obelisk/Command/Run.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 3d4518595..39a0d479c 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -101,7 +101,7 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do putLog Debug $ T.pack $ "Storing profiled data under base name of " <> profileBaseName - liftIO $ createDirectoryIfMissing True $ takeDirectory profileBaseName + liftIO $ createDirectoryIfMissing True $ takeDirectory $ root profileBaseName outPath <- withSpinner "Building profiled executable" $ fmap (T.unpack . T.strip) $ readProcessAndLogStderr Debug $ setCwd (Just root) $ nixCmdProc $ From cf06275161147444326fece071cceb43929cb68c Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Mon, 24 Feb 2020 14:08:34 -0500 Subject: [PATCH 38/39] Don't create ob profile out directories until everything has built --- lib/command/src/Obelisk/Command/Run.hs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Run.hs b/lib/command/src/Obelisk/Command/Run.hs index 39a0d479c..7a03e1c91 100644 --- a/lib/command/src/Obelisk/Command/Run.hs +++ b/lib/command/src/Obelisk/Command/Run.hs @@ -96,13 +96,6 @@ profile profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do putLog Debug "Using profiled build of project." - time <- liftIO getCurrentTime - let profileBaseName = formatTime defaultTimeLocale profileBasePattern time - - putLog Debug $ T.pack $ "Storing profiled data under base name of " <> profileBaseName - - liftIO $ createDirectoryIfMissing True $ takeDirectory $ root profileBaseName - outPath <- withSpinner "Building profiled executable" $ fmap (T.unpack . T.strip) $ readProcessAndLogStderr Debug $ setCwd (Just root) $ nixCmdProc $ NixCmd_Build $ def @@ -114,6 +107,10 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do } assets <- findProjectAssets root putLog Debug $ "Assets impurely loaded from: " <> assets + time <- liftIO getCurrentTime + let profileBaseName = formatTime defaultTimeLocale profileBasePattern time + liftIO $ createDirectoryIfMissing True $ takeDirectory $ root profileBaseName + putLog Debug $ "Storing profiled data under base name of " <> T.pack (root profileBaseName) freePort <- getFreePort (_, _, _, ph) <- createProcess_ "runProfExe" $ setCwd (Just root) $ setDelegateCtlc True $ proc (outPath "bin" "ob-run") $ [ show freePort From b09c540381fe1b26fe77d93c9555de6f6934cac8 Mon Sep 17 00:00:00 2001 From: Elliot Cameron Date: Fri, 13 Mar 2020 18:02:06 -0400 Subject: [PATCH 39/39] Put ob profile ChangeLog entry in correct place --- ChangeLog.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 394adb5b7..a59003722 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,6 +9,7 @@ This project's release branch is `master`. This log is written from the perspect * Fix `ob deploy test android` to work. ([#645](https://github.com/obsidiansystems/obelisk/pull/645)) * Fix vulnerability where Android deployments would leave signing keys in the nix store which is world readable. ([#645](https://github.com/obsidiansystems/obelisk/pull/645)) (Thanks to [kmicklas](https://github.com/kmicklas) for the report.) * Add `Obelisk.Backend.runBackendWith` to allow customization of how GHCJS resources are loaded in the page. ([#668](https://github.com/obsidiansystems/obelisk/pull/668)) +* Add `ob profile` command to run Obelisk projects with profiling. `ob profile` works like `ob run`, but instead of using `ghci`, it builds an executable that is built with profiling enabled. ([#654](https://github.com/obsidiansystems/obelisk/pull/654)) ## v0.6.0.0 - 2020-02-21 @@ -30,9 +31,6 @@ This project's release branch is `master`. This log is written from the perspect * `ob thunk pack` will now attempt to automatically detect if the thunk is a private or public repo. To avoid this detection, specify `--private` or `--public` manually. ([#607](https://github.com/obsidiansystems/obelisk/pull/607)) * Fix a bug in the plain git thunk loader for thunks marked as 'private' when the revision is not in the default branch. ([#648](https://github.com/obsidiansystems/obelisk/pull/648)) * Improve handling of runtime nix dependencies. This may fix some issues encountered particularly by users on systems other than NixOS. -* Add `ob profile` command to run Obelisk project with profiling. `ob -profile` works like ob run, but instead of using ghci, it builds an -executable that is built with profiling enabled. ## v0.4.0.0 - 2020-01-10