From 22bef48bac8f0b69cbed06c9f4da1f5bd437dc34 Mon Sep 17 00:00:00 2001 From: Ali Abrar Date: Wed, 30 Dec 2020 14:59:10 -0500 Subject: [PATCH 1/4] static: Don't wait for nix show-derivation when deciding whether to provide UI feedback --- lib/command/src/Obelisk/Command/Project.hs | 82 +++++++++++++++------- 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index e3a200ccb..962ba4824 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -26,7 +26,7 @@ module Obelisk.Command.Project ) where import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked) -import Control.Lens ((.~), (?~), (<&>), (^.), _2, _3) +import Control.Lens ((.~), (?~), (<&>)) import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class (liftIO) @@ -37,9 +37,11 @@ import qualified Data.ByteString.UTF8 as BSU import Data.Bits import qualified Data.ByteString.Lazy as BSL import Data.Default (def) +import qualified Data.Foldable as F (toList) import Data.Function ((&), on) +import Data.List (isPrefixOf) import Data.Map (Map) -import Data.Maybe (isJust) +import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, encodeUtf8) @@ -402,25 +404,64 @@ watchStaticFilesDerivation -> m () watchStaticFilesDerivation root = do ob <- getObelisk - drv0 <- showDerivation liftIO $ runHeadlessApp $ do pb <- getPostBuild + let filterEvents x = + let fn = takeFileName x + dirs = Set.fromList $ splitDirectories x + ignoredFilenames = Set.fromList + [ "ghcid-output.txt" + , ".cabal-sandbox" + , "cabal.sandbox.config" + , ".attr-cache" + , "result" + , "ctags" + , "tags" + , "TAGS" + , "cabal.project.local" + , "4913" -- Vim temporary file + ] + ignoredDirectories = Set.fromList + [ ".git" + , "dist" + , "dist-newstyle" + , "profile" + ] + ignoredExtensions = Set.fromList + [ ".hi" + , ".o" + , ".swo" + , ".swp" + ] + in not $ + "static.out" `isPrefixOf` fn || + "result-" `isPrefixOf` fn || + fn `Set.member` ignoredFilenames || + takeExtension fn `Set.member` ignoredExtensions || + not (Set.null $ Set.intersection ignoredDirectories dirs) + checkForChanges <- batchOccurrences 0.25 =<< watchDirectoryTree -- On macOS, use the polling backend due to https://github.com/luite/hfsevents/issues/13 (defaultConfig { confUsePolling = SysInfo.os == "darwin", confPollInterval = 250000 }) (root <$ pb) - ((/="static.out") . takeFileName . eventPath) - drv <- performEvent $ ffor checkForChanges $ \_ -> - liftIO $ runObelisk ob showDerivation - drvs <- foldDyn (\new (_, old, _) -> (old, new, old /= new)) (drv0, drv0, False) drv - void $ throttleBatchWithLag - (\e -> performEvent $ ffor e $ \_ -> liftIO $ runObelisk ob $ do + (filterEvents . eventPath) + performEvent_ + $ liftIO + . runObelisk ob + . putLog Debug + . ("Regenerating static.out due to file changes: "<>) + . T.intercalate ", " + . fmap (T.pack . eventPath) + . F.toList + <$> checkForChanges + void $ flip throttleBatchWithLag checkForChanges $ \e -> + performEvent $ ffor e $ \_ -> liftIO $ runObelisk ob $ do putLog Notice "Static assets being built..." buildStaticCatchErrors >>= \case Nothing -> pure () - Just _ -> putLog Notice "Static assets built and symlinked to static.out" - ) - ((() <$) . ffilter (\x -> isJust (x ^._2) && x ^._3) $ updated drvs) + Just n -> do + putLog Notice $ "Static assets built and symlinked to static.out" + putLog Debug $ "Generated static asset nix path: " <> n pure never where handleBuildFailure @@ -428,23 +469,14 @@ watchStaticFilesDerivation root = do => (ExitCode, String, String) -> m (Maybe Text) handleBuildFailure (ex, out, err) = case ex of - ExitSuccess -> pure $ Just $ T.pack out + ExitSuccess -> + let out' = T.strip $ T.pack out + in pure $ if T.null out' then Nothing else Just out' _ -> do putLog Error $ ("Static assets build failed: " <>) $ - T.unlines $ reverse $ take 10 $ reverse $ T.lines $ T.pack err + T.unlines $ reverse $ take 20 $ reverse $ T.lines $ T.pack err pure Nothing - showDerivation :: MonadObelisk m => m (Maybe Text) - showDerivation = - handleBuildFailure <=< readCreateProcessWithExitCode $ - setCwd (Just root) $ ProcessSpec - { _processSpec_createProcess = Proc.proc nixExePath - [ "show-derivation" - , "-f", "." - , "passthru.staticFilesImpure" - ] - , _processSpec_overrideEnv = Nothing - } buildStaticCatchErrors :: MonadObelisk m => m (Maybe Text) buildStaticCatchErrors = handleBuildFailure =<< buildStaticFilesDerivationAndSymlink From eb4d86ec8fcd2208e6390da9d6b6fb2094df4db8 Mon Sep 17 00:00:00 2001 From: Ali Abrar Date: Wed, 30 Dec 2020 19:45:43 -0500 Subject: [PATCH 2/4] Update changelog --- ChangeLog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 05383028c..ffc0e0c7f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,10 +7,10 @@ This project's release branch is `master`. This log is written from the perspect * Use TemplateHaskell to determine asset file paths * Migration: All uses of `static @"some/path"` become `$(static "some/path")`. Instead of requiring `TypeApplications` and `DataKinds`, modules calling `static` must now enable `TemplateHaskell`. * Deprecation: Deprecate static asset modules generated via 'obelisk-asset-manifest-generate' in favor of modules generated via 'obelisk-asset-th-generate'. The new executable takes the same arguments as the old and should be a drop-in replacement. To preserve the old behavior, set `__deprecated.useObeliskAssetManifestGenerate = true;` in your obelisk project configuration. - * Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted +* Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted * Feature: When `staticFiles` is a derivation, as opposed to a regular directory, produce a symlink to the result of that derivation at `static.out` and have `ob run` serve static assets from that symlink. This makes it possible for the static asset derivation to be rebuilt and the new results served without restarting `ob run`. * Feature: Rebuild static asset derivations while `ob run` is active as long as the change to the derivation is within the project folder. `ob run` now displays a message ("Static assets rebuilt and symlinked to static.out") whenever static assets have been rebuilt and the new static assets are being served. -* Feature: Add `staticFilePath` to `Obelisk.Generated.Static`. Like `static`, this uses TH to generate a reference to a file. Unlike `static`, this `staticFilePath` generates a path on the filesystem instead of URL path. +* Feature: Add `staticFilePath` to `Obelisk.Generated.Static`. Like `static`, this uses TH to generate a reference to a file. Unlike `static`, this `staticFilePath` generates a path on the filesystem instead of URL path. To support this functionality, `ob run` now places a symlink, `static.out` in the project directory whether or not `staticFiles` is a derivation. ## v0.9.1.0 From b073715c3037e90338ba7d4f037c0790f3a0e40b Mon Sep 17 00:00:00 2001 From: Ali Abrar Date: Wed, 30 Dec 2020 20:21:17 -0500 Subject: [PATCH 3/4] static: Add another ignored folder --- lib/command/src/Obelisk/Command/Project.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index 6af45a1b0..ab377d960 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -410,6 +410,8 @@ watchStaticFilesDerivation root = do ob <- getObelisk liftIO $ runHeadlessApp $ do pb <- getPostBuild + -- TODO: Instead of filtering like this, we should figure out what the derivation + -- actually relies on, or at least use the gitignore let filterEvents x = let fn = takeFileName x dirs = Set.fromList $ splitDirectories x @@ -430,6 +432,7 @@ watchStaticFilesDerivation root = do , "dist" , "dist-newstyle" , "profile" + , "db" ] ignoredExtensions = Set.fromList [ ".hi" @@ -443,7 +446,6 @@ watchStaticFilesDerivation root = do fn `Set.member` ignoredFilenames || takeExtension fn `Set.member` ignoredExtensions || not (Set.null $ Set.intersection ignoredDirectories dirs) - checkForChanges <- batchOccurrences 0.25 =<< watchDirectoryTree -- On macOS, use the polling backend due to https://github.com/luite/hfsevents/issues/13 (defaultConfig { confUsePolling = SysInfo.os == "darwin", confPollInterval = 250000 }) From c2548930f40897e74eccf08b7125aae9952f9657 Mon Sep 17 00:00:00 2001 From: Ali Abrar Date: Tue, 20 Dec 2022 14:55:23 -0500 Subject: [PATCH 4/4] static: only watch frontend, backend, common, and static --- ChangeLog.md | 6 ++- lib/command/src/Obelisk/Command/Project.hs | 58 ++++++++++------------ 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1f9f9a96c..3e2f05f39 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -40,6 +40,10 @@ This project's release branch is `master`. This log is written from the perspect * [#930](https://github.com/obsidiansystems/obelisk/pull/930): Add an error to `ob run` when `static` is called with a path to a file that doesn't exist * [#940](https://github.com/obsidiansystems/obelisk/pull/940): Automatically restart the server when configuration is updated via `ob deploy push`. * [#959](https://github.com/obsidiansystems/obelisk/pull/959): Add an error to `ob run` when `staticFilePath` is called with a path to a file that doesn't exist + * [#835](https://github.com/obsidiansystems/obelisk/pull/835): Rebuild static assets in fewer circumstances: + * Watch `frontend`, `backend`, `common`, and `static` instead of the project root to avoid spurious rebuilds when other files change + * Don't call `nix show-derivation` to decide whether to rebuild since it seems to do about as much work as a no-op nix-build + * Add a debug message indicating which file changes triggered the static file rebuild ## v1.0.0.0 - 2022-01-04 @@ -69,7 +73,7 @@ This project's release branch is `master`. This log is written from the perspect * Use TemplateHaskell to determine asset file paths * Migration: All uses of `static @"some/path"` become `$(static "some/path")`. Instead of requiring `TypeApplications` and `DataKinds`, modules calling `static` must now enable `TemplateHaskell`. * Deprecation: Deprecate static asset modules generated via 'obelisk-asset-manifest-generate' in favor of modules generated via 'obelisk-asset-th-generate'. The new executable takes the same arguments as the old and should be a drop-in replacement. To preserve the old behavior, set `__deprecated.useObeliskAssetManifestGenerate = true;` in your obelisk project configuration. -* Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted + * Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted * Feature: When `staticFiles` is a derivation, as opposed to a regular directory, produce a symlink to the result of that derivation at `static.out` and have `ob run` serve static assets from that symlink. This makes it possible for the static asset derivation to be rebuilt and the new results served without restarting `ob run`. * Feature: Rebuild static asset derivations while `ob run` is active as long as the change to the derivation is within the project folder. `ob run` now displays a message ("Static assets rebuilt and symlinked to static.out") whenever static assets have been rebuilt and the new static assets are being served. * Feature: Add `staticFilePath` to `Obelisk.Generated.Static`. Like `static`, this uses TH to generate a reference to a file. Unlike `static`, this `staticFilePath` generates a path on the filesystem instead of URL path. diff --git a/lib/command/src/Obelisk/Command/Project.hs b/lib/command/src/Obelisk/Command/Project.hs index f5bb7980f..3057a9ff3 100644 --- a/lib/command/src/Obelisk/Command/Project.hs +++ b/lib/command/src/Obelisk/Command/Project.hs @@ -42,7 +42,6 @@ import qualified Data.ByteString.Lazy as BSL import Data.Default (def) import qualified Data.Foldable as F (toList) import Data.Function ((&), on) -import Data.List (isPrefixOf) import Data.Map (Map) import qualified Data.Set as Set import Data.Text (Text) @@ -429,8 +428,9 @@ getHaskellManifestProjectPath root = fmap T.strip $ readProcessAndLogStderr Debu , "(let a = import ./. {}; in a.passthru.processedStatic.haskellManifest)" ] --- | Watch the project directory for file changes and check whether those file changes --- cause changes in the static files nix derivation. If so, rebuild it. +-- | Watch the common, backend, frontend, and static directories for file +-- changes and check whether those file changes cause changes in the static +-- files nix derivation. If so, rebuild it. watchStaticFilesDerivation :: (MonadIO m, MonadObelisk m) => FilePath @@ -439,29 +439,13 @@ watchStaticFilesDerivation root = do ob <- getObelisk liftIO $ runHeadlessApp $ do pb <- getPostBuild - -- TODO: Instead of filtering like this, we should figure out what the derivation - -- actually relies on, or at least use the gitignore + -- TODO: Instead of filtering like this, we should figure out what the + -- derivation actually relies on, or at least use the gitignore let filterEvents x = let fn = takeFileName x dirs = Set.fromList $ splitDirectories x ignoredFilenames = Set.fromList - [ "ghcid-output.txt" - , ".cabal-sandbox" - , "cabal.sandbox.config" - , ".attr-cache" - , "result" - , "ctags" - , "tags" - , "TAGS" - , "cabal.project.local" - , "4913" -- Vim temporary file - ] - ignoredDirectories = Set.fromList - [ ".git" - , "dist" - , "dist-newstyle" - , "profile" - , "db" + [ "4913" -- Vim temporary file ] ignoredExtensions = Set.fromList [ ".hi" @@ -470,26 +454,34 @@ watchStaticFilesDerivation root = do , ".swp" ] in not $ - "static.out" `isPrefixOf` fn || - "result-" `isPrefixOf` fn || fn `Set.member` ignoredFilenames || - takeExtension fn `Set.member` ignoredExtensions || - not (Set.null $ Set.intersection ignoredDirectories dirs) - checkForChanges <- batchOccurrences 0.25 =<< watchDirectoryTree - -- On macOS, use the polling backend due to https://github.com/luite/hfsevents/issues/13 - (defaultConfig { confUsePolling = SysInfo.os == "darwin", confPollInterval = 250000 }) - (root <$ pb) - (filterEvents . eventPath) + takeExtension fn `Set.member` ignoredExtensions + cfg = defaultConfig + -- On macOS, use the polling backend due to + -- https://github.com/luite/hfsevents/issues/13 + { confUsePolling = SysInfo.os == "darwin" + , confPollInterval = 250000 + } + watch' pkg = fmap (:[]) <$> watchDirectoryTree cfg (root pkg <$ pb) (filterEvents . eventPath) + rebuild <- batchOccurrences 0.25 =<< mergeWith (<>) <$> mapM watch' + [ "frontend" + , "backend" + , "common" + , "static" + ] performEvent_ $ liftIO . runObelisk ob . putLog Debug . ("Regenerating static.out due to file changes: "<>) . T.intercalate ", " + . Set.toList + . Set.fromList . fmap (T.pack . eventPath) + . concat . F.toList - <$> checkForChanges - void $ flip throttleBatchWithLag checkForChanges $ \e -> + <$> rebuild + void $ flip throttleBatchWithLag rebuild $ \e -> performEvent $ ffor e $ \_ -> liftIO $ runObelisk ob $ do putLog Notice "Static assets being built..." buildStaticCatchErrors >>= \case