pytrainer: unpin python 3.10
[NixPkgs.git] / pkgs / build-support / build-bazel-package / default.nix
blob1131f73a1836a753a85709f6553c25fa9ac26bd4
1 { stdenv
2 , cacert
3 , lib
4 , writeCBin
5 }:
7 args@{
8   name ? "${args.pname}-${args.version}"
9 , bazel
10 , bazelFlags ? []
11 , bazelBuildFlags ? []
12 , bazelTestFlags ? []
13 , bazelRunFlags ? []
14 , runTargetFlags ? []
15 , bazelFetchFlags ? []
16 , bazelTargets ? []
17 , bazelTestTargets ? []
18 , bazelRunTarget ? null
19 , buildAttrs
20 , fetchAttrs
22   # Newer versions of Bazel are moving away from built-in rules_cc and instead
23   # allow fetching it as an external dependency in a WORKSPACE file[1]. If
24   # removed in the fixed-output fetch phase, building will fail to download it.
25   # This can be seen e.g. in #73097
26   #
27   # This option allows configuring the removal of rules_cc in cases where a
28   # project depends on it via an external dependency.
29   #
30   # [1]: https://github.com/bazelbuild/rules_cc
31 , removeRulesCC ? true
32 , removeLocalConfigCc ? true
33 , removeLocal ? true
35   # Use build --nobuild instead of fetch. This allows fetching the dependencies
36   # required for the build as configured, rather than fetching all the dependencies
37   # which may not work in some situations (e.g. Java code which ends up relying on
38   # Debian-specific /usr/share/java paths, but doesn't in the configured build).
39 , fetchConfigured ? true
41   # Don’t add Bazel --copt and --linkopt from NIX_CFLAGS_COMPILE /
42   # NIX_LDFLAGS. This is necessary when using a custom toolchain which
43   # Bazel wants all headers / libraries to come from, like when using
44   # CROSSTOOL. Weirdly, we can still get the flags through the wrapped
45   # compiler.
46 , dontAddBazelOpts ? false
47 , ...
50 let
51   fArgs = removeAttrs args [ "buildAttrs" "fetchAttrs" "removeRulesCC" ] // {
52     inherit
53       name
54       bazelFlags
55       bazelBuildFlags
56       bazelTestFlags
57       bazelRunFlags
58       runTargetFlags
59       bazelFetchFlags
60       bazelTargets
61       bazelTestTargets
62       bazelRunTarget
63       dontAddBazelOpts
64       ;
65   };
66   fBuildAttrs = fArgs // buildAttrs;
67   fFetchAttrs = fArgs // removeAttrs fetchAttrs [ "hash" "sha256" ];
68   bazelCmd = { cmd, additionalFlags, targets, targetRunFlags ? [ ] }:
69     lib.optionalString (targets != [ ]) ''
70       # See footnote called [USER and BAZEL_USE_CPP_ONLY_TOOLCHAIN variables]
71       BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \
72       USER=homeless-shelter \
73       bazel \
74         --batch \
75         --output_base="$bazelOut" \
76         --output_user_root="$bazelUserRoot" \
77         ${cmd} \
78         --curses=no \
79         "''${copts[@]}" \
80         "''${host_copts[@]}" \
81         "''${linkopts[@]}" \
82         "''${host_linkopts[@]}" \
83         $bazelFlags \
84         ${lib.strings.concatStringsSep " " additionalFlags} \
85         ${lib.strings.concatStringsSep " " targets} \
86         ${lib.optionalString (targetRunFlags != []) " -- " + lib.strings.concatStringsSep " " targetRunFlags}
87     '';
88   # we need this to chmod dangling symlinks on darwin, gnu coreutils refuses to do so:
89   # chmod: cannot operate on dangling symlink '$symlink'
90   chmodder = writeCBin "chmodder" ''
91     #include <stdio.h>
92     #include <stdlib.h>
93     #include <sys/types.h>
94     #include <sys/stat.h>
95     #include <errno.h>
96     #include <string.h>
98     int main(int argc, char** argv) {
99       mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
100       if (argc != 2) {
101         fprintf(stderr, "usage: chmodder file");
102         exit(EXIT_FAILURE);
103       }
104       if (lchmod(argv[1], mode) != 0) {
105         fprintf(stderr, "failed to lchmod '%s': %s", argv[0], strerror(errno));
106         exit(EXIT_FAILURE);
107       }
108     }
109   '';
111 stdenv.mkDerivation (fBuildAttrs // {
113   deps = stdenv.mkDerivation (fFetchAttrs // {
114     name = "${name}-deps.tar.gz";
116     impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ fFetchAttrs.impureEnvVars or [];
118     nativeBuildInputs = fFetchAttrs.nativeBuildInputs or [] ++ [ bazel ];
120     preHook = fFetchAttrs.preHook or "" + ''
121       export bazelOut="$(echo ''${NIX_BUILD_TOP}/output | sed -e 's,//,/,g')"
122       export bazelUserRoot="$(echo ''${NIX_BUILD_TOP}/tmp | sed -e 's,//,/,g')"
123       export HOME="$NIX_BUILD_TOP"
124       export USER="nix"
125       # This is needed for git_repository with https remotes
126       export GIT_SSL_CAINFO="${cacert}/etc/ssl/certs/ca-bundle.crt"
127       # This is needed for Bazel fetchers that are themselves programs (e.g.
128       # rules_go using the go toolchain)
129       export SSL_CERT_FILE="${cacert}/etc/ssl/certs/ca-bundle.crt"
130     '';
132     buildPhase = fFetchAttrs.buildPhase or ''
133       runHook preBuild
135       ${
136         bazelCmd {
137           cmd = if fetchConfigured then "build --nobuild" else "fetch";
138           additionalFlags = [
139             # We disable multithreading for the fetching phase since it can lead to timeouts with many dependencies/threads:
140             # https://github.com/bazelbuild/bazel/issues/6502
141             "--loading_phase_threads=1"
142             "$bazelFetchFlags"
143           ] ++ (if fetchConfigured then ["--jobs" "$NIX_BUILD_CORES"] else []);
144           targets = fFetchAttrs.bazelTargets ++ fFetchAttrs.bazelTestTargets;
145         }
146       }
148       runHook postBuild
149     '';
151     installPhase = fFetchAttrs.installPhase or (''
152       runHook preInstall
154       # Remove all built in external workspaces, Bazel will recreate them when building
155       rm -rf $bazelOut/external/{bazel_tools,\@bazel_tools.marker}
156       ${lib.optionalString removeRulesCC "rm -rf $bazelOut/external/{rules_cc,\\@rules_cc.marker}"}
157       rm -rf $bazelOut/external/{embedded_jdk,\@embedded_jdk.marker}
158       ${lib.optionalString removeLocalConfigCc "rm -rf $bazelOut/external/{local_config_cc,\\@local_config_cc.marker}"}
159       ${lib.optionalString removeLocal "rm -rf $bazelOut/external/{local_*,\\@local_*.marker}"}
161       # Clear markers
162       find $bazelOut/external -name '@*\.marker' -exec sh -c 'echo > {}' \;
164       # Remove all vcs files
165       rm -rf $(find $bazelOut/external -type d -name .git)
166       rm -rf $(find $bazelOut/external -type d -name .svn)
167       rm -rf $(find $bazelOut/external -type d -name .hg)
169       # Removing top-level symlinks along with their markers.
170       # This is needed because they sometimes point to temporary paths (?).
171       # For example, in Tensorflow-gpu build:
172       # platforms -> NIX_BUILD_TOP/tmp/install/35282f5123611afa742331368e9ae529/_embedded_binaries/platforms
173       find $bazelOut/external -maxdepth 1 -type l | while read symlink; do
174         name="$(basename "$symlink")"
175         rm "$symlink"
176         test -f "$bazelOut/external/@$name.marker" && rm "$bazelOut/external/@$name.marker" || true
177       done
179       # Patching symlinks to remove build directory reference
180       find $bazelOut/external -type l | while read symlink; do
181         new_target="$(readlink "$symlink" | sed "s,$NIX_BUILD_TOP,NIX_BUILD_TOP,")"
182         rm "$symlink"
183         ln -sf "$new_target" "$symlink"
184     '' + lib.optionalString stdenv.hostPlatform.isDarwin ''
185         # on linux symlink permissions cannot be modified, so we modify those on darwin to match the linux ones
186         ${chmodder}/bin/chmodder "$symlink"
187     '' + ''
188       done
190       echo '${bazel.name}' > $bazelOut/external/.nix-bazel-version
192       (cd $bazelOut/ && tar czf $out --sort=name --mtime='@1' --owner=0 --group=0 --numeric-owner external/)
194       runHook postInstall
195     '');
197     dontFixup = true;
198     allowedRequisites = [];
200     inherit (lib.fetchers.normalizeHash { hashTypes = [ "sha256" ]; } fetchAttrs)
201       outputHash
202       outputHashAlgo
203     ;
204   });
206   nativeBuildInputs = fBuildAttrs.nativeBuildInputs or [] ++ [ (bazel.override { enableNixHacks = true; }) ];
208   preHook = fBuildAttrs.preHook or "" + ''
209     export bazelOut="$NIX_BUILD_TOP/output"
210     export bazelUserRoot="$NIX_BUILD_TOP/tmp"
211     export HOME="$NIX_BUILD_TOP"
212   '';
214   preConfigure = ''
215     mkdir -p "$bazelOut"
217     (cd $bazelOut && tar xfz $deps)
219     test "${bazel.name}" = "$(<$bazelOut/external/.nix-bazel-version)" || {
220       echo "fixed output derivation was built for a different bazel version" >&2
221       echo "     got: $(<$bazelOut/external/.nix-bazel-version)" >&2
222       echo "expected: ${bazel.name}" >&2
223       exit 1
224     }
226     chmod -R +w $bazelOut
227     find $bazelOut -type l | while read symlink; do
228       if [[ $(readlink "$symlink") == *NIX_BUILD_TOP* ]]; then
229         ln -sf $(readlink "$symlink" | sed "s,NIX_BUILD_TOP,$NIX_BUILD_TOP,") "$symlink"
230       fi
231     done
232   '' + fBuildAttrs.preConfigure or "";
234   buildPhase = fBuildAttrs.buildPhase or ''
235     runHook preBuild
237     # Bazel sandboxes the execution of the tools it invokes, so even though we are
238     # calling the correct nix wrappers, the values of the environment variables
239     # the wrappers are expecting will not be set. So instead of relying on the
240     # wrappers picking them up, pass them in explicitly via `--copt`, `--linkopt`
241     # and related flags.
243     copts=()
244     host_copts=()
245     linkopts=()
246     host_linkopts=()
247     if [ -z "''${dontAddBazelOpts:-}" ]; then
248       for flag in $NIX_CFLAGS_COMPILE; do
249         copts+=( "--copt=$flag" )
250         host_copts+=( "--host_copt=$flag" )
251       done
252       for flag in $NIX_CXXSTDLIB_COMPILE; do
253         copts+=( "--copt=$flag" )
254         host_copts+=( "--host_copt=$flag" )
255       done
256       for flag in $NIX_LDFLAGS; do
257         linkopts+=( "--linkopt=-Wl,$flag" )
258         host_linkopts+=( "--host_linkopt=-Wl,$flag" )
259       done
260     fi
262     ${
263       bazelCmd {
264         cmd = "test";
265         additionalFlags =
266           ["--test_output=errors"] ++ fBuildAttrs.bazelTestFlags ++ ["--jobs" "$NIX_BUILD_CORES"];
267         targets = fBuildAttrs.bazelTestTargets;
268       }
269     }
270     ${
271       bazelCmd {
272         cmd = "build";
273         additionalFlags = fBuildAttrs.bazelBuildFlags ++ ["--jobs" "$NIX_BUILD_CORES"];
274         targets = fBuildAttrs.bazelTargets;
275       }
276     }
277     ${
278       bazelCmd {
279         cmd = "run";
280         additionalFlags = fBuildAttrs.bazelRunFlags ++ [ "--jobs" "$NIX_BUILD_CORES" ];
281         # Bazel run only accepts a single target, but `bazelCmd` expects `targets` to be a list.
282         targets = lib.optionals (fBuildAttrs.bazelRunTarget != null) [ fBuildAttrs.bazelRunTarget ];
283         targetRunFlags = fBuildAttrs.runTargetFlags;
284       }
285     }
286     runHook postBuild
287   '';
290 # [USER and BAZEL_USE_CPP_ONLY_TOOLCHAIN variables]:
291 #   Bazel computes the default value of output_user_root before parsing the
292 #   flag. The computation of the default value involves getting the $USER
293 #   from the environment. Code here :
294 #   https://github.com/bazelbuild/bazel/blob/9323c57607d37f9c949b60e293b573584906da46/src/main/cpp/startup_options.cc#L123-L124
296 #   On macOS Bazel will use the system installed Xcode or CLT toolchain instead of the one in the PATH unless we pass BAZEL_USE_CPP_ONLY_TOOLCHAIN.