on master help sign in
12 files changed+57−176
since splitsnapshots
unifiedsplit
ChangeLog.md−7 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−41 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−25 lib/command/src/Obelisk/Command/Project.hs−8 lib/command/src/Obelisk/Command/Run.hs+3−6 skeleton/frontend/src/Frontend.hs+4−3
ChangeLog.md−7
@@ -2,13 +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−
125## v0.9.1.0
136
147* [#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−41
@@ -1,41 +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 ) where"
29− , ""
30− , "import Obelisk.Asset.TH"
31− , "import Language.Haskell.TH"
32− , ""
33− , "static :: FilePath -> Q Exp"
34− , "#ifdef OBELISK_ASSET_PASSTHRU"
35− , "static = staticAssetRaw"
36− , "#else"
37− , "static = staticAssetHashed " <> show root
38− , "#endif"
39− ]
40− }
41− 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−25
@@ -1,36 +1,14 @@
1−{-|
2−Description:
3− Template Haskell for generating asset paths.
4−-}
51module Obelisk.Asset.TH
62 ( assetPath
7− , staticAssetRaw
8− , staticAssetHashed
93 ) where
104
115import Obelisk.Asset.Gather
126
13−import Language.Haskell.TH
147import Language.Haskell.TH.Syntax
158import System.FilePath.Posix
169
17−-- | Produces the hashed path of a file
18−hashedAssetFilePath :: FilePath -> FilePath -> Q FilePath
19−hashedAssetFilePath root relativePath = do
20− qAddDependentFile $ root </> relativePath
21− runIO (toHashedPath root relativePath)
22−
2310-- | Produces a string literal with the hashed path of the file
2411assetPath :: FilePath -> FilePath -> Q Exp
25−assetPath root relativePath =
26− LitE . StringL <$> hashedAssetFilePath root relativePath
27−
28−staticPrefix :: FilePath
29−staticPrefix = "/static"
30−
31−staticAssetRaw :: FilePath -> Q Exp
32−staticAssetRaw fp = returnQ $ LitE $ StringL $ staticPrefix </> fp
33−
34−staticAssetHashed :: FilePath -> FilePath -> Q Exp
35−staticAssetHashed root fp = do
36− LitE . StringL . (staticPrefix </>) <$> hashedAssetFilePath root fp
12+assetPath root relativePath = do
13+ qAddDependentFile $ root </> relativePath
14+ LitE . StringL <$> runIO (toHashedPath root relativePath)
lib/command/src/Obelisk/Command/Project.hs−8
@@ -16,7 +16,6 @@ module Obelisk.Command.Project
1616 , toObeliskDir
1717 , withProjectRoot
1818 , bashEscape
19− , getHaskellManifestProjectPath
2019 ) where
2120
2221import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked)
@@ -354,10 +353,3 @@ findProjectAssets root = do
354353 ]
355354 else readProcessAndLogStderr Debug $ setCwd (Just root) $
356355 proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
357−
358−getHaskellManifestProjectPath :: MonadObelisk m => FilePath -> m Text
359−getHaskellManifestProjectPath root = fmap T.strip $ readProcessAndLogStderr Debug $ setCwd (Just root) $
360− proc nixBuildExePath
361− [ "-E"
362− , "(let a = import ./. {}; in a.passthru.processedStatic.haskellManifest)"
363− ]
lib/command/src/Obelisk/Command/Run.hs+3−6
@@ -77,7 +77,7 @@ import Obelisk.CliApp (
7777 withSpinner,
7878 )
7979import Obelisk.Command.Nix
80−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape, getHaskellManifestProjectPath)
80+import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
8181import Obelisk.Command.Thunk (attrCacheFileName)
8282import Obelisk.Command.Utils (findExePath, ghcidExePath)
8383
@@ -164,9 +164,8 @@ run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
164164run root interpretPaths = do
165165 pkgs <- getParsedLocalPkgs root interpretPaths
166166 assets <- findProjectAssets root
167− manifestPkg <- parsePackagesOrFail . (:[]) . T.unpack =<< getHaskellManifestProjectPath root
168167 putLog Debug $ "Assets impurely loaded from: " <> assets
169− ghciArgs <- getGhciSessionSettings (pkgs <> manifestPkg) root True
168+ ghciArgs <- getGhciSessionSettings pkgs root True
170169 freePort <- getFreePort
171170 withGhciScriptArgs pkgs $ \dotGhciArgs -> do
172171 runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
@@ -401,8 +400,7 @@ withGhciScriptArgs
401400 => f CabalPackageInfo -- ^ List of packages to load into ghci
402401 -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
403402 -> m ()
404−withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp ->
405− f ["-XNoImplicitPrelude", "-ghci-script", fp]
403+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
406404 where
407405 -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
408406 -- is passed to the ghci session.
@@ -460,7 +458,6 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
460458
461459 pure
462460 $ baseGhciOptions
463− <> ["-DOBELISK_ASSET_PASSTHRU"] -- For passthrough static assets
464461 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
465462 <> concatMap (\p -> ["-optF", p]) pkgFiles
466463 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
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