postjevsql.git / nix / package.nix
1# The extension for one PostgreSQL major (contract §3): the `:ext` tree
2# (lib/postjevsql.so, the control file and the install SQL generated
3# from the .so) built by buck2 inside the sandbox, the same graph the
4# tests build. Called per major as `postgresql_NN.pkgs.callPackage`.
5#
6# Crates: third-party/BUCK fetches each crate with http_archive, which
7# needs the network. Every one is fetched here instead, by the url and
8# sha256 that file records (read at eval, so no IFD and no second lock),
9# and nix/offline_archive.bzl stands in for http_archive in the sandbox.
10{
11  lib,
12  stdenv,
13  fetchurl,
14  linkFarm,
15  buck2,
16  rustc,
17  rustfmt,
18  clang,
19  lld,
20  python3,
21  gnutar,
22  gzip,
23  cacert,
24  bubblewrap,
25  coreutils,
26  rustPlatform,
27  postgresql,
28}:
29let
30  # pgrx compiles against one major's headers, chosen by a cargo
31  # feature. third-party/Cargo.toml selects the default major for buck's
32  # own graph; for any other supported one the feature is swapped below.
33  majors = import ./majors.nix { inherit lib; };
34  major = lib.versions.major postgresql.version;
35
36  # Only what `//crates/postjevsql:ext` reads, so editing docs, tests or
37  # the other nix files does not change the hash and rebuild through buck.
38  # A new directory the build reads must be added here, or buck fails to
39  # find it in the sandbox.
40  src = lib.fileset.toSource {
41    root = ../.;
42    fileset = lib.fileset.difference (lib.fileset.unions [
43      ../.buckconfig
44      ../build
45      ../crates
46      ../platforms
47      ../third-party
48      ../toolchains
49      ../tools
50      ./offline_archive.bzl
51      # Not read by buck: the notices installed beside the binaries
52      # (THIRD-PARTY-sidecar by nix/sidecar-cli.nix).
53      ../THIRD-PARTY
54      ../THIRD-PARTY-sidecar
55      ../LICENSE-MIT
56      ../LICENSE-APACHE
57    ]) (
58      lib.fileset.fileFilter (
59        file:
60        lib.elem file.name [
61          "README.md"
62          "CLAUDE.md"
63          ".buckconfig.local"
64        ]
65      ) ../.
66    );
67  };
68
69  thirdParty = builtins.readFile ../third-party/BUCK;
70  archives = map (
71    block:
72    let
73      m = builtins.match ''[[:space:]]*name = "([^"]+)",[[:space:]]*sha256 = "([0-9a-f]+)",[[:space:]]*strip_prefix = "[^"]*",[[:space:]]*urls = [[]"([^"]+)"[]],[[:space:]]*visibility = [[][]],[[:space:]]*'' (lib.head (lib.splitString "\n)" block));
74    in
75    if m == null then
76      throw "nix/package.nix: an http_archive in third-party/BUCK has an unexpected shape"
77    else
78      {
79        name = lib.elemAt m 0;
80        path = fetchurl {
81          name = "${lib.elemAt m 0}.tar.gz";
82          url = lib.elemAt m 2;
83          sha256 = lib.elemAt m 1;
84        };
85      }
86  ) (lib.drop 1 (lib.splitString "\nhttp_archive(" thirdParty));
87  crates = linkFarm "postjevsql-crates" archives;
88
89  # Builds one buck target to `out`, as the tests build it. The prelude
90  # writes wrapper scripts (linker_wrapper.sh, ...) that start
91  # `#!/usr/bin/env bash`, and the sandbox has no /usr. bwrap gives buck
92  # a root that has it, with everything else bound as is. Passed on so
93  # nix/sidecar-cli.nix builds its binary the same way.
94  buck = target: out: ''
95    export HOME=$TMPDIR
96    # buck2 loads the trust store at startup, though nothing is fetched.
97    export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
98    bwrap \
99      --bind /build /build --bind /tmp /tmp --ro-bind /nix /nix \
100      --ro-bind /bin /bin --ro-bind /etc /etc --dev /dev --proc /proc \
101      --symlink ${coreutils}/bin/env /usr/bin/env \
102      --chdir "$PWD" \
103      -- sh -c 'buck2 build --target-platforms //platforms:pg${major} ${target} --out ${out} && buck2 kill'
104  '';
105
106  version = lib.head (
107    builtins.match ''[[:space:][:print:]]*VERSION = "([^"]+)"[[:space:][:print:]]*'' (builtins.readFile ../build/defs.bzl)
108  );
109in
110assert lib.assertMsg (lib.elem major majors.all)
111  "postjevsql: PostgreSQL ${major} is not supported; the supported majors are ${lib.concatStringsSep ", " majors.all} (PG_MAJORS in build/defs.bzl)";
112stdenv.mkDerivation {
113  pname = "postjevsql";
114  inherit version src;
115
116  nativeBuildInputs = [
117    buck2
118    rustc
119    # pgrx-pg-sys formats its generated bindings.
120    rustfmt
121    clang
122    lld
123    python3
124    gnutar
125    gzip
126    bubblewrap
127    # pgrx-pg-sys runs bindgen against the server headers.
128    rustPlatform.bindgenHook
129  ];
130  buildInputs = [ postgresql ];
131
132  PGRX_PG_CONFIG_PATH = "${postgresql.pg_config}/bin/pg_config";
133
134  postPatch = ''
135    # The docs were the none cell's only files, and a fileset keeps no
136    # empty directory; buck needs the cell to exist.
137    mkdir -p none
138    cp -rL ${crates} third-party/crates
139    chmod -R u+w third-party/crates
140    cp nix/offline_archive.bzl third-party/offline_archive.bzl
141    sed -i '1a load("//third-party:offline_archive.bzl", "http_archive")' third-party/BUCK
142    # Release packaging builds without debug assertions (toolchains/BUCK).
143    substituteInPlace toolchains/BUCK \
144      --replace-fail '"-Cdebug-assertions=on"' '"-Cdebug-assertions=off"'
145    cat > .buckconfig.local <<EOF
146    [nix]
147    cc = ${clang}/bin/clang
148    cxx = ${clang}/bin/clang++
149    ar = ${clang}/bin/ar
150    python = ${python3}/bin/python3
151    [postjevsql]
152    postgres_bin_${major} = ${postgresql}/bin
153    pg_config_${major} = ${postgresql.pg_config}/bin/pg_config
154    EOF
155  '';
156
157  buildPhase = ''
158    runHook preBuild
159    ${buck "//crates/postjevsql:ext" "ext"}
160    runHook postBuild
161  '';
162
163  installPhase = ''
164    runHook preInstall
165    # nixpkgs' layout, which postgresql.withPackages joins.
166    mkdir -p $out/lib $out/share/postgresql
167    cp ext/lib/postjevsql.so $out/lib/
168    cp -r ext/share/extension $out/share/postgresql/
169    # The .so links crates.io code whose licences require their notices.
170    install -Dm644 -t $out/share/doc/postjevsql THIRD-PARTY LICENSE-MIT LICENSE-APACHE
171    runHook postInstall
172  '';
173
174  passthru = { inherit buck; };
175
176  meta = {
177    description = "Ask TypeSafe's Jev typed questions about rows, from SQL";
178    platforms = postgresql.meta.platforms;
179  };
180}