summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2020-04-03 13:20:29 -0700
committerBen Sima <ben@bsima.me>2020-04-03 15:13:56 -0700
commit87b48d473bdb41670c9f3b26a628f34c3c5c9481 (patch)
tree9d515f4e5c49336e4db21b1892b8a0022e74682d
parent65c2b30a288385cf3df4027d50080ac595bbcf83 (diff)
Rewrite buildGhc and buildGhcjs
I wanted to even further simplify the build tooling overhead. My general goal is to not have to think about declaring packages, or dependencies, or really anything that you might find in a cabal file. Not all of these goals are possible, but we can get pretty close. With this commit all I need for the 'buildGhc/buildGhcjs' functions is the path to the entrypoint file; everything else is either inferred by the Nix code or declared in the Haskell code comments. The strategy is to map a Haskell module to an executable artifact, and pass just that module to 'ghc --make'. Then we can rely on ghc to handle walking the local filesystem for imports. The only thing ghc really needs to know is a name for the output executable; it is hard to automatically infer this, so we have a simple comment syntax to declare this in the file. The comment syntax is inspired by existing Haskell 'LANGUAGE' pragmas; having this in the same file keeps the configuration as close to the real code as possible. The Nix code then extracts this info from the code comments, and sets the required ghc flags. Second, we need to declare the set of 3rd-party packages that our program relies on. For this we can re-use the same comment syntax and just list the dependencies, then extract them in Nix and construct a package set as we were before. This reduces the amount of "package declaration" code we have to write in default.nix, and reduces the amount of time we have to spend switching between the Haskell code and the Nix code (I find such context switching super annoying). I also think having the configuration in with the Haskell code encourages us to write smaller, simpler modules and only write code that we need. Additionally, I refactored the bild and ghci (now called 'repl') scripts to work in any directory. The .envrc uses direnv to set the path so that you can run these scripts anywhere. That means the following works: $ cd Run/Que $ bild Website $ repl Server λ> :l Run.Que.Server I find this to be a rather nice workflow.
-rw-r--r--.envrc2
-rw-r--r--Com/InfluencedByBooks/Client.hs11
-rw-r--r--Com/InfluencedByBooks/Server.hs19
-rw-r--r--Com/MusicMeetsComics/Client.hs14
-rw-r--r--Com/MusicMeetsComics/Server.hs32
-rw-r--r--Com/Simatime/buildGhc.nix41
-rw-r--r--Com/Simatime/buildGhcjs.nix52
-rw-r--r--Run/Que/Server.hs11
-rw-r--r--Run/Que/Website.hs6
-rwxr-xr-xbild13
-rw-r--r--default.nix118
-rwxr-xr-xghci2
-rwxr-xr-xrepl11
13 files changed, 181 insertions, 151 deletions
diff --git a/.envrc b/.envrc
index f4c7001..1e97e1d 100644
--- a/.envrc
+++ b/.envrc
@@ -1,2 +1,4 @@
export GUILE_LOAD_PATH=$PWD
export NIX_PATH=$PWD:$NIX_PATH
+export PATH=$PWD:$PATH
+export BIZ_ROOT=$PWD
diff --git a/Com/InfluencedByBooks/Client.hs b/Com/InfluencedByBooks/Client.hs
index cf45511..a7da344 100644
--- a/Com/InfluencedByBooks/Client.hs
+++ b/Com/InfluencedByBooks/Client.hs
@@ -3,6 +3,17 @@
{-# LANGUAGE NoImplicitPrelude #-}
-- | Front-end
+--
+-- : exe ibb.js
+--
+-- : dep clay
+-- : dep miso
+-- : dep protolude
+-- : dep servant
+-- : dep text
+-- : dep aeson
+-- : dep containers
+-- : dep ghcjs-base
module Com.InfluencedByBooks.Client where
import Com.InfluencedByBooks.Core (Action(..), see, init)
diff --git a/Com/InfluencedByBooks/Server.hs b/Com/InfluencedByBooks/Server.hs
index dae17ef..868f258 100644
--- a/Com/InfluencedByBooks/Server.hs
+++ b/Com/InfluencedByBooks/Server.hs
@@ -8,6 +8,25 @@
{-# LANGUAGE TypeOperators #-}
-- | Server
+--
+-- : exe ibb
+--
+-- : dep clay
+-- : dep miso
+-- : dep protolude
+-- : dep servant
+-- : dep text
+-- : dep MonadRandom
+-- : dep acid-state
+-- : dep blaze-html
+-- : dep blaze-markup
+-- : dep bytestring
+-- : dep ixset
+-- : dep random
+-- : dep safecopy
+-- : dep scotty
+-- : dep servant-server
+-- : dep text
module Com.InfluencedByBooks.Server where
import qualified Clay
diff --git a/Com/MusicMeetsComics/Client.hs b/Com/MusicMeetsComics/Client.hs
index 2dad3b7..2361939 100644
--- a/Com/MusicMeetsComics/Client.hs
+++ b/Com/MusicMeetsComics/Client.hs
@@ -1,6 +1,20 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
+-- | Hero app frontend
+--
+-- : exe mmc.js
+--
+-- : dep aeson
+-- : dep clay
+-- : dep containers
+-- : dep miso
+-- : dep protolude
+-- : dep servant
+-- : dep split
+-- : dep string-quote
+-- : dep text
+-- : dep ghcjs-base
module Com.MusicMeetsComics.Client where
import Com.MusicMeetsComics.App ( Action(..)
diff --git a/Com/MusicMeetsComics/Server.hs b/Com/MusicMeetsComics/Server.hs
index 7bb94a2..5b12861 100644
--- a/Com/MusicMeetsComics/Server.hs
+++ b/Com/MusicMeetsComics/Server.hs
@@ -9,6 +9,38 @@
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
+-- | Hero web app
+--
+-- : exe mmc
+--
+-- : dep aeson
+-- : dep clay
+-- : dep containers
+-- : dep miso
+-- : dep protolude
+-- : dep servant
+-- : dep split
+-- : dep string-quote
+-- : dep text
+-- : dep dhall
+-- : dep ekg
+-- : dep fast-logger
+-- : dep http-types
+-- : dep katip
+-- : dep lucid
+-- : dep monad-logger
+-- : dep monad-metrics
+-- : dep mtl
+-- : dep network-uri
+-- : dep safe
+-- : dep servant-lucid
+-- : dep servant-server
+-- : dep split
+-- : dep wai
+-- : dep wai-app-static
+-- : dep wai-extra
+-- : dep wai-middleware-metrics
+-- : dep warp
module Com.MusicMeetsComics.Server where
import qualified Clay
diff --git a/Com/Simatime/buildGhc.nix b/Com/Simatime/buildGhc.nix
index 489651e..2b578b8 100644
--- a/Com/Simatime/buildGhc.nix
+++ b/Com/Simatime/buildGhc.nix
@@ -1,17 +1,30 @@
-
-nixpkgs:
-
-{ name # the main module namespace
-, nick # a short name, for the executable
-, deps # deps get passed to ghc
-}:
+nixpkgs: main:
with nixpkgs;
+with nixpkgs.lib;
let
- nsToPath = ns: builtins.toString (builtins.replaceStrings ["."] ["/"] ns);
+ # provided by .envrc
+ root = builtins.getEnv "BIZ_ROOT";
+
+ # general functions to put in a lib
+ lines = s: strings.splitString "\n" s;
+ seq = ls: builtins.filter (x: x!= null) ls;
+
+ # turn the file path into a Haskell module name
+ relpath = builtins.replaceStrings ["${root}/"] [""] (builtins.toString main);
+ module = builtins.replaceStrings ["/" ".hs"] ["." ""] relpath;
+
+ # extract info from special comments
+ content = builtins.readFile main;
+ exe = builtins.head (lists.flatten (seq
+ (map (builtins.match "^-- : exe ([[:alnum:]._-]*)$")
+ (lines content))));
+ deps = lists.flatten (seq
+ (map (builtins.match "^-- : dep ([[:alnum:]._-]*)$")
+ (lines content)));
- path = nsToPath name;
+ # do the build...
depsToPackageSet = packageSet: deps:
map (s: builtins.getAttr s packageSet) deps;
@@ -33,21 +46,21 @@ let
ghc = ghc865_.ghcWithPackages (hp: depsToPackageSet hp deps);
in stdenv.mkDerivation {
- name = name;
+ name = module;
version = "0";
src = ../../.; # this is the git root
nativeBuildInputs = [ ghc ];
strictDeps = true;
buildPhase = ''
#
- mkdir -p $out/{bin,static} ${path}
+ mkdir -p $out/{bin,static} ${baseNameOf relpath}
#
# compile with ghc
#
${ghc}/bin/ghc -Werror -i. \
- --make ${path}.hs \
- -main-is ${name} \
- -o $out/bin/${nick}
+ --make ${main} \
+ -main-is ${module} \
+ -o $out/bin/${exe}
'';
# the install process was handled above
installPhase = "exit 0";
diff --git a/Com/Simatime/buildGhcjs.nix b/Com/Simatime/buildGhcjs.nix
index 0a88ce7..0020fa4 100644
--- a/Com/Simatime/buildGhcjs.nix
+++ b/Com/Simatime/buildGhcjs.nix
@@ -1,17 +1,30 @@
-
-nixpkgs:
-
-{ name # the main module namespace
-, nick # a short name for the output
-, deps # passed to ghcjs
-}:
+nixpkgs: main:
with nixpkgs;
+with nixpkgs.lib;
let
- nsToPath = ns: builtins.toString (builtins.replaceStrings ["."] ["/"] ns);
+ # provided by .envrc
+ root = builtins.getEnv "BIZ_ROOT";
+
+ # general functions to put in a lib
+ lines = s: strings.splitString "\n" s;
+ seq = ls: builtins.filter (x: x!= null) ls;
- path = nsToPath name;
+ # turn the file path into a Haskell module name
+ relpath = builtins.replaceStrings ["${root}/"] [""] (builtins.toString main);
+ module = builtins.replaceStrings ["/" ".hs"] ["." ""] relpath;
+
+ # extract info from special comments
+ content = builtins.readFile main;
+ exe = builtins.head (lists.flatten (seq
+ (map (builtins.match "^-- : exe ([[:alnum:]._-]*)$")
+ (lines content))));
+ deps = lists.flatten (seq
+ (map (builtins.match "^-- : dep ([[:alnum:]._-]*)$")
+ (lines content)));
+
+ # do the build...
depsToPackageSet = packageSet: deps:
map (s: builtins.getAttr s packageSet) deps;
@@ -26,39 +39,42 @@ let
# ghcjs-8.6.0.1
ghcjs_ = pkgs.haskell.packages.ghcjs.override (oldAttrs: {
overrides = with pkgs.haskell.lib; self: super: {
+ QuickCheck = dontCheck super.QuickCheck;
+ base-compat-batteries = dontCheck super.http-types;
clay = dontCheck (self.callCabal2nix "clay" claySrc {});
+ comonad = dontCheck super.comonad;
http-types = dontCheck super.http-types;
- tasty-quickcheck = dontCheck super.tasty-quickcheck;
+ network-uri= dontCheck super.network-uri;
scientific = dontCheck super.scientific; # takes forever
servant = dontCheck super.servant;
- comonad = dontCheck super.comonad;
- QuickCheck = dontCheck super.QuickCheck;
+ tasty-quickcheck = dontCheck super.tasty-quickcheck;
+ time-compat = dontCheck super.time-compat;
};
});
ghcjs = ghcjs_.ghcWithPackages (hp: depsToPackageSet hp deps);
in stdenv.mkDerivation {
- name = name;
+ name = module;
version = "0";
src = ../../.; # the git root
nativeBuildInputs = [ ghcjs ];
strictDeps = true;
buildPhase = ''
#
- mkdir -p $out/{bin,static} ${path}
+ mkdir -p $out/{bin,static} ${baseNameOf relpath}
#
# compile with ghcjs
#
${ghcjs}/bin/ghcjs -Werror -i. \
- --make ${path}.hs \
- -main-is ${name} \
- -o ${path}
+ --make ${main} \
+ -main-is ${module} \
+ -o ${exe}
#
# optimize js output
#
${pkgs.closurecompiler}/bin/closure-compiler \
- ${path}.jsexe/all.js > $out/static/${nick}.js
+ ${exe}.jsexe/all.js > $out/static/${exe}
'';
installPhase = "exit 0";
} // { env = ghcjs; }
diff --git a/Run/Que/Server.hs b/Run/Que/Server.hs
index e5094cd..4974498 100644
--- a/Run/Que/Server.hs
+++ b/Run/Que/Server.hs
@@ -3,8 +3,15 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
-{- | Interprocess communication
--}
+-- | Interprocess communication
+--
+-- : exe que-server
+-- : dep async
+-- : dep protolude
+-- : dep scotty
+-- : dep stm
+-- : dep unagi-chan
+-- : dep unordered-containers
module Run.Que.Server
( main
)
diff --git a/Run/Que/Website.hs b/Run/Que/Website.hs
index 910d97b..757f5e6 100644
--- a/Run/Que/Website.hs
+++ b/Run/Que/Website.hs
@@ -4,6 +4,12 @@
{-# LANGUAGE LambdaCase #-}
-- | spawns a few processes that serve the que.run website
+--
+-- : exe que-website
+-- : dep async
+-- : dep process
+-- : dep protolude
+-- : dep req
module Run.Que.Website where
import qualified Control.Concurrent.Async as Async
diff --git a/bild b/bild
index 5b74ffb..8ea07ea 100755
--- a/bild
+++ b/bild
@@ -1,8 +1,11 @@
#!/usr/bin/env bash
-nix build -o "_bild/$@" -f default.nix "$@"
-if [[ $? != 0 ]]
+set -ex
+prefix=$(echo $PWD | sed -e "s|^$BIZ_ROOT/*||g" -e "s|/|.|g")
+if [[ "$prefix" == "" ]]
then
- echo ":: bild fail"
- echo ":: replaying the log..."
- PAGER=cat nix log -f default.nix "$@"
+ target="$1"
+else
+ target="$prefix.$1"
fi
+nix build -o "$BIZ_ROOT/_bild/$target" \
+ -f $BIZ_ROOT/default.nix "$target" --show-trace
diff --git a/default.nix b/default.nix
index 5199252..41a1f0b 100644
--- a/default.nix
+++ b/default.nix
@@ -70,114 +70,12 @@ in rec {
boot.enableContainers = true;
};
};
- Com.InfluencedByBooks.Server = buildGhc {
- name = "Com.InfluencedByBooks.Server";
- nick = "ibb";
- deps = [
- "clay"
- "miso"
- "protolude"
- "servant"
- "text"
- "MonadRandom"
- "acid-state"
- "blaze-html"
- "blaze-markup"
- "bytestring"
- "ixset"
- "random"
- "safecopy"
- "scotty"
- "servant-server"
- "text"
- ];
- };
- Com.InfluencedByBooks.Client = buildGhcjs {
- name = "Com.InfluencedByBooks.Client";
- nick = "ibb";
- deps = [
- "clay"
- "miso"
- "protolude"
- "servant"
- "text"
- "aeson"
- "containers"
- "ghcjs-base"
- ];
- };
- Com.MusicMeetsComics = {
- Server = buildGhc {
- name = "Com.MusicMeetsComics.Server";
- nick = "mmc";
- deps = [
- "aeson"
- "clay"
- "containers"
- "miso"
- "protolude"
- "servant"
- "split"
- "string-quote"
- "text"
- "dhall"
- "ekg"
- "fast-logger"
- "http-types"
- "katip"
- "lucid"
- "monad-logger"
- "monad-metrics"
- "mtl"
- "network-uri"
- "safe"
- "servant-lucid"
- "servant-server"
- "split"
- "wai"
- "wai-app-static"
- "wai-extra"
- "wai-middleware-metrics"
- "warp"
- ];
- };
- Client = buildGhcjs {
- name = "Com.MusicMeetsComics.Client";
- nick = "mmc";
- deps = [
- "aeson"
- "clay"
- "containers"
- "miso"
- "protolude"
- "servant"
- "split"
- "string-quote"
- "text"
- "ghcjs-base"
- ];
- };
- };
- Run.Que.Server = buildGhc {
- name = "Run.Que.Server";
- nick = "que-server";
- deps = [
- "async"
- "protolude"
- "scotty"
- "stm"
- "unagi-chan"
- "unordered-containers"
- ];
- };
- Run.Que.Website = buildGhc {
- name = "Run.Que.Website";
- nick = "que-website";
- deps = [
- "async"
- "process"
- "protolude"
- "req"
- ];
- };
+ # Haskell targets
+ #
+ Com.InfluencedByBooks.Server = buildGhc Com/InfluencedByBooks/Server.hs;
+ Com.InfluencedByBooks.Client = buildGhcjs Com/InfluencedByBooks/Client.hs;
+ Com.MusicMeetsComics.Server = buildGhc Com/MusicMeetsComics/Server.hs;
+ Com.MusicMeetsComics.Client = buildGhcjs Com/MusicMeetsComics/Client.hs;
+ Run.Que.Server = buildGhc ./Run/Que/Server.hs;
+ Run.Que.Website = buildGhc ./Run/Que/Website.hs;
}
diff --git a/ghci b/ghci
deleted file mode 100755
index 0797ce5..0000000
--- a/ghci
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-nix run -f default.nix "$@.env" -c ghci
diff --git a/repl b/repl
new file mode 100755
index 0000000..40528c7
--- /dev/null
+++ b/repl
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+set -ex
+prefix=$(echo $PWD | sed -e "s|^$BIZ_ROOT/*||" -e "s|/|.|g")
+if [[ "$prefix" == "" ]]
+then
+ target="$1"
+else
+ target="$prefix.$1"
+fi
+nix run -f $BIZ_ROOT/default.nix "$target.env" \
+ -c ghci -i$BIZ_ROOT -ghci-script "$BIZ_ROOT/.ghci"