summaryrefslogtreecommitdiff
path: root/Biz/Bild/Builder.nix
blob: a5a31c7e056b57205a9b61dc2660874f7ba86e9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
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.
*/
{ analysisJSON, nixpkgs ? import ../Bild.nix {} }:
with nixpkgs;
let
  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);

    allSources = target.srcs ++ [target.quapath];

    isEmpty = x: x == null || x == [];

    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;

    # clean up empty dirs
    #postUnpack = "find $src -type d -empty -delete";

    src = lib.sources.cleanSourceWith {inherit filter; src = lib.sources.cleanSource root;};

    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 = ".";

    builders = {
      base = stdenv.mkDerivation rec {
        inherit name src BIZ_ROOT;
        buildInputs = langdeps_ ++ sysdeps_;
        installPhase = "install -D ${name} $out/bin/${name}";
        buildPhase = compileLine;
      };

      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)