on f43d375 help sign in
25 files changed+433−88
since splitsnapshots
unifiedsplit
ChangeLog.md+11 default.nix+10−4 dep/logging-effect/default.nix+2 dep/logging-effect/github.json+7 dep/logging-effect/thunk.nix+9 dep/reflex-platform/github.json+3−3 haskell-overlays/misc-deps.nix+4−2 lib/asset/README.md+2−2 lib/asset/manifest/obelisk-asset-manifest.cabal+11−1 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+8−27 lib/asset/manifest/src/Obelisk/Asset/TH.hs+34−3 lib/cliapp/src/Obelisk/CliApp/Logging.hs+5−1 lib/cliapp/src/Obelisk/CliApp/Types.hs−2 lib/command/obelisk-command.cabal+3 lib/command/src/Obelisk/Command/Preprocessor.hs+1 lib/command/src/Obelisk/Command/Project.hs+142−13 lib/command/src/Obelisk/Command/Run.hs+70−11 lib/command/src/Obelisk/Command/Utils.hs+3 lib/frontend/src/Obelisk/Frontend.hs+5−1 skeleton/.gitignore+1 skeleton/frontend/frontend.cabal+1−1 skeleton/frontend/src/Frontend.hs+3−4
ChangeLog.md+11
@@ -2,6 +2,17 @@
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+* Fix bug [#790](https://github.com/obsidiansystems/obelisk/issues/790) which prevented CSS file loading on ios
8+* Use TemplateHaskell to determine asset file paths
9+ * Migration: All uses of `static @"some/path"` become `$(static "some/path")`. Instead of requiring `TypeApplications` and `DataKinds`, modules calling `static` must now enable `TemplateHaskell`.
10+ * 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.
11+ * Feature: Files added to the static directory while `ob run` is active no longer require `ob run` to be restarted
12+* 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`.
13+* 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.
14+* 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.
15+
516## v0.9.1.0
617
718* [#801](https://github.com/obsidiansystems/obelisk/pull/801): Remove errors and warning for local packages without a library component
default.nix+10−4
@@ -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" }: pkgs.runCommand "asset-manifest" {
68+ processAssets = { src, packageName ? "obelisk-generated-static", moduleName ? "Obelisk.Generated.Static", exe ? "obelisk-asset-th-generate" }: 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− obelisk-asset-manifest-generate "$src" "$haskellManifest" ${packageName} ${moduleName} "$symlinked"
76+ ${exe} "$src" "$haskellManifest" ${packageName} ${moduleName} "$symlinked"
7777 '';
7878
7979 compressedJs = frontend: optimizationLevel: pkgs.runCommand "compressedJs" {} ''
@@ -237,13 +237,14 @@ 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 ? {}
240241 }:
241242 let
242243 allConfig = nixpkgs.lib.makeExtensible (self: {
243244 base = base';
244245 inherit args;
245246 userSettings = {
246− inherit android ios packages overrides tools shellToolOverrides withHoogle __closureCompilerOptimizationLevel __withGhcide;
247+ inherit android ios packages overrides tools shellToolOverrides withHoogle __closureCompilerOptimizationLevel __withGhcide __deprecated;
247248 staticFiles = if staticFiles == null then self.base + /static else staticFiles;
248249 };
249250 frontendName = "frontend";
@@ -251,7 +252,12 @@ in rec {
251252 commonName = "common";
252253 staticName = "obelisk-generated-static";
253254 staticFilesImpure = let fs = self.userSettings.staticFiles; in if lib.isDerivation fs then fs else toString fs;
254− processedStatic = processAssets { src = self.userSettings.staticFiles; };
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+ };
255261 # The packages whose names and roles are defined by this package
256262 predefinedPackages = lib.filterAttrs (_: x: x != null) {
257263 ${self.frontendName} = nullIfAbsent (self.base + "/frontend");
dep/logging-effect/default.nixnew file+2
@@ -0,0 +1,2 @@
1+# DO NOT HAND-EDIT THIS FILE
2+import (import ./thunk.nix)
dep/logging-effect/github.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "owner": "ocharles",
3+ "repo": "logging-effect",
4+ "private": false,
5+ "rev": "efc856b62aec15f0087be0edb7ba621ed49b0bb1",
6+ "sha256": "13nr3zw39415js1af2vd322vhzwqb2rkrv7b5lkd97h211d7zbj9"
7+}
dep/logging-effect/thunk.nixnew file+9
@@ -0,0 +1,9 @@
1+# DO NOT HAND-EDIT THIS FILE
2+let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
3+ if !fetchSubmodules && !private then builtins.fetchTarball {
4+ url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
5+ } else (import <nixpkgs> {}).fetchFromGitHub {
6+ inherit owner repo rev sha256 fetchSubmodules private;
7+ };
8+ json = builtins.fromJSON (builtins.readFile ./github.json);
9+in fetch json
dep/reflex-platform/github.json+3−3
@@ -1,8 +1,8 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "release/0.7.0.0",
4+ "branch": "aa/reflex-0.8.1.0",
55 "private": false,
6− "rev": "27a7851a780ff21472d85f9a02fb84635ef9027a",
7− "sha256": "1m4zlw91dpjlbdchjyxhvhv3k7lilm0qa17dhhww868xxc27sann"
6+ "rev": "0cf69c9352b5528942dcc19b0b9f88a49ae47930",
7+ "sha256": "1a0hdgcwnjp04rzzqigjwbz20ss7n1lqzdg0mm8kpc74ynh86vrp"
88}
haskell-overlays/misc-deps.nix+4−2
@@ -13,8 +13,6 @@ in
1313 regex-posix = self.callHackage "regex-posix" "0.96.0.0" {};
1414 regex-tdfa = self.callHackage "regex-tdfa" "1.3.1.0" {};
1515 test-framework = haskellLib.dontCheck (self.callHackage "test-framework" "0.8.2.0" {});
16− hnix = haskellLib.dontCheck (self.callHackage "hnix" "0.8.0" {});
17− hnix-store-core = self.callHackage "hnix-store-core" "0.2.0.0" {};
1816
1917 aeson-gadt-th = self.callHackage "aeson-gadt-th" "0.2.4" {};
2018
@@ -22,5 +20,9 @@ in
2220 # Exports more internals
2321 snap-core = haskellLib.dontCheck (self.callCabal2nix "snap-core" (hackGet ../dep/snap-core) {});
2422
23+ logging-effect = self.callCabal2nix "logging-effect" (hackGet ../dep/logging-effect) {};
24+ resourcet = self.callHackage "resourcet" "1.2.4.2" {};
25+ unliftio-core = self.callHackage "unliftio-core" "0.2.0.1" {};
2526 shelly = self.callHackage "shelly" "1.9.0" {};
27+ monad-logger = self.callHackage "monad-logger" "0.3.36" {};
2628}
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. (See `example/default.nix`, line 2)
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.
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 from `example/run-example` (line 3) 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 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+11−1
@@ -27,6 +27,7 @@ library
2727 , unix-compat
2828 , vector
2929 exposed-modules:
30+ Obelisk.Asset.Cabal
3031 Obelisk.Asset.Gather
3132 Obelisk.Asset.Promoted
3233 Obelisk.Asset.Symlink
@@ -35,7 +36,7 @@ library
3536 other-extensions: TemplateHaskell
3637 ghc-options:
3738 -Wall -Werror -Wredundant-constraints -Wincomplete-uni-patterns -Wincomplete-record-updates -O2
38− -fno-warn-unused-do-bind -funbox-strict-fields -fprof-auto-calls
39+ -fno-warn-unused-do-bind -funbox-strict-fields -fprof-auto-calls
3940
4041executable obelisk-asset-manifest-generate
4142 hs-source-dirs: src-bin
@@ -44,3 +45,12 @@ executable obelisk-asset-manifest-generate
4445 base
4546 , obelisk-asset-manifest
4647 , 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.hsnew file+43
@@ -0,0 +1,43 @@
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.hsnew file+42
@@ -0,0 +1,42 @@
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
9+import Control.DeepSeq (force)
1010import Control.Monad (forM)
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
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)
1616import Data.Map (Map)
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
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)
2424
2525-- | Given a path, recursively explore it, creating hashed paths for all files found
2626gatherHashedPaths
lib/asset/manifest/src/Obelisk/Asset/Promoted.hs+8−27
@@ -10,7 +10,6 @@ module Obelisk.Asset.Promoted
1010import Obelisk.Asset.Gather
1111
1212import Data.Foldable
13−import qualified Data.List as L
1413import Language.Haskell.TH (runQ, pprint)
1514import Language.Haskell.TH.Syntax hiding (lift)
1615import GHC.TypeLits
@@ -18,12 +17,11 @@ import Data.Sequence (Seq)
1817import qualified Data.Sequence as Seq
1918import Control.Monad.Trans.Writer
2019import System.FilePath
21−import System.Directory
2220import Data.Text (Text)
2321import qualified Data.Text as T
24−import qualified Data.Text.IO as T
2522import Data.Map (Map)
2623import qualified Data.Map as Map
24+import Obelisk.Asset.Cabal (writeCabalProject, SimplePkg(..))
2725
2826data StaticConfig = StaticConfig
2927 { _staticConfig_packageName :: Text --TODO: Better type
@@ -32,31 +30,14 @@ data StaticConfig = StaticConfig
3230
3331writeStaticProject :: Map FilePath FilePath -> FilePath -> StaticConfig -> IO ()
3432writeStaticProject paths target cfg = do
35− createDirectoryIfMissing True target
36− T.writeFile (target </> T.unpack (_staticConfig_packageName cfg) <.> "cabal") $ staticCabalFile cfg
3733 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
4234 modContents <- staticModuleFile modName paths
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− ]
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+ }
6041
6142staticModuleFile :: Text -> Map FilePath FilePath -> IO Text
6243staticModuleFile moduleName paths = do
@@ -69,7 +50,7 @@ staticModuleFile moduleName paths = do
6950 , "{-# LANGUAGE OverloadedStrings #-}"
7051 , "{-# LANGUAGE ScopedTypeVariables #-}"
7152 , "{-# LANGUAGE TypeApplications #-}"
72− , "module " <> moduleName <> " where"
53+ , "module " <> moduleName <> " {-# DEPRECATED \"Generate this module with the 'obelisk-asset-th-generate' executable instead.\" #-} where"
7354 , ""
7455 , "import qualified GHC.Types"
7556 , "import Data.Text (Text)"
lib/asset/manifest/src/Obelisk/Asset/TH.hs+34−3
@@ -1,14 +1,45 @@
1+{-|
2+Description:
3+ Template Haskell for generating asset paths.
4+-}
15module Obelisk.Asset.TH
26 ( assetPath
7+ , staticAssetRaw
8+ , staticAssetHashed
9+ , staticAssetFilePath
10+ , staticAssetFilePathRaw
311 ) where
412
513import Obelisk.Asset.Gather
614
15+import Language.Haskell.TH
716import Language.Haskell.TH.Syntax
817import System.FilePath.Posix
918
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+
1025-- | Produces a string literal with the hashed path of the file
1126assetPath :: FilePath -> FilePath -> Q Exp
12−assetPath root relativePath = do
13− qAddDependentFile $ root </> relativePath
14− LitE . StringL <$> runIO (toHashedPath root relativePath)
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
lib/cliapp/src/Obelisk/CliApp/Logging.hs+5−1
@@ -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 (Red, Yellow), ColorIntensity (Vivid),
47+import System.Console.ANSI (Color (..), ColorIntensity (Vivid),
4848 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
4949 SGR (SetColor, SetConsoleIntensity), clearLine)
5050import System.Exit (ExitCode (..))
@@ -184,6 +184,8 @@ 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
187189 | not noColor && severity >= Debug = TS.putStrWithSGR debugColors h withNewLine s
188190 | otherwise = liftIO $ putFn s
189191
@@ -191,6 +193,8 @@ writeLog withNewLine noColor (WithSeverity severity s) = if T.null s then pure (
191193 h = if severity <= Error then stderr else stdout
192194 errorColors = [SetColor Foreground Vivid Red]
193195 warningColors = [SetColor Foreground Vivid Yellow]
196+ infoColors = [SetColor Foreground Vivid Green]
197+ noticeColors = [SetColor Foreground Vivid Blue]
194198 debugColors = [SetConsoleIntensity FaintIntensity]
195199
196200-- | Allow the user to immediately switch to verbose logging upon pressing a particular key.
lib/cliapp/src/Obelisk/CliApp/Types.hs−2
@@ -47,8 +47,6 @@ type CliThrow e m = MonadError e m
4747putLog :: CliLog m => Severity -> Text -> m ()
4848putLog sev = logMessage . Output_Log . WithSeverity sev
4949
50−deriving instance MonadFail m => MonadFail (LoggingT Output m)
51−
5250newtype DieT e m a = DieT { unDieT :: ReaderT (e -> (Text, Int)) (LoggingT Output m) a }
5351 deriving
5452 ( Functor, Applicative, Monad, MonadIO, MonadFail
lib/command/obelisk-command.cabal+3
@@ -20,6 +20,7 @@ library
2020 , exceptions
2121 , extra
2222 , filepath
23+ , fsnotify
2324 , git
2425 , github
2526 , here
@@ -39,6 +40,8 @@ library
3940 , optparse-applicative
4041 , placeholders
4142 , process
43+ , reflex
44+ , reflex-fsnotify
4245 , shell-escape
4346 , temporary
4447 , terminal-size
lib/command/src/Obelisk/Command/Preprocessor.hs+1
@@ -59,6 +59,7 @@ 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
6263generateHeader :: FilePath -> CabalPackageInfo -> TL.Builder
6364generateHeader origPath packageInfo =
6465 hsExtensions <> ghcOptions <> lineNumberPragma origPath
lib/command/src/Obelisk/Command/Project.hs+142−13
@@ -1,6 +1,9 @@
11{-# LANGUAGE FlexibleContexts #-}
22{-# LANGUAGE LambdaCase #-}
33{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE TupleSections #-}
6+
47module Obelisk.Command.Project
58 ( InitSource (..)
69 , findProjectObeliskCommand
@@ -11,17 +14,24 @@ module Obelisk.Command.Project
1114 , nixShellRunProc
1215 , nixShellWithHoogle
1316 , nixShellWithoutPkgs
17+ , mkObNixShellProc
1418 , obeliskDirName
1519 , toImplDir
1620 , toObeliskDir
1721 , withProjectRoot
1822 , bashEscape
23+ , getHaskellManifestProjectPath
24+ , AssetSource(..)
25+ , describeImpureAssetSource
26+ , watchStaticFilesDerivation
1927 ) where
2028
2129import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked)
22−import Control.Lens ((.~), (?~), (<&>))
30+import Control.Lens ((.~), (?~), (<&>), (^.), _2, _3)
2331import Control.Monad
32+import Control.Monad.Except
2433import Control.Monad.IO.Class (liftIO)
34+import Control.Monad.Log
2535import Control.Monad.State
2636import qualified Data.Aeson as Json
2737import qualified Data.ByteString.UTF8 as BSU
@@ -30,28 +40,36 @@ import qualified Data.ByteString.Lazy as BSL
3040import Data.Default (def)
3141import Data.Function ((&), on)
3242import Data.Map (Map)
43+import Data.Maybe (isJust)
3344import Data.Text (Text)
3445import qualified Data.Text as T
3546import Data.Text.Encoding (decodeUtf8, encodeUtf8)
3647import Data.Traversable (for)
48+import Reflex
49+import Reflex.FSNotify
50+import Reflex.Host.Headless
3751import System.Directory
3852import System.Environment (lookupEnv)
53+import System.Exit (ExitCode(..))
3954import System.FilePath
55+import System.FSNotify (defaultConfig, eventPath, WatchConfig(..))
56+import qualified System.Info as SysInfo
4057import System.IO.Temp
4158import System.IO.Unsafe (unsafePerformIO)
4259import System.PosixCompat.Files
4360import System.PosixCompat.Types
4461import System.PosixCompat.User
62+import qualified System.Process as Proc
4563import Text.ShellEscape (bash, bytes)
4664
4765import GitHub.Data.GitData (Branch)
4866import GitHub.Data.Name (Name)
4967
50−import Obelisk.App (MonadObelisk)
68+import Obelisk.App (MonadObelisk, runObelisk, getObelisk)
5169import Obelisk.CliApp
5270import Obelisk.Command.Nix
5371import Obelisk.Command.Thunk
54−import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath, cp, nixShellPath)
72+import Obelisk.Command.Utils (nixBuildExePath, nixExePath, toNixPath, cp, nixShellPath, lnPath)
5573
5674--TODO: Make this module resilient to random exceptions
5775
@@ -300,7 +318,7 @@ bashEscape = BSU.toString . bytes . bash . BSU.fromString
300318nixShellRunProc :: NixShellConfig -> ProcessSpec
301319nixShellRunProc cfg = setDelegateCtlc True $ proc nixShellPath $ runNixShellConfig cfg
302320
303−nixShellWithoutPkgs
321+mkObNixShellProc
304322 :: MonadObelisk m
305323 => FilePath -- ^ Path to project root
306324 -> Bool -- ^ Should this be a pure shell?
@@ -308,12 +326,12 @@ nixShellWithoutPkgs
308326 -> Map Text FilePath -- ^ Package names mapped to their paths
309327 -> String -- ^ Shell attribute to use (e.g. @"ghc"@, @"ghcjs"@, etc.)
310328 -> Maybe String -- ^ If 'Just' run the given command; otherwise just open the interactive shell
311− -> m ()
312−nixShellWithoutPkgs root isPure chdirToRoot packageNamesAndPaths shellAttr command = do
329+ -> m ProcessSpec
330+mkObNixShellProc root isPure chdirToRoot packageNamesAndPaths shellAttr command = do
313331 packageNamesAndAbsPaths <- liftIO $ for packageNamesAndPaths makeAbsolute
314332 defShellConfig <- nixShellRunConfig root isPure command
315333 let setCwd_ = if chdirToRoot then setCwd (Just root) else id
316− runProcess_ $ setCwd_ $ nixShellRunProc $ defShellConfig
334+ pure $ setCwd_ $ nixShellRunProc $ defShellConfig
317335 & nixShellConfig_common . nixCmdConfig_target . target_expr ?~
318336 "{root, pkgs, shell}: ((import root {}).passthru.__unstable__.self.extend (_: _: {\
319337 \shellPackages = builtins.fromJSON pkgs;\
@@ -324,6 +342,18 @@ nixShellWithoutPkgs root isPure chdirToRoot packageNamesAndPaths shellAttr comma
324342 , strArg "shell" shellAttr
325343 ]
326344
345+nixShellWithoutPkgs
346+ :: MonadObelisk m
347+ => FilePath -- ^ Path to project root
348+ -> Bool -- ^ Should this be a pure shell?
349+ -> Bool -- ^ Should we chdir to the package root in the shell?
350+ -> Map Text FilePath -- ^ Package names mapped to their paths
351+ -> String -- ^ Shell attribute to use (e.g. @"ghc"@, @"ghcjs"@, etc.)
352+ -> Maybe String -- ^ If 'Just' run the given command; otherwise just open the interactive shell
353+ -> m ()
354+nixShellWithoutPkgs root isPure chdirToRoot packageNamesAndPaths shellAttr command = do
355+ runProcess_ =<< mkObNixShellProc root isPure chdirToRoot packageNamesAndPaths shellAttr command
356+
327357nixShellWithHoogle :: MonadObelisk m => FilePath -> Bool -> String -> Maybe String -> m ()
328358nixShellWithHoogle root isPure shell' command = do
329359 defShellConfig <- nixShellRunConfig root isPure command
@@ -334,7 +364,21 @@ nixShellWithHoogle root isPure shell' command = do
334364 \})).project.shells.${shell}"
335365 & nixShellConfig_common . nixCmdConfig_args .~ [ strArg "shell" shell' ]
336366
337−findProjectAssets :: MonadObelisk m => FilePath -> m Text
367+-- | Describes the provenance of static assets (i.e., are they the result of a derivation
368+-- that was built, or just a folder full of files.
369+data AssetSource = AssetSource_Derivation
370+ | AssetSource_Files
371+ deriving (Eq)
372+
373+-- | Some log messages to make it easier to tell where static files are coming from
374+describeImpureAssetSource :: AssetSource -> Text -> Text
375+describeImpureAssetSource src path = case src of
376+ AssetSource_Files -> "Assets impurely loaded from: " <> path
377+ AssetSource_Derivation -> "Assets derivation built and impurely loaded from: " <> path
378+
379+-- | Determine where the static files of a project are and whether they're plain files or a derivation.
380+-- If they are a derivation, that derivation will be built.
381+findProjectAssets :: MonadObelisk m => FilePath -> m (AssetSource, Text)
338382findProjectAssets root = do
339383 isDerivation <- readProcessAndLogStderr Debug $ setCwd (Just root) $
340384 proc nixExePath
@@ -346,10 +390,95 @@ findProjectAssets root = do
346390 ]
347391 -- Check whether the impure static files are a derivation (and so must be built)
348392 if isDerivation == "1"
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"
393+ then do
394+ _ <- buildStaticFilesDerivationAndSymlink
395+ (readProcessAndLogStderr Debug)
396+ root
397+ pure (AssetSource_Derivation, T.pack $ root </> "static.out")
398+ else fmap (AssetSource_Files,) $ do
399+ path <- readProcessAndLogStderr Debug $ setCwd (Just root) $
400+ proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
401+ _ <- readProcessAndLogStderr Debug $ setCwd (Just root) $
402+ proc lnPath ["-sfT", T.unpack path, "./static.out"]
403+ pure path
404+
405+-- | Get the nix store path to the generated static asset manifest module (e.g., "obelisk-generated-static")
406+getHaskellManifestProjectPath :: MonadObelisk m => FilePath -> m Text
407+getHaskellManifestProjectPath root = fmap T.strip $ readProcessAndLogStderr Debug $ setCwd (Just root) $
408+ proc nixBuildExePath
409+ [ "--no-out-link"
410+ , "-E"
411+ , "(let a = import ./. {}; in a.passthru.processedStatic.haskellManifest)"
412+ ]
413+
414+-- | Watch the project directory for file changes and check whether those file changes
415+-- cause changes in the static files nix derivation. If so, rebuild it.
416+watchStaticFilesDerivation
417+ :: (MonadIO m, MonadObelisk m)
418+ => FilePath
419+ -> m ()
420+watchStaticFilesDerivation root = do
421+ ob <- getObelisk
422+ drv0 <- showDerivation
423+ liftIO $ runHeadlessApp $ do
424+ pb <- getPostBuild
425+ checkForChanges <- batchOccurrences 0.25 =<< watchDirectoryTree
426+ -- On macOS, use the polling backend due to https://github.com/luite/hfsevents/issues/13
427+ (defaultConfig { confUsePolling = SysInfo.os == "darwin", confPollInterval = 250000 })
428+ (root <$ pb)
429+ ((/="static.out") . takeFileName . eventPath)
430+ drv <- performEvent $ ffor checkForChanges $ \_ ->
431+ liftIO $ runObelisk ob showDerivation
432+ drvs <- foldDyn (\new (_, old, _) -> (old, new, old /= new)) (drv0, drv0, False) drv
433+ void $ throttleBatchWithLag
434+ (\e -> performEvent $ ffor e $ \_ -> liftIO $ runObelisk ob $ do
435+ putLog Notice "Static assets being built..."
436+ buildStaticCatchErrors >>= \case
437+ Nothing -> pure ()
438+ Just _ -> putLog Notice "Static assets built and symlinked to static.out"
439+ )
440+ ((() <$) . ffilter (\x -> isJust (x ^._2) && x ^._3) $ updated drvs)
441+ pure never
442+ where
443+ handleBuildFailure
444+ :: MonadObelisk m
445+ => (ExitCode, String, String)
446+ -> m (Maybe Text)
447+ handleBuildFailure (ex, out, err) = case ex of
448+ ExitSuccess -> pure $ Just $ T.pack out
449+ _ -> do
450+ putLog Error $
451+ ("Static assets build failed: " <>) $
452+ T.unlines $ reverse $ take 10 $ reverse $ T.lines $ T.pack err
453+ pure Nothing
454+ showDerivation :: MonadObelisk m => m (Maybe Text)
455+ showDerivation =
456+ handleBuildFailure <=< readCreateProcessWithExitCode $
457+ setCwd (Just root) $ ProcessSpec
458+ { _processSpec_createProcess = Proc.proc nixExePath
459+ [ "show-derivation"
460+ , "-f", "."
461+ , "passthru.staticFilesImpure"
462+ ]
463+ , _processSpec_overrideEnv = Nothing
464+ }
465+ buildStaticCatchErrors :: MonadObelisk m => m (Maybe Text)
466+ buildStaticCatchErrors = handleBuildFailure =<<
467+ buildStaticFilesDerivationAndSymlink
468+ readCreateProcessWithExitCode
469+ root
470+
471+buildStaticFilesDerivationAndSymlink
472+ :: MonadObelisk m
473+ => (ProcessSpec -> m a)
474+ -> FilePath
475+ -> m a
476+buildStaticFilesDerivationAndSymlink f root = f $
477+ setCwd (Just root) $ ProcessSpec
478+ { _processSpec_createProcess = Proc.proc
479+ nixBuildExePath
480+ [ "-o", "static.out"
352481 , "-E", "(import ./. {}).passthru.staticFilesImpure"
353482 ]
354− else readProcessAndLogStderr Debug $ setCwd (Just root) $
355− proc nixExePath ["eval", "-f", ".", "passthru.staticFilesImpure", "--raw"]
483+ , _processSpec_overrideEnv = Nothing
484+ }
lib/command/src/Obelisk/Command/Run.hs+70−11
@@ -14,7 +14,8 @@ module Obelisk.Command.Run where
1414import Control.Arrow ((&&&))
1515import Control.Exception (Exception, bracket)
1616import Control.Lens (ifor, (.~), (&))
17−import Control.Monad (filterM)
17+import Control.Concurrent (forkIO)
18+import Control.Monad (filterM, void)
1819import Control.Monad.Except (runExceptT, throwError)
1920import Control.Monad.IO.Class (liftIO)
2021import Control.Monad.Reader (MonadIO)
@@ -45,13 +46,23 @@ import Debug.Trace (trace)
4546import Distribution.Compiler (CompilerFlavor(..))
4647import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
4748import 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)
4854import qualified Distribution.System as Dist
49−import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options)
55+import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options, targetBuildDepends)
5056import Distribution.Types.CondTree (simplifyCondTree)
57+import Distribution.Types.Dependency (Dependency (..), depPkgName)
5158import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
59+import Distribution.Types.InstalledPackageInfo (compatPackageKey)
5260import Distribution.Types.Library (libBuildInfo)
61+import Distribution.Types.PackageName (mkPackageName)
62+import Distribution.Types.VersionRange (anyVersion)
5363import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
5464import qualified Distribution.Parsec.Common as Dist
65+import qualified Distribution.Verbosity as Verbosity (silent)
5566import qualified Hpack.Config as Hpack
5667import qualified Hpack.Render as Hpack
5768import qualified Hpack.Yaml as Hpack
@@ -63,7 +74,7 @@ import System.FilePath
6374import qualified System.Info
6475import System.IO.Temp (withSystemTempDirectory)
6576
66−import Obelisk.App (MonadObelisk)
77+import Obelisk.App (MonadObelisk, getObelisk, runObelisk)
6778import Obelisk.CliApp (
6879 Severity (..),
6980 failWith,
@@ -77,7 +88,7 @@ import Obelisk.CliApp (
7788 withSpinner,
7889 )
7990import Obelisk.Command.Nix
80−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
91+import Obelisk.Command.Project
8192import Obelisk.Command.Thunk (attrCacheFileName)
8293import Obelisk.Command.Utils (findExePath, ghcidExePath)
8394
@@ -96,6 +107,8 @@ data CabalPackageInfo = CabalPackageInfo
96107 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
97108 , _cabalPackageInfo_cppOptions :: [String]
98109 -- ^ List of CPP (C Preprocessor) options (e.g. the "cpp-options" field of the cabal file)
110+ , _cabalPackageInfo_buildDepends :: [Dependency]
111+ -- ^ List of build dependencies listed in the cabal file
99112 }
100113
101114-- | 'Bool' with a better name for its purpose.
@@ -144,8 +157,8 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
144157 , _target_attr = Just "__unstable__.profiledObRun"
145158 , _target_expr = Nothing
146159 }
147− assets <- findProjectAssets root
148− putLog Debug $ "Assets impurely loaded from: " <> assets
160+ (assetType, assets) <- findProjectAssets root
161+ putLog Debug $ describeImpureAssetSource assetType assets
149162 time <- liftIO getCurrentTime
150163 let profileBaseName = formatTime defaultTimeLocale profileBasePattern time
151164 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
@@ -163,9 +176,16 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
163176run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
164177run root interpretPaths = do
165178 pkgs <- getParsedLocalPkgs root interpretPaths
166− assets <- findProjectAssets root
167− putLog Debug $ "Assets impurely loaded from: " <> assets
168− ghciArgs <- getGhciSessionSettings pkgs root True
179+ (assetType, assets) <- findProjectAssets root
180+ manifestPkg <- parsePackagesOrFail . (:[]) . T.unpack =<< getHaskellManifestProjectPath root
181+ putLog Debug $ describeImpureAssetSource assetType assets
182+ case assetType of
183+ AssetSource_Derivation -> do
184+ ob <- getObelisk
185+ putLog Debug "Starting static file derivation watcher..."
186+ void $ liftIO $ forkIO $ runObelisk ob $ watchStaticFilesDerivation root
187+ _ -> pure ()
188+ ghciArgs <- getGhciSessionSettings (pkgs <> manifestPkg) root True
169189 freePort <- getFreePort
170190 withGhciScriptArgs pkgs $ \dotGhciArgs -> do
171191 runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
@@ -222,7 +242,12 @@ getLocalPkgs root interpretPaths = do
222242 let rootsAndExclusions = calcIntepretFinds "" interpretPaths
223243
224244 fmap fold $ for (MMap.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
225− let allExclusions = obeliskPackageExclusions <> exclusions <> Set.singleton ("*" </> attrCacheFileName)
245+ let allExclusions = obeliskPackageExclusions
246+ <> exclusions
247+ <> Set.singleton ("*" </> attrCacheFileName)
248+ <> Set.singleton ("*" </> "lib/asset/manifest") -- NB: obelisk-asset-manifest is excluded because it generates
249+ -- a module that in turn imports it. This will cause ob run to
250+ -- fail in its current implementation.
226251 in fmap (Set.fromList . map normalise) $ runFind $
227252 ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
228253 <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
@@ -354,6 +379,7 @@ parseCabalPackage' pkg = runExceptT $ do
354379 defaultLanguage $ libBuildInfo lib
355380 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
356381 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
382+ , _cabalPackageInfo_buildDepends = targetBuildDepends $ libBuildInfo lib
357383 }
358384 Right Nothing -> pure Nothing
359385 Left (_, errors) ->
@@ -400,7 +426,8 @@ withGhciScriptArgs
400426 => f CabalPackageInfo -- ^ List of packages to load into ghci
401427 -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
402428 -> m ()
403−withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
429+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp ->
430+ f ["-XNoImplicitPrelude", "-ghci-script", fp]
404431 where
405432 -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
406433 -- is passed to the ghci session.
@@ -450,6 +477,7 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
450477 -- all paths to 'pathBase'.
451478 selfExe <- liftIO $ canonicalizePath =<< getExecutablePath
452479 canonicalPathBase <- liftIO $ canonicalizePath pathBase
480+ installedPackageIndex <- loadPackageIndex packageInfos pathBase
453481
454482 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
455483 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
@@ -458,16 +486,47 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
458486
459487 pure
460488 $ baseGhciOptions
489+ <> ["-DOBELISK_ASSET_PASSTHRU"] -- For passthrough static assets
461490 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
462491 <> concatMap (\p -> ["-optF", p]) pkgFiles
463492 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
493+ <> concatMap (\packageId -> ["-package-id", packageId ])
494+ (packageIds installedPackageIndex)
464495 where
465496 relativeTo' = if useRelativePaths then relativeTo else const
497+ -- Package names we're building and not needed from the package DB
498+ packageNames =
499+ map (mkPackageName . T.unpack . _cabalPackageInfo_packageName)
500+ packageInfos
501+ packageIds installedPackageIndex =
502+ map (dependencyPackageId installedPackageIndex) $
503+ filter ((`notElem` packageNames) . depPkgName) $
504+ concatMap _cabalPackageInfo_buildDepends packageInfos <>
505+ [Dependency (mkPackageName "obelisk-run") anyVersion]
506+ dependencyPackageId installedPackageIndex dep =
507+ case lookupDependency installedPackageIndex dep of
508+ ((_version,installedPackageInfo:_) :_) ->
509+ compatPackageKey installedPackageInfo
510+ _ -> error $ "Couldn't resolve dependency for " <> prettyShow dep
511+
512+-- Load the package index used by the GHC in this path's nix project
513+loadPackageIndex :: MonadObelisk m => [CabalPackageInfo] -> FilePath -> m InstalledPackageIndex
514+loadPackageIndex packageInfos root = do
515+ ghcPath <- getPathInNixEnvironment "bash -c 'type -p ghc'"
516+ ghcPkgPath <- getPathInNixEnvironment "bash -c 'type -p ghc-pkg'"
517+ (compiler, _platform, programDb) <- liftIO
518+ $ configCompilerEx (Just GHC) (Just ghcPath) (Just ghcPkgPath) defaultProgramDb Verbosity.silent
519+ liftIO $ getInstalledPackages Verbosity.silent compiler [GlobalPackageDB] programDb
520+ where
521+ getPathInNixEnvironment cmd = do
522+ path <- readProcessAndLogStderr Debug =<< mkObNixShellProc root False True (packageInfoToNamePathMap packageInfos) "ghc" (Just cmd)
523+ liftIO $ canonicalizePath $ T.unpack $ T.strip path
466524
467525baseGhciOptions :: [String]
468526baseGhciOptions =
469527 [ "-ignore-dot-ghci"
470528 , "-no-user-package-db"
529+ , "-hide-all-packages"
471530 , "-package-env", "-"
472531 ]
473532
lib/command/src/Obelisk/Command/Utils.hs+3
@@ -75,6 +75,9 @@ gitPath = $(staticWhich "git")
7575whichPath :: FilePath
7676whichPath = $(staticWhich "which")
7777
78+lnPath :: FilePath
79+lnPath = $(staticWhich "ln")
80+
7881sshKeygenPath :: FilePath
7982sshKeygenPath = $(staticWhich "ssh-keygen")
8083
lib/frontend/src/Obelisk/Frontend.hs+5−1
@@ -58,6 +58,7 @@ import Reflex.Host.Class
5858import Obelisk.Configs
5959import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
6060import qualified Obelisk.ExecutableConfig.Lookup as Lookup
61+import System.Info (os)
6162import Web.Cookie
6263
6364import Debug.Trace
@@ -102,7 +103,10 @@ data Frontend route = Frontend
102103 }
103104
104105baseTag :: forall route js t m. ObeliskWidget js t route m => RoutedT t route m ()
105−baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
106+baseTag =
107+ if os == "ios"
108+ then blank
109+ else elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
106110
107111removeHTMLConfigs :: JSM ()
108112removeHTMLConfigs = void $ runMaybeT $ do
skeleton/.gitignore+1
@@ -13,3 +13,4 @@ result
1313result-*
1414tags
1515TAGS
16+static.out
skeleton/frontend/frontend.cabal+1−1
@@ -10,7 +10,7 @@ library
1010 , obelisk-frontend
1111 , obelisk-route
1212 , jsaddle
13− , reflex-dom
13+ , reflex-dom-core
1414 , obelisk-executable-config-lookup
1515 , obelisk-generated-static
1616 , text
skeleton/frontend/src/Frontend.hs+3−4
@@ -1,6 +1,5 @@
1−{-# LANGUAGE DataKinds #-}
21{-# LANGUAGE OverloadedStrings #-}
3−{-# LANGUAGE TypeApplications #-}
2+{-# LANGUAGE TemplateHaskell #-}
43
54module Frontend where
65
@@ -27,7 +26,7 @@ frontend :: Frontend (R FrontendRoute)
2726frontend = Frontend
2827 { _frontend_head = do
2928 el "title" $ text "Obelisk Minimal Example"
30− elAttr "link" ("href" =: static @"main.css" <> "type" =: "text/css" <> "rel" =: "stylesheet") blank
29+ elAttr "link" ("href" =: $(static "main.css") <> "type" =: "text/css" <> "rel" =: "stylesheet") blank
3130 , _frontend_body = do
3231 el "h1" $ text "Welcome to Obelisk!"
3332 el "p" $ text $ T.pack commonStuff
@@ -38,7 +37,7 @@ frontend = Frontend
3837 -- print "Hello, World!" on the client.
3938 prerender_ blank $ liftJSM $ void $ eval ("console.log('Hello, World!')" :: T.Text)
4039
41− elAttr "img" ("src" =: static @"obelisk.jpg") blank
40+ elAttr "img" ("src" =: $(static "obelisk.jpg")) blank
4241 el "div" $ do
4342 exampleConfig <- getConfig "common/example"
4443 case exampleConfig of