on master help sign in
Merge remote-tracking branch 'origin/qa' into depend-on-reflex-dom-core
commit3c7fdb72120afe1ace8ddb5d0651907ed53d7020 authorElliot Cameron <eacameron@gmail.com> dateTue 14 Jan 2020, 14:48 (7 years ago) parent719b582c969e99
17 files changed+472−287
ChangeLog.md+6−2 README.md+4−3 all-builds.nix+117 all-tests.nix+145 default.nix+21−11 dep/reflex-platform/github.json+3−3 jobsets.nix−71 lib/command/obelisk-command.cabal+1−1 lib/command/src/Obelisk/Command/Deploy.hs+2−1 lib/command/src/Obelisk/Command/Run.hs+66−61 lib/command/src/Obelisk/Command/Thunk.hs+3−3 lib/command/src/Obelisk/Command/Utils.hs+6−1 lib/selftest/obelisk-selftest.cabal+4−2 lib/selftest/src/Obelisk/SelfTest.hs+73−33 release.nix+8−95 skeleton/frontend/frontend.cabal+1 skeleton/frontend/src/Frontend.hs+12
ChangeLog.md+6−2
@@ -4,14 +4,18 @@ This project's release branch is `master`. This log is written from the perspect
44
55## Unreleased
66
7+* Obelisk now depends directly on `reflex-dom-core` instead of `reflex-dom`. This avoids requiring a `jsaddle-webkit2gtk` dependency in apps that are only using `jsaddle-warp`. ([#606](https://github.com/obsidiansystems/obelisk/pull/606))
8+
9+## v0.4.0.0 - 2020-01-10
10+
711* Bump reflex-platform which, notably, bumps nixpkgs to 19.09. ([#585](https://github.com/obsidiansystems/obelisk/pull/585))
812* Add new thunk loader for Git repositories that supports `file://` Git remotes and supports private repositories via `builtins.fetchGit` for private repositories (when the `git.json` file specifies `"private": true`). ([#594](https://github.com/obsidiansystems/obelisk/pull/594))
913* Add a new thunk loader for GitHub repositories that uses `builtins.fetchTarball` for public repositories to increase loader performance and uses `fetchFromGitHub` for private repositories (when the `github.json` file specifies `"private": true`). Note that `fetchFromGitHub` requires some Nix configuration for the Nix builder to access the repository. If `ob thunk pack` fails in this case, use `-v` to see Nix's helpful message. ([#594](https://github.com/obsidiansystems/obelisk/pull/594))
1014* Add `--public`/`--private` options to `ob thunk pack` to specify if a repository should be treated as a public or private. ([#594](https://github.com/obsidiansystems/obelisk/pull/594))
1115* Improve error messaging when a dependency doesn't have the expected `.cabal` or `package.yaml` file. ([#597](https://github.com/obsidiansystems/obelisk/pull/597))
1216* Improve the skeleton in small ways. ([#593](https://github.com/obsidiansystems/obelisk/pull/593), [#589](https://github.com/obsidiansystems/obelisk/pull/589))
13−* Fix `ob` commands to again support running from any subdirectory of an obelisk project. ([#591](https://github.com/obsidiansystems/obelisk/pull/591))
14−* Obelisk now depends directly on `reflex-dom-core` instead of `reflex-dom`. This avoids requiring a `jsaddle-webkit2gtk` dependency in apps that are only using `jsaddle-warp`. ([#606](https://github.com/obsidiansystems/obelisk/pull/606))
17+* Fix `ob` commands to again support running from any subdirectory of an obelisk project ([#591](https://github.com/obsidiansystems/obelisk/pull/591))
18+* Add `reflex-platform-func` argument to Obelisk's `default.nix`. It defaults to it's prior behavior of using the reflex-platform in in `dep`. ([#612](https://github.com/obsidiansystems/obelisk/pull/612))
1519
1620## v0.3.0.0 - 2019-12-20
1721
README.md+4−3
@@ -23,14 +23,15 @@ Obelisk provides an easy way to develop and deploy your [Reflex](https://github.
23231. Set up nix caches
2424 1. If you are running NixOS, add this to `/etc/nixos/configuration.nix`:
2525 ```
26− nix.binaryCaches = [ "https://cache.nixos.org/" "https://nixcache.reflex-frp.org" ];
26+ nix.binaryCaches = [ "https://nixcache.reflex-frp.org" ];
2727 nix.binaryCachePublicKeys = [ "ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=" ];
2828 ```
2929 and rebuild your NixOS configuration (e.g. `sudo nixos-rebuild switch`).
3030 1. If you are using another operating system or linux distribution, ensure that these lines are present in your Nix configuration file (`/etc/nix/nix.conf` on most systems; [see full list](https://nixos.org/nix/manual/#sec-conf-file)):
3131 ```
32− substituters = https://cache.nixos.org https://nixcache.reflex-frp.org
33− trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
32+ binary-caches = https://cache.nixos.org https://nixcache.reflex-frp.org
33+ binary-cache-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
34+ binary-caches-parallel-connections = 40
3435 ```
3536 * other Linux: enable sandboxing (see these [issue172](https://github.com/obsidiansystems/obelisk/issues/172#issuecomment-411507818) or [issue6](https://github.com/obsidiansystems/obelisk/issues/6) if you run into build problems)
3637 ```
all-builds.nixnew file+117
@@ -0,0 +1,117 @@
1+{ self-args ? {
2+ config.android_sdk.accept_license = true;
3+ iosSdkVersion = "10.2";
4+ }
5+, local-self ? import ./. self-args
6+, supportedSystems ? [
7+ "x86_64-linux"
8+ "x86_64-darwin"
9+ ]
10+}:
11+
12+let
13+ inherit (local-self.nixpkgs) lib runCommand nix;
14+ cacheBuildSystems = supportedSystems;
15+
16+ obeliskPackagesCommon = [
17+ "obelisk-frontend"
18+ "obelisk-route"
19+ "obelisk-executable-config-lookup"
20+ ];
21+
22+ obeliskPackagesBackend = obeliskPackagesCommon ++ [
23+ "obelisk-asset-manifest"
24+ "obelisk-asset-serve-snap"
25+ "obelisk-backend"
26+ "obelisk-cliapp"
27+ "obelisk-command"
28+ "obelisk-executable-config-inject"
29+ "obelisk-frontend"
30+ "obelisk-run"
31+ "obelisk-route"
32+ "obelisk-selftest"
33+ "obelisk-snap-extras"
34+ ];
35+
36+ pnameToAttrs = pkgsSet: pnames:
37+ lib.listToAttrs (map
38+ (name: { inherit name; value = pkgsSet.${name}; })
39+ pnames);
40+
41+ concatDepends = let
42+ extractDeps = x: (x.override {
43+ mkDerivation = drv: {
44+ out = builtins.concatLists [
45+ (drv.buildDepends or [])
46+ (drv.libraryHaskellDepends or [])
47+ (drv.executableHaskellDepends or [])
48+ ];
49+ };
50+ }).out;
51+ in pkgAttrs: builtins.concatLists (map extractDeps (builtins.attrValues pkgAttrs));
52+
53+ perPlatform = lib.genAttrs cacheBuildSystems (system: let
54+ reflex-platform = import ./dep/reflex-platform { inherit system; };
55+ perProfiling = profiling: let
56+ obelisk = import ./. (self-args // { inherit system profiling; });
57+ ghc = pnameToAttrs
58+ obelisk.haskellPackageSets.ghc
59+ obeliskPackagesBackend;
60+ ghcjs = pnameToAttrs
61+ obelisk.haskellPackageSets.ghcjs
62+ obeliskPackagesCommon;
63+ cachePackages = builtins.concatLists [
64+ (builtins.attrValues ghc)
65+ (lib.optionals (!profiling) (builtins.attrValues ghcjs))
66+ (concatDepends ghc)
67+ (lib.optionals (!profiling) (concatDepends ghcjs))
68+ (lib.optional reflex-platform.androidSupport androidSkeleton)
69+ (lib.optional reflex-platform.iosSupport iosSkeleton)
70+ (lib.optionals (!profiling) [
71+ command
72+ serverSkeletonExe
73+ serverSkeletonShell
74+ ])
75+ ];
76+ command = obelisk.command;
77+ skeleton = import ./skeleton { inherit obelisk; };
78+ serverSkeletonExe = skeleton.exe;
79+ # TODO fix nixpkgs so it doesn't try to run the result of haskell shells as setup hooks.
80+ serverSkeletonShell = local-self.nixpkgs.runCommand "shell-safe-for-dep" {} ''
81+ touch "$out"
82+ echo "return" >> "$out"
83+ cat "${skeleton.shells.ghc}" >> "$out"
84+ '';
85+ androidSkeleton = (import ./skeleton { inherit obelisk; }).android.frontend;
86+ iosSkeleton = (import ./skeleton { inherit obelisk; }).ios.frontend;
87+ nameSuffix = if profiling then "profiled" else "unprofiled";
88+ in {
89+ inherit
90+ ghc
91+ ;
92+ cache = reflex-platform.pinBuildInputs "obelisk-${system}-${nameSuffix}" cachePackages;
93+ } // lib.optionalAttrs (!profiling) {
94+ inherit
95+ command
96+ ghcjs
97+ serverSkeletonExe
98+ serverSkeletonShell
99+ ;
100+ } // lib.optionalAttrs reflex-platform.androidSupport {
101+ inherit androidSkeleton;
102+ } // lib.optionalAttrs reflex-platform.iosSupport {
103+ inherit iosSkeleton;
104+ };
105+ profiled = perProfiling true;
106+ unprofiled = perProfiling false;
107+ cachePackages = map (p: p.cache) [ profiled unprofiled ];
108+ in {
109+ inherit profiled unprofiled;
110+ cache = reflex-platform.pinBuildInputs "obelisk-${system}" cachePackages;
111+ });
112+
113+ metaCache = local-self.reflex-platform.pinBuildInputs
114+ "obelisk-everywhere"
115+ (map (a: a.cache) (builtins.attrValues perPlatform));
116+
117+in perPlatform // { inherit metaCache; }
all-tests.nixnew file+145
@@ -0,0 +1,145 @@
1+{
2+ supportedSystems ? [ builtins.currentSystem ]
3+}:
4+let
5+ nginxRoot = "/run/nginx";
6+ obelisk = import ./default.nix {};
7+ # Get NixOS a pre-release 20.03 that contains the python based tests and recursive nix
8+ pkgs = import (builtins.fetchTarball https://github.com/nixos/nixpkgs/archive/3de5266.tar.gz) {};
9+ sshKeys = import (pkgs.path + /nixos/tests/ssh-keys.nix) pkgs;
10+ make-test = import (pkgs.path + /nixos/tests/make-test-python.nix);
11+ obelisk-everywhere = (import ./all-builds.nix { inherit supportedSystems; }).x86_64-linux.cache;
12+ snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
13+ snakeOilPublicKey = sshKeys.snakeOilPublicKey;
14+in
15+ make-test ({...}: {
16+ name = "obelisk";
17+ nodes = {
18+ githost = {
19+ networking.firewall.allowedTCPPorts = [ 22 80 ];
20+ services.openssh = {
21+ enable = true;
22+ };
23+ environment.systemPackages = [
24+ pkgs.git
25+ ];
26+ users.users.root.openssh.authorizedKeys.keys = [
27+ snakeOilPublicKey
28+ ];
29+ };
30+
31+ client = {
32+ imports = [
33+ (pkgs.path + /nixos/modules/installer/cd-dvd/channel.nix)
34+ ];
35+ nix.useSandbox = false;
36+ nix.binaryCaches = [];
37+ environment.systemPackages = [
38+ obelisk.command
39+ obelisk.shell
40+ obelisk-everywhere
41+ pkgs.git
42+ ];
43+ };
44+ };
45+
46+ testScript =
47+ let
48+ privateKeyFile = pkgs.writeText "id_rsa" ''${snakeOilPrivateKey}'';
49+ thunkableSample = pkgs.writeText "default.nix" ''
50+ let pkgs = import <nixpkgs> {}; in pkgs.git
51+ '';
52+ invalidThunkableSample = pkgs.writeText "default.nix" ''
53+ let pkgs = import <nixpkgs> {}; in pkgtypo.git
54+ '';
55+ sshConfigFile = pkgs.writeText "ssh_config" ''
56+ Host *
57+ StrictHostKeyChecking no
58+ UserKnownHostsFile=/dev/null
59+ ConnectionAttempts=1
60+ ConnectTimeout=1
61+ IdentityFile=~/.ssh/id_rsa
62+ User=root
63+ '';
64+ in ''
65+ start_all()
66+ githost.wait_for_open_port("22")
67+
68+ with subtest("test obelisk is installed"):
69+ client.succeed("ob --help")
70+
71+ with subtest("test the client can access the server via ssh"):
72+ client.succeed("mkdir -p ~/.ssh/")
73+ client.succeed(
74+ "cp ${privateKeyFile} ~/.ssh/id_rsa"
75+ )
76+ client.succeed("chmod 600 ~/.ssh/id_rsa")
77+ client.wait_until_succeeds(
78+ "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa githost true"
79+ )
80+ client.succeed(
81+ "cp ${sshConfigFile} ~/.ssh/config"
82+ )
83+ client.wait_until_succeeds("ssh githost true")
84+
85+ with subtest("test a remote bare repo can be started"):
86+ githost.succeed("mkdir -p ~/myorg/myapp.git")
87+ githost.succeed("cd ~/myorg/myapp.git && git init --bare")
88+
89+ with subtest("test a git project can be configured with a remote using ssh"):
90+ client.succeed("mkdir -p ~/code/myapp")
91+ client.succeed("cd ~/code/myapp && git init")
92+ client.succeed(
93+ "cp ${thunkableSample} ~/code/myapp/default.nix"
94+ )
95+ client.succeed("cd ~/code/myapp && git add .")
96+ client.succeed('git config --global user.email "you@example.com"')
97+ client.succeed('git config --global user.name "Your Name"')
98+ client.succeed('cd ~/code/myapp && git commit -m "Initial"')
99+ client.succeed(
100+ "cd ~/code/myapp && git remote add origin root@githost:/root/myorg/myapp.git"
101+ )
102+
103+
104+ with subtest("test pushing code to the remote"):
105+ client.succeed("cd ~/code/myapp && git push -u origin master")
106+ client.succeed("cd ~/code/myapp && git status")
107+
108+ with subtest("test obelisk can pack"):
109+ client.succeed("ob -v thunk pack ~/code/myapp")
110+ client.succeed("grep -qF 'git' ~/code/myapp/default.nix")
111+ client.succeed("grep -qF 'myorg' ~/code/myapp/git.json")
112+ client.succeed("ob -v thunk unpack ~/code/myapp")
113+
114+ with subtest("test obelisk can set the public / private flag"):
115+ client.succeed("ob -v thunk pack ~/code/myapp --private")
116+ client.fail("""grep -qF '"private": practice' ~/code/myapp/git.json""")
117+ client.succeed("""grep -qF '"private": true' ~/code/myapp/git.json""")
118+ client.succeed("nix-build ~/code/myapp")
119+ client.succeed("ob -v thunk unpack ~/code/myapp")
120+ client.succeed("ob -v thunk pack ~/code/myapp --public")
121+ client.succeed("""grep -qF '"private": false' ~/code/myapp/git.json""")
122+ client.succeed("nix-build ~/code/myapp")
123+ client.succeed("ob -v thunk unpack ~/code/myapp")
124+
125+ with subtest("test building an invalid thunk fails"):
126+ client.succeed("cd ~/code/myapp && git checkout -b bad")
127+ client.succeed(
128+ "cp ${invalidThunkableSample} ~/code/myapp/default.nix"
129+ )
130+ client.succeed("cd ~/code/myapp && git add .")
131+ client.succeed('git config --global user.email "you@example.com"')
132+ client.succeed('git config --global user.name "Your Name"')
133+ client.succeed('cd ~/code/myapp && git commit -m "Bad commit"')
134+ client.succeed("cd ~/code/myapp && git push -u origin bad")
135+ client.succeed("ob -v thunk pack ~/code/myapp --public")
136+ client.fail("nix-build ~/code/myapp")
137+ client.succeed("ob -v thunk unpack ~/code/myapp")
138+ client.succeed("cd ~/code/myapp && git checkout master")
139+
140+ with subtest("test obelisk can detect private repos"):
141+ client.succeed("ob -v thunk pack ~/code/myapp")
142+ client.succeed("""grep -qF '"private": false' ~/code/myapp/git.json""")
143+ client.succeed("ob -v thunk unpack ~/code/myapp")
144+ '';
145+ }) {}
default.nix+21−11
@@ -2,6 +2,7 @@
22, profiling ? false
33, iosSdkVersion ? "10.2"
44, config ? {}
5+, reflex-platform-func ? import ./dep/reflex-platform
56}:
67let
78 cleanSource = builtins.filterSource (name: _: let baseName = builtins.baseNameOf name; in !(
@@ -16,7 +17,7 @@ let
1617 openssh
1718 ];
1819
19− getReflexPlatform = sys: import ./dep/reflex-platform {
20+ getReflexPlatform = sys: reflex-platform-func {
2021 inherit iosSdkVersion config;
2122 system = sys;
2223 enableLibraryProfiling = profiling;
@@ -47,6 +48,8 @@ let
4748 pkgs = self.callPackage ({ pkgs }: pkgs) {};
4849 onLinux = pkg: f: if pkgs.stdenv.isLinux then f pkg else pkg;
4950 in {
51+ shelly = self.callHackage "shelly" "1.9.0" {};
52+
5053 obelisk-executable-config-inject = pkgs.obeliskExecutableConfig.platforms.web.inject self;
5154
5255 obelisk-asset-manifest = self.callCabal2nix "obelisk-asset-manifest" (cleanSource ./lib/asset/manifest) {};
@@ -64,7 +67,12 @@ let
6467 haskellLib.overrideCabal pkg (drv: { librarySystemDepends = [ pkgs.iproute ]; })
6568 );
6669 obelisk-route = self.callCabal2nix "obelisk-route" (cleanSource ./lib/route) {};
67− obelisk-selftest = self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {};
70+ obelisk-selftest = haskellLib.overrideCabal (self.callCabal2nix "obelisk-selftest" (cleanSource ./lib/selftest) {}) {
71+ librarySystemDepends = [
72+ pkgs.cabal-install
73+ pkgs.coreutils
74+ ];
75+ };
6876 obelisk-snap-extras = self.callCabal2nix "obelisk-snap-extras" (cleanSource ./lib/snap-extras) {};
6977 tabulation = self.callCabal2nix "tabulation" (cleanSource ./lib/tabulation) {};
7078 })
@@ -147,6 +155,7 @@ in rec {
147155 compressedJs = frontend: optimizationLevel: pkgs.runCommand "compressedJs" {} ''
148156 mkdir $out
149157 cd $out
158+ # TODO profiling + static shouldn't break and need an ad-hoc workaround like that
150159 ln -s "${haskellLib.justStaticExecutables frontend}/bin/frontend.jsexe/all.js" all.unminified.js
151160 ${if optimizationLevel == null then ''
152161 ln -s all.unminified.js all.js
@@ -229,7 +238,7 @@ in rec {
229238 pkgs.runCommand "serverExe" {} ''
230239 mkdir $out
231240 set -eux
232− ln -s "${haskellLib.justStaticExecutables backend}"/bin/* $out/
241+ ln -s "${if profiling then backend else haskellLib.justStaticExecutables backend}"/bin/* $out/
233242 ln -s "${mkAssets assets}" $out/static.assets
234243 ln -s ${mkAssets (compressedJs frontend optimizationLevel)} $out/frontend.jsexe.assets
235244 echo ${version} > $out/version
@@ -327,18 +336,19 @@ in rec {
327336 passthru = { inherit android ios packages overrides tools shellToolOverrides withHoogle staticFiles staticFilesImpure __closureCompilerOptimizationLevel processedStatic __iosWithConfig __androidWithConfig; };
328337 };
329338 in mkProject (projectDefinition args));
330− serverOn = sys: version: serverExe
331− (projectOut sys).ghc.backend
332− (projectOut system).ghcjs.frontend
333− (projectOut sys).passthru.staticFiles
334− (projectOut sys).passthru.__closureCompilerOptimizationLevel
339+ mainProjectOut = projectOut system;
340+ serverOn = projectInst: version: serverExe
341+ projectInst.ghc.backend
342+ mainProjectOut.ghcjs.frontend
343+ projectInst.passthru.staticFiles
344+ projectInst.passthru.__closureCompilerOptimizationLevel
335345 version;
336− linuxExe = serverOn "x86_64-linux";
346+ linuxExe = serverOn (projectOut "x86_64-linux");
337347 dummyVersion = "Version number is only available for deployments";
338− in projectOut system // {
348+ in mainProjectOut // {
339349 linuxExeConfigurable = linuxExe;
340350 linuxExe = linuxExe dummyVersion;
341− exe = serverOn system dummyVersion;
351+ exe = serverOn mainProjectOut dummyVersion;
342352 server = args@{ hostName, adminEmail, routeHost, enableHttps, version }:
343353 server (args // { exe = linuxExe version; });
344354 obelisk = import (base + "/.obelisk/impl") {};
dep/reflex-platform/github.json+3−3
@@ -1,7 +1,7 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "master",
5− "rev": "72ca177da290dc5311c08683b8d167a205c9322e",
6− "sha256": "06pqml1iqc5fawwsqb7wq77gh5v07bgb92hz8r1hf0ysr0abafcg"
4+ "branch": "develop",
5+ "rev": "90351bdb2c99d416ea95fd5b8b474e8f9fab2942",
6+ "sha256": "0bbzz4ckdx0ilhhpd3drfbsc0zdb2rnhw2m3nls9villbs3lz3lh"
77}
jobsets.nixdeleted−71
@@ -1,71 +0,0 @@
1−{ prs }:
2−
3−let
4− self = import ./. {};
5− pkgs = self.nixpkgs;
6− mkFetchGithub = value: {
7− inherit value;
8− type = "git";
9− emailresponsible = false;
10− };
11−in
12−with pkgs.lib;
13−let
14− defaults = jobs: {
15− inherit (jobs) description;
16− enabled = 1;
17− hidden = false;
18− keepnr = 10;
19− schedulingshares = 100;
20− checkinterval = 120;
21− enableemail = false;
22− emailoverride = "";
23− nixexprinput = "obelisk";
24− nixexprpath = "release.nix";
25− inputs = jobs.inputs // {
26− nixpkgs = {
27− type = "git";
28− value = "https://github.com/NixOS/nixpkgs-channels nixos-unstable";
29− emailresponsible = false;
30− };
31− };
32− self-args = {
33− type = "nix";
34− value = "{ config.android_sdk.accept_license = true; }";
35− emailresponsible = false;
36− };
37− };
38− branchJobset = branch: defaults {
39− description = "obelisk-${branch}";
40− inputs = {
41− obelisk = {
42− value = "https://github.com/obsidiansystems/obelisk ${branch}";
43− type = "git";
44− emailresponsible = false;
45− };
46− };
47− };
48− makePr = num: info: {
49− name = "obelisk-pr-${num}";
50− value = defaults {
51− description = "#${num}: ${info.title}";
52− inputs = {
53− obelisk = {
54− #NOTE: This should really use "pull/${num}/merge"; however, GitHub's
55− #status checks only operate on PR heads. This creates a race
56− #condition, which can currently only be solved by requiring PRs to be
57− #up to date before they're merged. See
58− #https://github.com/isaacs/github/issues/1002
59− value = "https://github.com/obsidiansystems/obelisk pull/${num}/head";
60− type = "git";
61− emailresponsible = false;
62− };
63− };
64− };
65− };
66− processedPrs = mapAttrs' makePr (builtins.fromJSON (builtins.readFile prs));
67− jobsetsAttrs = processedPrs //
68− genAttrs ["master"] branchJobset;
69−in {
70− jobsets = pkgs.writeText "spec.json" (builtins.toJSON jobsetsAttrs);
71−}
lib/command/obelisk-command.cabal+1−1
@@ -1,5 +1,5 @@
11name: obelisk-command
2−version: 0.3
2+version: 0.4
33cabal-version: >= 1.8
44build-type: Simple
55
lib/command/src/Obelisk/Command/Deploy.hs+2−1
@@ -41,6 +41,7 @@ import Obelisk.Command.Project
4141import Obelisk.Command.Thunk
4242import Obelisk.Command.Utils
4343
44+
4445deployInit
4546 :: MonadObelisk m
4647 => ThunkPtr
@@ -57,7 +58,7 @@ deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps
5758 False -> failWith $ T.pack $ "ob deploy init: file does not exist: " <> sshKeyPath
5859 True -> pure $ deployDir </> "ssh_key"
5960 callProcessAndLogOutput (Notice, Error) $
60− proc "cp" [sshKeyPath, localKey]
61+ proc cp [sshKeyPath, localKey]
6162 liftIO $ setFileMode localKey $ ownerReadMode .|. ownerWriteMode
6263 return localKey
6364 withSpinner "Validating configuration" $ do
lib/command/src/Obelisk/Command/Run.hs+66−61
@@ -4,13 +4,17 @@
44{-# LANGUAGE OverloadedStrings #-}
55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE TupleSections #-}
78module Obelisk.Command.Run where
89
9−import Control.Exception (bracket)
10+import Control.Applicative (liftA2)
11+import Control.Exception (Exception, bracket)
1012import Control.Monad
13+import Control.Monad.Except (runExceptT, throwError)
1114import Control.Monad.IO.Class (liftIO)
1215import Control.Monad.Reader (MonadIO)
1316import Data.Either
17+import Data.Foldable (for_)
1418import qualified Data.List as L
1519import qualified Data.List.NonEmpty as NE
1620import Data.Maybe
@@ -24,22 +28,18 @@ import Distribution.Types.CondTree
2428import Distribution.Types.GenericPackageDescription
2529import Distribution.Types.Library
2630import Distribution.Utils.Generic
27−import Hpack.Config
28−import Hpack.Render
29−import Hpack.Yaml
31+import qualified Hpack.Config as Hpack
32+import qualified Hpack.Render as Hpack
33+import qualified Hpack.Yaml as Hpack
3034import Language.Haskell.Extension
3135import Network.Socket hiding (Debug)
3236import System.Directory
3337import System.FilePath
3438import System.IO.Temp (withSystemTempDirectory)
3539import System.Which (staticWhich)
36−import Data.ByteString (ByteString)
3740
3841import Obelisk.App (MonadObelisk, ObeliskT)
39−import Obelisk.CliApp
40− ( CliT (..), HasCliConfig, Severity (..)
41− , callCommand, failWith, getCliConfig, putLog, proc
42− , readProcessAndLogStderr, runCli)
42+import Obelisk.CliApp (Severity (..) , callCommand, failWith, putLog, proc, readProcessAndLogStderr)
4343import Obelisk.Command.Project (inProjectShell, withProjectRoot)
4444
4545data CabalPackageInfo = CabalPackageInfo
@@ -105,6 +105,27 @@ runWatch = do
105105 pkgs <- getLocalPkgs
106106 withGhciScript pkgs $ \dotGhciPath -> runGhcid dotGhciPath Nothing
107107
108+data GuessPackageFileError = GuessPackageFileError_Ambiguous [FilePath] | GuessPackageFileError_NotFound
109+ deriving (Eq, Ord, Show)
110+instance Exception GuessPackageFileError
111+
112+-- | Given a directory, try to guess what the appropriate @.cabal@ or @package.yaml@ file is for the package.
113+guessCabalPackageFile
114+ :: MonadIO m
115+ => FilePath -- ^ Directory to search for cabal package
116+ -> m (Either GuessPackageFileError (Either FilePath FilePath))
117+ -- ^ 'Right' 'Left' for hpack package, 'Right' 'Right' for cabal package; 'FilePath' is relative to given directory.
118+guessCabalPackageFile dir = do
119+ candidates <- liftIO $
120+ filterM (doesFileExist . (dir </>))
121+ =<< filter (liftA2 (||) (== Hpack.packageConfig) (".cabal" `L.isSuffixOf`))
122+ <$> listDirectory dir
123+ pure $ case L.partition (== Hpack.packageConfig) candidates of
124+ ([hpack], _) -> Right $ Left hpack
125+ ([], [cabal]) -> Right $ Right cabal
126+ ([], []) -> Left GuessPackageFileError_NotFound
127+ (hpacks, cabals) -> Left $ GuessPackageFileError_Ambiguous $ hpacks <> cabals
128+
108129-- | Relative paths to local packages of an obelisk project
109130-- TODO a way to query this
110131getLocalPkgs :: Applicative f => f [FilePath]
@@ -114,54 +135,38 @@ parseCabalPackage
114135 :: (MonadObelisk m)
115136 => FilePath -- ^ package directory
116137 -> m (Maybe CabalPackageInfo)
117−parseCabalPackage dir = do
118− let cabalFp = dir </> (takeBaseName dir <> ".cabal")
119− hpackFp = dir </> "package.yaml"
120− hasCabal <- liftIO $ doesFileExist cabalFp
121− hasHpack <- liftIO $ doesFileExist hpackFp
122−
123− mCabalContents <- if hasCabal
124− then Just <$> liftIO (readUTF8File cabalFp)
125− else if hasHpack
126− then do
127− let decodeOptions = DecodeOptions (ProgramName "ob") hpackFp Nothing decodeYaml
128− liftIO (readPackageConfig decodeOptions) >>= \case
129− Left err -> do
130− putLog Error $ T.pack $ "Failed to parse " <> hpackFp <> ": " <> err
131− return Nothing
132− Right (DecodeResult hpackPackage _ _ _) -> do
133− return $ Just $ renderPackage [] hpackPackage
134− else do
135− putLog Error $ T.pack "Found neither cabal nor hpack file"
136− return Nothing
137−
138− fmap join $ forM mCabalContents $ \cabalContents -> do
139− let (warnings, result) = runParseResult $ parseGenericPackageDescription $
140− toUTF8BS cabalContents
141− mapM_ (putLog Warning) $ fmap (T.pack . show) warnings
142− case result of
143− Right gpkg -> do
144− return $ do
145− (_, lib) <- simplifyCondTree (const $ pure True) <$> condLibrary gpkg
146− pure $ CabalPackageInfo
147− { _cabalPackageInfo_packageRoot = takeDirectory cabalFp
148− , _cabalPackageInfo_sourceDirs =
149− fromMaybe (pure ".") $ NE.nonEmpty $ hsSourceDirs $ libBuildInfo lib
150− , _cabalPackageInfo_defaultExtensions =
151− defaultExtensions $ libBuildInfo lib
152− , _cabalPackageInfo_defaultLanguage =
153− defaultLanguage $ libBuildInfo lib
154− , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
155− }
156− Left (_, errors) -> do
157− putLog Error $ T.pack $ "Failed to parse " <> cabalFp <> ":"
158− mapM_ (putLog Error) $ fmap (T.pack . show) errors
159− return Nothing
138+parseCabalPackage dir = either ((Nothing <$) . putLog Error) (pure . Just) <=< runExceptT $ do
139+ (cabalContents, packageFile) <- guessCabalPackageFile dir >>= \case
140+ Left GuessPackageFileError_NotFound -> throwError $ "No .cabal or package.yaml file found in " <> T.pack dir
141+ Left (GuessPackageFileError_Ambiguous _) -> throwError $ "Unable to determine which .cabal file to use in " <> T.pack dir
142+ Right (Right cabalFileName) -> let file = dir </> cabalFileName in (, file) <$> liftIO (readUTF8File file)
143+ Right (Left hpackFileName) -> do
144+ let
145+ file = dir </> hpackFileName
146+ decodeOptions = Hpack.DecodeOptions (Hpack.ProgramName "ob") file Nothing Hpack.decodeYaml
147+ liftIO (Hpack.readPackageConfig decodeOptions) >>= \case
148+ Left err -> throwError $ T.pack $ "Failed to parse " <> file <> ": " <> err
149+ Right (Hpack.DecodeResult hpackPackage _ _ _) -> pure (Hpack.renderPackage [] hpackPackage, file)
160150
161−withUTF8FileContentsM :: (MonadIO m, HasCliConfig e m) => FilePath -> (ByteString -> CliT e IO a) -> m a
162−withUTF8FileContentsM fp f = do
163− c <- getCliConfig
164− liftIO $ withUTF8FileContents fp $ runCli c . f . toUTF8BS
151+ let (warnings, result) = runParseResult $ parseGenericPackageDescription $
152+ toUTF8BS cabalContents
153+ for_ warnings $ putLog Warning . T.pack . show
154+ case condLibrary <$> result of
155+ Right (Just condLib) -> do
156+ let (_, lib) = simplifyCondTree (const $ pure True) condLib
157+ pure $ CabalPackageInfo
158+ { _cabalPackageInfo_packageRoot = dir
159+ , _cabalPackageInfo_sourceDirs =
160+ fromMaybe (pure ".") $ NE.nonEmpty $ hsSourceDirs $ libBuildInfo lib
161+ , _cabalPackageInfo_defaultExtensions =
162+ defaultExtensions $ libBuildInfo lib
163+ , _cabalPackageInfo_defaultLanguage =
164+ defaultLanguage $ libBuildInfo lib
165+ , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
166+ }
167+ Right Nothing -> throwError "Haskell package has no library component"
168+ Left (_, errors) ->
169+ throwError $ T.pack $ "Failed to parse " <> packageFile <> ":\n" <> unlines (map show errors)
165170
166171-- | Create ghci configuration to load the given packages
167172withGhciScript
@@ -169,14 +174,14 @@ withGhciScript
169174 => [FilePath] -- ^ List of packages to load into ghci
170175 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
171176 -> m ()
172−withGhciScript pkgs f = do
173− (pkgDirErrs, packageInfos) <- fmap partitionEithers $ forM pkgs $ \pkg -> do
174− flip fmap (parseCabalPackage pkg) $ \case
175− Nothing -> Left pkg
177+withGhciScript dirs f = do
178+ (pkgDirErrs, packageInfos) <- fmap partitionEithers $ forM dirs $ \dir -> do
179+ flip fmap (parseCabalPackage dir) $ \case
180+ Nothing -> Left dir
176181 Just packageInfo -> Right packageInfo
177182
178183 when (null packageInfos) $
179− failWith $ T.pack $ "No valid pkgs found in " <> intercalate ", " pkgs
184+ failWith $ T.pack $ "No valid pkgs found in " <> intercalate ", " dirs
180185 unless (null pkgDirErrs) $
181186 putLog Warning $ T.pack $ "Failed to find pkgs in " <> intercalate ", " pkgDirErrs
182187
lib/command/src/Obelisk/Command/Thunk.hs+3−3
@@ -635,13 +635,13 @@ updateThunk p f = withSystemTempDirectory "obelisk-thunkptr-" $ \tmpDir -> do
635635 Right (ThunkData_Packed _) -> do
636636 let tmpThunk = tmpDir </> "thunk"
637637 callProcessAndLogOutput (Notice, Error) $
638− proc "cp" ["-r", "-T", thunkDir, tmpThunk]
638+ proc cp ["-r", "-T", thunkDir, tmpThunk]
639639 return tmpThunk
640640 Right _ -> failWith "Thunk is not packed"
641641 updateThunkFromTmp p' = do
642642 _ <- packThunk' True (ThunkPackConfig False (ThunkConfig Nothing)) p'
643643 callProcessAndLogOutput (Notice, Error) $
644− proc "cp" ["-r", "-T", p', p]
644+ proc cp ["-r", "-T", p', p]
645645
646646finalMsg :: Bool -> (a -> Text) -> Maybe (a -> Text)
647647finalMsg noTrail s = if noTrail then Nothing else Just s
@@ -682,7 +682,7 @@ unpackThunk' noTrail thunkDir = checkThunkDirectory "Can't pack/unpack from with
682682
683683 liftIO $ createDirectory obGitDir
684684 callProcessAndLogOutput (Notice, Error) $
685− proc "cp" ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
685+ proc cp ["-r", "-T", thunkDir </> ".", obGitDir </> "orig-thunk"]
686686 callProcessAndLogOutput (Notice, Error) $
687687 proc "rm" ["-r", thunkDir]
688688 callProcessAndLogOutput (Notice, Error) $
lib/command/src/Obelisk/Command/Utils.hs+6−1
@@ -4,6 +4,7 @@
44{-# LANGUAGE ScopedTypeVariables #-}
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE PatternGuards #-}
7+{-# LANGUAGE TemplateHaskell #-}
78module Obelisk.Command.Utils where
89
910import Control.Applicative hiding (many)
@@ -23,6 +24,7 @@ import Data.Void (Void)
2324import System.Directory (canonicalizePath)
2425import System.Environment (getExecutablePath)
2526import System.Exit (ExitCode)
27+import System.Which (staticWhich)
2628import Text.Megaparsec as MP
2729import Text.Megaparsec.Char as MP
2830
@@ -32,6 +34,9 @@ import Obelisk.CliApp
3234getObeliskExe :: IO FilePath
3335getObeliskExe = getExecutablePath >>= canonicalizePath
3436
37+cp :: FilePath
38+cp = $(staticWhich "cp")
39+
3540-- Check whether the working directory is clean
3641checkGitCleanStatus :: MonadObelisk m => FilePath -> Bool -> m Bool
3742checkGitCleanStatus repo withIgnored = do
@@ -83,7 +88,7 @@ isolateGitProc = setEnvOverride (overrides <>)
8388-- | Recursively copy a directory using `cp -a` -- TODO: Should use -rT instead of -a
8489copyDir :: FilePath -> FilePath -> ProcessSpec
8590copyDir src dest =
86− setCwd (Just src) $ proc "cp" ["-a", ".", dest] -- TODO: This will break if dest is relative since we change cwd
91+ setCwd (Just src) $ proc cp ["-a", ".", dest] -- TODO: This will break if dest is relative since we change cwd
8792
8893readGitProcess :: MonadObelisk m => FilePath -> [String] -> m Text
8994readGitProcess repo = readProcessAndLogOutput (Debug, Notice) . gitProc repo
lib/selftest/obelisk-selftest.cabal+4−2
@@ -11,16 +11,18 @@ library
1111 base
1212 , containers
1313 , directory
14+ , filepath
1415 , hspec
1516 , http-client
1617 , http-types
1718 , HUnit
19+ , network
1820 , obelisk-cliapp
1921 , obelisk-executable-config-lookup
2022 , obelisk-run
23+ , which
2124 , process
22− , network
23− , shelly
25+ , shelly >= 1.9.0
2426 , temporary
2527 , text
2628 exposed-modules:
lib/selftest/src/Obelisk/SelfTest.hs+73−33
@@ -4,6 +4,7 @@
44{-# LANGUAGE PatternGuards #-}
55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE ViewPatterns #-}
7+{-# LANGUAGE TemplateHaskell #-}
78module Obelisk.SelfTest where
89
910import Control.Exception (bracket, throw)
@@ -11,7 +12,7 @@ import Control.Monad
1112import Control.Monad.IO.Class
1213import Data.Bool (bool)
1314import Data.Function (fix)
14−import Data.Semigroup (Semigroup, (<>))
15+import Data.Semigroup ((<>))
1516import qualified Data.Set as Set
1617import Data.String
1718import Data.Text (Text)
@@ -21,10 +22,12 @@ import qualified Data.Text.IO as T
2122import qualified Network.HTTP.Client as HTTP
2223import qualified Network.HTTP.Types as HTTP
2324import qualified Network.Socket as Socket
24−import Shelly
25+import Shelly hiding (cp)
2526import System.Directory (withCurrentDirectory, getDirectoryContents)
2627import System.Environment
2728import System.Exit (ExitCode (..))
29+import System.FilePath (replaceBaseName, takeBaseName)
30+import System.Which (staticWhich)
2831import qualified System.Info
2932import System.IO (Handle, hClose)
3033import System.IO.Temp
@@ -43,16 +46,21 @@ data ObRunState
4346 | ObRunState_BackendStarted
4447 deriving (Eq, Show)
4548
46−doubleQuotes :: (IsString a, Semigroup a) => a -> a
47−doubleQuotes s = "\"" <> s <> "\""
49+cp :: FilePath
50+cp = $(staticWhich "cp")
51+
52+cabalPath :: FilePath
53+cabalPath = $(staticWhich "cabal")
54+
55+gitUserConfig :: [Text]
56+gitUserConfig = ["-c", "user.name=Obelisk Selftest", "-c", "user.email=noreply@example.com"]
4857
4958commit :: Text -> Sh ()
50−commit msg = void $ run "git"
51− [ "commit"
59+commit msg = void $ run "git" $ gitUserConfig <> [ "commit"
5260 , "--no-gpg-sign"
5361 , "--allow-empty"
5462 , "-m"
55− , doubleQuotes msg
63+ , msg
5664 ]
5765
5866-- TODO replace Void and stop using loosely typed synchronous exceptions
@@ -68,6 +76,7 @@ tshow = T.pack . show
6876shellyOb :: MonadIO m => (Sh a -> Sh a) -> Sh a -> m a
6977shellyOb f obTest = shelly $ f obTest
7078
79+
7180main :: IO ()
7281main = do
7382 -- Note: you can pass hspec arguments as well, eg: `-m <pattern>`
@@ -82,33 +91,49 @@ main = do
8291 withSystemTempDirectory "obelisk-impl-copy" $ \(fromString -> obeliskImpl) -> do
8392 void . shellyOb verbosity $ chdir obeliskImpl $ do
8493 user <- T.strip <$> run "whoami" []
85− run_ "cp" ["-rT", toTextIgnore obeliskImplRaw, toTextIgnore obeliskImpl]
94+ run_ cp ["-rT", toTextIgnore obeliskImplRaw, toTextIgnore obeliskImpl]
8695 run_ "chown" ["-R", user, toTextIgnore obeliskImpl]
8796 run_ "chmod" ["-R", "g-rw,o-rw", toTextIgnore obeliskImpl]
97+ f obeliskImpl
98+
99+ withInitCache f obeliskImpl =
100+ withSystemTempDirectory "init Cache λ" $ \initCache -> do
101+ -- Setup the ob init cache
102+ void . shellyOb verbosity $ chdir initCache $ do
103+ run_ "ob" ["init", "--symlink", toTextIgnore obeliskImpl]
104+ run_ "git" ["init"]
88105
89− withSystemTempDirectory "init Cache λ" $ \initCache -> do
90− -- Setup the ob init cache
91− void . shellyOb verbosity $ chdir (fromString initCache) $ do
92− run_ "ob" ["init", "--symlink", toTextIgnore obeliskImpl]
93− run_ "git" ["init"]
106+ f initCache
94107
95− f obeliskImpl initCache
108+ withObeliskImplAndInitCache f =
109+ withObeliskImpl $ \impl -> withInitCache (f impl) impl
96110
97111 httpManager <- HTTP.newManager HTTP.defaultManagerSettings
98− [p0, p1, p2, p3] <- liftIO $ getFreePorts 4
99112
100− withObeliskImpl $ \obeliskImpl initCache ->
113+ withObeliskImplAndInitCache $ \obeliskImpl initCache ->
101114 hspec $ parallel $ do
102115 let shelly_ = void . shellyOb verbosity
103116
104− inTmp :: (Shelly.FilePath -> Sh a) -> IO ()
105− inTmp f = withTmp (chdir <*> f)
117+ defaultTmpDirName = "test λ"
106118
107− withTmp f = shelly_ . withSystemTempDirectory "test λ" $ f . fromString
119+ inTmp' :: FilePath -> (FilePath -> Sh a) -> IO ()
120+ inTmp' dirname f = withTmp' dirname (chdir <*> f)
121+ inTmp = inTmp' defaultTmpDirName
108122
109− inTmpObInit f = inTmp $ \dir -> do
110− run_ "cp" ["-rT", fromString initCache, toTextIgnore dir]
123+ withTmp' dirname f = shelly_ . withSystemTempDirectory dirname $ f . fromString
124+ withTmp = withTmp' defaultTmpDirName
125+
126+ inTmpObInit' dirname f = inTmp' dirname $ \dir -> do
127+ run_ cp ["-rT", fromString initCache, toTextIgnore dir]
111128 f dir
129+ inTmpObInit = inTmpObInit' defaultTmpDirName
130+
131+ -- To be used in tests that change the obelisk impl directory
132+ inTmpObInitWithImplCopy f = inTmpObInit $ \dir ->
133+ withObeliskImpl $ \(fromString -> implCopy) -> do
134+ run_ "rm" [thunk]
135+ run_ "ln" ["-s", implCopy, thunk]
136+ f dir
112137
113138 assertRevEQ a b = liftIO . assertEqual "" "" =<< diff a b
114139 assertRevNE a b = liftIO . assertBool "" . (/= "") =<< diff a b
@@ -149,9 +174,12 @@ main = do
149174
150175 -- These tests fail with "Could not find module 'Obelisk.Generated.Static'"
151176 -- when not run by 'nix-build --attr selftest'
152− describe "ob run" $ parallel $ do
153− it "works in root directory" $ inTmpObInit $ \_ -> testObRunInDir p0 p1 Nothing httpManager
154− it "works in sub directory" $ inTmpObInit $ \_ -> testObRunInDir p2 p3 (Just "frontend") httpManager
177+ describe "ob run" $ {- NOT parallel $ -} do
178+ it "works in root directory" $ inTmpObInit $ \_ -> testObRunInDir Nothing httpManager
179+ it "works in sub directory" $ inTmpObInit $ \_ -> testObRunInDir (Just "frontend") httpManager
180+ it "works with differently named cabal files" $ inTmpObInit $ \_ -> do
181+ changeCabalPackageName "backend/backend.cabal" "new-backend"
182+ testObRunInDir Nothing httpManager
155183
156184 describe "obelisk project" $ parallel $ do
157185 it "can build obelisk command" $ inTmpObInit $ \_ -> nixBuild ["-A", "command" , toTextIgnore obeliskImpl]
@@ -167,7 +195,7 @@ main = do
167195 if System.Info.os == "darwin"
168196 then it "can build ios" $ inTmpObInit $ \_ -> nixBuild ["-A", "ios.frontend"]
169197 else it "can build android after accepting license" $ inTmpObInit $ \dir -> do
170− let defaultNixPath = dir </> ("default.nix" :: Shelly.FilePath)
198+ let defaultNixPath = dir </> ("default.nix" :: FilePath)
171199 writefile defaultNixPath
172200 =<< T.replace
173201 "# config.android_sdk.accept_license = false;"
@@ -178,18 +206,21 @@ main = do
178206 forM_ ["ghc", "ghcjs"] $ \compiler -> do
179207 let
180208 shellName = "shells." <> compiler
181− inShell cmd' = run "nix-shell" ["default.nix", "-A", fromString shellName, "--run", cmd']
209+ inShell cmd' = run_ "nix-shell" ["default.nix", "-A", fromString shellName, "--run", cmd']
182210 it ("can enter " <> shellName) $ inTmpObInit $ \_ -> inShell "exit"
183− it ("can build in " <> shellName) $ inTmpObInit $ \_ -> inShell $ "cabal new-build --" <> fromString compiler <> " all"
211+ -- NOTE: We override the temporary directory name because cabal has a bug preventing new-build from working
212+ -- in a path that has unicode characters.
213+ it ("can build in " <> shellName) $ inTmpObInit' "test" $ \_ -> inShell $
214+ T.pack cabalPath <> " --version; " <> T.pack cabalPath <> " new-build --" <> T.pack compiler <> " all"
184215
185− it "has idempotent thunk update" $ inTmpObInit $ \_ -> do
216+ it "has idempotent thunk update" $ inTmpObInitWithImplCopy $ \_ -> do
186217 _ <- pack
187218 u <- update
188219 uu <- update
189220 assertRevEQ u uu
190221
191222 describe "ob thunk pack/unpack" $ parallel $ do
192− it "has thunk pack and unpack inverses" $ inTmpObInit $ \_ -> do
223+ it "has thunk pack and unpack inverses" $ inTmpObInitWithImplCopy $ \_ -> do
193224
194225 _ <- pack
195226 e <- commitAll
@@ -249,8 +280,9 @@ main = do
249280
250281
251282-- | Run `ob run` in the given directory (maximum of one level deep)
252−testObRunInDir :: Socket.PortNumber -> Socket.PortNumber -> Maybe Shelly.FilePath -> HTTP.Manager -> Sh ()
253−testObRunInDir p0 p1 mdir httpManager = handle_sh (\case ExitSuccess -> pure (); e -> throw e) $ do
283+testObRunInDir :: Maybe FilePath -> HTTP.Manager -> Sh ()
284+testObRunInDir mdir httpManager = handle_sh (\case ExitSuccess -> pure (); e -> throw e) $ do
285+ [p0, p1] <- liftIO $ getFreePorts 2
254286 let uri p = "http://localhost:" <> T.pack (show p) <> "/" -- trailing slash required for comparison
255287 writefile "config/common/route" $ uri p0
256288 maybe id chdir mdir $ runHandle "ob" ["run", "-v"] $ \stdout -> do
@@ -264,7 +296,7 @@ testObRunInDir p0 p1 mdir httpManager = handle_sh (\case ExitSuccess -> pure ();
264296 then errorExit $ "Reloading failed: expected " <> newUri <> " but got " <> runningUri
265297 else exit 0
266298
267−testThunkPack :: Shelly.FilePath -> Sh ()
299+testThunkPack :: FilePath -> Sh ()
268300testThunkPack path' = withTempFile (T.unpack $ toTextIgnore path') "test-file" $ \file handle -> do
269301 let pack' = readProcessWithExitCode "ob" ["thunk", "pack", T.unpack $ toTextIgnore path'] ""
270302 ensureThunkPackFails q = liftIO $ pack' >>= \case
@@ -285,7 +317,7 @@ testThunkPack path' = withTempFile (T.unpack $ toTextIgnore path') "test-file" $
285317 liftIO $ T.hPutStrLn handle "test file" >> hClose handle
286318 ensureThunkPackFails "modified"
287319 -- Existing stashes
288− void $ git ["stash"]
320+ void $ git $ gitUserConfig <> [ "stash" ]
289321 ensureThunkPackFails "has stashes"
290322
291323-- | Blocks until a non-empty line is available
@@ -330,6 +362,14 @@ obRunCheck httpManager _stdout frontendUri = do
330362 req frontendUri >>= \r -> when (HTTP.responseStatus r /= HTTP.ok200) $ errorExit $
331363 "Request to frontend server failed: " <> T.pack (show r)
332364
365+-- | Rename a cabal file and do a really dumb, brittle search/replace in its content to update the name.
366+changeCabalPackageName :: FilePath -> Text -> Sh ()
367+changeCabalPackageName cabalFile newName = do
368+ contents <- readfile cabalFile
369+ writefile (replaceBaseName cabalFile (T.unpack newName)) $
370+ T.replace (" " <> T.pack (takeBaseName cabalFile)) (" " <> newName) contents -- WARNING: Super brittle
371+ rm cabalFile
372+
333373getFreePorts :: Int -> IO [Socket.PortNumber]
334374getFreePorts 0 = pure []
335375getFreePorts n = Socket.withSocketsDo $ do
release.nix+8−95
@@ -1,97 +1,10 @@
1−{ self-args ? {
2− config.android_sdk.accept_license = true;
3− iosSdkVersion = "10.2";
4− }
5−, local-self ? import ./. self-args
1+{
2+ supportedSystems ? [ "x86_64-linux" "x86_64-darwin" ]
63}:
4+rec {
5+ recurseForDerivations = true;
6+ build = import ./all-builds.nix { inherit supportedSystems; };
7+ test = import ./all-tests.nix { inherit supportedSystems; };
78
8−let
9− inherit (local-self.nixpkgs) lib runCommand nix;
10−
11− cacheBuildSystems = [ "x86_64-linux" "x86_64-darwin" ];
12−
13− obeliskPackagesCommon = [
14− "obelisk-frontend"
15− "obelisk-route"
16− "obelisk-executable-config-lookup"
17− ];
18−
19− obeliskPackagesBackend = obeliskPackagesCommon ++ [
20− "obelisk-asset-manifest"
21− "obelisk-asset-serve-snap"
22− "obelisk-backend"
23− "obelisk-cliapp"
24− "obelisk-command"
25− "obelisk-executable-config-inject"
26− "obelisk-frontend"
27− "obelisk-run"
28− "obelisk-route"
29− "obelisk-selftest"
30− "obelisk-snap-extras"
31− ];
32−
33− pnameToAttrs = pkgsSet: pnames:
34− lib.listToAttrs (map
35− (name: { inherit name; value = pkgsSet.${name}; })
36− pnames);
37−
38− concatDepends = let
39− extractDeps = x: (x.override {
40− mkDerivation = drv: {
41− out = builtins.concatLists [
42− (drv.buildDepends or [])
43− (drv.libraryHaskellDepends or [])
44− (drv.executableHaskellDepends or [])
45− ];
46− };
47− }).out;
48− in pkgAttrs: builtins.concatLists (map extractDeps (builtins.attrValues pkgAttrs));
49−
50− perPlatform = lib.genAttrs cacheBuildSystems (system: let
51− obelisk = import ./. (self-args // { inherit system; });
52− reflex-platform = obelisk.reflex-platform;
53− ghc = pnameToAttrs
54− obelisk.haskellPackageSets.ghc
55− obeliskPackagesBackend;
56− ghcjs = pnameToAttrs
57− obelisk.haskellPackageSets.ghcjs
58− obeliskPackagesCommon;
59− cachePackages = builtins.concatLists [
60− (builtins.attrValues ghc)
61− (builtins.attrValues ghcjs)
62− (concatDepends ghc)
63− (concatDepends ghcjs)
64− (lib.optional reflex-platform.androidSupport androidSkeleton)
65− (lib.optional reflex-platform.iosSupport iosSkeleton)
66− [ command serverSkeletonExe serverSkeletonShell ]
67− ];
68− command = obelisk.command;
69− skeleton = import ./skeleton { inherit obelisk; };
70− serverSkeletonExe = skeleton.exe;
71− # TODO fix nixpkgs so it doesn't try to run the result of haskell shells as setup hooks.
72− serverSkeletonShell = local-self.nixpkgs.runCommand "shell-safe-for-dep" {} ''
73− touch "$out"
74− echo "return" >> "$out"
75− cat "${skeleton.shells.ghc}" >> "$out"
76− '';
77− androidSkeleton = (import ./skeleton { inherit obelisk; }).android.frontend;
78− iosSkeleton = (import ./skeleton { inherit obelisk; }).ios.frontend;
79− in {
80− inherit
81− command
82− ghc ghcjs
83− serverSkeletonExe
84− serverSkeletonShell
85− ;
86− cache = reflex-platform.pinBuildInputs "obelisk-${system}" cachePackages;
87− } // lib.optionalAttrs reflex-platform.androidSupport {
88− inherit androidSkeleton;
89− } // lib.optionalAttrs reflex-platform.iosSupport {
90− inherit iosSkeleton;
91− });
92−
93− metaCache = local-self.reflex-platform.pinBuildInputs
94− "obelisk-everywhere"
95− (map (a: a.cache) (builtins.attrValues perPlatform));
96−
97−in perPlatform // { inherit metaCache; }
9+ inherit (build) metaCache;
10+}
skeleton/frontend/frontend.cabal+1
@@ -9,6 +9,7 @@ library
99 , common
1010 , obelisk-frontend
1111 , obelisk-route
12+ , jsaddle
1213 , reflex-dom
1314 , obelisk-executable-config-lookup
1415 , obelisk-generated-static
skeleton/frontend/src/Frontend.hs+12
@@ -4,8 +4,10 @@
44
55module Frontend where
66
7+import Control.Monad
78import qualified Data.Text as T
89import qualified Data.Text.Encoding as T
10+import Language.Javascript.JSaddle (eval, liftJSM)
911
1012import Obelisk.Frontend
1113import Obelisk.Configs
@@ -18,6 +20,9 @@ import Common.Api
1820import Common.Route
1921
2022
23+-- This runs in a monad that can be run on the client or the server.
24+-- To run code in a pure client or pure server context, use one of the
25+-- `prerender` functions.
2126frontend :: Frontend (R FrontendRoute)
2227frontend = Frontend
2328 { _frontend_head = do
@@ -26,6 +31,13 @@ frontend = Frontend
2631 , _frontend_body = do
2732 el "h1" $ text "Welcome to Obelisk!"
2833 el "p" $ text $ T.pack commonStuff
34+
35+ -- `prerender` and `prerender_` let you choose a widget to run on the server
36+ -- during prerendering and a different widget to run on the client with
37+ -- JavaScript. The following will generate a `blank` widget on the server and
38+ -- print "Hello, World!" on the client.
39+ prerender_ blank $ liftJSM $ void $ eval ("console.log('Hello, World!')" :: T.Text)
40+
2941 elAttr "img" ("src" =: static @"obelisk.jpg") blank
3042 el "div" $ do
3143 exampleConfig <- getConfig "common/example"