cpus/ibex/opentitan/bazelisk.sh
#!/bin/bash
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

# This is a wrapper script for `bazelisk` that downloads and executes bazelisk.
# Bazelisk is a wrapper for `bazel` that can download and execute the project's
# required bazel version.
#
# CI jobs should use ci/bazelisk.sh instead, which performs CI-friendly additional
# setup.

set -euo pipefail

# Change to this script's directory, as it is the location of the bazel workspace.
cd "$(dirname "$0")"

: "${CURL_FLAGS:=--silent}"
: "${REPO_TOP:=$(git rev-parse --show-toplevel)}"
: "${BINDIR:=.bin}"
: "${BAZEL_BIN:=$(which bazel)}"

readonly release="v1.11.0"
declare -A hashes=(
    # sha256sums for v1.11.0.  Update this if you update the release.
    [linux-amd64]="231ec5ca8115e94c75a1f4fbada1a062b48822ca04f21f26e4cb1cd8973cd458"
)

declare -A architectures=(
    # Map `uname -m -o` to bazelisk's precompiled binary target names.
    [x86_64 GNU/Linux]="linux-amd64"
)

function os_arch() {
    local arch
    arch="$(uname -m -o)"
    echo "${architectures[$arch]:-${arch}}"
}

function check_hash() {
    local file target
    file="$1"
    target="$(os_arch)"
    echo "${hashes[$target]}  $file" | sha256sum --check --quiet
}

function prepare() {
    local target
    target="$(os_arch)"
    local bindir="${REPO_TOP}/${BINDIR}"
    local file="${bindir}/bazelisk"
    local url="https://github.com/bazelbuild/bazelisk/releases/download/${release}/bazelisk-${target}"

    mkdir -p "$bindir"
    echo "Downloading bazelisk ${release} (${url})." >> $bindir/bazelisk.log
    curl ${CURL_FLAGS} --location "$url" --output "$file"
    chmod +x "$file"
}

function up_to_date() {
    local file="$1"
    # We need an update if the file doesn't exist or it has the wrong hash
    test -f "$file" || return 1
    check_hash "$file" || return 1
    return 0
}

function outquery_starlark_expr() {
    local query="$1"
    shift
    if [[ ${query} == "outquery" ]]; then
        q="-one"
    else
        q=${query#outquery}
    fi

    case "$q" in
        -one)
            echo "target.files.to_list()[0].path"
            ;;
        -all)
            echo "\"\\n\".join([f.path for f in target.files.to_list()])"
            ;;
        -*)
            echo "\"\\n\".join([f.path for f in target.files.to_list() if \"$q\"[1:] in f.path])"
            ;;
        .*)
            echo "\"\\n\".join([f.path for f in target.files.to_list() if f.path.endswith(\"$q\")])"
            ;;
    esac
}

function do_outquery() {
    local qexpr="$1"
    shift
    "$file" cquery "$@" \
        --output=starlark --starlark:expr="$qexpr" \
        --ui_event_filters=-info --noshow_progress
}

function main() {
    local bindir="${REPO_TOP}/${BINDIR}"
    local file="${BAZEL_BIN:-${bindir}/bazelisk}"
    local lockfile="${bindir}/bazelisk.lock"

    # Are we using bazel from the user's PATH or using bazelisk?
    if expr match "${file}" ".*bazelisk$" >/dev/null; then
        if ! up_to_date "$file"; then
            # Grab the lock, blocking until success. Upon success, check again
            # whether we're up to date (because some other process might have
            # downloaded bazelisk in the meantime). If not, download it ourselves.
            mkdir -p "$bindir"
            (flock -x 9; up_to_date "$file" || prepare) 9>>"$lockfile"
        fi
        if ! check_hash "$file"; then
            echo "sha256sum doesn't match expected value"
            exit 1
        fi
    fi

    case "$1" in
        outquery*)
            # The custom 'outquery' command can be used to query bazel for the
            # outputs associated with labels.
            # The outquery command can take several forms:
            #   outquery: return one output file associated with the label.
            #   outquery-all: return all output files associated with the label.
            #   outquery-x: return output files containing the substring "x".
            #   outquery.x: return output files ending with the substring ".x".
            local qexpr
            qexpr="$(outquery_starlark_expr "$1")"
            shift
            do_outquery "$qexpr" "$@"
            ;;
        build-then)
            # The 'build-then' command builds the requested targets and then
            # evaluates the given command template, replacing "%s" with the path
            # to an output file.
            #
            # For example, the command below would build "//:foo" and run "less"
            # on one of the output files.
            #
            #     ./bazelisk.sh build-then "less %s" //:foo
            shift
            local command_template="$1"
            shift
            local qexpr outfile
            qexpr="$(outquery_starlark_expr outquery)"
            outfile=$(do_outquery "$qexpr" "$@")
            "$file" build "$@"
            # shellcheck disable=SC2059
            # We are intentionally using $command_template as a format string.
            eval "$(printf "$command_template" "$outfile")"
            ;;
        *)
            exec "$file" "$@"
            ;;
    esac
}

main "$@"
