on master help sign in
17 files changed+75−393
since splitsnapshots
unifiedsplit
ChangeLog.md−10 default.nix+4−10 lib/asset/README.md+2−2 lib/asset/manifest/obelisk-asset-manifest.cabal+1−11 lib/asset/manifest/src-bin/static-th.hs−43 lib/asset/manifest/src/Obelisk/Asset/Cabal.hs−42 lib/asset/manifest/src/Obelisk/Asset/Gather.hs+13−13 lib/asset/manifest/src/Obelisk/Asset/Promoted.hs+27−8 lib/asset/manifest/src/Obelisk/Asset/TH.hs+3−34 lib/cliapp/src/Obelisk/CliApp/Logging.hs+1−5 lib/command/obelisk-command.cabal−3 lib/command/src/Obelisk/Command/Preprocessor.hs−1 lib/command/src/Obelisk/Command/Project.hs+9−125 lib/command/src/Obelisk/Command/Run.hs+11−79 lib/command/src/Obelisk/Command/Utils.hs−3 skeleton/.gitignore−1 skeleton/frontend/src/Frontend.hs+4−3
ChangeLog.md−10
@@ -2,16 +2,6 @@
22
33This project's release branch is `master`. This log is written from the perspective of the release branch: when changes hit `master`, they are considered released.
44
5−## Unreleased
6−
7−* Use TemplateHaskell to determine asset file paths
8− * Migration: All uses of `static @"some/path"` become `$(static "some/path")`. Instead of requiring `TypeApplications` and `DataKinds`, modules calling `static` must now enable `TemplateHaskell`.
9− * 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.
10− * Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted
11−* 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`.
12−* 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.
13−* 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.
14−
155## v0.9.1.0
166
177* [#801](https://github.com/obsidiansystems/obelisk/pull/801): Remove errors and warning for local packages without a library component
default.nix+4−10
@@ -65,7 +65,7 @@ in rec {
6565 '';
6666 nullIfAbsent = p: if lib.pathExists p then p else null;
6767 #TODO: Avoid copying files within the nix store. Right now, obelisk-asset-manifest-generate copies files into a big blob so that the android/ios static assets can be imported from there; instead, we should get everything lined up right before turning it into an APK, so that copies, if necessary, only exist temporarily.
68− processAssets = { src, packageName ? "obelisk-generated-static", moduleName ? "Obelisk.Generated.Static", exe ? "obelisk-asset-th-generate" }: pkgs.runCommand "asset-manifest" {
68+ processAssets = { src, packageName ? "obelisk-generated-static", moduleName ? "Obelisk.Generated.Static" }: pkgs.runCommand "asset-manifest" {
6969 inherit src;
7070 outputs = [ "out" "haskellManifest" "symlinked" ];
7171 nativeBuildInputs = [ ghcObelisk.obelisk-asset-manifest ];
@@ -73,7 +73,7 @@ in rec {
7373 set -euo pipefail
7474 touch "$out"
7575 mkdir -p "$symlinked"
76− ${exe} "$src" "$haskellManifest" ${packageName} ${moduleName} "$symlinked"
76+ obelisk-asset-manifest-generate "$src" "$haskellManifest" ${packageName} ${moduleName} "$symlinked"
7777 '';
7878
7979 compressedJs = frontend: optimizationLevel: pkgs.runCommand "compressedJs" {} ''
@@ -237,14 +237,13 @@ in rec {
237237 , withHoogle ? false # Setting this to `true` makes shell reloading far slower
238238 , __closureCompilerOptimizationLevel ? "ADVANCED" # Set this to `null` to skip the closure-compiler step
239239 , __withGhcide ? false
240− , __deprecated ? {}
241240 }:
242241 let
243242 allConfig = nixpkgs.lib.makeExtensible (self: {
244243 base = base';
245244 inherit args;
246245 userSettings = {
247− inherit android ios packages overrides tools shellToolOverrides withHoogle __closureCompilerOptimizationLevel __withGhcide __deprecated;
246+ inherit android ios packages overrides tools shellToolOverrides withHoogle __closureCompilerOptimizationLevel __withGhcide;
248247 staticFiles = if staticFiles == null then self.base + /static else staticFiles;
249248 };
250249 frontendName = "frontend";
@@ -252,12 +251,7 @@ in rec {
252251 commonName = "common";
253252 staticName = "obelisk-generated-static";
254253 staticFilesImpure = let fs = self.userSettings.staticFiles; in if lib.isDerivation fs then fs else toString fs;
255− processedStatic = processAssets {
256− src = self.userSettings.staticFiles;
257− exe = if lib.attrByPath ["userSettings" "__deprecated" "useObeliskAssetManifestGenerate"] false self
258− then builtins.trace "obelisk-asset-manifest-generate is deprecated. Use obelisk-asset-th-generate instead." "obelisk-asset-manifest-generate"
259− else "obelisk-asset-th-generate";
260− };
254+ processedStatic = processAssets { src = self.userSettings.staticFiles; };
261255 # The packages whose names and roles are defined by this package
262256 predefinedPackages = lib.filterAttrs (_: x: x != null) {
263257 ${self.frontendName} = nullIfAbsent (self.base + "/frontend");
lib/asset/README.md+2−2
@@ -5,7 +5,7 @@
55
66**Efficiently served, aggressively cached static web resources.**
77
8−The `assets.nix` file contains nix expressions that are a mutually recursive set of attributes used to create static asset directories with hashable file encodings. This file should be incorporated within your project's `default.nix` file.
8+The `assets.nix` file contains nix expressions that are a mutually recursive set of attributes used to create static asset directories with hashable file encodings. This file should be incorporated within your project's `default.nix` file. (See `example/default.nix`, line 2)
99
1010```nix
1111{ assets ? import ./../assets.nix { inherit nixpkgs; } }:
@@ -19,7 +19,7 @@ In this example, `mkAssets` is used on a directory that has a `.png` file inside
1919myAssets = assets.mkAssets ./static;
2020```
2121
22−Once you have successfully incorporated `assets.nix` into your project's nix file(s), use `nix-build` to generate a symlink of your `mkAssets` expression. The following command will generate an immutable symlink that holds hashed static assets.
22+Once you have successfully incorporated `assets.nix` into your project's nix file(s), use `nix-build` to generate a symlink of your `mkAssets` expression. The following command from `example/run-example` (line 3) will generate an immutable symlink that holds hashed static assets.
2323
2424```bash
2525nix-build -o static.assets -A myAssets
lib/asset/manifest/obelisk-asset-manifest.cabal+1−11
@@ -27,7 +27,6 @@ library
2727 , unix-compat
2828 , vector
2929 exposed-modules:
30− Obelisk.Asset.Cabal
3130 Obelisk.Asset.Gather
3231 Obelisk.Asset.Promoted
3332 Obelisk.Asset.Symlink
@@ -36,7 +35,7 @@ library
3635 other-extensions: TemplateHaskell
3736 ghc-options:
3837 -Wall -Werror -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O2
39− -fno-warn-unused-do-bind -funbox-strict-fields -fprof-auto-calls
38+ -fno-warn-unused-do-bind -funbox-strict-fields -fprof-auto-calls
4039
4140executable obelisk-asset-manifest-generate
4241 hs-source-dirs: src-bin
@@ -45,12 +44,3 @@ executable obelisk-asset-manifest-generate
4544 base
4645 , obelisk-asset-manifest
4746 , text
48−
49−executable obelisk-asset-th-generate
50− hs-source-dirs: src-bin
51− main-is: static-th.hs
52− build-depends:
53− base
54− , obelisk-asset-manifest
55− , filepath
56− , text
lib/asset/manifest/src-bin/static-th.hsdeleted−43
@@ -1,43 +0,0 @@
1−import qualified Data.Text as T
2−import Obelisk.Asset.Cabal
3−import Obelisk.Asset.Gather
4−import Obelisk.Asset.Symlink
5−import System.Environment
6−import System.FilePath
7−
8−main :: IO ()
9−main = do
10− --TODO: Usage
11− [root, haskellTarget, packageName, moduleName, fileTarget] <- getArgs
12− paths <- gatherHashedPaths root
13− writeCabalProject haskellTarget $ SimplePkg
14− { _simplePkg_name = T.pack packageName
15− , _simplePkg_moduleName = T.pack moduleName
16− , _simplePkg_dependencies = map T.pack
17− [ "base"
18− , "obelisk-asset-manifest"
19− , "template-haskell"
20− ]
21− , _simplePkg_moduleContents = T.pack $ unlines
22− [ "{-# Language CPP #-}"
23− , "{-|"
24− , " Description:"
25− , " Automatically generated module that provides the 'static' TH function"
26− , " to generate paths to static assets."
27− , "-}"
28− , "module " <> moduleName <> " ( static, staticFilePath ) where"
29− , ""
30− , "import Obelisk.Asset.TH"
31− , "import Language.Haskell.TH"
32− , ""
33− , "static, staticFilePath :: FilePath -> Q Exp"
34− , "#ifdef OBELISK_ASSET_PASSTHRU"
35− , "static = staticAssetRaw"
36− , "staticFilePath = staticAssetFilePathRaw \"static.out\""
37− , "#else"
38− , "static = staticAssetHashed " <> show root
39− , "staticFilePath = staticAssetFilePathRaw " <> show root
40− , "#endif"
41− ]
42− }
43− copyAndSymlink paths root fileTarget
lib/asset/manifest/src/Obelisk/Asset/Cabal.hsdeleted−42
@@ -1,42 +0,0 @@
1−{-# Language OverloadedStrings #-}
2−module Obelisk.Asset.Cabal where
3−
4−import qualified Data.List as L
5−import Data.Text (Text)
6−import qualified Data.Text as T
7−import qualified Data.Text.IO as T
8−import System.Directory
9−import System.FilePath
10−
11−-- | A generated single-module package
12−data SimplePkg = SimplePkg
13− { _simplePkg_name :: Text
14− , _simplePkg_moduleName :: Text
15− , _simplePkg_moduleContents :: Text
16− , _simplePkg_dependencies :: [Text]
17− }
18−
19−cabalFile :: SimplePkg -> Text
20−cabalFile (SimplePkg packageName moduleName _ deps) = T.unlines
21− [ "name: " <> packageName
22− , "version: 0"
23− , "cabal-version: >= 1.2"
24− , "build-type: Simple"
25− , ""
26− , "library"
27− , " hs-source-dirs: src"
28− , " build-depends:"
29− , " " <> T.intercalate ", " deps
30− , " exposed-modules: " <> moduleName
31− ]
32−
33−writeCabalProject :: FilePath -> SimplePkg -> IO ()
34−writeCabalProject target pkg = do
35− createDirectoryIfMissing True target
36− T.writeFile (target </> T.unpack (_simplePkg_name pkg) <.> "cabal") $ cabalFile pkg
37− let moduleName = _simplePkg_moduleName pkg
38− (modName', moduleDirPath) = case L.uncons (reverse $ T.splitOn "." moduleName) of
39− Nothing -> error $ "writeStaticProject: invalid module name " <> T.unpack moduleName
40− Just (name, parents) -> (name, target </> "src" </> T.unpack (T.intercalate "/" $ reverse parents))
41− createDirectoryIfMissing True moduleDirPath
42− T.writeFile (moduleDirPath </> T.unpack modName' <.> "hs") $ _simplePkg_moduleContents pkg
lib/asset/manifest/src/Obelisk/Asset/Gather.hs+13−13
@@ -6,21 +6,21 @@ module Obelisk.Asset.Gather
66 , toHashedPath
77 ) where
88
9−import Control.DeepSeq (force)
9+import Control.DeepSeq
1010import Control.Monad (forM)
11−import Data.Bits (shift, (.|.), (.&.))
12−import qualified Data.ByteString.Lazy as LBS (readFile, toStrict, ByteString, length, index)
13−import qualified Data.ByteString.Lazy.Builder as LBS (toLazyByteString, word8)
14−import Data.Char (ord)
15−import Data.Digest.Pure.SHA (bytestringDigest, sha256)
11+import Data.Bits
12+import qualified Data.ByteString.Lazy as LBS
13+import qualified Data.ByteString.Lazy.Builder as LBS
14+import Data.Char
15+import Data.Digest.Pure.SHA
1616import Data.Map (Map)
17−import qualified Data.Map as Map (singleton)
18−import qualified Data.Text as T (unpack)
19−import Data.Text.Encoding (decodeUtf8)
20−import qualified Data.Vector.Unboxed as UV ((!), fromList)
21−import Data.Word (Word8)
22−import System.FilePath.Posix ((</>), splitFileName, normalise)
23−import System.Directory (listDirectory, doesFileExist)
17+import qualified Data.Map as Map
18+import qualified Data.Text as T
19+import Data.Text.Encoding
20+import qualified Data.Vector.Unboxed as UV
21+import Data.Word
22+import System.FilePath.Posix
23+import System.Directory
2424
2525-- | Given a path, recursively explore it, creating hashed paths for all files found
2626gatherHashedPaths
lib/asset/manifest/src/Obelisk/Asset/Promoted.hs+27−8
@@ -10,6 +10,7 @@ module Obelisk.Asset.Promoted
1010import Obelisk.Asset.Gather
1111
1212import Data.Foldable
13+import qualified Data.List as L
1314import Language.Haskell.TH (runQ, pprint)
1415import Language.Haskell.TH.Syntax hiding (lift)
1516import GHC.TypeLits
@@ -17,11 +18,12 @@ import Data.Sequence (Seq)
1718import qualified Data.Sequence as Seq
1819import Control.Monad.Trans.Writer
1920import System.FilePath
21+import System.Directory
2022import Data.Text (Text)
2123import qualified Data.Text as T
24+import qualified Data.Text.IO as T
2225import Data.Map (Map)
2326import qualified Data.Map as Map
24−import Obelisk.Asset.Cabal (writeCabalProject, SimplePkg(..))
2527
2628data StaticConfig = StaticConfig
2729 { _staticConfig_packageName :: Text --TODO: Better type
@@ -30,14 +32,31 @@ data StaticConfig = StaticConfig
3032
3133writeStaticProject :: Map FilePath FilePath -> FilePath -> StaticConfig -> IO ()
3234writeStaticProject paths target cfg = do
35+ createDirectoryIfMissing True target
36+ T.writeFile (target </> T.unpack (_staticConfig_packageName cfg) <.> "cabal") $ staticCabalFile cfg
3337 let modName = _staticConfig_moduleName cfg
38+ (modName', moduleDirPath) = case L.uncons (reverse $ T.splitOn "." modName) of
39+ Nothing -> error $ "writeStaticProject: invalid module name " <> T.unpack modName
40+ Just (name, parents) -> (name, target </> "src" </> T.unpack (T.intercalate "/" $ reverse parents))
41+ createDirectoryIfMissing True moduleDirPath
3442 modContents <- staticModuleFile modName paths
35− writeCabalProject target $ SimplePkg
36− { _simplePkg_name = _staticConfig_packageName cfg
37− , _simplePkg_moduleName = _staticConfig_moduleName cfg
38− , _simplePkg_moduleContents = modContents
39− , _simplePkg_dependencies = ["base", "ghc-prim", "text"]
40− }
43+ T.writeFile (moduleDirPath </> T.unpack modName' <.> "hs") modContents
44+
45+staticCabalFile :: StaticConfig -> Text
46+staticCabalFile cfg = T.unlines
47+ [ "name: " <> _staticConfig_packageName cfg
48+ , "version: 0"
49+ , "cabal-version: >= 1.2"
50+ , "build-type: Simple"
51+ , ""
52+ , "library"
53+ , " hs-source-dirs: src"
54+ , " build-depends:"
55+ , " base,"
56+ , " ghc-prim,"
57+ , " text"
58+ , " exposed-modules: " <> _staticConfig_moduleName cfg
59+ ]
4160
4261staticModuleFile :: Text -> Map FilePath FilePath -> IO Text
4362staticModuleFile moduleName paths = do
@@ -50,7 +69,7 @@ staticModuleFile moduleName paths = do
5069 , "{-# LANGUAGE OverloadedStrings #-}"
5170 , "{-# LANGUAGE ScopedTypeVariables #-}"
5271 , "{-# LANGUAGE TypeApplications #-}"
53− , "module " <> moduleName <> " {-# DEPRECATED \"Generate this module with the 'obelisk-asset-th-generate' executable instead.\" #-} where"
72+ , "module " <> moduleName <> " where"
5473 , ""
5574 , "import qualified GHC.Types"
5675 , "import Data.Text (Text)"
lib/asset/manifest/src/Obelisk/Asset/TH.hs+3−34
@@ -1,45 +1,14 @@
1−{-|
2−Description:
3− Template Haskell for generating asset paths.
4−-}
51module Obelisk.Asset.TH
62 ( assetPath
7− , staticAssetRaw
8− , staticAssetHashed
9− , staticAssetFilePath
10− , staticAssetFilePathRaw
113 ) where
124
135import Obelisk.Asset.Gather
146
15−import Language.Haskell.TH
167import Language.Haskell.TH.Syntax
178import System.FilePath.Posix
189
19−-- | Produces the hashed path of a file
20−hashedAssetFilePath :: FilePath -> FilePath -> Q FilePath
21−hashedAssetFilePath root relativePath = do
22− qAddDependentFile $ root </> relativePath
23− runIO (toHashedPath root relativePath)
24−
2510-- | Produces a string literal with the hashed path of the file
2611assetPath :: FilePath -> FilePath -> Q Exp
27−assetPath root relativePath =
28− LitE . StringL <$> hashedAssetFilePath root relativePath
29−
30−staticPrefix :: FilePath
31−staticPrefix = "/static"
32−
33−staticAssetRaw :: FilePath -> Q Exp
34−staticAssetRaw fp = returnQ $ LitE $ StringL $ staticPrefix </> fp
35−
36−staticAssetHashed :: FilePath -> FilePath -> Q Exp
37−staticAssetHashed root fp = do
38− LitE . StringL . (staticPrefix </>) <$> hashedAssetFilePath root fp
39−
40−staticAssetFilePathRaw :: FilePath -> FilePath -> Q Exp
41−staticAssetFilePathRaw root fp = returnQ $ LitE $ StringL $ root </> fp
42−
43−staticAssetFilePath :: FilePath -> FilePath -> Q Exp
44−staticAssetFilePath root fp = do
45− LitE . StringL . (root </>) <$> hashedAssetFilePath root fp
12+assetPath root relativePath = do
13+ qAddDependentFile $ root </> relativePath
14+ LitE . StringL <$> runIO (toHashedPath root relativePath)
lib/cliapp/src/Obelisk/CliApp/Logging.hs+1−5
@@ -44,7 +44,7 @@ import Data.Text (Text)
4444import qualified Data.Text as T
4545import qualified Data.Text.IO as T
4646import GHC.IO.Encoding.Types
47−import System.Console.ANSI (Color (..), ColorIntensity (Vivid),
47+import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
4848 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
4949 SGR (SetColor, SetConsoleIntensity), clearLine)
5050import System.Exit (ExitCode (..))
@@ -184,8 +184,6 @@ writeLog withNewLine noColor (WithSeverity severity s) = if T.null s then pure (
184184 | noColor && severity <= Warning = liftIO $ putFn $ T.pack (show severity) <> ": " <> s
185185 | not noColor && severity <= Error = TS.putStrWithSGR errorColors h withNewLine s
186186 | not noColor && severity <= Warning = TS.putStrWithSGR warningColors h withNewLine s
187− | not noColor && severity == Notice = TS.putStrWithSGR noticeColors h withNewLine s
188− | not noColor && severity == Informational = TS.putStrWithSGR infoColors h withNewLine s
189187 | not noColor && severity >= Debug = TS.putStrWithSGR debugColors h withNewLine s
190188 | otherwise = liftIO $ putFn s
191189
@@ -193,8 +191,6 @@ writeLog withNewLine noColor (WithSeverity severity s) = if T.null s then pure (
193191 h = if severity <= Error then stderr else stdout
194192 errorColors = [SetColor Foreground Vivid Red]
195193 warningColors = [SetColor Foreground Vivid Yellow]
196− infoColors = [SetColor Foreground Vivid Green]
197− noticeColors = [SetColor Foreground Vivid Blue]
198194 debugColors = [SetConsoleIntensity FaintIntensity]
199195
200196-- | Allow the user to immediately switch to verbose logging upon pressing a particular key.
lib/command/obelisk-command.cabal−3
@@ -20,7 +20,6 @@ library
2020 , exceptions
2121 , extra
2222 , filepath
23− , fsnotify
2423 , git
2524 , github
2625 , here
@@ -40,8 +39,6 @@ library
4039 , optparse-applicative
4140 , placeholders
4241 , process
43− , reflex
44− , reflex-fsnotify
4542 , shell-escape
4643 , temporary
4744 , terminal-size
lib/command/src/Obelisk/Command/Preprocessor.hs−1
@@ -59,7 +59,6 @@ writeOutput packageInfo' origPath outPath = withFile outPath WriteMode $ \hOut -
5959 where
6060 hPutTextBuilder h = BU.hPutBuilder h . TL.encodeUtf8Builder . TL.toLazyText
6161
62−--NOTE: We cannot restrict the package set by adding '-package' flags to OPTIONS_GHC, because GHC rejects them there. It seems that we won't be able to properly handle that situation until GHC itself supports loading multiple packages officially in GHCi
6362generateHeader :: FilePath -> CabalPackageInfo -> TL.Builder
6463generateHeader origPath packageInfo =
6564 hsExtensions <> ghcOptions <> lineNumberPragma origPath
lib/command/src/Obelisk/Command/Project.hs+9−125
@@ -1,9 +1,6 @@
11{-# LANGUAGE FlexibleContexts #-}
22{-# LANGUAGE LambdaCase #-}
33{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE TupleSections #-}
6−
74module Obelisk.Command.Project
85 ( InitSource (..)
96 , findProjectObeliskCommand
@@ -19,18 +16,12 @@ module Obelisk.Command.Project
1916 , toObeliskDir
2017 , withProjectRoot
2118 , bashEscape
22− , getHaskellManifestProjectPath
23− , AssetSource(..)
24− , describeImpureAssetSource
25− , watchStaticFilesDerivation
2619 ) where
2720
2821import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked)
29−import Control.Lens ((.~), (?~), (<&>), (^.), _2, _3)
22+import Control.Lens ((.~), (?~), (<&>))
3023import Control.Monad
31−import Control.Monad.Except
3224import Control.Monad.IO.Class (liftIO)
33−import Control.Monad.Log
3425import Control.Monad.State
3526import qualified Data.Aeson as Json
3627import qualified Data.ByteString.UTF8 as BSU
@@ -39,36 +30,28 @@ import qualified Data.ByteString.Lazy as BSL
3930import Data.Default (def)
4031import Data.Function ((&), on)
4132import Data.Map (Map)
42−import Data.Maybe (isJust)
4333import Data.Text (Text)
4434import qualified Data.Text as T
4535import Data.Text.Encoding (decodeUtf8, encodeUtf8)
4636import Data.Traversable (for)
47−import Reflex
48−import Reflex.FSNotify
49−import Reflex.Host.Headless
5037import System.Directory
5138import System.Environment (lookupEnv)
52−import System.Exit (ExitCode(..))
5339import System.FilePath
54−import System.FSNotify (defaultConfig, eventPath, WatchConfig(..))
55−import qualified System.Info as SysInfo
5640import System.IO.Temp
5741import System.IO.Unsafe (unsafePerformIO)
5842import System.PosixCompat.Files
5943import System.PosixCompat.Types
6044import System.PosixCompat.User
61−import qualified System.Process as Proc
6245import Text.ShellEscape (bash, bytes)
6346
6447import GitHub.Data.GitData (Branch)
6548import GitHub.Data.Name (Name)
6649
67−import Obelisk.App (MonadObelisk, runObelisk, getObelisk)
50+import Obelisk.App (MonadObelisk)
6851import Obelisk.CliApp
6952import Obelisk.Command.Nix
7053import Obelisk.Command.Thunk
71−import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath, cp, nixShellPath, lnPath)
54+import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath, cp, nixShellPath)
7255
7356--TODO: Make this module resilient to random exceptions
7457
@@ -351,21 +334,7 @@ nixShellWithHoogle root isPure shell' command = do
351334 \})).project.shells.${shell}"
352335 & nixShellConfig_common . nixCmdConfig_args .~ [ strArg "shell" shell' ]
353336
354−-- | Describes the provenance of static assets (i.e., are they the result of a derivation
355−-- that was built, or just a folder full of files.
356−data AssetSource = AssetSource_Derivation
357− | AssetSource_Files
358− deriving (Eq)
359−
360−-- | Some log messages to make it easier to tell where static files are coming from
361−describeImpureAssetSource :: AssetSource -> Text -> Text
362−describeImpureAssetSource src path = case src of
363− AssetSource_Files -> "Assets impurely loaded from: " <> path
364− AssetSource_Derivation -> "Assets derivation built and impurely loaded from: " <> path
365−
366−-- | Determine where the static files of a project are and whether they're plain files or a derivation.
367−-- If they are a derivation, that derivation will be built.
368−findProjectAssets :: MonadObelisk m => FilePath -> m (AssetSource, Text)
337+findProjectAssets :: MonadObelisk m => FilePath -> m Text
369338findProjectAssets root = do
370339 isDerivation <- readProcessAndLogStderr Debug $ setCwd (Just root) $
371340 proc nixExePath
@@ -377,95 +346,10 @@ findProjectAssets root = do
377346 ]
378347 -- Check whether the impure static files are a derivation (and so must be built)
379348 if isDerivation == "1"
380− then do
381− _ <- buildStaticFilesDerivationAndSymlink
382− (readProcessAndLogStderr Debug)
383− root
384− pure (AssetSource_Derivation, T.pack $ root </> "static.out")
385− else fmap (AssetSource_Files,) $ do
386− path <- readProcessAndLogStderr Debug $ setCwd (Just root) $
387− proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
388− _ <- readProcessAndLogStderr Debug $ setCwd (Just root) $
389− proc lnPath ["-sfT", T.unpack path, "./static.out"]
390− pure path
391−
392−-- | Get the nix store path to the generated static asset manifest module (e.g., "obelisk-generated-static")
393−getHaskellManifestProjectPath :: MonadObelisk m => FilePath -> m Text
394−getHaskellManifestProjectPath root = fmap T.strip $ readProcessAndLogStderr Debug $ setCwd (Just root) $
395− proc nixBuildExePath
396− [ "--no-out-link"
397− , "-E"
398− , "(let a = import ./. {}; in a.passthru.processedStatic.haskellManifest)"
399− ]
400−
401−-- | Watch the project directory for file changes and check whether those file changes
402−-- cause changes in the static files nix derivation. If so, rebuild it.
403−watchStaticFilesDerivation
404− :: (MonadIO m, MonadObelisk m)
405− => FilePath
406− -> m ()
407−watchStaticFilesDerivation root = do
408− ob <- getObelisk
409− drv0 <- showDerivation
410− liftIO $ runHeadlessApp $ do
411− pb <- getPostBuild
412− checkForChanges <- batchOccurrences 0.25 =<< watchDirectoryTree
413− -- On macOS, use the polling backend due to https://github.com/luite/hfsevents/issues/13
414− (defaultConfig { confUsePolling = SysInfo.os == "darwin", confPollInterval = 250000 })
415− (root <$ pb)
416− ((/="static.out") . takeFileName . eventPath)
417− drv <- performEvent $ ffor checkForChanges $ \_ ->
418− liftIO $ runObelisk ob showDerivation
419− drvs <- foldDyn (\new (_, old, _) -> (old, new, old /= new)) (drv0, drv0, False) drv
420− void $ throttleBatchWithLag
421− (\e -> performEvent $ ffor e $ \_ -> liftIO $ runObelisk ob $ do
422− putLog Notice "Static assets being built..."
423− buildStaticCatchErrors >>= \case
424− Nothing -> pure ()
425− Just _ -> putLog Notice "Static assets built and symlinked to static.out"
426− )
427− ((() <$) . ffilter (\x -> isJust (x ^._2) && x ^._3) $ updated drvs)
428− pure never
429− where
430− handleBuildFailure
431− :: MonadObelisk m
432− => (ExitCode, String, String)
433− -> m (Maybe Text)
434− handleBuildFailure (ex, out, err) = case ex of
435− ExitSuccess -> pure $ Just $ T.pack out
436− _ -> do
437− putLog Error $
438− ("Static assets build failed: " <>) $
439− T.unlines $ reverse $ take 10 $ reverse $ T.lines $ T.pack err
440− pure Nothing
441− showDerivation :: MonadObelisk m => m (Maybe Text)
442− showDerivation =
443− handleBuildFailure <=< readCreateProcessWithExitCode $
444− setCwd (Just root) $ ProcessSpec
445− { _processSpec_createProcess = Proc.proc nixExePath
446− [ "show-derivation"
447− , "-f", "."
448− , "passthru.staticFilesImpure"
449− ]
450− , _processSpec_overrideEnv = Nothing
451− }
452− buildStaticCatchErrors :: MonadObelisk m => m (Maybe Text)
453− buildStaticCatchErrors = handleBuildFailure =<<
454− buildStaticFilesDerivationAndSymlink
455− readCreateProcessWithExitCode
456− root
457−
458−buildStaticFilesDerivationAndSymlink
459− :: MonadObelisk m
460− => (ProcessSpec -> m a)
461− -> FilePath
462− -> m a
463−buildStaticFilesDerivationAndSymlink f root = f $
464− setCwd (Just root) $ ProcessSpec
465− { _processSpec_createProcess = Proc.proc
466− nixBuildExePath
467− [ "-o", "static.out"
349+ then fmap T.strip $ readProcessAndLogStderr Debug $ setCwd (Just root) $ -- Strip whitespace here because nix-build has no --raw option
350+ proc nixBuildExePath
351+ [ "--no-out-link"
468352 , "-E", "(import ./. {}).passthru.staticFilesImpure"
469353 ]
470− , _processSpec_overrideEnv = Nothing
471− }
354+ else readProcessAndLogStderr Debug $ setCwd (Just root) $
355+ proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
lib/command/src/Obelisk/Command/Run.hs+11−79
@@ -14,8 +14,7 @@ module Obelisk.Command.Run where
1414import Control.Arrow ((&&&))
1515import Control.Exception (Exception, bracket)
1616import Control.Lens (ifor, (.~), (&))
17−import Control.Concurrent (forkIO)
18−import Control.Monad (filterM, void)
17+import Control.Monad (filterM)
1918import Control.Monad.Except (runExceptT, throwError)
2019import Control.Monad.IO.Class (liftIO)
2120import Control.Monad.Reader (MonadIO)
@@ -46,23 +45,13 @@ import Debug.Trace (trace)
4645import Distribution.Compiler (CompilerFlavor(..))
4746import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
4847import Distribution.Parsec.ParseResult (runParseResult)
49−import Distribution.Pretty (prettyShow)
50−import Distribution.Simple.Compiler (PackageDB (GlobalPackageDB))
51−import Distribution.Simple.Configure (configCompilerEx, getInstalledPackages)
52−import Distribution.Simple.PackageIndex (InstalledPackageIndex, lookupDependency)
53−import Distribution.Simple.Program.Db (defaultProgramDb)
5448import qualified Distribution.System as Dist
55−import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options, targetBuildDepends)
49+import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options)
5650import Distribution.Types.CondTree (simplifyCondTree)
57−import Distribution.Types.Dependency (Dependency (..), depPkgName)
5851import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
59−import Distribution.Types.InstalledPackageInfo (compatPackageKey)
6052import Distribution.Types.Library (libBuildInfo)
61−import Distribution.Types.PackageName (mkPackageName)
62−import Distribution.Types.VersionRange (anyVersion)
6353import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
6454import qualified Distribution.Parsec.Common as Dist
65−import qualified Distribution.Verbosity as Verbosity (silent)
6655import qualified Hpack.Config as Hpack
6756import qualified Hpack.Render as Hpack
6857import qualified Hpack.Yaml as Hpack
@@ -72,16 +61,12 @@ import System.Directory
7261import System.Environment (getExecutablePath)
7362import System.FilePath
7463import qualified System.Info
75−import System.IO (hClose, hGetLine)
7664import System.IO.Temp (withSystemTempDirectory)
77−import System.Process (CreateProcess (..), StdStream (..))
7865
79−import Obelisk.App (MonadObelisk, getObelisk, runObelisk)
66+import Obelisk.App (MonadObelisk)
8067import Obelisk.CliApp (
8168 Severity (..),
82− createProcess_,
8369 failWith,
84− overCreateProcess,
8570 proc,
8671 putLog,
8772 readCreateProcessWithExitCode,
@@ -92,7 +77,7 @@ import Obelisk.CliApp (
9277 withSpinner,
9378 )
9479import Obelisk.Command.Nix
95−import Obelisk.Command.Project
80+import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
9681import Obelisk.Command.Thunk (attrCacheFileName)
9782import Obelisk.Command.Utils (findExePath, ghcidExePath)
9883
@@ -111,8 +96,6 @@ data CabalPackageInfo = CabalPackageInfo
11196 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
11297 , _cabalPackageInfo_cppOptions :: [String]
11398 -- ^ List of CPP (C Preprocessor) options (e.g. the "cpp-options" field of the cabal file)
114− , _cabalPackageInfo_buildDepends :: [Dependency]
115− -- ^ List of build dependencies listed in the cabal file
11699 }
117100
118101-- | 'Bool' with a better name for its purpose.
@@ -161,8 +144,8 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
161144 , _target_attr = Just "__unstable__.profiledObRun"
162145 , _target_expr = Nothing
163146 }
164− (assetType, assets) <- findProjectAssets root
165− putLog Debug $ describeImpureAssetSource assetType assets
147+ assets <- findProjectAssets root
148+ putLog Debug $ "Assets impurely loaded from: " <> assets
166149 time <- liftIO getCurrentTime
167150 let profileBaseName = formatTime defaultTimeLocale profileBasePattern time
168151 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
@@ -180,16 +163,9 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
180163run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
181164run root interpretPaths = do
182165 pkgs <- getParsedLocalPkgs root interpretPaths
183− (assetType, assets) <- findProjectAssets root
184− manifestPkg <- parsePackagesOrFail . (:[]) . T.unpack =<< getHaskellManifestProjectPath root
185− putLog Debug $ describeImpureAssetSource assetType assets
186− case assetType of
187− AssetSource_Derivation -> do
188− ob <- getObelisk
189− putLog Debug "Starting static file derivation watcher..."
190− void $ liftIO $ forkIO $ runObelisk ob $ watchStaticFilesDerivation root
191− _ -> pure ()
192− ghciArgs <- getGhciSessionSettings (pkgs <> manifestPkg) root True
166+ assets <- findProjectAssets root
167+ putLog Debug $ "Assets impurely loaded from: " <> assets
168+ ghciArgs <- getGhciSessionSettings pkgs root True
193169 freePort <- getFreePort
194170 withGhciScriptArgs pkgs $ \dotGhciArgs -> do
195171 runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
@@ -246,12 +222,7 @@ getLocalPkgs root interpretPaths = do
246222 let rootsAndExclusions = calcIntepretFinds "" interpretPaths
247223
248224 fmap fold $ for (MMap.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
249− let allExclusions = obeliskPackageExclusions
250− <> exclusions
251− <> Set.singleton ("*" </> attrCacheFileName)
252− <> Set.singleton ("*" </> "lib/asset/manifest") -- NB: obelisk-asset-manifest is excluded because it generates
253− -- a module that in turn imports it. This will cause ob run to
254− -- fail in its current implementation.
225+ let allExclusions = obeliskPackageExclusions <> exclusions <> Set.singleton ("*" </> attrCacheFileName)
255226 in fmap (Set.fromList . map normalise) $ runFind $
256227 ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
257228 <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
@@ -383,7 +354,6 @@ parseCabalPackage' pkg = runExceptT $ do
383354 defaultLanguage $ libBuildInfo lib
384355 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
385356 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
386− , _cabalPackageInfo_buildDepends = targetBuildDepends $ libBuildInfo lib
387357 }
388358 Right Nothing -> pure Nothing
389359 Left (_, errors) ->
@@ -430,8 +400,7 @@ withGhciScriptArgs
430400 => f CabalPackageInfo -- ^ List of packages to load into ghci
431401 -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
432402 -> m ()
433−withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp ->
434− f ["-XNoImplicitPrelude", "-ghci-script", fp]
403+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
435404 where
436405 -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
437406 -- is passed to the ghci session.
@@ -481,7 +450,6 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
481450 -- all paths to 'pathBase'.
482451 selfExe <- liftIO $ canonicalizePath =<< getExecutablePath
483452 canonicalPathBase <- liftIO $ canonicalizePath pathBase
484− installedPackageIndex <- loadPackageIndex pathBase
485453
486454 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
487455 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
@@ -490,52 +458,16 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
490458
491459 pure
492460 $ baseGhciOptions
493− <> ["-DOBELISK_ASSET_PASSTHRU"] -- For passthrough static assets
494461 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
495462 <> concatMap (\p -> ["-optF", p]) pkgFiles
496463 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
497− <> concatMap (\packageId -> ["-package-id", packageId ])
498− (packageIds installedPackageIndex)
499464 where
500465 relativeTo' = if useRelativePaths then relativeTo else const
501− -- Package names we're building and not needed from the package DB
502− packageNames =
503− map (mkPackageName . T.unpack . _cabalPackageInfo_packageName)
504− packageInfos
505− packageIds installedPackageIndex =
506− map (dependencyPackageId installedPackageIndex) $
507− filter ((`notElem` packageNames) . depPkgName) $
508− concatMap _cabalPackageInfo_buildDepends packageInfos <>
509− [Dependency (mkPackageName "obelisk-run") anyVersion]
510− dependencyPackageId installedPackageIndex dep =
511− case lookupDependency installedPackageIndex dep of
512− ((_version,installedPackageInfo:_) :_) ->
513− compatPackageKey installedPackageInfo
514− _ -> error $ "Couldn't resolve dependency for " <> prettyShow dep
515−
516−-- Load the package index used by the GHC in this path's nix project
517−loadPackageIndex :: (MonadObelisk m) => FilePath -> m InstalledPackageIndex
518−loadPackageIndex root = do
519− ghcPath <- getPathInNixEnvironment "bash -c 'type -p ghc'"
520− ghcPkgPath <- getPathInNixEnvironment "bash -c 'type -p ghc-pkg'"
521− (compiler, _platform, programDb) <- liftIO
522− $ configCompilerEx (Just GHC) (Just ghcPath) (Just ghcPkgPath) defaultProgramDb Verbosity.silent
523− liftIO $ getInstalledPackages Verbosity.silent compiler [GlobalPackageDB] programDb
524− where
525− getPathInNixEnvironment cmd = do
526− procSpec <- runProc <$> nixShellRunConfig root True (Just cmd)
527− (_,Just h,_,_) <- createProcess_ "loadPackageIndex" procSpec
528− path <- liftIO $ hGetLine h >>= canonicalizePath
529− liftIO $ hClose h
530− pure path
531− runProc =
532− overCreateProcess (\cs -> cs { std_out = CreatePipe }) . nixShellRunProc
533466
534467baseGhciOptions :: [String]
535468baseGhciOptions =
536469 [ "-ignore-dot-ghci"
537470 , "-no-user-package-db"
538− , "-hide-all-packages"
539471 , "-package-env", "-"
540472 ]
541473
lib/command/src/Obelisk/Command/Utils.hs−3
@@ -75,9 +75,6 @@ gitPath = $(staticWhich "git")
7575whichPath :: FilePath
7676whichPath = $(staticWhich "which")
7777
78−lnPath :: FilePath
79−lnPath = $(staticWhich "ln")
80−
8178sshKeygenPath :: FilePath
8279sshKeygenPath = $(staticWhich "ssh-keygen")
8380
skeleton/.gitignore−1
@@ -13,4 +13,3 @@ result
1313result-*
1414tags
1515TAGS
16−static.out
skeleton/frontend/src/Frontend.hs+4−3
@@ -1,5 +1,6 @@
1+{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE TemplateHaskell #-}
3+{-# LANGUAGE TypeApplications #-}
34
45module Frontend where
56
@@ -26,7 +27,7 @@ frontend :: Frontend (R FrontendRoute)
2627frontend = Frontend
2728 { _frontend_head = do
2829 el "title" $ text "Obelisk Minimal Example"
29− elAttr "link" ("href" =: $(static "main.css") <> "type" =: "text/css" <> "rel" =: "stylesheet") blank
30+ elAttr "link" ("href" =: static @"main.css" <> "type" =: "text/css" <> "rel" =: "stylesheet") blank
3031 , _frontend_body = do
3132 el "h1" $ text "Welcome to Obelisk!"
3233 el "p" $ text $ T.pack commonStuff
@@ -37,7 +38,7 @@ frontend = Frontend
3738 -- print "Hello, World!" on the client.
3839 prerender_ blank $ liftJSM $ void $ eval ("console.log('Hello, World!')" :: T.Text)
3940
40− elAttr "img" ("src" =: $(static "obelisk.jpg")) blank
41+ elAttr "img" ("src" =: static @"obelisk.jpg") blank
4142 el "div" $ do
4243 exampleConfig <- getConfig "common/example"
4344 case exampleConfig of