| #!/bin/bash |
| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # A wrapper script used to invoke Bazel in the Fuchsia build's workspace. |
| # This requires a file named "bazel.sh.config" to be located in the same |
| # directory containing specific variable definitions (see below). |
| # |
| # Do not use directly, the //build/regenerator script will copy this |
| # file to a specific location in the build directory and generate the |
| # appropriate configuration file. |
| _SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" |
| readonly _SCRIPT_DIR |
| |
| _SRCDIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" >/dev/null 2>&1 && pwd)" |
| readonly _SRCDIR |
| |
| function die { |
| echo >&2 "ERROR: $*" |
| exit 1 |
| } |
| |
| # Read the configuration file. This should define the following variables: |
| # |
| # _BAZEL_BIN: Path to Bazel launcher script. |
| # _BAZEL_OUTPUT_BASE: Bazel output base directory path. |
| # _BAZEL_OUTPUT_USER_ROOT: Bazel output user root directory path. |
| # _BAZEL_WORKSPACE: Bazel workspace directory path. |
| # _NINJA_BUILD_DIR: Ninja build directory path. |
| # _PREBUILT_NINJA: Prebuilt Ninja binary path. |
| # _PREBUILT_PYTHON_DIR: Prebuilt python binary path. |
| # |
| # All paths should be absolute. |
| # |
| # LINT.IfChange(bazel.sh.config) |
| readonly _SCRIPT_ARGS_FILE="${_SCRIPT_DIR}/bazel.sh.config" |
| # LINT.ThenChange(//build/bazel/scripts/workspace_utils.py:bazel.sh.config) |
| [[ -f "${_SCRIPT_ARGS_FILE}" ]] || die "Missing Bazel script config file: ${_SCRIPT_ARGS_FILE}" |
| |
| # shellcheck source=/dev/null |
| source "${_SCRIPT_ARGS_FILE}" |
| |
| [[ -n "${_BAZEL_WORKSPACE}" ]] || die "Missing _BAZEL_WORKSPACE config variable" |
| [[ -d "${_BAZEL_WORKSPACE}" ]] || die "_BAZEL_WORKSPACE should be a directory: ${_BAZEL_WORKSPACE}" |
| |
| # These directories are created on demand. |
| [[ -n "${_BAZEL_OUTPUT_BASE}" ]] || die "Missing _BAZEL_OUTPUT_BASE config variable" |
| [[ -n "${_BAZEL_OUTPUT_USER_ROOT}" ]] || die "Missing _BAZEL_OUTPUT_USER_ROOT config variable" |
| |
| [[ -n "${_BAZEL_BIN}" ]] || die "Missing _BAZEL_BIN config variable" |
| [[ -f "${_BAZEL_BIN}" ]] || die "_BAZEL_BIN should be a file: ${_BAZEL_BIN}" |
| |
| [[ -n "${_NINJA_BUILD_DIR}" ]] || die "Missing _NINJA_BUILD_DIR config variable" |
| [[ -d "${_NINJA_BUILD_DIR}" ]] || die "_NINJA_BUILD_DIR should be a directory: ${_NINJA_BUILD_DIR}" |
| |
| [[ -n "${_PREBUILT_NINJA}" ]] || die "Missing _PREBUILT_NINJA config variable" |
| [[ -f "${_PREBUILT_NINJA}" ]] || die "_PREBUILT_NINJA should be a file: ${_PREBUILT_NINJA}" |
| |
| [[ -n "${_PREBUILT_PYTHON_DIR}" ]] || die "Missing _PREBUILT_PYTHON_DIR config variable" |
| [[ -d "${_PREBUILT_PYTHON_DIR}" ]] || die "_PREBUILT_PYTHON_DIR should be a directory: ${_PREBUILT_PYTHON_DIR}" |
| |
| readonly _REMOTE_SERVICES_BAZELRC="${_NINJA_BUILD_DIR}/regenerator_outputs/remote_services.bazelrc" |
| |
| readonly _GENERATE_INVOCATION_BAZELRC="${_SRCDIR}/scripts/generate_invocation_bazelrc.py" |
| readonly _INVOCATION_BAZELRC="${_BAZEL_WORKSPACE}/invocation.bazelrc" |
| |
| # Exported explicitly to be used by repository rules to reference the |
| # Ninja output directory and binary. |
| export BAZEL_FUCHSIA_NINJA_OUTPUT_DIR="${_NINJA_BUILD_DIR}" |
| export BAZEL_FUCHSIA_NINJA_PREBUILT="${_NINJA_PREBUILT}" |
| |
| # Ensure our prebuilt Python3 executable is in the PATH to run repository |
| # rules that invoke Python programs correctly in containers or jails that |
| # do not expose the system-installed one. |
| export PATH="${_PREBUILT_PYTHON_DIR}/bin:${PATH}" |
| |
| # Provide a direct path to the prebuilt Python3 executable as well. |
| readonly PREBUILT_PYTHON3="${_PREBUILT_PYTHON_DIR}/bin/python3" |
| |
| # An undocumented, but widely used, environment variable that tells Bazel to |
| # not auto-detect the host C++ installation. This makes workspace setup faster |
| # and ensures this can be used on containers where GCC or Clang are not |
| # installed (Bazel would complain otherwise with an error). |
| export BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 |
| |
| # Determines the command used in this invocation, and separate arguments |
| # that follow a -- from the rest, so we can inject extra arguments |
| # *before* it, as this is crucial to properly wrap invocations such |
| # as `bazel run <config_args> -- <target> <cmd_args>`. |
| # |
| # This sets the following global variable: |
| # |
| # _BAZEL_COMMAND: The bazel command (e.g. "version", "info" or "build") |
| # _BAZEL_PRE_COMMAND_ARGS: All options that appear before the command |
| # _BAZEL_POST_COMMAND_ARGS: All options that appear after the command, |
| # up to -- if provided. |
| # _BAZEL_REST_ARGS: Empty list, or "--" followed by other arguments that |
| # follow it. |
| # |
| function parse_bazel_command () { |
| _BAZEL_COMMAND= |
| _BAZEL_PRE_COMMAND_ARGS=() |
| _BAZEL_POST_COMMAND_ARGS=() |
| _BAZEL_REST_ARGS=() |
| |
| while [[ "${#@}" -gt 0 ]]; do |
| case "$1" in |
| --) |
| _BAZEL_REST_ARGS=("$@") |
| break |
| ;; |
| -*) |
| if [[ -z "${_BAZEL_COMMAND}" ]]; then |
| _BAZEL_PRE_COMMAND_ARGS+=("$1") |
| else |
| _BAZEL_POST_COMMAND_ARGS+=("$1") |
| fi |
| ;; |
| *) |
| if [[ -z "${_BAZEL_COMMAND}" ]]; then |
| _BAZEL_COMMAND="$1" |
| else |
| _BAZEL_POST_COMMAND_ARGS+=("$1") |
| fi |
| ;; |
| esac |
| shift |
| done |
| } |
| |
| parse_bazel_command "$@" |
| |
| # The original invocation without -- and the args that follow it. |
| _BAZEL_DIRECT_ARGS=( |
| "${_BAZEL_PRE_COMMAND_ARGS[@]}" |
| "${_BAZEL_COMMAND}" |
| "${_BAZEL_POST_COMMAND_ARGS[@]}" |
| ) |
| |
| # Make bazel_command_does_configuration non-empty when the current |
| # Bazel command requires analysis / build configurations. |
| bazel_command_does_configuration= |
| case "${_BAZEL_COMMAND}" in |
| cquery | aquery | build | run | test) |
| bazel_command_does_configuration=true |
| ;; |
| esac |
| |
| # We generate our own invocation id, to make it easier to propagate its value |
| # to sub-build invocations. |
| readonly RESULTSTORE_invocation_id="$("${PREBUILT_PYTHON3}" -S -c 'import uuid; print(uuid.uuid4())')" |
| |
| # This is for ResultStore. |
| # TODO: depend on GN arg bazel_upload_build_events = {"sponge", "resultstore"} |
| readonly RESULTSTORE_URL="http://go/fxbtx" |
| readonly RESULTSTORE_SUB_BUILDS_LINK="$RESULTSTORE_URL/?q=PARENT_BUILD_ID:$RESULTSTORE_invocation_id" |
| |
| # A list of extra Bazel arguments that must appear after the |
| # command. |
| _BAZEL_EXTRA_ARGS=( |
| --invocation_id="$RESULTSTORE_invocation_id" |
| ) |
| |
| # The following step is sensitive to special environment variables: |
| # * per-invocation build metadata |
| # When uploading results to build event services like ResultStore or |
| # Sponge, include extra metadata that links related invocations. |
| # * proxy overrides sockets for remote services |
| # For infra builds, connections to various remote services are tunneled |
| # through local socket relays, launched by |
| # [infra/infra]/cmd/buildproxywrap/main.go. |
| # These environment-sensitive modifications to config definitions |
| # manifest in the short-lived 'invocation.bazelrc'. |
| # Delete after use. |
| trap "rm -f ${_INVOCATION_BAZELRC}" EXIT |
| "${_GENERATE_INVOCATION_BAZELRC}" \ |
| --sub_builds_link="$RESULTSTORE_SUB_BUILDS_LINK" \ |
| > "${_INVOCATION_BAZELRC}" |
| |
| # Save per-invocation logs. |
| # Log directories are generated dynamically per fx-build invocation, |
| # organized by bazel invocations. |
| readonly _BAZEL_INVOCATION_DATE="$(date +%Y%m%d-%H%M%S)" |
| readonly _BAZEL_INVOCATION_LOG_DIR_NAME="invocation-${_BAZEL_INVOCATION_DATE}--${RESULTSTORE_invocation_id}" |
| |
| if [[ -n "$FX_BUILD_LOGDIR" ]]; then |
| readonly _BAZEL_INVOCATION_LOG_PARENT="$FX_BUILD_LOGDIR/bazel_logs" |
| else |
| # This can happen if this script is run outside of `fx build`. |
| readonly _BAZEL_INVOCATION_LOG_PARENT="${_NINJA_BUILD_DIR}/bazel_logs" |
| echo >&2 "WARNING: FX_BUILD_LOGDIR is not set. Using fallback log directory: ${_BAZEL_INVOCATION_LOG_PARENT}" |
| fi |
| readonly _BAZEL_INVOCATION_LOG_DIR="${_BAZEL_INVOCATION_LOG_PARENT}/${_BAZEL_INVOCATION_LOG_DIR_NAME}" |
| mkdir -p "${_BAZEL_INVOCATION_LOG_DIR}" |
| |
| # Override log locations for this specific invocation. |
| # Bazel doesn't have a single log-dir control for writing all logs, |
| # so we must direct each one individually. |
| cat >> "${_INVOCATION_BAZELRC}" <<EOF |
| # Logs for this specific invocation. |
| common --experimental_workspace_rules_log_file=${_BAZEL_INVOCATION_LOG_DIR}/workspace_events.log |
| common:exec_log --execution_log_compact_file=${_BAZEL_INVOCATION_LOG_DIR}/exec_log.pb.zstd |
| EOF |
| |
| # Save a copy of the final invocation.bazelrc for debugging. |
| cp "${_INVOCATION_BAZELRC}" "${_BAZEL_INVOCATION_LOG_DIR}/invocation.bazelrc" |
| |
| # Maintain a 'recent' symlink to the latest log directory. |
| # Use a relative path for the symlink target so it works in archives. |
| ln -snf "${_BAZEL_INVOCATION_LOG_DIR_NAME}" "${_BAZEL_INVOCATION_LOG_PARENT}/recent" |
| |
| # For convenience, link from the top_dir to the most recent "bazel_logs" dir. |
| # Note that _BAZEL_WORKSPACE is top_dir/workspace. |
| ln -snf "${_BAZEL_INVOCATION_LOG_PARENT}" "${_BAZEL_WORKSPACE}/../bazel_logs" |
| |
| |
| # Non-remote configuration permits use of a disk-cache, below. |
| has_remote_config= |
| for arg in "${_BAZEL_DIRECT_ARGS[@]}" |
| do |
| # Check for infra and non-infra config variations to allow for local testing. |
| # "sponge" and "resultstore" come from 'build/bazel/remote_services.gni', |
| # and are added in 'build/bazel/bazel_action.gni'. |
| # TODO(https://fxbug.dev/445093719): This method doesn't work if the same |
| # configs are indirectly enabled. |
| case "$arg" in |
| --config=remote | --config=remote_cache_only) # Remote build execution service |
| has_remote_config=true |
| ;; |
| esac |
| done |
| |
| # In Corp environments with valid gcert credentials, use the credential helper |
| # to automatically exchange LOAS for OAuth (Google Cloud Platform) tokens. |
| # This requires less interaction from the user than 'gcloud auth ...'. |
| use_gcert_auth=() |
| [[ "$FX_BUILD_LOAS_TYPE" == "unrestricted" ]] && { |
| use_gcert_auth=(--config=gcertauth) |
| # Don't set this, otherwise bazel will look for it |
| # (and fail if it doesn't exist). |
| unset GOOGLE_APPLICATION_CREDENTIALS |
| } |
| |
| _BAZEL_PRE_COMMAND_ARGS+=( |
| |
| # Do not parse $HOME/.bazelrc |
| --nohome_rc |
| |
| # --nosystem_rc prevent parsing /etc/bazel.bazelrc, but prints a WARNING |
| # to stderr on each invocation which is annoyingly noisy. Uncomment the |
| # line below once this is fixed. See https://fxbug.dev/445090005. |
| # --nosystem_rc |
| |
| # Use the output base and user root specific to the current |
| # Fuchsia build directory, instead of default locations that |
| # are under $HOME/.bazel/ |
| --output_base="${_BAZEL_OUTPUT_BASE}" |
| --output_user_root="${_BAZEL_OUTPUT_USER_ROOT}" |
| |
| # Parse the .bazelrc defining configs related to remote builds. |
| # TODO(digit): Import this from the workspace's .bazelrc file. |
| --bazelrc="${_REMOTE_SERVICES_BAZELRC}" |
| |
| # For ephemeral configuration that amends remote_services.bazelrc: |
| --bazelrc="${_INVOCATION_BAZELRC}" |
| ) |
| |
| # Use a shared disk cache if FUCHSIA_BAZEL_DISK_CACHE is set. Bazel |
| # documentation states that --disk_cache is compatible with remote caching. |
| # |
| # This is useful when several checkouts are used on the same machine, |
| # or when performing repeated clean builds frequently. |
| # |
| # Define FUCHSIA_BAZEL_DISK_CACHE_SIZE to enable automatic garbage |
| # collection, which will be performed by Bazel in the background when idle. |
| # If undefined, the cache is unlimited, and users will need to clean it |
| # manually. |
| [[ -n "${FUCHSIA_BAZEL_DISK_CACHE}" && -n "${_BAZEL_COMMAND}" ]] && { |
| if [[ "${FUCHSIA_BAZEL_DISK_CACHE}" =~ ^/ ]]; then |
| _BAZEL_EXTRA_ARGS+=(--disk_cache="${FUCHSIA_BAZEL_DISK_CACHE}") |
| if [[ -n "${FUCHSIA_BAZEL_DISK_CACHE_SIZE}" ]]; then |
| # The size should be a size in bytes, optionally followed by K, M, G or T. |
| _BAZEL_EXTRA_ARGS+=(--experimental_disk_cache_gc_max_size="${FUCHSIA_BAZEL_DISK_CACHE_SIZE}") |
| fi |
| else |
| echo >&2 "ERROR: FUCHSIA_BAZEL_DISK_CACHE ignored (not absolute): $FUCHSIA_BAZEL_DISK_CACHE" |
| exit 1 |
| fi |
| } |
| |
| # Add remote related arguments for configuration-sensitive commands. |
| # It is likely that this is only required for commands that build artifacts |
| # (i.e. build, run, test but not cquery and aquery) but err on the side of |
| # caution. |
| [[ -n "${bazel_command_does_configuration}" ]] && |
| _BAZEL_EXTRA_ARGS+=( |
| "${use_gcert_auth[@]}" |
| ) |
| |
| # Setting $USER so `bazel` won't fail in environments with fake UIDs. Even if |
| # the USER is not actually used. See https://fxbug.dev/42063551#c9. |
| # In developer environments, use the real username so that authentication |
| # and credential helpers will work, e.g. go/bazel-google-sso. |
| _user="${USER:-unused-bazel-build-user}" |
| |
| _bazel_env=( |
| env USER="${_user}" |
| |
| # Inform sub-builds of this bazel invocation where to find related builds. |
| # These environment variables are read by generate_invocation_bazelrc.py |
| # and rsninja.sh, but do not impact actions from this bazel invocation. |
| # LINT.IfChange(related_invocations_env_vars) |
| RESULTSTORE_PARENT_BUILD_ID="$RESULTSTORE_invocation_id" |
| RESULTSTORE_PARENT_BUILD_LINK="$RESULTSTORE_URL/$RESULTSTORE_invocation_id" |
| RESULTSTORE_SIBLING_BUILDS_LINK="$RESULTSTORE_SUB_BUILDS_LINK" |
| # LINT.ThenChange( |
| # //build/bazel/scripts/generate_invocation_bazelrc.py:related_invocations_env_vars, |
| # //build/bazel/scripts/rsninja.sh:related_invocations_env_vars |
| # ) |
| ) |
| |
| _bazel_command=( |
| "${_bazel_env[@]}" |
| "${_BAZEL_BIN}" |
| "${_BAZEL_PRE_COMMAND_ARGS[@]}" |
| ) |
| # When "${BAZEL_COMMAND} is empty, do not add it nor _BAZEL_EXTRA_ARGS to _bazel_command, |
| [[ -n "${_BAZEL_COMMAND}" ]] && _bazel_command+=("${_BAZEL_COMMAND}" "${_BAZEL_EXTRA_ARGS[@]}") |
| |
| _bazel_command+=( |
| "${_BAZEL_POST_COMMAND_ARGS[@]}" |
| "${_BAZEL_REST_ARGS[@]}" |
| ) |
| |
| # Save the final invocation to a log file. |
| echo "${_bazel_command[*]}" >> "${_BAZEL_INVOCATION_LOG_DIR}/bazel_invocation" |
| |
| cd "${_BAZEL_WORKSPACE}" && "${_bazel_command[@]}" |