diff options
author | Ben Sima <ben@bsima.me> | 2023-08-21 20:36:12 -0400 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2023-08-21 21:09:34 -0400 |
commit | e5a6175e044d69b8f598a2c2acb9bcfd77b9001c (patch) | |
tree | e7b96ff09dd46444cb1c5fd9575ef897392800eb /Biz/Bild/Builder.nix | |
parent | 3f9bef378810eb259e9fdc28cc06ebf2be9d6cd8 (diff) |
Refactor the build system for readability
Lots of changes here but the code is much improved. The nix code is clearer and
structured better.
The Haskell code improved in response to the nix changes. I needed to use a
qualified path instead of the abspath because the BIZ_ROOT changes based on
whether bild runs in nix or runs in the user environment.
Rather than passing every argument into Builder.nix, now I just pass the json
from bild and deconstruct it in nix. This is obviously a much better design and
it only came to be after sleeping on it the other night.
Diffstat (limited to 'Biz/Bild/Builder.nix')
-rw-r--r-- | Biz/Bild/Builder.nix | 214 |
1 files changed, 112 insertions, 102 deletions
diff --git a/Biz/Bild/Builder.nix b/Biz/Bild/Builder.nix index 2b62b89..a5a31c7 100644 --- a/Biz/Bild/Builder.nix +++ b/Biz/Bild/Builder.nix @@ -3,115 +3,125 @@ This is the library of nix builders. Some rules to follow: - Keep this code as minimal as possible. I'd rather write Haskell than Nix, wouldn't you? - Try to reuse as much upstream Nix as possible. -- Path-like args such as 'srcs' should always be absolute paths. */ -{ srcs ? "" # list of all source files, as a space-separated string -, main # the entrypoint or main module (not a path) -, root # path to git root -, packageSet # name mapped to private.${packageSet}, e.g. 'ghcWith' -, langdeps ? null # list of deps (as a string), split and passed to packageSet -, sysdeps ? null -, name # exe name -, compileLine ? "" # Target.compiler <> Target.compilerFlags -}: -with import (/. + root + "/Biz/Bild.nix") {}; -with builtins; +{ analysisJSON, nixpkgs ? import ../Bild.nix {} }: +with nixpkgs; let - srcs_ = (lib.strings.splitString " " srcs) ++ [main]; + analysis = builtins.fromJSON analysisJSON; + build = _: target: let + name = target.out; + root = builtins.getEnv "BIZ_ROOT"; + mainModule = target.mainModule; + compileLine = + lib.strings.concatStringsSep " " ([target.compiler] ++ target.compilerFlags); - isEmpty = x: x == null || x == []; + allSources = target.srcs ++ [target.quapath]; - skip = ["_" ".direnv"]; - filter = file: type: - if elem (baseNameOf file) skip then false - # TODO: this means any new directory will cause a rebuild. this bad. i - # should recurse into the directory and match against the srcs. for now I - # just use postUnpack to delete empty dirs - else if type == "directory" then true - else if type == "regular" then (builtins.elem file srcs_) - else false; + isEmpty = x: x == null || x == []; - # clean up empty dirs - postUnpack = "find . -type d -empty -delete"; + skip = ["_" ".direnv"]; + filter = file: type: + if lib.lists.elem (builtins.baseNameOf file) skip then false + # TODO: this means any new directory will cause a rebuild. this bad. i + # should recurse into the directory and match against the srcs. for now I + # just use postUnpack to delete empty dirs + else if type == "directory" then true + else if type == "regular" then lib.trivial.pipe file + [ (f: lib.strings.removePrefix "${root}/" f) + (f: lib.lists.elem f allSources) + ] + else false; - src = lib.sources.cleanSourceWith {inherit filter; src = lib.sources.cleanSource root;}; + # clean up empty dirs + #postUnpack = "find $src -type d -empty -delete"; - langdeps_ = - if isEmpty langdeps then - [] - else - private.selectAttrs (lib.strings.splitString " " langdeps) private.${packageSet}; - sysdeps_ = - if isEmpty sysdeps then - [] - else - private.selectAttrs (lib.strings.splitString " " sysdeps) private.nixpkgs.pkgs; - BIZ_ROOT = "."; -in { - base = stdenv.mkDerivation rec { - inherit name src BIZ_ROOT postUnpack; - buildInputs = langdeps_ ++ sysdeps_; - installPhase = "install -D ${name} $out/bin/${name}"; - buildPhase = compileLine; - }; + src = lib.sources.cleanSourceWith {inherit filter; src = lib.sources.cleanSource root;}; - haskell = stdenv.mkDerivation rec { - inherit name src BIZ_ROOT postUnpack; - buildInputs = sysdeps_ ++ [ - (private.ghcWith (p: - (private.selectAttrs (lib.strings.splitString " " langdeps) p) - )) - ]; - installPhase = "install -D ${name} $out/bin/${name}"; - buildPhase = compileLine; - }; + langdeps_ = + if isEmpty target.langdeps then + [] + else + lib.attrsets.attrVals + target.langdeps + (lib.attrsets.getAttrFromPath (lib.strings.splitString "." target.packageSet) bild); + sysdeps_ = + if isEmpty target.sysdeps then + [] + else + lib.attrsets.attrVals target.sysdeps pkgs; + BIZ_ROOT = "."; - c = stdenv.mkDerivation rec { - inherit name src BIZ_ROOT postUnpack; - buildInputs = langdeps_ ++ sysdeps_; - installPhase = "install -D ${name} $out/bin/${name}"; - buildPhase = lib.strings.concatStringsSep " " [ - compileLine - (if isEmpty langdeps then "" else - "$(pkg-config --cflags ${langdeps})") - (if isEmpty sysdeps then "" else - "$(pkg-config --libs ${sysdeps})") - ]; - }; + builders = { + base = stdenv.mkDerivation rec { + inherit name src BIZ_ROOT; + buildInputs = langdeps_ ++ sysdeps_; + installPhase = "install -D ${name} $out/bin/${name}"; + buildPhase = compileLine; + }; - python = buildPythonApplication rec { - inherit name src BIZ_ROOT postUnpack; - propagatedBuildInputs = [ (private.pythonWith (_: langdeps_)) ] ++ sysdeps_; - buildInputs = sysdeps_; - checkInputs = [(private.pythonWith (p: with p; [black mypy pylint]))]; - checkPhase = '' - check() { - $@ || { echo "fail: $name: $3"; exit 1; } - } - check python -m black --quiet --exclude 'setup\.py$' --check . - check python -m pylint --errors-only . - check python -m mypy --strict --no-error-summary --exclude 'setup\.py$' . - check python -m ${main} test - ''; - preBuild = '' - # initialize possibly-empty subdirectories as python modules - find . -type d -exec touch {}/__init__.py \; - # generate a minimal setup.py - cat > setup.py << EOF - from setuptools import setup, find_packages - setup( - name='${name}', - entry_points={'console_scripts':['${name} = ${main}:main']}, - version='0.0.0', - url='git://simatime.com/biz.git', - author='dev', - author_email='dev@simatime.com', - description='nil', - packages=find_packages(), - install_requires=[], - ) - EOF - ''; - pythonImportsCheck = [main]; # sanity check - }; -} + haskell = stdenv.mkDerivation rec { + inherit name src BIZ_ROOT; + buildInputs = sysdeps_ ++ [ + (bild.haskell.ghcWith (p: + (lib.attrsets.attrVals target.langdeps p) + )) + ]; + installPhase = "install -D ${name} $out/bin/${name}"; + buildPhase = compileLine; + }; + + c = stdenv.mkDerivation rec { + inherit name src BIZ_ROOT; + buildInputs = langdeps_ ++ sysdeps_; + installPhase = "install -D ${name} $out/bin/${name}"; + buildPhase = lib.strings.concatStringsSep " " [ + compileLine + (if isEmpty langdeps_ then "" else + "$(pkg-config --cflags ${lib.strings.concatStringsSep " " target.langdeps})") + (if isEmpty sysdeps_ then "" else + "$(pkg-config --libs ${lib.strings.concatStringsSep " " target.sysdeps})") + ]; + }; + + python = bild.python.buildPythonApplication rec { + inherit name src BIZ_ROOT; + propagatedBuildInputs = [ (bild.python.pythonWith (_: langdeps_)) ] ++ sysdeps_; + buildInputs = sysdeps_; + checkInputs = [(bild.python.pythonWith (p: with p; [black mypy pylint]))]; + checkPhase = '' + check() { + $@ || { echo "fail: $name: $3"; exit 1; } + } + check python -m black --quiet --exclude 'setup\.py$' --check . + check python -m pylint --errors-only . + check python -m mypy --strict --no-error-summary --exclude 'setup\.py$' . + check python -m ${mainModule} test + ''; + preBuild = '' + # initialize possibly-empty subdirectories as python modules + find . -type d -exec touch {}/__init__.py \; + # generate a minimal setup.py + cat > setup.py << EOF + from setuptools import setup, find_packages + setup( + name='${name}', + entry_points={'console_scripts':['${name} = ${mainModule}:main']}, + version='0.0.0', + url='git://simatime.com/biz.git', + author='dev', + author_email='dev@simatime.com', + description='nil', + packages=find_packages(), + install_requires=[], + ) + EOF + ''; + pythonImportsCheck = [mainModule]; # sanity check + }; + }; + in builders.${target.builder}; +# the caller gives us the Analysis type, which is a hashmap, but i need to +# return a single drv, so just take the first one for now. ideally i would only +# pass Target, one at a time, (perhaps parallelized in haskell land) and then i +# wouldn't need all of this let nesting +in builtins.head (lib.attrsets.mapAttrsToList build analysis) |