blob: d7afcbd21018a62b4f1d7f5837f8bf8aa7f01b51 [file] [log] [blame]
#!/bin/bash
# Copyright 2023 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.
#### CATEGORY=Build
### Find binaries among build directories, the checkout, and Google Cloud
### Storage (GCS) matching a given build ID.
## usage: fx ls-buildid [--help|-h] [--verbose|-v] [--local|-l] BUILD_ID
##
## Searches for all binaries having the given GNU build ID across all build
## directories, prebuilt .build-id directories, and (optionally) within
## Google Cloud Storage (GCS).
##
## Search includes GCS by default, but this can be overridden to be strictly
## local with `--local`. Remote search takes a dependency on `gcloud` and
## `gsutil` both being on one's $PATH, as well as users having authenticated
## with GCS via `gcloud auth login`.
##
## It is assumed that build directories are of all of the form
## $FUCHSIA_DIR/out/foo.
##
## --verbose|-v Be explicit in the build directories and GCS buckets being
## searched.
##
## --local|-l Limit the search to local files (i.e., excluding cloud
## storage).
##
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../lib/vars.sh || exit $?
fx-config-read
# These are the standard buckets to which official Fuchsia infrastructure
# uploads binaries.
readonly GSUTIL_BUCKETS=(
fuchsia-artifacts
fuchsia-artifacts-internal
fuchsia-artifacts-release
)
id=""
local=false
verbose=false
while [[ $# -ne 0 ]]; do
case "$1" in
--help|-h)
fx-command-help
exit 0
;;
--remote|-r)
fx-error "Remote search is now the default!"
fx-command-help
exit 0
;;
--local|-l)
local=true
;;
--verbose|-v)
verbose=true
;;
-*)
fx-error "Unknown flag: $1"
fx-command-help
exit 1
;;
*)
if [[ -z "${id}" ]]; then
id="$1"
else
fx-error "Unexpected argument: '$1'"
exit 1
fi
;;
esac
shift
done
# With a remote search, let's make sure we can actually access GCS.
if ! $local ; then
if ( ! type gsutil &>/dev/null ); then
fx-warn "gsutil not on \$PATH. Proceeding with a strictly local search..."
local=true
fi
if ( ! type gcloud &>/dev/null ); then
fx-warn "\`gcloud\` not on \$PATH. Proceeding with a strictly local search..."
local=true
elif ( ! gcloud auth print-access-token &>/dev/null ); then
fx-warn "You've not yet run \`gcloud auth login\`! Proceeding with a strictly local search..."
local=true
fi
fi
# Relativize against the current working directory on a best-effort basis,
# because that makes for nicer output.
readonly out_dir="${FUCHSIA_OUT_DIR#"$(pwd)"/}"
prebuilt_dir="${FUCHSIA_DIR}/prebuilt"
readonly prebuilt_dir="${prebuilt_dir#"$(pwd)"/}"
# Aggregate all .build-id directories across the various build directories, as
# well as those shipped with prebuilts in the checkout.
build_id_dirs=("${out_dir}"/**/.build-id)
mapfile -t -O "${#build_id_dirs[@]}" build_id_dirs < <(find "${prebuilt_dir}" -type d -name .build-id)
if $verbose ; then
fx-info "Searching the following .build-id directories:"
printf "* %s\n" "${build_id_dirs[@]}"
fi
matches=()
for build_id_dir in "${build_id_dirs[@]}"; do
# The would-be entry in the .build-id directory.
dirent="${build_id_dir}/${id:0:2}/${id:2}"
# Whether the .build-id directory is in a build or prebuilt directory, the
# binaries it corresponds to should all lie within the parent.
search_dir="$(dirname "${build_id_dir}")"
if [[ -f "${dirent}" ]]; then
mapfile -t -O "${#matches[@]}" matches < <(find "${search_dir}" -samefile "${dirent}")
fi
done
# Build up a list of inaccessible buckets.
inaccessible_buckets=()
if ! $local ; then
if $verbose ; then
fx-info "Searching the following GCS buckets:"
printf "* gs://%s\n" "${GSUTIL_BUCKETS[@]}"
fi
for bucket in "${GSUTIL_BUCKETS[@]}"; do
# This gives the current GCS scheme for binaries indexed by build ID.
url="gs://$bucket/debug/${id}.debug"
# gsutil offers no convenient way to check whether the user has access to a
# given GCS bucket. Worse yet, in the case of a stat with insufficient
# privileges, the quiet, error suppression option `-q` is ignored - and the
# resulting error code cannot be differentiated from the case of a
# successful stat call reporting that the object does not exit. Putting all
# this together, the seemingly least worst way to check permissions is to
# see whether `gsutil -q stat ...` returns any output.
output=$(gsutil -q stat "${url}" 2>&1)
result=$?
if [[ -n "${output}" ]]; then
inaccessible_buckets+=("gs://${bucket}")
continue
fi
if [[ $result == 0 ]]; then
matches+=("${url}")
fi
done
fi
if [[ ${#matches[@]} -eq 0 ]] ; then
fx-warn "No binaries found with build ID \"${id}\""
# It's only worth printing this in the event of no matches.
if [[ ${#inaccessible_buckets[@]} -ne 0 ]] ; then
fx-warn "Insufficient privileges to access the following buckets:"
printf "* %s\n" "${inaccessible_buckets[@]}"
fi
else
printf "%s\n" "${matches[@]}"
fi