| # Copyright 2017 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. |
| |
| # Lack of shebang in this file is intentional |
| |
| # This file can be sourced multiple times when running certain tests. |
| # Guard against it to avoid errors about redefining readonly variables. |
| if [[ -z "${lib_vars_sh_sourced:-}" ]]; then |
| readonly lib_vars_sh_sourced=true |
| |
| if [[ -n "${ZSH_VERSION:-}" ]]; then |
| # shellcheck disable=SC2296,SC2298 |
| devshell_lib_dir=${${(%):-%x}:a:h} |
| FUCHSIA_DIR="$(dirname "$(dirname "$(dirname "${devshell_lib_dir}")")")" |
| else |
| # NOTE: Replace use of dirname with BASH substitutions to save 20ms of |
| # startup time for all fx commands. |
| # |
| # Equivalent to "$(dirname ${BASH_SOURCE[0]"}) but 350x faster. |
| # Exception: BASH_SOURCE[0] will be the script name when invoked directly |
| # from Bash (e.g. cd tools/devshell/lib && bash vars.sh). In this case |
| # the substitution will not change anything, so provide fallback case. |
| devshell_lib_dir="${BASH_SOURCE[0]%/*}" |
| if [[ "${devshell_lib_dir}" == "${BASH_SOURCE[0]}" ]]; then |
| devshell_lib_dir="." |
| fi |
| # Get absolute path. |
| devshell_lib_dir="$(cd "${devshell_lib_dir}" >/dev/null 2>&1 && pwd)" |
| # Compute absolute path to $devshell_lib_dir/../../.. |
| FUCHSIA_DIR="${devshell_lib_dir}" |
| FUCHSIA_DIR="${FUCHSIA_DIR%/*}" |
| FUCHSIA_DIR="${FUCHSIA_DIR%/*}" |
| FUCHSIA_DIR="${FUCHSIA_DIR%/*}" |
| fi |
| |
| export FUCHSIA_DIR |
| export FUCHSIA_OUT_DIR="${FUCHSIA_OUT_DIR:-${FUCHSIA_DIR}/out}" |
| # shellcheck source=/dev/null |
| source "${devshell_lib_dir}/platform.sh" |
| # shellcheck source=/dev/null |
| source "${devshell_lib_dir}/fx-cmd-locator.sh" |
| # shellcheck source=/dev/null |
| source "${devshell_lib_dir}/fx-optional-features.sh" |
| # shellcheck source=/dev/null |
| source "${devshell_lib_dir}/generate-ssh-config.sh" |
| unset devshell_lib_dir |
| |
| # Subcommands can use this directory to cache artifacts and state that should |
| # persist between runs. //scripts/fx ensures that it exists. |
| # |
| # fx commands that make use of this directory should include the command name |
| # in the names of any cached artifacts to make naming collisions less likely. |
| export FX_CACHE_DIR="${FUCHSIA_DIR}/.fx" |
| |
| # This allows LLVM utilities to perform debuginfod lookups for public artifacts. |
| # See https://sourceware.org/elfutils/Debuginfod.html. |
| # TODO(111990): Replace this with a local authenticating proxy to support access |
| # control. |
| public_url="https://storage.googleapis.com/fuchsia-artifacts" |
| if [[ "$DEBUGINFOD_URLS" != *"$public_url"* ]]; then |
| export DEBUGINFOD_URLS="${DEBUGINFOD_URLS:+$DEBUGINFOD_URLS }$public_url" |
| fi |
| unset public_url |
| |
| if [[ "${FUCHSIA_DEVSHELL_VERBOSITY:-0}" -eq 1 ]]; then |
| set -x |
| fi |
| |
| # If build profiling is enabled, collect system stats during build, |
| # including CPU, memory, disk I/O... |
| BUILD_PROFILE_ENABLED=1 |
| readonly fx_build_profile_config="${FUCHSIA_DIR}/.fx/config/build-profile" |
| readonly fx_build_profile_config_old="${FUCHSIA_DIR}/.fx-build-profile-config" |
| if [[ -f "$fx_build_profile_config_old" ]]; then |
| fx-info "Moving $fx_build_profile_config_old to new location $fx_build_profile_config. No further action is necessary." |
| mv "$fx_build_profile_config_old" "$fx_build_profile_config" |
| fi |
| if [[ -f "$fx_build_profile_config" ]]; then |
| # shellcheck source=/dev/null |
| source "$fx_build_profile_config" |
| # This sets BUILD_PROFILE_ENABLED to 0 or 1. |
| fi |
| |
| # This wrapper script collects system CPU/mem/IO info while |
| # another process is running. |
| readonly profile_wrap="${FUCHSIA_DIR}/build/profile/profile_wrap.sh" |
| |
| # If ResultStore is enabled, wrap builds with ResultStore tools. |
| RESULTSTORE_ENABLED=0 |
| readonly fx_resultstore_config="${FUCHSIA_DIR}/.fx/config/resultstore" |
| if [[ -f "$fx_resultstore_config" ]]; then |
| # shellcheck source=/dev/null |
| source "$fx_resultstore_config" |
| # This sets RESULTSTORE_ENABLED to 0 or 1. |
| fi |
| # Use re-client's credentials helper tool to exchange LOAS for OAuth2 tokens. |
| readonly credshelper="${PREBUILT_RECLIENT_DIR}/credshelper" |
| |
| date="$(date +%Y%m%d-%H%M%S)" |
| readonly date |
| |
| readonly jq="$PREBUILT_JQ" |
| |
| # For commands whose subprocesses may use reclient for RBE, prefix those |
| # commands conditioned on 'if fx-rbe-enabled' (function). |
| # This could not be made into a shell-function because it is used |
| # as both a function and non-built-in command, and functions do not compose |
| # by prefixing in shell. |
| RBE_WRAPPER=( "$FUCHSIA_DIR"/build/rbe/fuchsia-reproxy-wrap.sh ) |
| # Propagate tracing option from `fx -x build` to the wrapper script. |
| # This is less invasive than re-exporting SHELLOPTS. |
| if [[ -o xtrace ]]; then |
| RBE_WRAPPER=(/bin/bash -x "${RBE_WRAPPER[@]}" ) |
| fi |
| |
| # fx-command-stdout-to-array runs a command and stores its standard output |
| # into an array (expecting one item per line, preserving spaces). |
| # $1: variable name |
| # $2+: command arguments. |
| if [[ "${BASH_VERSION}" =~ ^3 ]]; then |
| # MacOS comes with Bash 3.x which doesn't have readarray at all |
| # so read one line at a time. |
| function fx-command-stdout-to-array { |
| local varname="$1" |
| local line output |
| shift |
| output="$("$@")" |
| while IFS= read -r line; do |
| eval "${varname}+=(\"${line}\")" |
| done <<< "${output}" |
| } |
| else |
| function fx-command-stdout-to-array { |
| local varname="$1" |
| shift |
| readarray -t "${varname}" <<< "$("$@")" |
| } |
| fi |
| |
| readonly BUILD_LOGS_DIR=_build_logs |
| readonly build_logs_root="${FUCHSIA_OUT_DIR}/${BUILD_LOGS_DIR}" |
| |
| # Establishes a centralized log directory for a build invocation. |
| # The directory structure is: |
| # ${build_logs_root}/$(basename ${FUCHSIA_BUILD_DIR})/build.<timestamp> |
| function fx-new-build-log-dir() { |
| fx-config-read |
| local -r out_dir_name="$(basename "${FUCHSIA_BUILD_DIR}")" |
| local -r log_dir_base="${build_logs_root}/${out_dir_name}" |
| mkdir -p "${log_dir_base}" |
| |
| local timestamp |
| timestamp="$(date +%Y%m%d-%H%M%S)" |
| local log_dir |
| log_dir="$(mktemp -d "${log_dir_base}/build.${timestamp}.XXXXXXXX")" |
| echo "${log_dir}" |
| } |
| |
| # Prints the path to the most recent build log directory |
| # created by fx-new-build-log-dir(). |
| function fx-get-last-build-log-dir() { |
| fx-build-dir-if-present || return |
| local -r out_dir_name="$(basename "${FUCHSIA_BUILD_DIR}")" |
| local -r log_dir_base="${build_logs_root}/${out_dir_name}" |
| if [[ -d "$log_dir_base" ]]; then |
| # shellcheck disable=SC2012 |
| local -r recent_log_dir="$(ls -1 -d "${log_dir_base}"/build.* | tail -n 1)" |
| if [[ -d "$recent_log_dir" ]]; then |
| echo "$recent_log_dir" |
| return |
| fi |
| fi |
| } |
| |
| # Use this to conditionally prefix a command with "${RBE_WRAPPER[@]}". |
| # NOTE: this function depends on FUCHSIA_BUILD_DIR which is set only after |
| # initialization. |
| # The cached version of this function is 'fx-rbe-enabled', below. |
| function recheck-fx-rbe-enabled { |
| # This function is called during tests without a build directory. |
| # Returns 1 to indicate that RBE is not enabled. |
| fx-build-dir-if-present || return 1 |
| |
| # This RBE settings file is created at GN gen time. |
| local -r rbe_settings_file="${FUCHSIA_BUILD_DIR}/rbe_settings.json" |
| |
| # Check to see if the rbe settings indicate that the reproxy wrapper is |
| # needed. |
| # shellcheck disable=SC2207 |
| local -a needs_reproxy |
| fx-command-stdout-to-array needs_reproxy "$jq" '-r' '.final.needs_reproxy' "${rbe_settings_file}" |
| if [[ "${needs_reproxy[0]}" != "true" ]]; then |
| return 1 |
| fi |
| } |
| |
| _fx_rbe_enabled_cache_var= |
| function fx-rbe-enabled { |
| if [[ -z "$_fx_rbe_enabled_cache_var" ]]; then |
| recheck-fx-rbe-enabled |
| # Cache the return status. |
| _fx_rbe_enabled_cache_var="$?" |
| fi |
| return "$_fx_rbe_enabled_cache_var" |
| } |
| |
| # Returns 0 if the build is configured to use authenticated services. |
| # The value of this function is cached by 'fx-build-needs-auth'. |
| function recheck-fx-build-needs-auth() { |
| # For testing-only, return 1 to indicate that authentication is not needed. |
| fx-build-dir-if-present || return 1 |
| |
| # The ResultStore service requires authentication. |
| if [[ "${RESULTSTORE_ENABLED}" -eq 1 ]]; then |
| return 0 |
| fi |
| |
| # This RBE settings file is created at GN gen time. |
| local -r rbe_settings_file="${FUCHSIA_BUILD_DIR}/rbe_settings.json" |
| |
| # Return 0 if authentication is needed for build remote services, |
| # otherwise return 1. |
| local -a needs_auth |
| fx-command-stdout-to-array needs_auth "$jq" '-r' '.final.needs_auth' "${rbe_settings_file}" |
| if [[ "${needs_auth[0]}" != "true" ]]; then |
| return 1 |
| fi |
| } |
| |
| _fx_build_needs_auth_cache_var= |
| function fx-build-needs-auth { |
| if [[ -z "$_fx_build_needs_auth_cache_var" ]]; then |
| recheck-fx-build-needs-auth |
| # Cache the return status. |
| _fx_build_needs_auth_cache_var="$?" |
| fi |
| return "$_fx_build_needs_auth_cache_var" |
| } |
| |
| # fx-is-stderr-tty exits with success if stderr is a tty. |
| function fx-is-stderr-tty { |
| [[ -t 2 ]] |
| } |
| |
| # fx-info prints a line to stderr with a blue INFO: prefix. |
| function fx-info { |
| if fx-is-stderr-tty; then |
| echo -e >&2 "\033[1;34mINFO:\033[0m $*" |
| else |
| echo -e >&2 "INFO: $*" |
| fi |
| } |
| |
| # fx-warn prints a line to stderr with a yellow WARNING: prefix. |
| function fx-warn { |
| if fx-is-stderr-tty; then |
| echo -e >&2 "\033[1;33mWARNING:\033[0m $*" |
| else |
| echo -e >&2 "WARNING: $*" |
| fi |
| } |
| |
| # fx-error prints a line to stderr with a red ERROR: prefix. |
| function fx-error { |
| if fx-is-stderr-tty; then |
| echo -e >&2 "\033[1;31mERROR:\033[0m $*" |
| else |
| echo -e >&2 "ERROR: $*" |
| fi |
| } |
| |
| # fx-fatal prints a line to stderr with a red FATAL: prefix then aborts the current script. |
| # Only use this for important assertion failures, and provide user-actionable instructions |
| # in the error message when that happens! |
| function fx-fatal { |
| if fx-is-stderr-tty; then |
| echo -e >&2 "\033[1;31mFATAL:\033[0m $*" |
| else |
| echo -e >&2 "FATAL: $*" |
| fi |
| exit 1 |
| } |
| |
| function fx-gn { |
| "${PREBUILT_GN}" "$@" |
| } |
| |
| function fx-is-bringup { |
| grep '^[^#]*import("//products/bringup.gni")' "${FUCHSIA_BUILD_DIR}/args.gn" >/dev/null 2>&1 |
| } |
| |
| function fx-fail-if-main-pb-is-not-set { |
| # Read the three relevant args all at once. |
| local -a values |
| mapfile -t values < <(fx-command-run jq -r \ |
| '.main_pb_label // "", |
| .use_bazel_images_only // "", |
| .bazel_product_bundle_full // "", |
| .bazel_product_bundle_prefix // ""' \ |
| "${FUCHSIA_BUILD_DIR}/args.json") |
| |
| # Pull the args out of the array and into their own variables. |
| local main_pb_label use_bazel_images_only bazel_product_bundle_full bazel_product_bundle_prefix |
| main_pb_label="${values[0]}" |
| use_bazel_images_only="${values[1]}" |
| bazel_product_bundle_full="${values[2]}" |
| bazel_product_bundle_prefix="${values[3]}" |
| |
| # Fail if this is a multi-product build and main_pb_label is not set. |
| if [[ "${use_bazel_images_only}" == "true" && -z "${bazel_product_bundle_full}" && -z "${bazel_product_bundle_prefix}" && -z "${main_pb_label}" ]]; then |
| fx-error "The 'main_pb_label' GN argument is not set. Please set it with: fx set-main-pb <name>" |
| exit 1 |
| fi |
| } |
| |
| function fx-regenerator { |
| "${FUCHSIA_DIR}/build/regenerator" \ |
| --fuchsia-dir="${FUCHSIA_DIR}" \ |
| --fuchsia-build-dir="${FUCHSIA_BUILD_DIR}" \ |
| "$@" |
| } |
| |
| # shellcheck disable=SC2120 |
| function fx-gen { |
| fx-regenerator "$@" |
| } |
| |
| function fx-gn-args { |
| fx-regenerator --update-args "$@" |
| } |
| |
| function fx-build-config-load { |
| # Paths are relative to FUCHSIA_DIR unless they're absolute paths. |
| if [[ "${FUCHSIA_BUILD_DIR:0:1}" != "/" ]]; then |
| FUCHSIA_BUILD_DIR="${FUCHSIA_DIR}/${FUCHSIA_BUILD_DIR}" |
| fi |
| |
| if [[ ! -f "${FUCHSIA_BUILD_DIR}/fx.config" ]]; then |
| if [[ ! -f "${FUCHSIA_BUILD_DIR}/args.gn" ]]; then |
| fx-error "Build directory missing or removed. (${FUCHSIA_BUILD_DIR})" |
| fx-error "run \"fx set\", or specify a build dir with --dir or \"fx use\"" |
| return 1 |
| fi |
| |
| fx-gen || return 1 |
| fi |
| |
| # shellcheck source=/dev/null |
| if ! source "${FUCHSIA_BUILD_DIR}/fx.config"; then |
| fx-error "${FUCHSIA_BUILD_DIR}/fx.config caused internal error" |
| return 1 |
| fi |
| |
| export FUCHSIA_BUILD_DIR FUCHSIA_ARCH |
| |
| if [[ "${HOST_OUT_DIR:0:1}" != "/" ]]; then |
| HOST_OUT_DIR="${FUCHSIA_BUILD_DIR}/${HOST_OUT_DIR}" |
| fi |
| |
| fx-export-default-target |
| |
| return 0 |
| } |
| |
| function fx-export-default-target { |
| # Set the device specified at the build directory level, if any. |
| if [[ -z "${FUCHSIA_NODENAME}" || "${FUCHSIA_NODENAME_IS_FROM_FILE}" == "true" ]]; then |
| if [[ -f "${FUCHSIA_BUILD_DIR}.device" ]]; then |
| FUCHSIA_NODENAME="$(<"${FUCHSIA_BUILD_DIR}.device")" |
| export FUCHSIA_NODENAME_IS_FROM_FILE="true" |
| fi |
| export FUCHSIA_NODENAME |
| fi |
| } |
| |
| # Forces the command to fail if a user specifies the -t flag |
| function fx-fail-if-device-specified { |
| if [[ -n "${FUCHSIA_NODENAME_SET_BY_FX_FLAG}" ]]; then |
| fx-error "The -t flag is not supported when calling this function" |
| exit 1 |
| fi |
| } |
| |
| # Sets FUCHSIA_BUILD_DIR once, to an absolute path. |
| function fx-build-dir-if-present { |
| if [[ -n "${FUCHSIA_BUILD_DIR:-}" ]]; then |
| # already set by this function earlier |
| return 0 |
| elif [[ -n "${_FX_BUILD_DIR:-}" ]]; then |
| export FUCHSIA_BUILD_DIR="${_FX_BUILD_DIR}" |
| # This can be set by --dir. |
| # Unset to prevent subprocess from acting on it again. |
| unset _FX_BUILD_DIR |
| else |
| if [[ ! -f "${FUCHSIA_DIR}/.fx-build-dir" ]]; then |
| return 1 |
| fi |
| |
| # .fx-build-dir contains $FUCHSIA_BUILD_DIR |
| FUCHSIA_BUILD_DIR="$(<"${FUCHSIA_DIR}/.fx-build-dir")" |
| if [[ -z "${FUCHSIA_BUILD_DIR}" ]]; then |
| return 1 |
| fi |
| # Paths are relative to FUCHSIA_DIR unless they're absolute paths. |
| if [[ "${FUCHSIA_BUILD_DIR:0:1}" != "/" ]]; then |
| FUCHSIA_BUILD_DIR="${FUCHSIA_DIR}/${FUCHSIA_BUILD_DIR}" |
| fi |
| fi |
| return 0 |
| } |
| |
| function fx-config-read { |
| if ! fx-build-dir-if-present; then |
| fx-error "No build directory found." |
| fx-error "Run \"fx set\" to create a new build directory, or specify one with --dir" |
| exit 1 |
| fi |
| |
| fx-build-config-load || exit $? |
| |
| _FX_LOCK_FILE="${FUCHSIA_BUILD_DIR}.build_lock" |
| } |
| |
| # Evaluates $@ if $FUCHSIA_NODENAME or $FUCHSIA_DEVICE_ADDR have been set by the |
| # user outside of `fx set-device` or `fx -t|--target`. |
| # |
| # Argument expressions are evaluated with the following environment variables |
| # provided: |
| # - ENV_VAR_NAMES: A human readable message fragment of the environment |
| # variable names that are overriding the default target. |
| # Example: '$FUCHSIA_NODENAME and $FUCHSIA_DEVICE_ADDR' |
| # - ENV_VARS: A human readable message fragment of the environment variable |
| # key-value pairs that are overriding the default target. |
| # Example: '$FUCHSIA_NODENAME="foo" and $FUCHSIA_DEVICE_ADDR="bar"' |
| # |
| # Examples: |
| # > fx-if-target-set-by-env echo '$ENV_VARS overrides the default target.' |
| # |
| # > fx-if-target-set-by-env echo '$ENV_VARS overrides the default target.' |
| # $FUCHSIA_NODENAME="foo" overrides the default target. |
| # |
| # > fx-if-target-set-by-env echo '$ENV_VAR_NAMES overrides the default target.' |
| # $FUCHSIA_NODENAME and $FUCHSIA_DEVICE_ADDR overrides the default target. |
| _FUCHSIA_NODENAME_FROM_PARENT="${FUCHSIA_NODENAME:-}" |
| function fx-if-target-set-by-env { |
| if [[ -n "${_FUCHSIA_NODENAME_FROM_PARENT}" && -n "${FUCHSIA_DEVICE_ADDR}" ]]; then |
| ENV_VAR_NAMES="\$FUCHSIA_NODENAME and \$FUCHSIA_DEVICE_ADDR" \ |
| ENV_VARS="\$FUCHSIA_NODENAME=\"${_FUCHSIA_NODENAME_FROM_PARENT}\" and \$FUCHSIA_DEVICE_ADDR=\"${FUCHSIA_DEVICE_ADDR}\"" \ |
| "$@" |
| elif [[ -n "${_FUCHSIA_NODENAME_FROM_PARENT}" ]]; then |
| ENV_VAR_NAMES="\$FUCHSIA_NODENAME" \ |
| ENV_VARS="\$FUCHSIA_NODENAME=\"${_FUCHSIA_NODENAME_FROM_PARENT}\"" \ |
| "$@" |
| elif [[ -n "${FUCHSIA_DEVICE_ADDR}" ]]; then |
| ENV_VAR_NAMES="\$FUCHSIA_DEVICE_ADDR" \ |
| ENV_VARS="\$FUCHSIA_DEVICE_ADDR=\"${FUCHSIA_DEVICE_ADDR}\"" \ |
| "$@" |
| fi |
| } |
| |
| function fx-change-build-dir { |
| local build_dir="$1" |
| |
| local -r tempfile="$(mktemp)" |
| echo "${build_dir}" > "${tempfile}" |
| mv -f "${tempfile}" "${FUCHSIA_DIR}/.fx-build-dir" |
| |
| # Now update the environment and root-symlinked build artifacts to reflect |
| # the change. |
| fx-config-read |
| |
| fx-regenerator "--symlinks-only" |
| } |
| |
| function ffx-default-repository-name { |
| # Use the build directory's name by default. Note that package URLs are not |
| # allowed to have underscores, so replace them with hyphens. |
| basename "${FUCHSIA_BUILD_DIR}" | tr '_' '-' |
| } |
| |
| # Runs a jq command against an existing file that will edit it, taking care |
| # of piping output to a tempfile and replacing it if jq succeeds. |
| # `_jq_edit <jq args> path/to/file.json` |
| # |
| # Note that the last argument is expected to be the filename being edited, to |
| # match the normal argument order of jq, but unlike with jq it's required. |
| function _jq_edit { |
| # Take the last argument as the path to the edited file. |
| # shellcheck disable=SC2124 |
| local json_file="${@: -1}" |
| |
| local -r tempfile="$(mktemp)" |
| if fx-command-run jq "$@" > "${tempfile}" ; then |
| mv -f "${tempfile}" "${json_file}" |
| return 0 |
| else |
| return $? |
| fi |
| } |
| |
| # Set a configuration value in a build config file: |
| # `json-config-set path/to/file.json path.to.value "value"` |
| function json-config-set { |
| local json_file="$1" |
| local path="$2" |
| local value="$3" |
| |
| # There needs to be a file there in the first place, so if there isn't one |
| # create one with an empty object for jq to update. |
| if ! [[ -f "${json_file}" ]] ; then |
| echo "{}" > "${json_file}" |
| fi |
| |
| _jq_edit -e -cS --arg value "${value}" ".${path} = \$value" "${json_file}" |
| return $? |
| } |
| |
| # Remove a configuration value in a build config file: |
| # `json-config-del path/to/file.json path.to.value` |
| # |
| # Will return a non-zero status code if the file or value did not already |
| # exist. |
| function json-config-del { |
| local json_file="$1" |
| local path="$2" |
| |
| if [[ -f "${json_file}" ]] ; then |
| # check the path exists, delete it if so, exit with error code otherwise. |
| _jq_edit -e -cS "if .${path} then del(.${path}) else empty | halt_error(1) end" "${json_file}" |
| return $? |
| else |
| return 1 |
| fi |
| } |
| |
| # Get a configuration value in a build config file: |
| # `json-config-get path/to/file.json path.to.value` |
| # |
| # Returns 1 and prints an empty line if the file does not exist or |
| # the path given was not already set. |
| function json-config-get { |
| local json_file="$1" |
| local path="$2" |
| |
| if ! [[ -f "${json_file}" ]] ; then |
| echo |
| return 1 |
| else |
| fx-command-run jq -e -r ".${path} | values" "${json_file}" |
| fi |
| } |
| |
| function get-device-raw { |
| fx-config-read |
| local device="" |
| # Suppress the unset default target message from stderr. |
| device="$(ffx target default get 2>/dev/null)" |
| |
| if ! is-valid-device "${device}"; then |
| fx-error "Invalid device name or address: '${device}'. Some valid examples are: |
| strut-wind-ahead-turf, 192.168.3.1:8022, [fe80::7:8%eth0], [fe80::7:8%eth0]:5222, [::1]:22" |
| exit 1 |
| fi |
| echo "${device}" |
| } |
| |
| function is-valid-device { |
| local device="$1" |
| if [[ -n "${device}" ]] \ |
| && ! _looks_like_ipv4 "${device}" \ |
| && ! _looks_like_ipv6 "${device}" \ |
| && ! _looks_like_hostname "${device}"; then |
| return 1 |
| fi |
| } |
| |
| # Shared among a few subcommands to configure and identify a remote forward |
| # target for a device. |
| export _FX_REMOTE_WORKFLOW_DEVICE_ADDR='[::1]:8022' |
| |
| function is-remote-workflow-device { |
| [[ $(get-device-raw 2>/dev/null) == "${_FX_REMOTE_WORKFLOW_DEVICE_ADDR}" ]] |
| } |
| |
| # fx-export-device-address is "public API" to commands that wish to |
| # have the exported variables set. |
| function fx-export-device-address { |
| FX_DEVICE_NAME="$(get-device-name)" |
| FX_DEVICE_ADDR="$(get-fuchsia-device-addr)" |
| FX_SSH_ADDR="$(get-device-addr-resource)" |
| FX_SSH_PORT="$(get-device-ssh-port)" |
| export FX_DEVICE_NAME FX_DEVICE_ADDR FX_SSH_ADDR FX_SSH_PORT |
| } |
| |
| function get-device-ssh-port { |
| local device |
| device="$(get-device-raw)" || exit $? |
| local addr_port="$(fx-target-finder-resolve "${device}")" |
| local port="" |
| # If there's more than one device returned by `ffx target list`, |
| # return an empty port |
| if [[ "$addr_port" != *$'\n'* ]] ; then |
| # extract port, if present |
| if [[ "${addr_port}" =~ :([0-9]+)$ ]]; then |
| port="${BASH_REMATCH[1]}" |
| fi |
| fi |
| echo "${port}" |
| } |
| |
| function get-device-name { |
| local device |
| device="$(get-device-raw)" || exit $? |
| # remove ssh port if present |
| if _looks_like_hostname "${device}" || _looks_like_ipv4 "${device}"; then |
| if [[ "${device}" =~ ^(.*):[0-9]{1,5}$ ]]; then |
| device="${BASH_REMATCH[1]}" |
| fi |
| elif _looks_like_ipv6 "${device}"; then |
| # parse the address into parts. |
| local expression='^\[([0-9a-fA-F:]+(%[0-9a-zA-Z-]{1,})?)\](:[0-9]{1,5})?$' |
| if ! [[ "$device" =~ ${expression} ]]; then |
| # try again but wrap the arg in "[]" |
| local wrapped="[${device}]" |
| if ! [[ "$device" =~ ${expression} ]]; then |
| echo "$device" |
| return |
| fi |
| fi |
| device="[${BASH_REMATCH[1]}]" |
| |
| fi |
| echo "${device}" |
| } |
| |
| function _looks_like_hostname { |
| [[ "$1" =~ ^([a-z0-9][.a-z0-9-]*)?(:[0-9]{1,5})?$ ]] || return 1 |
| } |
| |
| function _looks_like_ipv4 { |
| [[ "$1" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(:[0-9]{1,5})?$ ]] || return 1 |
| } |
| |
| function _looks_like_ipv6 { |
| local expression |
| expression='^\[([0-9a-fA-F:]+(%[0-9a-zA-Z-]{1,})?)\](:[0-9]{1,5})?$' |
| if ! [[ "$1" =~ ${expression} ]]; then |
| # try again but wrap the arg in "[]" |
| local wrapped |
| wrapped="[${1}]" |
| [[ "${wrapped}" =~ ${expression} ]] || return 1 |
| # Okay check that the address is not just a bare port |
| ! [[ "$1" =~ ^:[0-9a-fA-F]+$ ]] || return 1 |
| # Okay check that the address is not just a host name |
| ! [[ "$1" =~ ^[0-9a-fA-F\.]+$ ]] || return 1 |
| fi |
| local colons="${BASH_REMATCH[1]//[^:]}" |
| # validate that there are no more than 7 colons |
| [[ "${#colons}" -le 7 ]] || return 1 |
| } |
| |
| function _print_ssh_warning { |
| fx-warn "Cannot load device SSH credentials. $*" |
| fx-warn "Run 'tools/ssh-keys/gen-ssh-keys.sh' to regenerate." |
| } |
| |
| # Checks if default SSH keys are missing. |
| # |
| # The argument specifies which line of the manifest to retrieve and verify for |
| # existence. |
| # |
| # "key": The SSH identity file (private key). Append ".pub" for the |
| # corresponding public key. |
| # "auth": The authorized_keys file. |
| function _get-ssh-key { |
| local -r _SSH_MANIFEST="${FUCHSIA_DIR}/.fx-ssh-path" |
| |
| local filepath |
| local -r which="$1" |
| if [[ ! "${which}" =~ ^(key|auth)$ ]]; then |
| fx-error "_get-ssh-key: invalid argument '$1'. Must be either 'key' or 'auth'" |
| exit 1 |
| fi |
| |
| if [[ ! -f "${_SSH_MANIFEST}" ]]; then |
| _print_ssh_warning "File not found: ${_SSH_MANIFEST}." |
| return 1 |
| fi |
| |
| # Set -r flag to avoid interpreting backslashes as escape characters, e.g. it |
| # won't convert "\n" to a newline character. |
| { read -r privkey && read -r authkey; } < "${_SSH_MANIFEST}" |
| |
| if [[ -z $privkey || -z $authkey ]]; then |
| _print_ssh_warning "Manifest file ${_SSH_MANIFEST} is malformed." |
| return 1 |
| fi |
| |
| if [[ $which == "auth" ]]; then |
| filepath="${authkey}" |
| elif [[ $which == "key" ]]; then |
| filepath="${privkey}" |
| fi |
| |
| echo "${filepath}" |
| |
| if [[ ! -f "${filepath}" ]]; then |
| _print_ssh_warning "File not found: ${filepath}." |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # Invoke the ffx tool. This function invokes ffx via the tools/devshell/ffx |
| # wrapper that passes configuration information such as default target and build |
| # directory locations to the ffx tool as needed to provide a seamless fx/ffx |
| # integration. |
| function ffx { |
| fx-command-run ffx --config fuchsia.analytics.ffx_invoker=fx "$@" |
| } |
| |
| # Prints path to the default SSH key. These credentials are created |
| # and configured via ffx. |
| # |
| # The corresponding public key is stored in "$(get-ssh-privkey).pub". |
| function get-ssh-privkey { |
| init="$(fx-command-run ffx --config fuchsia.analytics.ffx_invoker=fx config check-ssh-keys)" |
| RESULT=$? |
| if [ $RESULT -ne 0 ]; then |
| fx-error "$init" |
| return 1 |
| fi |
| val="$(fx-command-run ffx --config fuchsia.analytics.ffx_invoker=fx config get --process file ssh.priv)" |
| temp="${val%\"}" |
| authkeys="${temp#\"}" |
| echo "${authkeys}" |
| } |
| |
| # Prints path to the default authorized_keys to include on Fuchsia devices. |
| function get-ssh-authkeys { |
| init="$(fx-command-run ffx --config fuchsia.analytics.ffx_invoker=fx config check-ssh-keys)" |
| RESULT=$? |
| if [ $RESULT -ne 0 ]; then |
| fx-error "$init" |
| return 1 |
| fi |
| val="$(fx-command-run ffx --config fuchsia.analytics.ffx_invoker=fx config get --process file ssh.pub)" |
| temp="${val%\"}" |
| authkeys="${temp#\"}" |
| echo "${authkeys}" |
| } |
| |
| # Checks the ssh_config file exists and references the private key, otherwise |
| # (re)creates it |
| function check-ssh-config { |
| privkey="$(get-ssh-privkey)" |
| conffile="${FUCHSIA_BUILD_DIR}/ssh-keys/ssh_config" |
| if [[ ! -f "${conffile}" ]] || ! grep -q "IdentityFile\s*$privkey" "$conffile"; then |
| generate-ssh-config "$privkey" "$conffile" |
| if [[ $? -ne 0 || ! -f "${conffile}" ]] || ! grep -q "IdentityFile\s*$privkey" "$conffile"; then |
| fx-error "Unexpected error, cannot generate ssh_config: ${conffile}" |
| exit 1 |
| fi |
| fi |
| } |
| |
| function fx-target-finder-resolve { |
| if [[ $# -ne 1 ]]; then |
| fx-error "Invalid arguments to fx-target-finder-resolve: [$*]" |
| return 1 |
| fi |
| ffx target list --format addresses "$1" |
| } |
| |
| function fx-target-finder-list { |
| ffx target list --format addresses |
| } |
| |
| function fx-target-finder-info { |
| ffx target list --format s |
| } |
| |
| function fx-target-ssh-address { |
| ffx target list --format addresses |
| } |
| |
| function multi-device-fail { |
| local output devices |
| fx-error "Multiple devices found." |
| fx-error "Please specify one of the following devices using either \`fx -t <device-name>\` or \`fx set-device <device-name>\`." |
| devices="$(fx-target-finder-info)" || { |
| code=$? |
| fx-error "Device discovery failed with status: $code" |
| exit $code |
| } |
| while IFS="" read -r line; do |
| fx-error "\t${line}" |
| done < <(printf '%s\n' "${devices}") |
| exit 1 |
| } |
| |
| function remove-address-brackets-and-port { |
| local device="$1" |
| |
| # Remove any trailing port (e.g. :22, :34567) from the address |
| local regex='(.*):[0-9]+$' |
| [[ "$device" =~ $regex ]] && device="${BASH_REMATCH[1]}" |
| |
| # Treat IPv4 addresses in the device name as an already resolved |
| # device address. |
| if _looks_like_ipv4 "${device}"; then |
| echo "${device}" |
| return 0 |
| fi |
| if _looks_like_ipv6 "${device}"; then |
| # remove brackets |
| device="${device%]}" |
| device="${device#[}" |
| echo "${device}" |
| return 0 |
| fi |
| return 1 |
| } |
| |
| function get-fuchsia-device-addr { |
| fx-config-read |
| local device |
| device="$(get-device-name)" || exit $? |
| |
| addr=$(remove-address-brackets-and-port "${device}") |
| if [ $? -eq 0 ]; then |
| echo "${addr}" |
| return |
| fi |
| |
| local output devices |
| case "$device" in |
| "") |
| output="$(fx-target-finder-list)" || { |
| code=$? |
| fx-error "Device discovery failed with status: $code" |
| exit $code |
| } |
| if [[ "$(echo "${output}" | wc -l)" -gt "1" ]]; then |
| multi-device-fail |
| fi |
| addr=$(remove-address-brackets-and-port "${output}") |
| echo "${addr}" ;; |
| *) output=$(fx-target-finder-resolve "$device") |
| addr=$(remove-address-brackets-and-port "${output}") |
| echo "${addr}" ;; |
| esac |
| } |
| |
| function get-fuchsia-device-port { |
| fx-config-read |
| local port |
| port="$(get-device-ssh-port)" || exit $? |
| |
| if [[ -z "${port}" ]]; then |
| local device |
| device="$(fx-target-ssh-address)" || { |
| code=$? |
| fx-error "Device discovery failed with status: ${code}" |
| exit ${code} |
| } |
| if [[ "$(echo "${device}" | wc -l)" -gt "1" ]]; then |
| multi-device-fail |
| fi |
| if [[ "${device}" =~ :([0-9]+)$ ]]; then |
| port="${BASH_REMATCH[1]}" |
| fi |
| fi |
| echo "${port}" |
| } |
| |
| # get-device-addr-resource returns an address that is properly encased |
| # for resource addressing for tools that expect that. In practical |
| # terms this just means encasing the address in square brackets if it |
| # is an ipv6 address. Note: this is not URL safe as-is, use the -url |
| # variant instead for URL formulation. |
| function get-device-addr-resource { |
| local addr |
| addr="$(get-fuchsia-device-addr)" || exit $? |
| if _looks_like_ipv4 "${addr}"; then |
| echo "${addr}" |
| return 0 |
| fi |
| |
| echo "[${addr}]" |
| } |
| |
| function get-device-addr-url { |
| get-device-addr-resource | sed 's#%#%25#' |
| } |
| |
| function fx-command-run { |
| local -r command_name="$1" |
| local command_path |
| # Use an array because the command may be multiple elements, if it comes from |
| # a .fx metadata file. |
| command_path="$(find_executable "${command_name}")" |
| if [[ $? -ne 0 || ! -x "${command_path}" ]]; then |
| fx-error "Unknown command ${command_name}" |
| exit 1 |
| fi |
| |
| shift |
| env FX_CALLER="$0" "${command_path}" "$@" |
| } |
| |
| function fx-command-exec { |
| local -r command_name="$1" |
| local command_path |
| # Use an array because the command may be multiple elements, if it comes from |
| # a .fx metadata file. |
| command_path="$(find_executable "${command_name}")" |
| if [[ $? -ne 0 || ! -x "${command_path}" ]]; then |
| fx-error "Unknown command ${command_name}" |
| exit 1 |
| fi |
| |
| shift |
| exec env FX_CALLER="$0" "${command_path}" "$@" |
| } |
| |
| function fx-print-command-help { |
| local command_path="$1" |
| if grep '^## ' "$command_path" > /dev/null; then |
| sed -n -e 's/^## //p' -e 's/^##$//p' < "$command_path" |
| else |
| local -r command_name=$(basename "$command_path" ".fx") |
| echo "No help found. Try \`fx $command_name -h\`" |
| fi |
| } |
| |
| function fx-command-help { |
| fx-print-command-help "$0" |
| echo -e "\nFor global options, try \`fx help\`." |
| } |
| |
| |
| # This function massages arguments to an fx subcommand so that a single |
| # argument `--switch=value` becomes two arguments `--switch` `value`. |
| # This lets each subcommand's main function use simpler argument parsing |
| # while still supporting the preferred `--switch=value` syntax. It also |
| # handles the `--help` argument by redirecting to what `fx help command` |
| # would do. Because of the complexities of shell quoting and function |
| # semantics, the only way for this function to yield its results |
| # reasonably is via a global variable. FX_ARGV is an array of the |
| # results. The standard boilerplate for using this looks like: |
| # function main { |
| # fx-standard-switches "$@" |
| # set -- "${FX_ARGV[@]}" |
| # ... |
| # } |
| # Arguments following a `--` are also added to FX_ARGV but not split, as they |
| # should usually be forwarded as-is to subprocesses. |
| function fx-standard-switches { |
| # In bash 4, this can be `declare -a -g FX_ARGV=()` to be explicit |
| # about setting a global array. But bash 3 (shipped on macOS) does |
| # not support the `-g` flag to `declare`. |
| FX_ARGV=() |
| while [[ $# -gt 0 ]]; do |
| if [[ "$1" = "--help" || "$1" = "-h" ]]; then |
| fx-print-command-help "$0" |
| # Exit rather than return, so we bail out of the whole command early. |
| exit 0 |
| elif [[ "$1" == --*=* ]]; then |
| # Turn --switch=value into --switch value. |
| FX_ARGV+=("${1%%=*}" "${1#*=}") |
| elif [[ "$1" == "--" ]]; then |
| # Do not parse remaining parameters after -- |
| FX_ARGV+=("$@") |
| return |
| else |
| FX_ARGV+=("$1") |
| fi |
| shift |
| done |
| } |
| |
| function fx-uuid { |
| # Emit a uuid string, same as the `uuidgen` tool. |
| # Using Python avoids requiring a separate tool. |
| "${PREBUILT_PYTHON3}" -S -c 'import uuid; print(uuid.uuid4())' |
| } |
| |
| function fx-choose-build-concurrency { |
| # If any remote execution is enabled (e.g. via RBE), |
| # allow ninja to launch many more concurrent actions than what local |
| # resources can support. |
| if fx-rbe-enabled ; then |
| # The recommendation from the Goma team is to use 10*cpu-count for C++. |
| local cpus |
| cpus="$(fx-cpu-count)" |
| echo $((cpus * 10)) |
| else |
| fx-cpu-count |
| fi |
| } |
| |
| function fx-cpu-count { |
| local -r cpu_count=$(getconf _NPROCESSORS_ONLN) |
| echo "$cpu_count" |
| } |
| |
| |
| # Use a lock file around a command if possible. |
| # Print a message if the lock isn't immediately entered, |
| # and block until it is. |
| function fx-try-locked { |
| if [[ -z "${_FX_LOCK_FILE}" ]]; then |
| fx-error "fx internal error: attempt to run locked command before fx-config-read" |
| exit 1 |
| fi |
| if ! command -v shlock >/dev/null; then |
| # Can't lock! Fall back to unlocked operation. |
| fx-exit-on-failure "$@" |
| elif shlock -f "${_FX_LOCK_FILE}" -p $$; then |
| # This will cause a deadlock if any subcommand calls back to fx build, |
| # because shlock isn't reentrant by forked processes. |
| fx-cmd-locked "$@" |
| else |
| echo "Locked by ${_FX_LOCK_FILE}..." |
| while ! shlock -f "${_FX_LOCK_FILE}" -p $$; do sleep .1; done |
| fx-cmd-locked "$@" |
| fi |
| } |
| |
| function fx-cmd-locked { |
| if [[ -z "${_FX_LOCK_FILE}" ]]; then |
| fx-error "fx internal error: attempt to run locked command before fx-config-read" |
| exit 1 |
| fi |
| # Exit trap to clean up lock file. Intentionally use the current value of |
| # $_FX_LOCK_FILE rather than the value at the time that trap executes to |
| # ensure we delete the original file even if the value of $_FX_LOCK_FILE |
| # changes for whatever reason. |
| trap "[[ -n \"\${_FX_LOCK_FILE}\" ]] && rm -f \"\${_FX_LOCK_FILE}\"" EXIT |
| fx-exit-on-failure "$@" |
| } |
| |
| function fx-exit-on-failure { |
| "$@" || exit $? |
| } |
| |
| # Run a Ninja or Bazel command with the right environment, after eventually |
| # prepending necessary RBE wrapper scripts to it. |
| # |
| # Arguments: |
| # print_full_cmd if true, prints the full ninja command line before |
| # executing it |
| # command_type either "ninja" or "bazel" |
| # |
| # build_command The build command itself. |
| # |
| function fx-run-build-command { |
| local print_full_cmd="$1" |
| shift |
| local command_type="$1" |
| shift |
| local build_command=("$@") |
| |
| # Check for a bad element in $PATH. |
| # We build tools in the build, such as touch(1), targeting Fuchsia. Those |
| # tools end up in the root of the build directory, which is also $PWD for |
| # tool invocations. As we don't have hermetic locations for all of these |
| # tools, when a user has an empty/pwd path component in their $PATH, |
| # the Fuchsia target tool will be invoked, and will fail. |
| # Implementation detail: Normally you would split path with IFS or a similar |
| # strategy, but catching the case where the first or last components are |
| # empty can be tricky in that case, so the pattern match strategy here covers |
| # the cases more easily. We check for three cases: empty prefix, empty suffix |
| # and empty inner. |
| case "${PATH}" in |
| :*|*:|*::*) |
| fx-error "Your \$PATH contains an empty element that will result in build failure." |
| fx-error "Remove the empty element from \$PATH and try again." |
| echo "${PATH}" | grep --color -E '^:|::|:$' >&2 |
| exit 1 |
| ;; |
| .:*|*:.|*:.:*) |
| fx-error "Your \$PATH contains the working directory ('.') that will result in build failure." |
| fx-error "Remove the '.' element from \$PATH and try again." |
| echo "${PATH}" | grep --color -E '^.:|:.:|:.$' >&2 |
| exit 1 |
| ;; |
| esac |
| |
| # TERM is passed for the pretty ninja UI |
| # PATH is passed through. The ninja actions should invoke tools without |
| # relying on PATH. |
| # TMPDIR was passed for Goma on macOS, but it might have other uses. |
| # NINJA_STATUS, NINJA_STATUS_MAX_COMMANDS and NINJA_STATUS_REFRESH_MILLIS |
| # are passed to control Ninja progress status. |
| # |
| # rbe_wrapper is used to auto-start/stop a (reclient) proxy process for the |
| # duration of the build, so that RBE-enabled build actions can operate |
| # through the proxy. |
| # |
| local -r build_uuid="$(fx-uuid)" |
| local -a user_rbe_env=() |
| |
| [[ "${FX_BUILD_AUTO_AUTH:-NOT_SET}" == "NOT_SET" ]] || { |
| fx-warn "FX_BUILD_AUTO_AUTH is no longer used from the environment." |
| cat <<EOF |
| To disable gcert-based authentication in build tools, manually set in .fx/config/build-auth: |
| |
| loas_cert_type=restricted |
| |
| You can confirm that authentication still works with 'fx rbe auth'. |
| EOF |
| } |
| |
| local -a rbe_wrapper_loas_args=() |
| if fx-build-needs-auth |
| then |
| local -r loas_type_detected="$(fx-command-run rbe _check_loas_type)" |
| local -r loas_type_for_reclient="$loas_type_detected" |
| local -r loas_type_for_bazel="$loas_type_detected" |
| rbe_wrapper_loas_args+=( --loas-type="$loas_type_for_reclient" ) |
| user_rbe_env+=( |
| # Automatic auth with gcert (from re-client bootstrap) needs $USER. |
| "USER=${USER}" |
| "FX_BUILD_LOAS_TYPE=$loas_type_for_bazel" |
| # A few tools need application credentials for authentication, |
| # like 'remotetool'. |
| # Explicitly set this variable without forwarding $HOME. |
| # User-overridable. |
| # Note: When using gcert to authenticate for bazel, |
| # unset this variable to prevent bazel from looking for a file |
| # that it doesn't need. This is handled in bazel wrappers. |
| "GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS:-$HOME/.config/gcloud/application_default_credentials.json}" |
| # For bazel subinvocations to be able to authenticate with gcert, |
| # need to forward the authentication socket (used by gnubby). |
| "SSH_AUTH_SOCK=${SSH_AUTH_SOCK}" |
| ) |
| fi |
| |
| # Create a unique log directory for this build invocation. |
| # Do this unconditionally, so that other build-log-consuming |
| # tools can use it. |
| local -r build_log_dir="$(fx-new-build-log-dir)" |
| # Record the invocation Id, which is also used for the top-level |
| # ninja invocation results streamed to ResultStore (if enabled). |
| echo "$build_uuid" > "$build_log_dir/invocation_id" |
| |
| local -a rbe_wrapper=() |
| if fx-rbe-enabled |
| then |
| local -r reproxy_logdir="${build_log_dir}/reproxy_logs" |
| mkdir -p "${reproxy_logdir}" |
| |
| # reproxy wants temporary space on the same physical device where the build happens. |
| # Re-use the randomly generated dir name in a custom tempdir. |
| local -r reproxy_tmpdir="$FUCHSIA_BUILD_DIR/.reproxy_tmpdirs/$(basename "${build_log_dir}")" |
| mkdir -p "$reproxy_tmpdir" |
| |
| # Honor additional cfg files from the current build dir. |
| local -r rbe_config_json="$FUCHSIA_BUILD_DIR/rbe_config.json" |
| local proxy_cfg_args=() |
| if [[ -r "$rbe_config_json" ]] |
| then |
| # shellcheck disable=SC2207 |
| all_proxy_cfgs=($("$jq" '.[] | .path' "$rbe_config_json" | sed -e 's|"\(.*\)"|\1|')) |
| # Adjust paths to be absolute. |
| for f in "${all_proxy_cfgs[@]}" |
| do proxy_cfg_args+=(--cfg "$FUCHSIA_BUILD_DIR/$f") # cumulative, repeatable |
| done |
| fi |
| |
| rbe_wrapper=( |
| env |
| "${RBE_WRAPPER[@]}" |
| --logdir "$reproxy_logdir" |
| --tmpdir "$reproxy_tmpdir" |
| "${proxy_cfg_args[@]}" |
| "${rbe_wrapper_loas_args[@]}" |
| -- |
| ) |
| [[ "${USER-NOT_SET}" != "NOT_SET" ]] || { |
| echo "Error: USER is not set" |
| exit 1 |
| } |
| user_rbe_env+=( |
| # Honor environment variable to disable RBE build metrics. |
| "FX_REMOTE_BUILD_METRICS=${FX_REMOTE_BUILD_METRICS}" |
| ) |
| fi |
| |
| envs=( |
| "FX_BUILD_UUID=$build_uuid" |
| "${user_rbe_env[@]}" |
| "TERM=${TERM}" |
| "PATH=${PATH}" |
| "NINJA_BUILD_ID=$build_uuid" # for the top-level ResultStore invocation |
| # By default, also show the number of actively running actions. |
| "NINJA_STATUS=${NINJA_STATUS:-"[%f/%t][%p/%w](%r) "}" |
| # By default, print the 4 oldest commands that are still running. |
| "NINJA_STATUS_MAX_COMMANDS=${NINJA_STATUS_MAX_COMMANDS:-4}" |
| "NINJA_STATUS_REFRESH_MILLIS=${NINJA_STATUS_REFRESH_MILLIS:-100}" |
| "NINJA_PERSISTENT_MODE=${NINJA_PERSISTENT_MODE:-0}" |
| # Forward the following only if the environment already sets them: |
| ${MAKEFLAGS+"MAKEFLAGS=${MAKEFLAGS}"} |
| ${FUCHSIA_BAZEL_DISK_CACHE+"FUCHSIA_BAZEL_DISK_CACHE=${FUCHSIA_BAZEL_DISK_CACHE}"} |
| ${FUCHSIA_BAZEL_JOB_COUNT+"FUCHSIA_BAZEL_JOB_COUNT=$FUCHSIA_BAZEL_JOB_COUNT"} |
| ${FUCHSIA_DEBUG_BAZEL_SANDBOX+"FUCHSIA_DEBUG_BAZEL_SANDBOX=${FUCHSIA_DEBUG_BAZEL_SANDBOX}"} |
| ${NINJA_PERSISTENT_TIMEOUT_SECONDS+"NINJA_PERSISTENT_TIMEOUT_SECONDS=$NINJA_PERSISTENT_TIMEOUT_SECONDS"} |
| ${NINJA_PERSISTENT_LOG_FILE+"NINJA_PERSISTENT_LOG_FILE=$NINJA_PERSISTENT_LOG_FILE"} |
| ${TMPDIR+"TMPDIR=$TMPDIR"} |
| ${CLICOLOR_FORCE+"CLICOLOR_FORCE=$CLICOLOR_FORCE"} |
| ${FX_BUILD_RBE_STATS+"FX_BUILD_RBE_STATS=$FX_BUILD_RBE_STATS"} |
| ${FX_BUILD_QUIET+"FX_BUILD_QUIET=$FX_BUILD_QUIET"} |
| ) |
| |
| local profile_wrapper=() |
| if [[ "$BUILD_PROFILE_ENABLED" == 1 ]] |
| then |
| # Collect system profile data while build is running. |
| local profile_dir="${build_log_dir}/build_profile" |
| mkdir -p "$profile_dir" |
| local vmstat_log="${profile_dir}/vmstat.log" |
| local ifconfig_log="${profile_dir}/ifconfig.log" |
| profile_wrapper=( |
| "$profile_wrap" |
| --vmstat-log "$vmstat_log" |
| --ifconfig-log "$ifconfig_log" |
| -n 2 |
| -- |
| ) |
| fi |
| |
| local resultstore_wrapper=() |
| if [[ "${RESULTSTORE_ENABLED}" -eq 1 ]]; then |
| # This path is defined by the 'rsclient' prebuilt package. |
| # See the rsclient entry in manifests/prebuilts. |
| local -r rsclient_prebuilt_dir="${FUCHSIA_DIR}/prebuilt/rsclient/$HOST_PLATFORM" |
| local -r rsproxy_wrap="${rsclient_prebuilt_dir}/bin/rsproxy-wrap.sh" |
| if [[ -x "${rsproxy_wrap}" ]]; then |
| # Select the right rsproxy configuration, depending on the LOAS cert type. |
| # "unrestricted" credentials can use gcert for authentication. |
| local -r rsproxy_bin="${rsclient_prebuilt_dir}/bin/rsproxy" |
| local loas_type |
| loas_type="$(fx-command-run rbe _check_loas_type)" |
| local config_file="" |
| local rsproxy_options=() |
| case "$loas_type" in |
| unrestricted) |
| config_file="${FUCHSIA_DIR}/build/resultstore/fuchsia-resultstore-gcertauth.cfg" |
| rsproxy_options=( |
| --cfg "${config_file}" |
| --credentials_helper "${credshelper}" |
| ) |
| ;; |
| restricted) |
| config_file="${FUCHSIA_DIR}/build/resultstore/fuchsia-resultstore.cfg" |
| rsproxy_options=( |
| --cfg "${config_file}" |
| ) |
| ;; |
| esac |
| local -r rsproxy_log_dir="${build_log_dir}/rsproxy_logs" |
| resultstore_wrapper=( |
| "${rsproxy_wrap}" |
| --log-dir "${rsproxy_log_dir}" |
| --rsproxy "${rsproxy_bin}" |
| --rsproxy_options |
| "${rsproxy_options[@]}" |
| -- |
| ) |
| fi |
| fi |
| |
| local full_cmdline=( |
| env -i "${envs[@]}" |
| "${resultstore_wrapper[@]}" |
| "${profile_wrapper[@]}" |
| "${rbe_wrapper[@]}" |
| "${build_command[@]}" |
| ) |
| |
| if [[ "${print_full_cmd}" = true ]]; then |
| echo "${full_cmdline[@]}" |
| echo |
| fi |
| if [[ "${command_type}" == "ninja" ]]; then |
| fx-try-locked "${full_cmdline[@]}" |
| else |
| # There is no need to use a lock file for Bazel |
| "${full_cmdline[@]}" |
| fi |
| } |
| |
| # Massage a ninja command line to add default -j and/or -l switches. |
| # Arguments: |
| # print_full_cmd if true, prints the full ninja command line before |
| # executing it |
| # ninja command the ninja command itself. This can be used both to run |
| # ninja directly or to run a wrapper script around ninja. |
| function fx-run-ninja { |
| # Separate the command from the arguments so we can prepend default -j/-l |
| # switch arguments. They need to come before the user's arguments in case |
| # those include -- or something else that makes following arguments not be |
| # handled as normal switches. |
| local print_full_cmd="$1" |
| shift |
| local cmd="$1" |
| shift |
| |
| local args=() |
| local full_cmdline |
| local cpu_load |
| local concurrency |
| local have_load=false |
| local have_jobs=false |
| while [[ $# -gt 0 ]]; do |
| case "$1" in |
| -l) |
| have_load=true |
| cpu_load="$2" |
| ;; |
| -j) |
| have_jobs=true |
| concurrency="$2" |
| ;; |
| -l*) |
| have_load=true |
| cpu_load="${1#-l}" |
| ;; |
| -j*) |
| have_jobs=true |
| concurrency="${1#-j}" |
| ;; |
| esac |
| args+=("$1") |
| shift |
| done |
| |
| if ! "$have_load"; then |
| if [[ "$(uname -s)" == "Darwin" ]]; then |
| # Load level on Darwin is quite different from that of Linux, wherein a |
| # load level of 1 per CPU is not necessarily a prohibitive load level. An |
| # unscientific study of build side effects suggests that cpus*20 is a |
| # reasonable value to prevent catastrophic load (i.e. user can not kill |
| # the build, can not lock the screen, etc). |
| local cpus |
| cpus="$(fx-cpu-count)" |
| cpu_load=$((cpus * 20)) |
| args=("-l" "${cpu_load}" "${args[@]}") |
| fi |
| elif [[ -z "${cpu_load}" ]]; then |
| echo "ERROR: Missing cpu load (-l) argument." |
| exit 1 |
| fi |
| |
| if ! "$have_jobs"; then |
| concurrency="$(fx-choose-build-concurrency)" |
| # macOS in particular has a low default for number of open file descriptors |
| # per process, which is prohibitive for higher job counts. Here we raise |
| # the number of allowed file descriptors per process if it appears to be |
| # low in order to avoid failures due to the limit. See `getrlimit(2)` for |
| # more information. |
| local min_limit=$((concurrency * 2)) |
| if [[ $(ulimit -n) -lt "${min_limit}" ]]; then |
| ulimit -n "${min_limit}" |
| fi |
| args=("-j" "${concurrency}" "${args[@]}") |
| elif [[ -z "${concurrency}" ]]; then |
| echo "ERROR: Missing job count (-j) argument." |
| exit 1 |
| fi |
| |
| if [[ "${have_jobs}" ]]; then |
| # Pass any _explicit_ job count provided by the user to the Bazel |
| # launcher script through an environment variable. |
| # See https://fxbug.dev/351623259 |
| FUCHSIA_BAZEL_JOB_COUNT=${concurrency} |
| fi |
| |
| fx-run-build-command "${print_full_cmd}" "ninja" "${cmd}" "${args[@]}" |
| } |
| |
| # Run a Bazel build command after setting up the environment and prepending |
| # optional wrappers for RBE and profiling to it. |
| # Arguments: |
| # print_full_cmd if true, prints the full ninja command line before |
| # executing it |
| # ninja command the ninja command itself. This can be used both to run |
| # ninja directly or to run a wrapper script around ninja. |
| function fx-run-bazel { |
| local print_full_cmd="$1" |
| shift |
| fx-run-build-command "${print_full_cmd}" bazel "$@" |
| } |
| |
| function fx-get-image { |
| fx-command-run list-build-artifacts --name "$1" --type "$2" --expect-one images |
| } |
| |
| function fx-get-zbi { |
| fx-get-image "$1" zbi |
| } |
| |
| function fx-get-qemu-kernel { |
| fx-get-image qemu-kernel kernel |
| } |
| |
| function fx-zbi { |
| "${FUCHSIA_BUILD_DIR}/$(fx-command-run list-build-artifacts --name zbi --expect-one tools)" --compressed="$FUCHSIA_ZBI_COMPRESSION" "$@" |
| } |
| |
| function fx-zbi-default-compression { |
| "${FUCHSIA_BUILD_DIR}/$(fx-command-run list-build-artifacts --name zbi --expect-one tools)" "$@" |
| } |
| |
| # Failsafe check for global TUI functionality, defaults to 'text' in case of |
| # errors. Call with e.g. $(fx-get-ui-mode "fx-use") to check for command |
| # specific overrides. By convention use snake-case to identify commands in |
| # overrides, e.g. "fx-use" or "ffx-target-list". |
| # Echos "tui" or "text" for the two modes, returns 0 on error-free invocation |
| # and returns 1 if any of the called commands have errors. |
| function fx-get-ui-mode() { |
| local command="$1" |
| local override_mode |
| local mode |
| |
| mode="text" # default to text |
| # We are NOT using fx-command-run to avoid having to build `ffx` - |
| # one instance of this call chooses which out dir to use with `fx use` |
| local ffx_binary="${FUCHSIA_BUILD_DIR}/host-tools/ffx" |
| if [[ ! -x "${ffx_binary}" ]]; then |
| fx-warn "ffx not found in build directory. It is needed to check \`ffx.ui.mode\`. Defaulting to 'text'" |
| echo "text" |
| return 1 |
| fi |
| # check for the ffx.ui.mode |
| tui_setting_raw="$("${ffx_binary}" config get ffx.ui.mode 2>/dev/null)" |
| exit_status=$? |
| if [[ "$exit_status" -ne 0 ]]; then |
| fx-warn "error running ffx config get ffx.ui.mode, defaulting to 'text'" |
| echo "text" |
| return 1 |
| elif [[ -n "$tui_setting_raw" ]]; then |
| tui_setting="${tui_setting_raw#\"}" # Remove leading " if it exists |
| tui_setting="${tui_setting%\"}" # Remove trailing " if it exists |
| mode="${tui_setting}" |
| fi |
| # there can be command specific overrides, check for those |
| if [[ -n $command ]]; then |
| overrides="$("${ffx_binary}" config get ffx.ui.overrides 2>/dev/null)" |
| exit_status=$? |
| if [[ "$exit_status" -ne 0 && "$exit_status" -ne 2 ]]; then # 2 is Value not found, which not an error |
| fx-warn "Error calling ffx config get ffx.ui.overrides, $mode" |
| echo "$mode" |
| return 1 |
| else |
| override_mode="$(fx jq --arg cmd "$command" -r '.[$cmd] //empty' <<< "$overrides")" |
| exit_status=$? |
| if [[ "$exit_status" -ne 0 ]]; then |
| fx-warn "Error parsing overrides in fx-get-ui-mode, using $mode" |
| echo "$mode" |
| return 1 |
| elif [[ -n "$override_mode" ]]; then |
| mode="$override_mode" |
| fi |
| fi |
| fi |
| # check the mode set is in the right format (or default to text) |
| case "$mode" in |
| text|tui) |
| echo "$mode" |
| ;; |
| *) |
| fx-warn "Warning: Invalid mode '$mode' detected for ffx.ui.mode, but 'tui' or 'text' expected: defaulting to 'text'\n\t (Change the setting with e.g. 'ffx config set ffx.ui.mode tui' )" >&2 |
| echo "text" |
| ;; |
| esac |
| return 0 |
| } |
| |
| # Gum - wrapped functionality for choosing amongst options |
| function fx-choose-tui { |
| fx-command-run gum choose "$@" |
| } |
| |
| # Gum - wrapped functionality for choosing amongst options |
| function fx-filter-tui { |
| fx-command-run gum filter "$@" |
| } |
| |
| fi # -z "${lib_vars_sh_sourced:-}" |