blob: 9f05835fe0f846eb29aa51a3f2b21614974c0e24 [file] [log] [blame]
#!/bin/bash
# Copyright 2026 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.
# This is a wrapper script that orchestrates multiple feature-enabling
# wrappers for running builds.
# Features:
# --rbe : enable RBE using reproxy
# --resultstore : enable uploading build metadata to ResultStore service
# --profile : enable system profiling during build
set -euo pipefail
readonly SCRIPT_NAME="${BASH_SOURCE[0]##*/}" # basename
readonly SCRIPT_DIR="${BASH_SOURCE[0]%/*}" # dirname
# TODO: remove dependence on FUCHSIA_DIR and fuchsia-specific conventions,
# apart from locations of essential scripts and tools.
# Sourcing platform.sh requires FUCHSIA_DIR to be set.
readonly FUCHSIA_DIR="$(readlink -f "$SCRIPT_DIR/../..")"
source "${FUCHSIA_DIR}/tools/devshell/lib/platform.sh"
readonly profile_wrapper="${FUCHSIA_DIR}/build/profile/profile_wrap.sh"
readonly reproxy_wrapper="${FUCHSIA_DIR}/build/rbe/fuchsia-reproxy-wrap.sh"
readonly rsproxy_wrapper="${FUCHSIA_DIR}/build/resultstore/fuchsia-rsproxy-wrap.sh"
verbose=0
function debug() {
[[ "$verbose" == 0 ]] || echo "[$SCRIPT_NAME]: $*"
}
function die() {
echo "[$SCRIPT_NAME] Error: $*"
exit 1
}
function usage() {
cat <<EOF
usage: $SCRIPT_NAME [options] -- build-command...
options:
-h | --help : print help and exit
-v | --verbose : run verbosely
--dry-run : print the final command instead of running it
--build-dir DIR : (required) build output dir (depth=2, e.g. "out/foo")
When building with ninja, the -C argument is interpreted as the build dir.
--log-dir DIR : where to produce logs for various build tools.
Defaults to using a timestamp-based directory name under out/_build_logs.
--loas-type : LOAS type used to choose authentication method
values: restricted, unrestricted, auto, skip
--profile[=0] : enable system profiling during build
--rbe[=0] : enable support for remote execution
--reproxy-cfg : additional reproxy configs to merge and use
--resultstore[=0] : uploading build metadata and results to ResultStore
--pre-build-uploads : configure-time invocation artifacts to upload
--post-build-uploads : end-of-build invocation artifacts to upload
The wrapped build command follows --.
EOF
}
### Defaults.
collect_system_profile=0 # fx build-profile
dry_run=0
enable_resultstore=0
needs_reproxy_rbe=0
### Configuration.
# Parse command-line arguments.
build_dir=
log_dir=
loas_type=
reproxy_cfgs=()
pre_build_uploads=()
post_build_uploads=()
build_tool_args=() # The wrapped command
prev_opt=
prev_opt_append=
for opt # "$@"
do
# handle --option arg
if [[ -n "$prev_opt" ]]
then
eval "$prev_opt"=\$opt
prev_opt=
shift
continue
fi
if [[ -n "$prev_opt_append" ]]
then
eval "$prev_opt_append"+=\(\$opt\)
prev_opt_append=
shift
continue
fi
# Extract optarg from --opt=optarg
optarg=
case "$opt" in
-*=*) optarg="${opt#*=}" ;; # remove-prefix, shortest-match
esac
case "$opt" in
-h | --help) usage; exit ;;
-v | --verbose) verbose=1 ;;
--dry-run) dry_run=1 ;;
--build-dir=*) build_dir="$optarg" ;;
--build-dir) prev_opt=build_dir ;;
--log-dir=*) log_dir="$optarg" ;;
--log-dir) prev_opt=log_dir ;;
--loas-type=*) loas_type="$optarg" ;;
--loas-type) prev_opt=loas_type ;;
--profile=*) collect_system_profile="$optarg" ;;
--profile) collect_system_profile=1 ;;
--rbe=*) needs_reproxy_rbe="$optarg" ;;
--rbe) needs_reproxy_rbe=1 ;;
--reproxy-cfg=*) reproxy_cfgs+=( "$optarg" ) ;;
--reproxy-cfg) prev_opt_append=reproxy_cfgs ;;
--resultstore=*) enable_resultstore="$optarg" ;;
--resultstore) enable_resultstore=1 ;;
--pre-build-uploads=*) pre_build_uploads="$optarg" ;;
--pre-build-uploads) prev_opt=pre_build_uploads ;;
--post-build-uploads=*) post_build_uploads="$optarg" ;;
--post-build-uploads) prev_opt=post_build_uploads ;;
# Stop option processing.
--) shift; break ;;
esac
shift
done
[[ -z "$prev_opt" ]] || {
die "Missing --${prev_opt} argument."
}
[[ -z "$prev_opt_append" ]] || {
die "Missing --${prev_opt_append} argument."
}
build_tool_args+=( "$@" )
[[ "${#build_tool_args[@]}" -ge 1 ]] || {
die "Missing wrapped command after --."
}
# Determine if this is a build-like command that needs wrapper orchestration.
# Bazel's build-like commands include 'build', 'test', and 'run'.
# Non-build commands (like bazel info, help, query) should bypass wrappers.
# Also try to infer build_dir from build tool args (e.g. ninja -C).
is_build=0
inferred_build_dir=
state="START"
for arg in "${build_tool_args[@]}"; do
case "$state" in
START)
case "$arg" in
env | *=*) continue ;; # skip environment variable prefixes
*/bazel | bazel) state="GOT_BAZEL_LOOKING_FOR_SUBCOMMAND" ;;
*/fint | fint) state="GOT_FINT_LOOKING_FOR_SUBCOMMAND" ;;
*ninja*)
is_build=1
state="GOT_NINJA_LOOKING_FOR_ARGS"
;;
*) state="DONE" ;;
esac
;;
GOT_BAZEL_LOOKING_FOR_SUBCOMMAND)
case "$arg" in
-*) continue ;;
build|test|run)
is_build=1
state="DONE"
;;
*) state="DONE" ;;
esac
;;
GOT_FINT_LOOKING_FOR_SUBCOMMAND)
case "$arg" in
-*) continue ;;
build)
is_build=1
state="DONE"
;;
*) state="DONE" ;;
esac
;;
GOT_NINJA_LOOKING_FOR_ARGS)
case "$arg" in
-C) state="GOT_NINJA_EXPECT_C_DIR" ;;
-C*)
inferred_build_dir="${arg#-C}"
;;
-n | --dry-run | -t | -t* | -h | --help | --version)
is_build=0
;;
esac
;;
GOT_NINJA_EXPECT_C_DIR)
inferred_build_dir="$arg"
state="GOT_NINJA_LOOKING_FOR_ARGS"
;;
esac
[[ "$state" != "DONE" ]] || break
done
if [[ "$is_build" == 0 ]]; then
debug "Bypassing wrapper orchestration for non-build command: ${build_tool_args[*]}"
if [[ "$dry_run" == 1 ]]; then
echo "${build_tool_args[@]}"
exit 0
fi
exec "${build_tool_args[@]}"
fi
[[ -n "$build_dir" ]] || {
build_dir="$inferred_build_dir"
[[ -z "$build_dir" ]] || debug "Inferred build-dir: $build_dir"
}
[[ -n "$build_dir" ]] || die "Missing required --build-dir option."
readonly build_dir_basename="${build_dir##*/}" # basename
[[ -n "$log_dir" ]] || {
# Then choose our own log dir, based on $build_dir.
readonly out_dir_root="${FUCHSIA_DIR}/out"
readonly build_logs_root="$out_dir_root/_build_logs"
readonly log_dir_base="$build_logs_root/$build_dir_basename"
mkdir -p "$log_dir_base"
readonly timestamp="$(date +%Y%m%d-%H%M%S)"
log_dir="$(mktemp -d "${log_dir_base}/build.${timestamp}.XXXXXXXX")"
debug "Using newly created log dir: $log_dir"
}
loas_type_arg=()
[[ -z "$loas_type" ]] || loas_type_arg+=( --loas-type "$loas_type" )
### Composition.
# Stack prefix wrappers based on enabled features.
maybe_fint_build_wrap=()
# TODO: support fint build wrapper (for infra)
maybe_profile_wrap=()
if [[ "$collect_system_profile" == 1 ]]
then
debug "Profiling enabled."
readonly profile_log_dir="$log_dir/build_profile"
mkdir -p "$profile_log_dir"
readonly vmstat_log="${profile_log_dir}/vmstat.log"
readonly ifconfig_log="${profile_log_dir}/ifconfig.log"
maybe_profile_wrap=(
"$profile_wrapper"
--vmstat-log "$vmstat_log"
--ifconfig-log "$ifconfig_log"
--
)
post_build_uploads+=(
# trace files
"$vmstat_log.json"
"$ifconfig_log.json"
)
fi
maybe_rbe_wrap=()
if [[ "$needs_reproxy_rbe" == 1 ]]
then
debug "RBE enabled."
readonly reproxy_logdir="$log_dir/reproxy_logs"
mkdir -p "$reproxy_logdir"
# reproxy works best when it uses a temp dir on the same physical device
# as the build dir.
# Reuse the timestamp-based part of $log_dir.
readonly reproxy_tmpdir="$build_dir/.reproxy_tmpdirs/${log_dir##*/}"
mkdir -p "$reproxy_tmpdir"
reproxy_cfg_args=()
for f in "${reproxy_cfgs[@]}"
do reproxy_cfg_args+=( --cfg "$f" )
done
reproxy_shutdown_opts=()
if [[ "$enable_resultstore" == 0 ]]
then
# When resultstore is enabled, we need to wait for reproxy to fully
# shutdown to guarantee that produces the logs and metrics that will
# be uploaded as post-build artifacts by rsproxy.
# Otherwise, allow reproxy to shutdown asynchronously.
reproxy_shutdown_opts+=( --async_reproxy_termination )
fi
maybe_rbe_wrap=(
"$reproxy_wrapper"
--logdir "$reproxy_logdir"
--tmpdir "$reproxy_tmpdir"
"${loas_type_arg[@]}"
"${reproxy_cfg_args[@]}"
"${reproxy_shutdown_opts[@]}"
--
)
post_build_uploads+=(
"$reproxy_logdir/rbe_metrics.txt"
# TODO: consider compressing the reproxy log
"$reproxy_logdir/reproxy.rrpl"
)
fi
maybe_resultstore_wrap=()
if [[ "$enable_resultstore" == 1 ]]
then
debug "ResultStore enabled."
readonly rsproxy_logdir="$log_dir/rsproxy_logs"
rsproxy_options=()
for f in "${pre_build_uploads[@]}"
do rsproxy_options+=( --pre_build_uploads "$f" )
done
for f in "${post_build_uploads[@]}"
do rsproxy_options+=( --post_build_uploads "$f" )
done
maybe_resultstore_wrap=(
"$rsproxy_wrapper"
"${loas_type_arg[@]}"
--log-dir "$rsproxy_logdir"
"${rsproxy_options[@]}"
--
)
fi
### Execution.
readonly full_cmd=(
"${maybe_fint_build_wrap[@]}"
"${maybe_resultstore_wrap[@]}"
"${maybe_profile_wrap[@]}"
"${maybe_rbe_wrap[@]}"
"${build_tool_args[@]}"
)
debug "full command: ${full_cmd[*]}"
if [[ "$dry_run" == 1 ]]; then
echo "${full_cmd[@]}"
else
exec "${full_cmd[@]}"
fi