diff options
author | Ben Sima <ben@bsima.me> | 2020-04-03 13:20:29 -0700 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2020-04-03 15:13:56 -0700 |
commit | 87b48d473bdb41670c9f3b26a628f34c3c5c9481 (patch) | |
tree | 9d515f4e5c49336e4db21b1892b8a0022e74682d /Com | |
parent | 65c2b30a288385cf3df4027d50080ac595bbcf83 (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.
Diffstat (limited to 'Com')
-rw-r--r-- | Com/InfluencedByBooks/Client.hs | 11 | ||||
-rw-r--r-- | Com/InfluencedByBooks/Server.hs | 19 | ||||
-rw-r--r-- | Com/MusicMeetsComics/Client.hs | 14 | ||||
-rw-r--r-- | Com/MusicMeetsComics/Server.hs | 32 | ||||
-rw-r--r-- | Com/Simatime/buildGhc.nix | 41 | ||||
-rw-r--r-- | Com/Simatime/buildGhcjs.nix | 52 |
6 files changed, 137 insertions, 32 deletions
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; } |