{ stdenv, cacert, lib, writeCBin, }: args@{ name ? "${args.pname}-${args.version}", bazel, bazelFlags ? [ ], bazelBuildFlags ? [ ], bazelTestFlags ? [ ], bazelRunFlags ? [ ], runTargetFlags ? [ ], bazelFetchFlags ? [ ], bazelTargets ? [ ], bazelTestTargets ? [ ], bazelRunTarget ? null, 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" ] // { inherit name bazelFlags bazelBuildFlags bazelTestFlags bazelRunFlags runTargetFlags bazelFetchFlags bazelTargets bazelTestTargets bazelRunTarget dontAddBazelOpts ; }; fBuildAttrs = fArgs // buildAttrs; fFetchAttrs = fArgs // removeAttrs fetchAttrs [ "sha256" ]; bazelCmd = { cmd, additionalFlags, targets, targetRunFlags ? [ ], }: 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 \ "''${copts[@]}" \ "''${host_copts[@]}" \ "''${linkopts[@]}" \ "''${host_linkopts[@]}" \ $bazelFlags \ ${lib.strings.concatStringsSep " " additionalFlags} \ ${lib.strings.concatStringsSep " " targets} \ ${ lib.optionalString (targetRunFlags != [ ]) " -- " + lib.strings.concatStringsSep " " targetRunFlags } ''; # we need this to chmod dangling symlinks on darwin, gnu coreutils refuses to do so: # chmod: cannot operate on dangling symlink '$symlink' chmodder = writeCBin "chmodder" '' #include #include #include #include #include #include int main(int argc, char** argv) { mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; if (argc != 2) { fprintf(stderr, "usage: chmodder file"); exit(EXIT_FAILURE); } if (lchmod(argv[1], mode) != 0) { fprintf(stderr, "failed to lchmod '%s': %s", argv[0], strerror(errno)); exit(EXIT_FAILURE); } } ''; in stdenv.mkDerivation ( fBuildAttrs // { deps = stdenv.mkDerivation ( fFetchAttrs // { name = "${name}-deps.tar.gz"; 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 ${bazelCmd { cmd = if fetchConfigured then "build --nobuild" else "fetch"; additionalFlags = [ # We disable multithreading for the fetching phase since it can lead to timeouts with many dependencies/threads: # https://github.com/bazelbuild/bazel/issues/6502 "--loading_phase_threads=1" "$bazelFetchFlags" ] ++ ( if fetchConfigured then [ "--jobs" "$NIX_BUILD_CORES" ] else [ ] ); targets = fFetchAttrs.bazelTargets ++ fFetchAttrs.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} ${lib.optionalString removeRulesCC "rm -rf $bazelOut/external/{rules_cc,\\@rules_cc.marker}"} rm -rf $bazelOut/external/{embedded_jdk,\@embedded_jdk.marker} ${lib.optionalString removeLocalConfigCc "rm -rf $bazelOut/external/{local_config_cc,\\@local_config_cc.marker}"} ${lib.optionalString removeLocal "rm -rf $bazelOut/external/{local_*,\\@local_*.marker}"} # 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" '' + lib.optionalString stdenv.isDarwin '' # on linux symlink permissions cannot be modified, so we modify those on darwin to match the linux ones ${chmodder}/bin/chmodder "$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 ""; 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" ] ++ fBuildAttrs.bazelTestFlags ++ [ "--jobs" "$NIX_BUILD_CORES" ]; targets = fBuildAttrs.bazelTestTargets; }} ${bazelCmd { cmd = "build"; additionalFlags = fBuildAttrs.bazelBuildFlags ++ [ "--jobs" "$NIX_BUILD_CORES" ]; targets = fBuildAttrs.bazelTargets; }} ${bazelCmd { cmd = "run"; additionalFlags = fBuildAttrs.bazelRunFlags ++ [ "--jobs" "$NIX_BUILD_CORES" ]; # Bazel run only accepts a single target, but `bazelCmd` expects `targets` to be a list. targets = lib.optionals (fBuildAttrs.bazelRunTarget != null) [ fBuildAttrs.bazelRunTarget ]; targetRunFlags = fBuildAttrs.runTargetFlags; }} 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.