diff options
author | Ben Sima <ben@bsima.me> | 2023-08-07 14:03:07 -0400 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2023-08-07 14:03:36 -0400 |
commit | edb55ff54b9f81c0942f4120eedd72357d7b3d7c (patch) | |
tree | 84c7db39e718c1a301815f0baa50e0900b7d4942 /Biz/Bild | |
parent | beb505a7125e61c4fedd02d77e37545b61b99f73 (diff) |
Working nixified python build
This represents quite a few evenings of hacking. It doesn't build all of my
Python code, because my Python code is not up to snuff, but it builds the
examples and pulls in third party dependencies.
Some design points:
- I'm using buildPythonApplication in Builder.nix because it was getting way too
annoying to wrap the Python script and set PYTHONPATH myself. Easier and more
robust to just use the upstream nix builder
- Because of this, I had to generate a setup.py. Maybe switch to pyproject.toml
in the future, whatever.
- Also because of this, Target.wrapper is becoming redundant. I'll just remove
it when I get Guile built in nix.
- Biz/Bild.nix is getting messy and could use a refactor.
- In Builder.nix, I worked around the empty directories bug by just finding and
deleting empty directories after unpacking. If its stupid but works it ain't
stupid!
- I had to touch __init__.py files in all directories before building. Annoying!
- `repl` just works, which is awesome
- To ensure good Python code, I moved lints and added type checking to the
build. So I can't build anything unless it passes those checks. This seems
restrictive, but if I want to run some non-passing code, I can still use
`repl`, so it's actually not inhibitory.
Diffstat (limited to 'Biz/Bild')
-rw-r--r-- | Biz/Bild/Builder.nix | 93 | ||||
-rw-r--r-- | Biz/Bild/Example.py | 30 |
2 files changed, 104 insertions, 19 deletions
diff --git a/Biz/Bild/Builder.nix b/Biz/Bild/Builder.nix index 04002d0..214c110 100644 --- a/Biz/Bild/Builder.nix +++ b/Biz/Bild/Builder.nix @@ -1,33 +1,88 @@ -{ srcs # list of files +/* +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 +, langdeps ? null # list of deps (as a string), split and passed to packageSet +, sysdeps ? null , name # exe name -, buildPhase +, compileLine ? "" # Target.compiler <> Target.compilerFlags }: with import (/. + root + "/Biz/Bild.nix") {}; with builtins; let - srcs_ = lib.strings.splitString " " srcs; + srcs_ = (lib.strings.splitString " " srcs) ++ [main]; + 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 srcsr + # 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 if type == "regular" then (builtins.elem file srcs_) else false; - deps = pkgset: - if langDeps != null then - private.selectAttrs (lib.strings.splitString " " langDeps) pkgset - else - []; -in stdenv.mkDerivation rec { - inherit name buildPhase; + + # clean up empty dirs + postUnpack = "find . -type d -empty -delete"; + src = lib.sources.cleanSourceWith {inherit filter; src = lib.sources.cleanSource root;}; - BIZ_ROOT = src; - buildInputs = [ (private.${packageSet} deps) ]; - installPhase = '' - mkdir -p $out/bin && cp ${name} $out/bin - ''; + + langdeps_ = pkgset: + if langdeps == null || langdeps == [] then + [] + else + private.selectAttrs (lib.strings.splitString " " langdeps) pkgset; + sysdeps_ = + if sysdeps == null || 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 = [ (private.${packageSet} langdeps_) ] ++ sysdeps_; + installPhase = "install -D ${name} $out/bin/${name}"; + buildPhase = compileLine; + }; + + python = buildPythonApplication rec { + inherit name src BIZ_ROOT postUnpack; + propagatedBuildInputs = [ (private.${packageSet} langdeps_) ] ++ sysdeps_; + buildInputs = sysdeps_; + checkInputs = [(private.pythonWith (p: with p; [black mypy pylint]))]; + checkPhase = '' + black --quiet --exclude 'setup\.py$' --check . + pylint --errors-only . + mypy --strict --no-error-summary --exclude 'setup\.py$' . + 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 + }; } diff --git a/Biz/Bild/Example.py b/Biz/Bild/Example.py new file mode 100644 index 0000000..78a8a6a --- /dev/null +++ b/Biz/Bild/Example.py @@ -0,0 +1,30 @@ +# : out example +# : dep cryptography +import sys +from typing import List + +from cryptography.fernet import Fernet + + +def cryptic_hello(name: str) -> str: + "Example taken from `cryptography` docs." + key = Fernet.generate_key() + f = Fernet(key) + token = f.encrypt(hello(name).encode("utf-8")) + ret = f.decrypt(token).decode("utf-8") + assert ret == hello(name) + return ret + + +def hello(name: str) -> str: + return f"Hello {name}" + + +def main() -> None: + if "test" in sys.argv: + print("testing success") + print(cryptic_hello("world")) + + +if __name__ == "__main__": + main() |