b739da0693
Tests from the bazelTestTargets argument will be run before the build. The new bazelTestFlags argument can be used to pass additional flags to this phase.
253 lines
8.9 KiB
Nix
253 lines
8.9 KiB
Nix
{ stdenv
|
||
, bazel
|
||
, cacert
|
||
, lib
|
||
}:
|
||
|
||
let
|
||
bazelPkg = bazel;
|
||
in
|
||
|
||
args@{
|
||
name ? "${args.pname}-${args.version}"
|
||
, bazel ? bazelPkg
|
||
, bazelFlags ? []
|
||
, bazelBuildFlags ? []
|
||
, bazelTestFlags ? []
|
||
, bazelFetchFlags ? []
|
||
, bazelTarget
|
||
, bazelTestTargets ? []
|
||
, buildAttrs
|
||
, fetchAttrs
|
||
|
||
# Newer versions of Bazel are moving away from built-in rules_cc and instead
|
||
# allow fetching it as an external dependency in a WORKSPACE file[1]. If
|
||
# removed in the fixed-output fetch phase, building will fail to download it.
|
||
# This can be seen e.g. in #73097
|
||
#
|
||
# This option allows configuring the removal of rules_cc in cases where a
|
||
# project depends on it via an external dependency.
|
||
#
|
||
# [1]: https://github.com/bazelbuild/rules_cc
|
||
, removeRulesCC ? true
|
||
, removeLocalConfigCc ? true
|
||
, removeLocal ? true
|
||
|
||
# Use build --nobuild instead of fetch. This allows fetching the dependencies
|
||
# required for the build as configured, rather than fetching all the dependencies
|
||
# which may not work in some situations (e.g. Java code which ends up relying on
|
||
# Debian-specific /usr/share/java paths, but doesn't in the configured build).
|
||
, fetchConfigured ? true
|
||
|
||
# Don’t add Bazel --copt and --linkopt from NIX_CFLAGS_COMPILE /
|
||
# NIX_LDFLAGS. This is necessary when using a custom toolchain which
|
||
# Bazel wants all headers / libraries to come from, like when using
|
||
# CROSSTOOL. Weirdly, we can still get the flags through the wrapped
|
||
# compiler.
|
||
, dontAddBazelOpts ? false
|
||
, ...
|
||
}:
|
||
|
||
let
|
||
fArgs = removeAttrs args [ "buildAttrs" "fetchAttrs" "removeRulesCC" ];
|
||
fBuildAttrs = fArgs // buildAttrs;
|
||
fFetchAttrs = fArgs // removeAttrs fetchAttrs [ "sha256" ];
|
||
bazelCmd = { cmd, additionalFlags, targets }:
|
||
lib.optionalString (targets != [ ]) ''
|
||
# See footnote called [USER and BAZEL_USE_CPP_ONLY_TOOLCHAIN variables]
|
||
BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \
|
||
USER=homeless-shelter \
|
||
bazel \
|
||
--batch \
|
||
--output_base="$bazelOut" \
|
||
--output_user_root="$bazelUserRoot" \
|
||
${cmd} \
|
||
--curses=no \
|
||
-j $NIX_BUILD_CORES \
|
||
"''${copts[@]}" \
|
||
"''${host_copts[@]}" \
|
||
"''${linkopts[@]}" \
|
||
"''${host_linkopts[@]}" \
|
||
$bazelFlags \
|
||
${lib.strings.concatStringsSep " " additionalFlags} \
|
||
${lib.strings.concatStringsSep " " targets}
|
||
'';
|
||
in
|
||
stdenv.mkDerivation (fBuildAttrs // {
|
||
inherit name bazelFlags bazelBuildFlags bazelTestFlags bazelFetchFlags bazelTarget bazelTestTargets;
|
||
|
||
deps = stdenv.mkDerivation (fFetchAttrs // {
|
||
name = "${name}-deps.tar.gz";
|
||
inherit bazelFlags bazelBuildFlags bazelTestFlags bazelFetchFlags bazelTarget bazelTestTargets;
|
||
|
||
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ fFetchAttrs.impureEnvVars or [];
|
||
|
||
nativeBuildInputs = fFetchAttrs.nativeBuildInputs or [] ++ [ bazel ];
|
||
|
||
preHook = fFetchAttrs.preHook or "" + ''
|
||
export bazelOut="$(echo ''${NIX_BUILD_TOP}/output | sed -e 's,//,/,g')"
|
||
export bazelUserRoot="$(echo ''${NIX_BUILD_TOP}/tmp | sed -e 's,//,/,g')"
|
||
export HOME="$NIX_BUILD_TOP"
|
||
export USER="nix"
|
||
# This is needed for git_repository with https remotes
|
||
export GIT_SSL_CAINFO="${cacert}/etc/ssl/certs/ca-bundle.crt"
|
||
# This is needed for Bazel fetchers that are themselves programs (e.g.
|
||
# rules_go using the go toolchain)
|
||
export SSL_CERT_FILE="${cacert}/etc/ssl/certs/ca-bundle.crt"
|
||
'';
|
||
|
||
buildPhase = fFetchAttrs.buildPhase or ''
|
||
runHook preBuild
|
||
|
||
# See footnote called [USER and BAZEL_USE_CPP_ONLY_TOOLCHAIN variables].
|
||
# We disable multithreading for the fetching phase since it can lead to timeouts with many dependencies/threads:
|
||
# https://github.com/bazelbuild/bazel/issues/6502
|
||
BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 \
|
||
USER=homeless-shelter \
|
||
bazel \
|
||
--batch \
|
||
--output_base="$bazelOut" \
|
||
--output_user_root="$bazelUserRoot" \
|
||
${if fetchConfigured then "build --nobuild" else "fetch"} \
|
||
--loading_phase_threads=1 \
|
||
$bazelFlags \
|
||
$bazelFetchFlags \
|
||
${bazelTarget} \
|
||
${lib.strings.concatStringsSep " " bazelTestTargets}
|
||
|
||
runHook postBuild
|
||
'';
|
||
|
||
installPhase = fFetchAttrs.installPhase or ''
|
||
runHook preInstall
|
||
|
||
# Remove all built in external workspaces, Bazel will recreate them when building
|
||
rm -rf $bazelOut/external/{bazel_tools,\@bazel_tools.marker}
|
||
${if removeRulesCC then "rm -rf $bazelOut/external/{rules_cc,\\@rules_cc.marker}" else ""}
|
||
rm -rf $bazelOut/external/{embedded_jdk,\@embedded_jdk.marker}
|
||
${if removeLocalConfigCc then "rm -rf $bazelOut/external/{local_config_cc,\\@local_config_cc.marker}" else ""}
|
||
${if removeLocal then "rm -rf $bazelOut/external/{local_*,\\@local_*.marker}" else ""}
|
||
|
||
# Clear markers
|
||
find $bazelOut/external -name '@*\.marker' -exec sh -c 'echo > {}' \;
|
||
|
||
# Remove all vcs files
|
||
rm -rf $(find $bazelOut/external -type d -name .git)
|
||
rm -rf $(find $bazelOut/external -type d -name .svn)
|
||
rm -rf $(find $bazelOut/external -type d -name .hg)
|
||
|
||
# Removing top-level symlinks along with their markers.
|
||
# This is needed because they sometimes point to temporary paths (?).
|
||
# For example, in Tensorflow-gpu build:
|
||
# platforms -> NIX_BUILD_TOP/tmp/install/35282f5123611afa742331368e9ae529/_embedded_binaries/platforms
|
||
find $bazelOut/external -maxdepth 1 -type l | while read symlink; do
|
||
name="$(basename "$symlink")"
|
||
rm "$symlink"
|
||
test -f "$bazelOut/external/@$name.marker" && rm "$bazelOut/external/@$name.marker" || true
|
||
done
|
||
|
||
# Patching symlinks to remove build directory reference
|
||
find $bazelOut/external -type l | while read symlink; do
|
||
new_target="$(readlink "$symlink" | sed "s,$NIX_BUILD_TOP,NIX_BUILD_TOP,")"
|
||
rm "$symlink"
|
||
ln -sf "$new_target" "$symlink"
|
||
done
|
||
|
||
echo '${bazel.name}' > $bazelOut/external/.nix-bazel-version
|
||
|
||
(cd $bazelOut/ && tar czf $out --sort=name --mtime='@1' --owner=0 --group=0 --numeric-owner external/)
|
||
|
||
runHook postInstall
|
||
'';
|
||
|
||
dontFixup = true;
|
||
allowedRequisites = [];
|
||
|
||
outputHashAlgo = "sha256";
|
||
outputHash = fetchAttrs.sha256;
|
||
});
|
||
|
||
nativeBuildInputs = fBuildAttrs.nativeBuildInputs or [] ++ [ (bazel.override { enableNixHacks = true; }) ];
|
||
|
||
preHook = fBuildAttrs.preHook or "" + ''
|
||
export bazelOut="$NIX_BUILD_TOP/output"
|
||
export bazelUserRoot="$NIX_BUILD_TOP/tmp"
|
||
export HOME="$NIX_BUILD_TOP"
|
||
'';
|
||
|
||
preConfigure = ''
|
||
mkdir -p "$bazelOut"
|
||
|
||
(cd $bazelOut && tar xfz $deps)
|
||
|
||
test "${bazel.name}" = "$(<$bazelOut/external/.nix-bazel-version)" || {
|
||
echo "fixed output derivation was built for a different bazel version" >&2
|
||
echo " got: $(<$bazelOut/external/.nix-bazel-version)" >&2
|
||
echo "expected: ${bazel.name}" >&2
|
||
exit 1
|
||
}
|
||
|
||
chmod -R +w $bazelOut
|
||
find $bazelOut -type l | while read symlink; do
|
||
if [[ $(readlink "$symlink") == *NIX_BUILD_TOP* ]]; then
|
||
ln -sf $(readlink "$symlink" | sed "s,NIX_BUILD_TOP,$NIX_BUILD_TOP,") "$symlink"
|
||
fi
|
||
done
|
||
'' + fBuildAttrs.preConfigure or "";
|
||
|
||
inherit dontAddBazelOpts;
|
||
|
||
buildPhase = fBuildAttrs.buildPhase or ''
|
||
runHook preBuild
|
||
|
||
# Bazel sandboxes the execution of the tools it invokes, so even though we are
|
||
# calling the correct nix wrappers, the values of the environment variables
|
||
# the wrappers are expecting will not be set. So instead of relying on the
|
||
# wrappers picking them up, pass them in explicitly via `--copt`, `--linkopt`
|
||
# and related flags.
|
||
|
||
copts=()
|
||
host_copts=()
|
||
linkopts=()
|
||
host_linkopts=()
|
||
if [ -z "''${dontAddBazelOpts:-}" ]; then
|
||
for flag in $NIX_CFLAGS_COMPILE; do
|
||
copts+=( "--copt=$flag" )
|
||
host_copts+=( "--host_copt=$flag" )
|
||
done
|
||
for flag in $NIX_CXXSTDLIB_COMPILE; do
|
||
copts+=( "--copt=$flag" )
|
||
host_copts+=( "--host_copt=$flag" )
|
||
done
|
||
for flag in $NIX_LDFLAGS; do
|
||
linkopts+=( "--linkopt=-Wl,$flag" )
|
||
host_linkopts+=( "--host_linkopt=-Wl,$flag" )
|
||
done
|
||
fi
|
||
|
||
${
|
||
bazelCmd {
|
||
cmd = "test";
|
||
additionalFlags =
|
||
["--test_output=errors"] ++ bazelTestFlags;
|
||
targets = bazelTestTargets;
|
||
}
|
||
}
|
||
${
|
||
bazelCmd {
|
||
cmd = "build";
|
||
additionalFlags = bazelBuildFlags;
|
||
targets = [bazelTarget];
|
||
}
|
||
}
|
||
runHook postBuild
|
||
'';
|
||
})
|
||
|
||
# [USER and BAZEL_USE_CPP_ONLY_TOOLCHAIN variables]:
|
||
# Bazel computes the default value of output_user_root before parsing the
|
||
# flag. The computation of the default value involves getting the $USER
|
||
# from the environment. Code here :
|
||
# https://github.com/bazelbuild/bazel/blob/9323c57607d37f9c949b60e293b573584906da46/src/main/cpp/startup_options.cc#L123-L124
|
||
#
|
||
# 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.
|