From edefaf3d5b7b1a31a146756eece4af2200cc4511 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 31 Oct 2024 12:10:40 +0100 Subject: [PATCH 1/3] Copy over runfiles library from Bazel This uses `root_symlinks` to preserve the fixed runfiles path at which the library must be available. Users will see an error if their runfiles trees contain both `@bazel_tools//tools/bash/runfiles` and `@rules_shell//shell/runfiles` with a Bazel version in which the former is not yet an alias to the latter. This ensures that conflicting versions can't be mixed. In fact, strict runfiles collision checking will be applied to all targets depending on `@rules_shell//shell/runfiles`, which may break some builds. If it turns out to be too breaking, we could add private API to `ctx.runfiles` that allows to disable the strict check from Starlark. --- shell/private/root_symlinks.bzl | 45 +++ shell/runfiles/BUILD | 15 + shell/runfiles/runfiles.bash | 468 ++++++++++++++++++++++++++++++ tests/BUILD.bazel | 7 - tests/bcr/BUILD | 6 +- tests/runfiles/BUILD | 7 + tests/runfiles/runfiles_test.bash | 457 +++++++++++++++++++++++++++++ tests/test.sh | 1 - 8 files changed, 995 insertions(+), 11 deletions(-) create mode 100644 shell/private/root_symlinks.bzl create mode 100644 shell/runfiles/BUILD create mode 100644 shell/runfiles/runfiles.bash delete mode 100644 tests/BUILD.bazel create mode 100644 tests/runfiles/BUILD create mode 100755 tests/runfiles/runfiles_test.bash delete mode 100755 tests/test.sh diff --git a/shell/private/root_symlinks.bzl b/shell/private/root_symlinks.bzl new file mode 100644 index 0000000..069e7a1 --- /dev/null +++ b/shell/private/root_symlinks.bzl @@ -0,0 +1,45 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper rule to preserve the legacy runfiles path for the runfiles lib.""" + +def _single_file_or_fail(target): + files = target[DefaultInfo].files.to_list() + if len(files) != 1: + fail("Expected exactly one file in {}, got {}".format(target.label, files)) + return files[0] + +def _root_symlinks_impl(ctx): + runfiles = ctx.runfiles( + root_symlinks = { + path: _single_file_or_fail(target) + for target, path in ctx.attr.root_symlinks.items() + }, + ) + return [ + DefaultInfo( + files = depset(), + runfiles = runfiles, + ), + ] + +root_symlinks = rule( + implementation = _root_symlinks_impl, + attrs = { + "root_symlinks": attr.label_keyed_string_dict( + allow_files = True, + mandatory = True, + ), + }, +) diff --git a/shell/runfiles/BUILD b/shell/runfiles/BUILD new file mode 100644 index 0000000..417af06 --- /dev/null +++ b/shell/runfiles/BUILD @@ -0,0 +1,15 @@ +load("//shell:sh_library.bzl", "sh_library") +load("//shell/private:root_symlinks.bzl", "root_symlinks") + +sh_library( + name = "runfiles", + data = [":runfiles_at_legacy_location"], + visibility = ["//visibility:public"], +) + +root_symlinks( + name = "runfiles_at_legacy_location", + root_symlinks = { + "runfiles.bash": "bazel_tools/tools/bash/runfiles/runfiles.bash", + }, +) diff --git a/shell/runfiles/runfiles.bash b/shell/runfiles/runfiles.bash new file mode 100644 index 0000000..8e1f944 --- /dev/null +++ b/shell/runfiles/runfiles.bash @@ -0,0 +1,468 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Runfiles lookup library for Bazel-built Bash binaries and tests, version 3. +# +# VERSION HISTORY: +# - version 3: Fixes a bug in the init code on macOS and makes the library aware +# of Bzlmod repository mappings. +# Features: +# - With Bzlmod enabled, rlocation now takes the repository mapping of the +# Bazel repository containing the calling script into account when +# looking up runfiles. The new, optional second argument to rlocation can +# be used to specify the canonical name of the Bazel repository to use +# instead of this default. The new runfiles_current_repository function +# can be used to obtain the canonical name of the N-th caller's Bazel +# repository. +# Fixed: +# - Sourcing a shell script that contains the init code from a shell script +# that itself contains the init code no longer fails on macOS. +# Compatibility: +# - The init script and the runfiles library are backwards and forwards +# compatible with version 2. +# - version 2: Shorter init code. +# Features: +# - "set -euo pipefail" only at end of init code. +# "set -e" breaks the source || source || ... scheme on +# macOS, because it terminates if path1 does not exist. +# - Not exporting any environment variables in init code. +# This is now done in runfiles.bash itself. +# Compatibility: +# - The v1 init code can load the v2 library, i.e. if you have older source +# code (still using v1 init) then you can build it with newer Bazel (which +# contains the v2 library). +# - The reverse is not true: the v2 init code CANNOT load the v1 library, +# i.e. if your project (or any of its external dependencies) use v2 init +# code, then you need a newer Bazel version (which contains the v2 +# library). +# - version 1: Original Bash runfiles library. +# +# ENVIRONMENT: +# - If RUNFILES_LIB_DEBUG=1 is set, the script will print diagnostic messages to +# stderr. +# +# USAGE: +# 1. Depend on this runfiles library from your build rule: +# +# sh_binary( +# name = "my_binary", +# ... +# deps = ["@bazel_tools//tools/bash/runfiles"], +# ) +# +# 2. Source the runfiles library. +# +# The runfiles library itself defines rlocation which you would need to look +# up the library's runtime location, thus we have a chicken-and-egg problem. +# Insert the following code snippet to the top of your main script: +# +# # --- begin runfiles.bash initialization v3 --- +# # Copy-pasted from the Bazel Bash runfiles library v3. +# set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +# # shellcheck disable=SC1090 +# source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ +# source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ +# source "$0.runfiles/$f" 2>/dev/null || \ +# source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ +# source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ +# { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# # --- end runfiles.bash initialization v3 --- +# +# +# 3. Use rlocation to look up runfile paths. +# +# cat "$(rlocation my_workspace/path/to/my/data.txt)" +# + +if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi + +case "$(uname -s | tr [:upper:] [:lower:])" in +msys*|mingw*|cygwin*) + # matches an absolute Windows path + export _RLOCATION_ISABS_PATTERN="^[a-zA-Z]:[/\\]" + # Windows paths are case insensitive and Bazel and MSYS2 capitalize differently, so we can't + # assume that all paths are in the same native case. + export _RLOCATION_GREP_CASE_INSENSITIVE_ARGS=-i + ;; +*) + # matches an absolute Unix path + export _RLOCATION_ISABS_PATTERN="^/[^/].*" + export _RLOCATION_GREP_CASE_INSENSITIVE_ARGS= + ;; +esac + +# Does not exit with a non-zero exit code if no match is found and performs a case-insensitive +# search on Windows. +function __runfiles_maybe_grep() { + grep $_RLOCATION_GREP_CASE_INSENSITIVE_ARGS "$@" || test $? = 1; +} +export -f __runfiles_maybe_grep + +# Prints to stdout the runtime location of a data-dependency. +# The optional second argument can be used to specify the canonical name of the +# repository whose repository mapping should be used to resolve the repository +# part of the provided path. If not specified, the repository of the caller is +# used. +function rlocation() { + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): start" + fi + if [[ "$1" =~ $_RLOCATION_ISABS_PATTERN ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): absolute path, return" + fi + # If the path is absolute, print it as-is. + echo "$1" + return 0 + elif [[ "$1" == ../* || "$1" == */.. || "$1" == ./* || "$1" == */./* || "$1" == "*/." || "$1" == *//* ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "ERROR[runfiles.bash]: rlocation($1): path is not normalized" + fi + return 1 + elif [[ "$1" == \\* ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "ERROR[runfiles.bash]: rlocation($1): absolute path without" \ + "drive name" + fi + return 1 + fi + + if [[ -f "$RUNFILES_REPO_MAPPING" ]]; then + local -r target_repo_apparent_name=$(echo "$1" | cut -d / -f 1) + # Use -s to get an empty remainder if the argument does not contain a slash. + # The repo mapping should not be applied to single segment paths, which may + # be root symlinks. + local -r remainder=$(echo "$1" | cut -s -d / -f 2-) + if [[ -n "$remainder" ]]; then + if [[ -z "${2+x}" ]]; then + local -r source_repo=$(runfiles_current_repository 2) + else + local -r source_repo=$2 + fi + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): looking up canonical name for ($target_repo_apparent_name) from ($source_repo) in ($RUNFILES_REPO_MAPPING)" + fi + local -r target_repo=$(__runfiles_maybe_grep -m1 "^$source_repo,$target_repo_apparent_name," "$RUNFILES_REPO_MAPPING" | cut -d , -f 3) + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): canonical name of target repo is ($target_repo)" + fi + if [[ -n "$target_repo" ]]; then + local -r rlocation_path="$target_repo/$remainder" + else + local -r rlocation_path="$1" + fi + else + local -r rlocation_path="$1" + fi + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): not using repository mapping ($RUNFILES_REPO_MAPPING) since it does not exist" + fi + local -r rlocation_path="$1" + fi + + runfiles_rlocation_checked "$rlocation_path" +} +export -f rlocation + +# Exports the environment variables that subprocesses need in order to use +# runfiles. +# If a subprocess is a Bazel-built binary rule that also uses the runfiles +# libraries under @bazel_tools//tools//runfiles, then that binary needs +# these envvars in order to initialize its own runfiles library. +function runfiles_export_envvars() { + if [[ ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" \ + && ! -d "${RUNFILES_DIR:-/dev/null}" ]]; then + return 1 + fi + + if [[ ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$RUNFILES_DIR/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$RUNFILES_DIR/MANIFEST" + elif [[ -f "${RUNFILES_DIR}_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="${RUNFILES_DIR}_manifest" + else + export RUNFILES_MANIFEST_FILE= + fi + elif [[ ! -d "${RUNFILES_DIR:-/dev/null}" ]]; then + if [[ "$RUNFILES_MANIFEST_FILE" == */MANIFEST \ + && -d "${RUNFILES_MANIFEST_FILE%/MANIFEST}" ]]; then + export RUNFILES_DIR="${RUNFILES_MANIFEST_FILE%/MANIFEST}" + export JAVA_RUNFILES="$RUNFILES_DIR" + elif [[ "$RUNFILES_MANIFEST_FILE" == *_manifest \ + && -d "${RUNFILES_MANIFEST_FILE%_manifest}" ]]; then + export RUNFILES_DIR="${RUNFILES_MANIFEST_FILE%_manifest}" + export JAVA_RUNFILES="$RUNFILES_DIR" + else + export RUNFILES_DIR= + fi + fi +} +export -f runfiles_export_envvars + +# Returns the canonical name of the Bazel repository containing the script that +# calls this function. +# The optional argument N, which defaults to 1, can be used to return the +# canonical name of the N-th caller instead. +# +# Note: This function only works correctly with Bzlmod enabled. Without Bzlmod, +# its return value is ignored if passed to rlocation. +function runfiles_current_repository() { + local -r idx=${1:-1} + local -r raw_caller_path="${BASH_SOURCE[$idx]}" + # Make the caller path absolute if needed to handle the case where the script is run directly + # from bazel-bin, with working directory a subdirectory of bazel-bin. + if [[ "$raw_caller_path" =~ $_RLOCATION_ISABS_PATTERN ]]; then + local -r caller_path="$raw_caller_path" + else + local -r caller_path="$(cd $(dirname "$raw_caller_path"); pwd)/$(basename "$raw_caller_path")" + fi + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): caller's path is ($caller_path)" + fi + + local rlocation_path= + + # If the runfiles manifest exists, search for an entry with target the caller's path. + if [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + # Escape $caller_path for use in the grep regex below. Also replace \ with / since the manifest + # uses / as the path separator even on Windows. + local -r normalized_caller_path="$(echo "$caller_path" | sed 's|\\\\*|/|g')" + local -r escaped_caller_path="$(echo "$normalized_caller_path" | sed 's/[.[\*^$]/\\&/g')" + rlocation_path=$(__runfiles_maybe_grep -m1 "^[^ ]* ${escaped_caller_path}$" "${RUNFILES_MANIFEST_FILE}" | cut -d ' ' -f 1) + if [[ -z "$rlocation_path" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "ERROR[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) is not the target of an entry in the runfiles manifest ($RUNFILES_MANIFEST_FILE)" + fi + # The binary may also be run directly from bazel-bin or bazel-out. + local -r repository=$(echo "$normalized_caller_path" | __runfiles_maybe_grep -E -o '(^|/)(bazel-out/[^/]+/bin|bazel-bin)/external/[^/]+/' | tail -1 | awk -F/ '{print $(NF-1)}') + if [[ -n "$repository" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) lies in repository ($repository) (parsed exec path)" + fi + echo "$repository" + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) lies in the main repository (parsed exec path)" + fi + echo "" + fi + return 1 + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) is the target of ($rlocation_path) in the runfiles manifest" + fi + fi + fi + + # If the runfiles directory exists, check if the caller's path is of the form + # $RUNFILES_DIR/rlocation_path and if so, set $rlocation_path. + if [[ -z "$rlocation_path" && -d "${RUNFILES_DIR:-/dev/null}" ]]; then + normalized_caller_path="$(echo "$caller_path" | sed 's|\\\\*|/|g')" + normalized_dir="$(echo "${RUNFILES_DIR%[\/]}" | sed 's|\\\\*|/|g')" + if [[ -n "${_RLOCATION_GREP_CASE_INSENSITIVE_ARGS}" ]]; then + # When comparing file paths insensitively, also normalize the case of the prefixes. + normalized_caller_path=$(echo "$normalized_caller_path" | tr '[:upper:]' '[:lower:]') + normalized_dir=$(echo "$normalized_dir" | tr '[:upper:]' '[:lower:]') + fi + if [[ "$normalized_caller_path" == "$normalized_dir"/* ]]; then + rlocation_path=${normalized_caller_path:${#normalized_dir}} + rlocation_path=${rlocation_path:1} + fi + if [[ -z "$rlocation_path" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) does not lie under the runfiles directory ($normalized_dir)" + fi + # The only shell script that is not executed from the runfiles directory (if it is populated) + # is the sh_binary entrypoint. Parse its path under the execroot, using the last match to + # allow for nested execroots (e.g. in Bazel integration tests). The binary may also be run + # directly from bazel-bin. + local -r repository=$(echo "$normalized_caller_path" | __runfiles_maybe_grep -E -o '(^|/)(bazel-out/[^/]+/bin|bazel-bin)/external/[^/]+/' | tail -1 | awk -F/ '{print $(NF-1)}') + if [[ -n "$repository" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) lies in repository ($repository) (parsed exec path)" + fi + echo "$repository" + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($normalized_caller_path) lies in the main repository (parsed exec path)" + fi + echo "" + fi + return 0 + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($caller_path) has path ($rlocation_path) relative to the runfiles directory ($RUNFILES_DIR)" + fi + fi + fi + + if [[ -z "$rlocation_path" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "ERROR[runfiles.bash]: runfiles_current_repository($idx): cannot determine repository for ($caller_path) since neither the runfiles directory (${RUNFILES_DIR:-}) nor the runfiles manifest (${RUNFILES_MANIFEST_FILE:-}) exist" + fi + return 1 + fi + + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($caller_path) corresponds to rlocation path ($rlocation_path)" + fi + # Normalize the rlocation path to be of the form repo/pkg/file. + rlocation_path=${rlocation_path#_main/external/} + rlocation_path=${rlocation_path#_main/../} + local -r repository=$(echo "$rlocation_path" | cut -d / -f 1) + if [[ "$repository" == _main ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($rlocation_path) lies in the main repository" + fi + echo "" + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: runfiles_current_repository($idx): ($rlocation_path) lies in repository ($repository)" + fi + echo "$repository" + fi +} +export -f runfiles_current_repository + +function runfiles_rlocation_checked() { + # FIXME: If the runfiles lookup fails, the exit code of this function is 0 if + # and only if the runfiles manifest exists. In particular, the exit code + # behavior is not consistent across platforms. + if [[ -e "${RUNFILES_DIR:-/dev/null}/$1" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): found under RUNFILES_DIR ($RUNFILES_DIR), return" + fi + echo "${RUNFILES_DIR}/$1" + elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): looking in RUNFILES_MANIFEST_FILE ($RUNFILES_MANIFEST_FILE)" + fi + # If the rlocation path contains a space or newline, it needs to be prefixed + # with a space and spaces, newlines, and backslashes have to be escaped as + # \s, \n, and \b. + if [[ "$1" == *" "* || "$1" == *$'\n'* ]]; then + local search_prefix=" $(echo -n "$1" | sed 's/\\/\\b/g; s/ /\\s/g')" + search_prefix="${search_prefix//$'\n'/\\n}" + local escaped=true + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): using escaped search prefix ($search_prefix)" + fi + else + local search_prefix="$1" + local escaped=false + fi + # The extra space below is added because cut counts from 1. + local trim_length=$(echo -n "$search_prefix " | wc -c | tr -d ' ') + # Escape the search prefix for use in the grep regex below *after* + # determining the trim length. + local result=$(__runfiles_maybe_grep -m1 "^$(echo -n "$search_prefix" | sed 's/[.[\*^$]/\\&/g') " "${RUNFILES_MANIFEST_FILE}" | cut -b "${trim_length}-") + if [[ -z "$result" ]]; then + # If path references a runfile that lies under a directory that itself + # is a runfile, then only the directory is listed in the manifest. Look + # up all prefixes of path in the manifest and append the relative path + # from the prefix if there is a match. + local prefix="$1" + local prefix_result= + local new_prefix= + while true; do + new_prefix="${prefix%/*}" + [[ "$new_prefix" == "$prefix" ]] && break + prefix="$new_prefix" + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): looking for prefix ($prefix)" + fi + if [[ "$prefix" == *" "* || "$prefix" == *$'\n'* ]]; then + search_prefix=" $(echo -n "$prefix" | sed 's/\\/\\b/g; s/ /\\s/g')" + search_prefix="${search_prefix//$'\n'/\\n}" + escaped=true + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): using escaped search prefix ($search_prefix)" + fi + else + search_prefix="$prefix" + escaped=false + fi + # The extra space below is added because cut counts from 1. + trim_length=$(echo -n "$search_prefix " | wc -c) + prefix_result=$(__runfiles_maybe_grep -m1 "$(echo -n "$search_prefix" | sed 's/[.[\*^$]/\\&/g') " "${RUNFILES_MANIFEST_FILE}" | cut -b ${trim_length}-) + if [[ "$escaped" = true ]]; then + prefix_result="${prefix_result//\\n/$'\n'}" + prefix_result="${prefix_result//\\b/\\}" + fi + [[ -z "$prefix_result" ]] && continue + local -r candidate="${prefix_result}${1#"${prefix}"}" + if [[ -e "$candidate" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): found in manifest as ($candidate) via prefix ($prefix)" + fi + echo "$candidate" + return 0 + fi + # At this point, the manifest lookup of prefix has been successful, + # but the file at the relative path given by the suffix does not + # exist. We do not continue the lookup with a shorter prefix for two + # reasons: + # 1. Manifests generated by Bazel never contain a path that is a + # prefix of another path. + # 2. Runfiles libraries for other languages do not check for file + # existence and would have returned the non-existent path. It seems + # better to return no path rather than a potentially different, + # non-empty path. + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): found in manifest as ($candidate) via prefix ($prefix), but file does not exist" + fi + break + done + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): not found in manifest" + fi + echo "" + else + if [[ "$escaped" = true ]]; then + result="${result//\\n/$'\n'}" + result="${result//\\b/\\}" + fi + if [[ -e "$result" ]]; then + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): found in manifest as ($result)" + fi + echo "$result" + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "INFO[runfiles.bash]: rlocation($1): found in manifest as ($result), but file does not exist" + fi + echo "" + fi + fi + else + if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then + echo >&2 "ERROR[runfiles.bash]: cannot look up runfile \"$1\" " \ + "(RUNFILES_DIR=\"${RUNFILES_DIR:-}\"," \ + "RUNFILES_MANIFEST_FILE=\"${RUNFILES_MANIFEST_FILE:-}\")" + fi + return 1 + fi +} +export -f runfiles_rlocation_checked + +export RUNFILES_REPO_MAPPING=$(runfiles_rlocation_checked _repo_mapping 2> /dev/null) diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel deleted file mode 100644 index faa7068..0000000 --- a/tests/BUILD.bazel +++ /dev/null @@ -1,7 +0,0 @@ -load("//shell:sh_test.bzl", "sh_test") - -# Placeholder test to get the release action's `bazel test` to pass. -sh_test( - name = "test", - srcs = ["test.sh"], -) diff --git a/tests/bcr/BUILD b/tests/bcr/BUILD index f5f3edd..fb884dd 100644 --- a/tests/bcr/BUILD +++ b/tests/bcr/BUILD @@ -6,7 +6,7 @@ sh_library( name = "lib", srcs = ["lib.sh"], data = ["greeting.txt"], - deps = ["@bazel_tools//tools/bash/runfiles"], + deps = ["@rules_shell//shell/runfiles"], ) sh_binary( @@ -14,7 +14,7 @@ sh_binary( srcs = ["bin.sh"], deps = [ ":lib", - "@bazel_tools//tools/bash/runfiles", + "@rules_shell//shell/runfiles", ], ) @@ -22,5 +22,5 @@ sh_test( name = "test", srcs = ["test.sh"], data = [":bin"], - deps = ["@bazel_tools//tools/bash/runfiles"], + deps = ["@rules_shell//shell/runfiles"], ) diff --git a/tests/runfiles/BUILD b/tests/runfiles/BUILD new file mode 100644 index 0000000..dce2922 --- /dev/null +++ b/tests/runfiles/BUILD @@ -0,0 +1,7 @@ +load("//shell:sh_test.bzl", "sh_test") + +sh_test( + name = "runfiles_test", + srcs = ["runfiles_test.bash"], + deps = ["//shell/runfiles"], +) diff --git a/tests/runfiles/runfiles_test.bash b/tests/runfiles/runfiles_test.bash new file mode 100755 index 0000000..e50925b --- /dev/null +++ b/tests/runfiles/runfiles_test.bash @@ -0,0 +1,457 @@ +#!/bin/bash +# +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -euo pipefail + +function _log_base() { + prefix=$1 + shift + echo >&2 "${prefix}[$(basename "${BASH_SOURCE[0]}"):${BASH_LINENO[1]} ($(date "+%H:%M:%S %z"))] $*" +} + +function fail() { + _log_base "FAILED" "$@" + exit 1 +} + +function log_fail() { + # non-fatal version of fail() + _log_base "FAILED" $* +} + +function log_info() { + _log_base "INFO" $* +} + +which uname >&/dev/null || fail "cannot locate GNU coreutils" + +case "$(uname -s | tr [:upper:] [:lower:])" in +msys*|mingw*|cygwin*) + function is_windows() { true; } + ;; +*) + function is_windows() { false; } + ;; +esac + +function find_runfiles_lib() { + # Unset existing definitions of the functions we want to test. + if type rlocation >&/dev/null; then + unset rlocation + unset runfiles_export_envvars + fi + + if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi + fi + if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + echo "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" + elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ + "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2- + else + echo >&2 "ERROR: cannot find //shell/runfiles:runfiles.bash" + exit 1 + fi +} + +function test_rlocation_call_requires_no_envvars() { + export RUNFILES_DIR=mock/runfiles + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + source "$runfiles_lib_path" || fail +} + +function test_rlocation_argument_validation() { + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + source "$runfiles_lib_path" + + # Test invalid inputs to make sure rlocation catches these. + if rlocation "../foo" >&/dev/null; then + fail + fi + if rlocation "foo/.." >&/dev/null; then + fail + fi + if rlocation "foo/../bar" >&/dev/null; then + fail + fi + if rlocation "./foo" >&/dev/null; then + fail + fi + if rlocation "foo/." >&/dev/null; then + fail + fi + if rlocation "foo/./bar" >&/dev/null; then + fail + fi + if rlocation "//foo" >&/dev/null; then + fail + fi + if rlocation "foo//" >&/dev/null; then + fail + fi + if rlocation "foo//bar" >&/dev/null; then + fail + fi + if rlocation "\\foo" >&/dev/null; then + fail + fi +} + +function test_rlocation_abs_path() { + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + source "$runfiles_lib_path" + + if is_windows; then + [[ "$(rlocation "c:/Foo" || echo failed)" == "c:/Foo" ]] || fail + [[ "$(rlocation "c:\\Foo" || echo failed)" == "c:\\Foo" ]] || fail + else + [[ "$(rlocation "/Foo" || echo failed)" == "/Foo" ]] || fail + fi +} + +function test_init_manifest_based_runfiles() { + local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)" + cat > $tmpdir/foo.runfiles_manifest << EOF +a/b $tmpdir/c/d +e/f $tmpdir/g h +y $tmpdir/y +c/dir $tmpdir/dir +unresolved $tmpdir/unresolved + h/\si $tmpdir/ j k + h/\s\bi $tmpdir/ j k b + h/\n\bi $tmpdir/ \bnj k \na + dir\swith\sspaces $tmpdir/dir with spaces + space\snewline\nbackslash\b_dir $tmpdir/space newline\nbackslash\ba +EOF + mkdir "${tmpdir}/c" + mkdir "${tmpdir}/y" + mkdir -p "${tmpdir}/dir/deeply/nested" + touch "${tmpdir}/c/d" "${tmpdir}/g h" + touch "${tmpdir}/dir/file" + ln -s /does/not/exist "${tmpdir}/dir/unresolved" + touch "${tmpdir}/dir/deeply/nested/file" + touch "${tmpdir}/dir/deeply/nested/file with spaces" + ln -s /does/not/exist "${tmpdir}/unresolved" + touch "${tmpdir}/ j k" + touch "${tmpdir}/ j k b" + mkdir -p "${tmpdir}/dir with spaces/nested" + touch "${tmpdir}/dir with spaces/nested/file" + if ! is_windows; then + touch "${tmpdir}/ \nj k "$'\n'a + mkdir -p "${tmpdir}/space newline"$'\n'"backslash\a" + touch "${tmpdir}/space newline"$'\n'"backslash\a/f i\le" + fi + + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE=$tmpdir/foo.runfiles_manifest + source "$runfiles_lib_path" + + [[ -z "$(rlocation a || echo failed)" ]] || fail + [[ -z "$(rlocation c/d || echo failed)" ]] || fail + [[ "$(rlocation a/b || echo failed)" == "$tmpdir/c/d" ]] || fail + [[ "$(rlocation e/f || echo failed)" == "$tmpdir/g h" ]] || fail + [[ "$(rlocation y || echo failed)" == "$tmpdir/y" ]] || fail + [[ -z "$(rlocation c || echo failed)" ]] || fail + [[ -z "$(rlocation c/di || echo failed)" ]] || fail + [[ "$(rlocation c/dir || echo failed)" == "$tmpdir/dir" ]] || fail + [[ "$(rlocation c/dir/file || echo failed)" == "$tmpdir/dir/file" ]] || fail + [[ -z "$(rlocation c/dir/unresolved || echo failed)" ]] || fail + [[ "$(rlocation c/dir/deeply/nested/file || echo failed)" == "$tmpdir/dir/deeply/nested/file" ]] || fail + [[ "$(rlocation "c/dir/deeply/nested/file with spaces" || echo failed)" == "$tmpdir/dir/deeply/nested/file with spaces" ]] || fail + [[ -z "$(rlocation unresolved || echo failed)" ]] || fail + [[ "$(rlocation "h/ i" || echo failed)" == "$tmpdir/ j k" ]] || fail + [[ "$(rlocation "h/ \i" || echo failed)" == "$tmpdir/ j k b" ]] || fail + [[ "$(rlocation "dir with spaces" || echo failed)" == "$tmpdir/dir with spaces" ]] || fail + [[ "$(rlocation "dir with spaces/nested/file" || echo failed)" == "$tmpdir/dir with spaces/nested/file" ]] || fail + if ! is_windows; then + [[ "$(rlocation $'h/\n\\i' || echo failed)" == "$tmpdir/ \nj k "$'\n'a ]] || fail + [[ "$(rlocation "space newline"$'\n'"backslash\_dir/f i\le" || echo failed)" == "${tmpdir}/space newline"$'\n'"backslash\a/f i\le" ]] || fail + fi + + rm -r "$tmpdir/c/d" "$tmpdir/g h" "$tmpdir/y" "$tmpdir/dir" "$tmpdir/unresolved" "$tmpdir/ j k" "$tmpdir/dir with spaces" + if ! is_windows; then + rm -r "$tmpdir/ \nj k "$'\n'a "${tmpdir}/space newline"$'\n'"backslash\a" + [[ -z "$(rlocation $'h/\n\\i' || echo failed)" ]] || fail + [[ -z "$(rlocation "space newline"$'\n'"backslash\_dir/f i\le" || echo failed)" ]] || fail + fi + [[ -z "$(rlocation a/b || echo failed)" ]] || fail + [[ -z "$(rlocation e/f || echo failed)" ]] || fail + [[ -z "$(rlocation y || echo failed)" ]] || fail + [[ -z "$(rlocation c/dir || echo failed)" ]] || fail + [[ -z "$(rlocation c/dir/file || echo failed)" ]] || fail + [[ -z "$(rlocation c/dir/deeply/nested/file || echo failed)" ]] || fail + [[ -z "$(rlocation "h/ i" || echo failed)" ]] || fail + [[ -z "$(rlocation "dir with spaces" || echo failed)" ]] || fail + [[ -z "$(rlocation "dir with spaces/nested/file" || echo failed)" ]] || fail +} + +function test_manifest_based_envvars() { + local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)" + echo "a b" > $tmpdir/foo.runfiles_manifest + + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE=$tmpdir/foo.runfiles_manifest + mkdir -p $tmpdir/foo.runfiles + source "$runfiles_lib_path" + + runfiles_export_envvars + [[ "${RUNFILES_DIR:-}" == "$tmpdir/foo.runfiles" ]] || fail + [[ "${RUNFILES_MANIFEST_FILE:-}" == "$tmpdir/foo.runfiles_manifest" ]] || fail +} + +function test_init_directory_based_runfiles() { + local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)" + + export RUNFILES_DIR=${tmpdir}/mock/runfiles + export RUNFILES_MANIFEST_FILE= + source "$runfiles_lib_path" + + mkdir -p "$RUNFILES_DIR/a" + touch "$RUNFILES_DIR/a/b" "$RUNFILES_DIR/c d" + [[ "$(rlocation a || echo failed)" == "$RUNFILES_DIR/a" ]] || fail + [[ "$(rlocation c/d || echo failed)" == failed ]] || fail + [[ "$(rlocation a/b || echo failed)" == "$RUNFILES_DIR/a/b" ]] || fail + [[ "$(rlocation "c d" || echo failed)" == "$RUNFILES_DIR/c d" ]] || fail + [[ "$(rlocation "c" || echo failed)" == failed ]] || fail + rm -r "$RUNFILES_DIR/a" "$RUNFILES_DIR/c d" + [[ "$(rlocation a || echo failed)" == failed ]] || fail + [[ "$(rlocation a/b || echo failed)" == failed ]] || fail + [[ "$(rlocation "c d" || echo failed)" == failed ]] || fail +} + +function test_directory_based_runfiles_with_repo_mapping_from_main() { + local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)" + + export RUNFILES_DIR=${tmpdir}/mock/runfiles + mkdir -p "$RUNFILES_DIR" + cat > "$RUNFILES_DIR/_repo_mapping" < "$RUNFILES_DIR/_repo_mapping" < "$tmpdir/foo.repo_mapping" < "$RUNFILES_MANIFEST_FILE" << EOF +_repo_mapping $tmpdir/foo.repo_mapping +config.json $tmpdir/config.json +protobuf+3.19.2/foo/runfile $tmpdir/protobuf+3.19.2/foo/runfile +_main/bar/runfile $tmpdir/_main/bar/runfile +protobuf+3.19.2/bar/dir $tmpdir/protobuf+3.19.2/bar/dir +EOF + source "$runfiles_lib_path" + + mkdir -p "$tmpdir/_main/bar" + touch "$tmpdir/_main/bar/runfile" + mkdir -p "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted" + touch "$tmpdir/protobuf+3.19.2/bar/dir/file" + touch "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" + mkdir -p "$tmpdir/protobuf+3.19.2/foo" + touch "$tmpdir/protobuf+3.19.2/foo/runfile" + touch "$tmpdir/config.json" + + [[ "$(rlocation "my_module/bar/runfile" "" || echo failed)" == "$tmpdir/_main/bar/runfile" ]] || fail + [[ "$(rlocation "my_workspace/bar/runfile" "" || echo failed)" == "$tmpdir/_main/bar/runfile" ]] || fail + [[ "$(rlocation "my_protobuf/foo/runfile" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/foo/runfile" ]] || fail + [[ "$(rlocation "my_protobuf/bar/dir" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir" ]] || fail + [[ "$(rlocation "my_protobuf/bar/dir/file" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/file" ]] || fail + [[ "$(rlocation "my_protobuf/bar/dir/de eply/nes ted/fi+le" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" ]] || fail + + [[ -z "$(rlocation "protobuf/foo/runfile" "" || echo failed)" ]] || fail + [[ -z "$(rlocation "protobuf/bar/dir/dir/de eply/nes ted/fi+le" "" || echo failed)" ]] || fail + + [[ "$(rlocation "_main/bar/runfile" "" || echo failed)" == "$tmpdir/_main/bar/runfile" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/foo/runfile" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/foo/runfile" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir/file" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/file" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" ]] || fail + + [[ "$(rlocation "config.json" "" || echo failed)" == "$tmpdir/config.json" ]] || fail +} + +function test_manifest_based_runfiles_with_repo_mapping_from_other_repo() { + local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)" + + cat > "$tmpdir/foo.repo_mapping" < "$RUNFILES_MANIFEST_FILE" << EOF +_repo_mapping $tmpdir/foo.repo_mapping +config.json $tmpdir/config.json +protobuf+3.19.2/foo/runfile $tmpdir/protobuf+3.19.2/foo/runfile +_main/bar/runfile $tmpdir/_main/bar/runfile +protobuf+3.19.2/bar/dir $tmpdir/protobuf+3.19.2/bar/dir +EOF + source "$runfiles_lib_path" + + mkdir -p "$tmpdir/_main/bar" + touch "$tmpdir/_main/bar/runfile" + mkdir -p "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted" + touch "$tmpdir/protobuf+3.19.2/bar/dir/file" + touch "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" + mkdir -p "$tmpdir/protobuf+3.19.2/foo" + touch "$tmpdir/protobuf+3.19.2/foo/runfile" + touch "$tmpdir/config.json" + + [[ "$(rlocation "protobuf/foo/runfile" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/foo/runfile" ]] || fail + [[ "$(rlocation "protobuf/bar/dir" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir" ]] || fail + [[ "$(rlocation "protobuf/bar/dir/file" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/file" ]] || fail + [[ "$(rlocation "protobuf/bar/dir/de eply/nes ted/fi+le" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" ]] || fail + + [[ -z "$(rlocation "my_module/bar/runfile" "protobuf+3.19.2" || echo failed)" ]] || fail + [[ -z "$(rlocation "my_protobuf/bar/dir/de eply/nes ted/fi+le" "protobuf+3.19.2" || echo failed)" ]] || fail + + [[ "$(rlocation "_main/bar/runfile" "protobuf+3.19.2" || echo failed)" == "$tmpdir/_main/bar/runfile" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/foo/runfile" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/foo/runfile" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir/file" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/file" ]] || fail + [[ "$(rlocation "protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" "protobuf+3.19.2" || echo failed)" == "$tmpdir/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le" ]] || fail + + [[ "$(rlocation "config.json" "protobuf+3.19.2" || echo failed)" == "$tmpdir/config.json" ]] || fail +} + +function test_directory_based_envvars() { + export RUNFILES_DIR=mock/runfiles + export RUNFILES_MANIFEST_FILE= + source "$runfiles_lib_path" + + runfiles_export_envvars + [[ "${RUNFILES_DIR:-}" == "mock/runfiles" ]] || fail + [[ -z "${RUNFILES_MANIFEST_FILE:-}" ]] || fail +} + +function main() { + local -r manifest_file="${RUNFILES_MANIFEST_FILE:-}" + local -r dir="${RUNFILES_DIR:-}" + local -r runfiles_lib_path=$(find_runfiles_lib) + + local -r tests=$(declare -F | grep " -f test" | awk '{print $3}') + local failure=0 + for t in $tests; do + export RUNFILES_MANIFEST_FILE="$manifest_file" + export RUNFILES_DIR="$dir" + if ! ($t); then + failure=1 + fi + done + return $failure +} + +main diff --git a/tests/test.sh b/tests/test.sh deleted file mode 100755 index f1f641a..0000000 --- a/tests/test.sh +++ /dev/null @@ -1 +0,0 @@ -#!/usr/bin/env bash From 27c55d880a5aacc2a78894bbf932f97612693f8d Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Nov 2024 08:48:22 +0100 Subject: [PATCH 2/3] Skip conflict checking for backwards compatibility --- shell/private/root_symlinks.bzl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shell/private/root_symlinks.bzl b/shell/private/root_symlinks.bzl index 069e7a1..44d1233 100644 --- a/shell/private/root_symlinks.bzl +++ b/shell/private/root_symlinks.bzl @@ -26,6 +26,10 @@ def _root_symlinks_impl(ctx): path: _single_file_or_fail(target) for target, path in ctx.attr.root_symlinks.items() }, + # Adding root symlinks from Starlark usually enables conflict checking, + # but that would break backwards compatibility as it affects all + # runfiles, not just the symlinks. + skip_conflict_checking = True, ) return [ DefaultInfo( From f37a26d881fa774126837fdf93ea24b7d20a4b94 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Tue, 5 Nov 2024 08:57:44 +0100 Subject: [PATCH 3/3] Fix Bazel 6 --- .bazelci/presubmit.yml | 2 +- WORKSPACE | 7 +++++++ shell/private/root_symlinks.bzl | 5 +++++ shell/runfiles/BUILD | 13 ++++++++++--- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 19cc938..ab97269 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -8,7 +8,7 @@ matrix: - windows bazel: - 6.5.0 - - 7.3.2 + - 7.4.0 tasks: test_module_bzlmod: diff --git a/WORKSPACE b/WORKSPACE index e69de29..b8d89d9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -0,0 +1,7 @@ +workspace(name = "rules_shell") + +load("//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") + +rules_shell_dependencies() + +rules_shell_toolchains() diff --git a/shell/private/root_symlinks.bzl b/shell/private/root_symlinks.bzl index 44d1233..76e0328 100644 --- a/shell/private/root_symlinks.bzl +++ b/shell/private/root_symlinks.bzl @@ -14,6 +14,11 @@ """Helper rule to preserve the legacy runfiles path for the runfiles lib.""" +# Requires the private skip_conflict_checking parameter on ctx.runfiles, which +# is only available as of Bazel 7.4.0. We only use it when the native shell +# rules are not available. +ROOT_SYMLINKS_SUPPORTED = not hasattr(native, "sh_binary") + def _single_file_or_fail(target): files = target[DefaultInfo].files.to_list() if len(files) != 1: diff --git a/shell/runfiles/BUILD b/shell/runfiles/BUILD index 417af06..fb86e50 100644 --- a/shell/runfiles/BUILD +++ b/shell/runfiles/BUILD @@ -1,15 +1,22 @@ load("//shell:sh_library.bzl", "sh_library") -load("//shell/private:root_symlinks.bzl", "root_symlinks") +load("//shell/private:root_symlinks.bzl", "ROOT_SYMLINKS_SUPPORTED", "root_symlinks") -sh_library( +alias( name = "runfiles", - data = [":runfiles_at_legacy_location"], + actual = ":runfiles_impl" if ROOT_SYMLINKS_SUPPORTED else "@bazel_tools//tools/bash/runfiles", visibility = ["//visibility:public"], ) +sh_library( + name = "runfiles_impl", + data = [":runfiles_at_legacy_location"], + tags = ["manual"], +) + root_symlinks( name = "runfiles_at_legacy_location", root_symlinks = { "runfiles.bash": "bazel_tools/tools/bash/runfiles/runfiles.bash", }, + tags = ["manual"], )