From 1bea21950afd34c2d29d79137ef1f082f11f7d65 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Sun, 8 Mar 2026 14:23:57 -0700 Subject: [PATCH 1/2] Added `@rules_shell//shell:current_sh_toolchain` --- shell/BUILD | 6 ++++++ shell/private/sh_toolchain_alias.bzl | 32 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 shell/private/sh_toolchain_alias.bzl diff --git a/shell/BUILD b/shell/BUILD index 4148901..953140e 100644 --- a/shell/BUILD +++ b/shell/BUILD @@ -11,12 +11,18 @@ # Toolchains registered for this type should have target constraints. load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//shell/private:sh_toolchain_alias.bzl", "sh_toolchain_alias") toolchain_type( name = "toolchain_type", visibility = ["//visibility:public"], ) +sh_toolchain_alias( + name = "current_sh_toolchain", + visibility = ["//visibility:public"], +) + bzl_library( name = "rules_bzl", srcs = [ diff --git a/shell/private/sh_toolchain_alias.bzl b/shell/private/sh_toolchain_alias.bzl new file mode 100644 index 0000000..3a029c8 --- /dev/null +++ b/shell/private/sh_toolchain_alias.bzl @@ -0,0 +1,32 @@ +# Copyright 2026 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. + +"""sh_toolchain_alias""" + +visibility("public") + +_SH_TOOLCHAIN_TYPE = Label("//shell:toolchain_type") + +def _sh_toolchain_alias_impl(ctx): + toolchain = ctx.toolchains[_SH_TOOLCHAIN_TYPE] + + return [ + toolchain, + ] + +sh_toolchain_alias = rule( + doc = "An accessor for the shell toolchain in the current configuration.", + implementation = _sh_toolchain_alias_impl, + toolchains = [_SH_TOOLCHAIN_TYPE], +) From e22d42fe1ab5226288cdae4a5f0db0b58558c6fa Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Tue, 10 Mar 2026 08:38:02 -0700 Subject: [PATCH 2/2] Added Posix Shell implementation of Runfiles. --- shell/runfiles/BUILD | 26 +- shell/runfiles/runfiles.sh | 764 ++++++++++++++++++++++++++++++ shell/settings/BUILD.bazel | 7 + tests/runfiles/BUILD | 26 +- tests/runfiles/runfiles_test.bash | 10 +- tests/runfiles/runfiles_test.sh | 570 ++++++++++++++++++++++ 6 files changed, 1393 insertions(+), 10 deletions(-) create mode 100644 shell/runfiles/runfiles.sh create mode 100644 shell/settings/BUILD.bazel create mode 100755 tests/runfiles/runfiles_test.sh diff --git a/shell/runfiles/BUILD b/shell/runfiles/BUILD index 63e6fee..494d57a 100644 --- a/shell/runfiles/BUILD +++ b/shell/runfiles/BUILD @@ -1,16 +1,25 @@ load("//shell:sh_library.bzl", "sh_library") load("//shell/private:root_symlinks.bzl", "ROOT_SYMLINKS_SUPPORTED", "root_symlinks") +config_setting( + name = "use_shell_runfiles", + flag_values = {"//shell/settings:experimental_use_shell_runfiles": "true"}, +) + alias( name = "runfiles", - actual = ":runfiles_impl" if ROOT_SYMLINKS_SUPPORTED else "@bazel_tools//tools/bash/runfiles", + actual = select({ + ":use_shell_runfiles": ":runfiles_sh", + "//conditions:default": ":runfiles_bash" if ROOT_SYMLINKS_SUPPORTED else "@bazel_tools//tools/bash/runfiles", + }), visibility = ["//visibility:public"], ) sh_library( - name = "runfiles_impl", + name = "runfiles_bash", + srcs = ["runfiles.bash"], data = [":runfiles_at_legacy_location"], - tags = ["manual"], + visibility = ["//visibility:public"], ) root_symlinks( @@ -21,7 +30,16 @@ root_symlinks( tags = ["manual"], ) +sh_library( + name = "runfiles_sh", + srcs = ["runfiles.sh"], + visibility = ["//visibility:public"], +) + exports_files( - ["runfiles.bash"], + [ + "runfiles.bash", + "runfiles.sh", + ], visibility = ["//tests/runfiles:__pkg__"], ) diff --git a/shell/runfiles/runfiles.sh b/shell/runfiles/runfiles.sh new file mode 100644 index 0000000..a11148c --- /dev/null +++ b/shell/runfiles/runfiles.sh @@ -0,0 +1,764 @@ +# shellcheck disable=SC2148,SC3043 +# Copyright 2026 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 shell binaries and tests. +# Pure POSIX shell implementation — no external binary dependencies. +# +# This is a POSIX shell port of runfiles.bash. All string processing is done +# with shell builtins and parameter expansion; no grep, sed, awk, cut, tr, +# dirname, basename, wc, tail, or uname calls. +# +# DIFFERENCES FROM runfiles.bash: +# - runfiles_current_repository() requires the caller's script path as its +# first argument instead of using BASH_SOURCE (no POSIX equivalent). +# - rlocation() without a second argument defaults to the main repository +# instead of auto-detecting via BASH_SOURCE. Pass the source repo name +# explicitly when calling from a non-main repository. +# - Functions are not exported (no POSIX equivalent of export -f). Each +# script that needs runfiles must source this library directly. +# +# 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 = ["@rules_shell//shell/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.sh initialization v1 --- +# # Copy-pasted from the Bazel POSIX shell runfiles library v1. +# set +e; f=shell/runfiles/runfiles.sh +# _rf_s() { [ -f "$1" ] || return 1; while IFS= read -r _rf_l; do \ +# case "$_rf_l" in "$f "*) . "${_rf_l#"$f "}"; return $?;; esac; \ +# done < "$1"; return 1; } +# # shellcheck disable=SC1090 +# . "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ +# _rf_s "${RUNFILES_MANIFEST_FILE:-/dev/null}" 2>/dev/null || \ +# . "$0.runfiles/$f" 2>/dev/null || \ +# _rf_s "$0.runfiles_manifest" 2>/dev/null || \ +# _rf_s "$0.exe.runfiles_manifest" 2>/dev/null || \ +# { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# unset -f _rf_s 2>/dev/null; unset _rf_l 2>/dev/null +# # --- end runfiles.sh initialization v1 --- +# +# 3. Use rlocation to look up runfile paths. +# +# cat "$(rlocation my_workspace/path/to/my/data.txt)" +# + +# --- Initialization --- + +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/sh/runfiles/runfiles.sh" ]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi + +# Platform detection: try uname, default to unix if unavailable. +_rf_os="$(uname -s 2>/dev/null)" || _rf_os="unknown" +case "$_rf_os" in + MSYS*|MINGW*|CYGWIN*|msys*|mingw*|cygwin*) + export _RLOCATION_ISABS_WINDOWS=1 + export _RLOCATION_CASE_INSENSITIVE=1 + ;; + *) + export _RLOCATION_ISABS_WINDOWS= + export _RLOCATION_CASE_INSENSITIVE= + ;; +esac + +# Literal newline for use in case patterns and string comparisons. +_RUNFILES_NL=' +' +export _RUNFILES_NL + +# --- Internal helper functions --- + +# Returns 0 if $1 is an absolute path, 1 otherwise. +__runfiles_is_abs() { + case "$1" in + /[!/]*) return 0 ;; + esac + if [ -n "$_RLOCATION_ISABS_WINDOWS" ]; then + case "$1" in + [a-zA-Z]:[/\\]*) return 0 ;; + esac + fi + return 1 +} + +# Convert ASCII uppercase to lowercase (pure shell, no tr). +# Only called on Windows for case-insensitive path comparison. +__runfiles_tolower() { + _rf_tl_in="$1" + _rf_tl_out="" + while [ -n "$_rf_tl_in" ]; do + _rf_tl_c="${_rf_tl_in%"${_rf_tl_in#?}"}" + _rf_tl_in="${_rf_tl_in#?}" + case "$_rf_tl_c" in + A) _rf_tl_c=a;; B) _rf_tl_c=b;; C) _rf_tl_c=c;; D) _rf_tl_c=d;; + E) _rf_tl_c=e;; F) _rf_tl_c=f;; G) _rf_tl_c=g;; H) _rf_tl_c=h;; + I) _rf_tl_c=i;; J) _rf_tl_c=j;; K) _rf_tl_c=k;; L) _rf_tl_c=l;; + M) _rf_tl_c=m;; N) _rf_tl_c=n;; O) _rf_tl_c=o;; P) _rf_tl_c=p;; + Q) _rf_tl_c=q;; R) _rf_tl_c=r;; S) _rf_tl_c=s;; T) _rf_tl_c=t;; + U) _rf_tl_c=u;; V) _rf_tl_c=v;; W) _rf_tl_c=w;; X) _rf_tl_c=x;; + Y) _rf_tl_c=y;; Z) _rf_tl_c=z;; + esac + _rf_tl_out="${_rf_tl_out}${_rf_tl_c}" + done + printf '%s' "$_rf_tl_out" +} + +# Replace one or more consecutive backslashes with a single forward slash. +# Equivalent to: sed 's|\\\\*|/|g' +__runfiles_normalize_backslashes() { + _rf_nb_in="$1" + _rf_nb_out="" + _rf_nb_bs=false + while [ -n "$_rf_nb_in" ]; do + _rf_nb_c="${_rf_nb_in%"${_rf_nb_in#?}"}" + _rf_nb_in="${_rf_nb_in#?}" + case "$_rf_nb_c" in + "\\") + if [ "$_rf_nb_bs" = false ]; then + _rf_nb_out="${_rf_nb_out}/" + _rf_nb_bs=true + fi + ;; + *) + _rf_nb_bs=false + _rf_nb_out="${_rf_nb_out}${_rf_nb_c}" + ;; + esac + done + printf '%s' "$_rf_nb_out" +} + +# Replace all occurrences of $2 in $1 with $3. +# Equivalent to: ${1//$2/$3} (bash-only). +__runfiles_gsub() { + _rf_gs_in="$1" + _rf_gs_old="$2" + _rf_gs_new="$3" + _rf_gs_out="" + while :; do + case "$_rf_gs_in" in + *"$_rf_gs_old"*) + _rf_gs_out="${_rf_gs_out}${_rf_gs_in%%"$_rf_gs_old"*}${_rf_gs_new}" + _rf_gs_in="${_rf_gs_in#*"$_rf_gs_old"}" + ;; + *) + _rf_gs_out="${_rf_gs_out}${_rf_gs_in}" + break + ;; + esac + done + printf '%s' "$_rf_gs_out" +} + +# Encode a runfiles path for manifest lookup: \ -> \b, space -> \s. +# Newlines must be handled separately by the caller (\n). +# Equivalent to: sed 's/\\/\\b/g; s/ /\\s/g' +__runfiles_encode_manifest_path() { + _rf_em_in="$1" + _rf_em_out="" + while [ -n "$_rf_em_in" ]; do + _rf_em_c="${_rf_em_in%"${_rf_em_in#?}"}" + _rf_em_in="${_rf_em_in#?}" + case "$_rf_em_c" in + "\\") _rf_em_out="${_rf_em_out}\\b" ;; + " ") _rf_em_out="${_rf_em_out}\\s" ;; + *) _rf_em_out="${_rf_em_out}${_rf_em_c}" ;; + esac + done + printf '%s' "$_rf_em_out" +} + +# Compute the wildcard prefix for repo mapping lookups. +# For repo names like "my_module++ext+repo1", replaces the trailing segment +# of safe chars ([-a-zA-Z0-9_.]) after the last separator with *. +# Returns the input unchanged if no separator is found. +# Equivalent to: sed 's/\(.*[^-a-zA-Z0-9_.]\)[-a-zA-Z0-9_.]\{1,\}/\1*/' +__runfiles_compute_repo_prefix() { + _rf_cp_repo="$1" + _rf_cp_trim="$_rf_cp_repo" + while [ -n "$_rf_cp_trim" ]; do + _rf_cp_last="${_rf_cp_trim#"${_rf_cp_trim%?}"}" + case "$_rf_cp_last" in + [-a-zA-Z0-9_.]) _rf_cp_trim="${_rf_cp_trim%?}" ;; + *) + printf '%s*' "$_rf_cp_trim" + return 0 + ;; + esac + done + printf '%s' "$_rf_cp_repo" +} + +# Find the first line in $2 starting with "$1 " and print the value +# (everything after the prefix and the separating space). +# On Windows (_RLOCATION_CASE_INSENSITIVE=1), matching is case-insensitive +# but the value is returned with its original casing. +__runfiles_find_line() { + _rf_fl_pfx="$1" + _rf_fl_file="$2" + _rf_fl_pfx_sp="${_rf_fl_pfx} " + + if [ -n "$_RLOCATION_CASE_INSENSITIVE" ]; then + _rf_fl_lpfx="$(__runfiles_tolower "$_rf_fl_pfx_sp")" + _rf_fl_plen=${#_rf_fl_pfx_sp} + while IFS= read -r _rf_fl_line || [ -n "$_rf_fl_line" ]; do + _rf_fl_lline="$(__runfiles_tolower "$_rf_fl_line")" + case "$_rf_fl_lline" in + "${_rf_fl_lpfx}"*) + _rf_fl_val="$_rf_fl_line" + _rf_fl_i=0 + while [ "$_rf_fl_i" -lt "$_rf_fl_plen" ]; do + _rf_fl_val="${_rf_fl_val#?}" + _rf_fl_i=$((_rf_fl_i + 1)) + done + printf '%s\n' "$_rf_fl_val" + return 0 + ;; + esac + done < "$_rf_fl_file" + else + while IFS= read -r _rf_fl_line || [ -n "$_rf_fl_line" ]; do + case "$_rf_fl_line" in + "${_rf_fl_pfx_sp}"*) + printf '%s\n' "${_rf_fl_line#"${_rf_fl_pfx_sp}"}" + return 0 + ;; + esac + done < "$_rf_fl_file" + fi + return 1 +} + +# Find the first non-escaped manifest line whose value (target path) matches +# $1. Prints the key (rlocation path) on stdout. +# On Windows, matching is case-insensitive. +__runfiles_find_by_target() { + _rf_ft_target="$1" + _rf_ft_file="$2" + + if [ -n "$_RLOCATION_CASE_INSENSITIVE" ]; then + _rf_ft_ltgt="$(__runfiles_tolower "$_rf_ft_target")" + while IFS= read -r _rf_ft_line || [ -n "$_rf_ft_line" ]; do + case "$_rf_ft_line" in " "*) continue ;; esac + _rf_ft_key="${_rf_ft_line%% *}" + _rf_ft_val="${_rf_ft_line#* }" + if [ "$(__runfiles_tolower "$_rf_ft_val")" = "$_rf_ft_ltgt" ]; then + printf '%s' "$_rf_ft_key" + return 0 + fi + done < "$_rf_ft_file" + else + while IFS= read -r _rf_ft_line || [ -n "$_rf_ft_line" ]; do + case "$_rf_ft_line" in " "*) continue ;; esac + _rf_ft_key="${_rf_ft_line%% *}" + _rf_ft_val="${_rf_ft_line#* }" + if [ "$_rf_ft_val" = "$_rf_ft_target" ]; then + printf '%s' "$_rf_ft_key" + return 0 + fi + done < "$_rf_ft_file" + fi + return 1 +} + +# Look up a repo mapping entry. +# Args: $1=source_repo $2=source_repo_prefix $3=target_apparent_name +# $4=mapping_file +# Prints the canonical target repo name. +# On Windows, matching is case-insensitive. +__runfiles_find_repo_mapping() { + _rf_rm_src="$1" + _rf_rm_pfx="$2" + _rf_rm_tgt="$3" + _rf_rm_file="$4" + + if [ -n "$_RLOCATION_CASE_INSENSITIVE" ]; then + _rf_rm_ls="$(__runfiles_tolower "$_rf_rm_src")" + _rf_rm_lp="$(__runfiles_tolower "$_rf_rm_pfx")" + _rf_rm_lt="$(__runfiles_tolower "$_rf_rm_tgt")" + while IFS= read -r _rf_rm_line || [ -n "$_rf_rm_line" ]; do + _rf_rm_ll="$(__runfiles_tolower "$_rf_rm_line")" + case "$_rf_rm_ll" in + "${_rf_rm_ls},${_rf_rm_lt},"*|"${_rf_rm_lp},${_rf_rm_lt},"*) + _rf_rm_rest="${_rf_rm_line#*,}" + printf '%s' "${_rf_rm_rest#*,}" + return 0 + ;; + esac + done < "$_rf_rm_file" + else + while IFS= read -r _rf_rm_line || [ -n "$_rf_rm_line" ]; do + case "$_rf_rm_line" in + "${_rf_rm_src},${_rf_rm_tgt},"*|"${_rf_rm_pfx},${_rf_rm_tgt},"*) + _rf_rm_rest="${_rf_rm_line#*,}" + printf '%s' "${_rf_rm_rest#*,}" + return 0 + ;; + esac + done < "$_rf_rm_file" + fi + return 1 +} + +# Parse the repository name from an exec path. +# Scans path segments for /bazel-out//bin/external// or +# /bazel-bin/external// and returns the last matching . +# Equivalent to: grep -E -o '...' | tail -1 | awk -F/ '{print $(NF-1)}' +__runfiles_parse_exec_path_repo() { + _rf_pe_path="$1" + _rf_pe_result="" + _rf_pe_rest="$_rf_pe_path" + + # Track last 4 path segments via a sliding window. + _rf_pe_p4="" _rf_pe_p3="" _rf_pe_p2="" _rf_pe_p1="" + while :; do + case "$_rf_pe_rest" in + */*) + _rf_pe_seg="${_rf_pe_rest%%/*}" + _rf_pe_rest="${_rf_pe_rest#*/}" + ;; + *) + _rf_pe_seg="$_rf_pe_rest" + _rf_pe_rest="" + ;; + esac + + # Pattern: bazel-bin/external/ + if [ "$_rf_pe_p2" = "bazel-bin" ] && [ "$_rf_pe_p1" = "external" ] \ + && [ -n "$_rf_pe_seg" ]; then + _rf_pe_result="$_rf_pe_seg" + fi + # Pattern: bazel-out//bin/external/ + if [ "$_rf_pe_p4" = "bazel-out" ] && [ "$_rf_pe_p2" = "bin" ] \ + && [ "$_rf_pe_p1" = "external" ] && [ -n "$_rf_pe_seg" ]; then + _rf_pe_result="$_rf_pe_seg" + fi + + _rf_pe_p4="$_rf_pe_p3" + _rf_pe_p3="$_rf_pe_p2" + _rf_pe_p2="$_rf_pe_p1" + _rf_pe_p1="$_rf_pe_seg" + + [ -z "$_rf_pe_rest" ] && break + done + + if [ -n "$_rf_pe_result" ]; then + printf '%s' "$_rf_pe_result" + return 0 + fi + return 1 +} + +# --- Public API --- + +# Prints to stdout the runtime location of a data-dependency. +# The optional second argument specifies 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 main repository is assumed. +# (In runfiles.bash, auto-detection via BASH_SOURCE is used instead.) +rlocation() { + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): start" + fi + if __runfiles_is_abs "$1"; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): absolute path, return" + fi + printf '%s\n' "$1" + return 0 + fi + case "$1" in + ../*|*/..|./*|*/./*|*/.|*//*) # shellcheck disable=SC2254 + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "ERROR[runfiles.sh]: rlocation($1): path is not normalized" + fi + return 1 + ;; + \\*) + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "ERROR[runfiles.sh]: rlocation($1): absolute path without" \ + "drive name" + fi + return 1 + ;; + esac + + if [ -f "${RUNFILES_REPO_MAPPING:-}" ]; then + local target_repo_apparent_name="${1%%/*}" + local remainder= + case "$1" in + */*) remainder="${1#*/}" ;; + esac + if [ -n "$remainder" ]; then + if [ -z "${2+x}" ]; then + local source_repo="" + else + local source_repo="$2" + fi + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): looking up canonical name for ($target_repo_apparent_name) from ($source_repo) in ($RUNFILES_REPO_MAPPING)" + fi + local source_repo_prefix + source_repo_prefix="$(__runfiles_compute_repo_prefix "$source_repo")" + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): matching source_repo ($source_repo) or prefix ($source_repo_prefix) with target ($target_repo_apparent_name)" + fi + local target_repo + target_repo="$(__runfiles_find_repo_mapping "$source_repo" "$source_repo_prefix" "$target_repo_apparent_name" "$RUNFILES_REPO_MAPPING")" || true + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): canonical name of target repo is ($target_repo)" + fi + if [ -n "$target_repo" ]; then + local rlocation_path="$target_repo/$remainder" + else + local rlocation_path="$1" + fi + else + local rlocation_path="$1" + fi + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): not using repository mapping (${RUNFILES_REPO_MAPPING:-}) since it does not exist" + fi + local rlocation_path="$1" + fi + + runfiles_rlocation_checked "$rlocation_path" +} + +# 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. +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 + case "$RUNFILES_MANIFEST_FILE" in + */MANIFEST) + if [ -d "${RUNFILES_MANIFEST_FILE%/MANIFEST}" ]; then + export RUNFILES_DIR="${RUNFILES_MANIFEST_FILE%/MANIFEST}" + export JAVA_RUNFILES="$RUNFILES_DIR" + else + export RUNFILES_DIR= + fi + ;; + *_manifest) + if [ -d "${RUNFILES_MANIFEST_FILE%_manifest}" ]; then + export RUNFILES_DIR="${RUNFILES_MANIFEST_FILE%_manifest}" + export JAVA_RUNFILES="$RUNFILES_DIR" + else + export RUNFILES_DIR= + fi + ;; + *) + export RUNFILES_DIR= + ;; + esac + fi +} + +# Returns the canonical name of the Bazel repository containing the given +# script path. +# +# Unlike runfiles.bash which uses BASH_SOURCE to auto-detect the caller, +# this function requires the caller's script path as the first argument: +# +# runfiles_current_repository "$0" +# +# Note: This function only works correctly with Bzlmod enabled. Without +# Bzlmod, its return value is ignored if passed to rlocation. +runfiles_current_repository() { + local raw_caller_path="$1" + if __runfiles_is_abs "$raw_caller_path"; then + local caller_path="$raw_caller_path" + else + # dirname/basename without external binaries + local _rf_dir _rf_base + case "$raw_caller_path" in + */*) _rf_dir="${raw_caller_path%/*}"; [ -z "$_rf_dir" ] && _rf_dir="/" ;; + *) _rf_dir="." ;; + esac + _rf_base="${raw_caller_path##*/}" + local caller_path + caller_path="$(cd "$_rf_dir" || return 1; pwd)/$_rf_base" + fi + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): 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 + local normalized_caller_path + normalized_caller_path="$(__runfiles_normalize_backslashes "$caller_path")" + local escaped_caller_path="$normalized_caller_path" + rlocation_path="$(__runfiles_find_by_target "$escaped_caller_path" "$RUNFILES_MANIFEST_FILE")" || true + if [ -z "$rlocation_path" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "ERROR[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) is not the target of an entry in the runfiles manifest ($RUNFILES_MANIFEST_FILE)" + fi + local repository + repository="$(__runfiles_parse_exec_path_repo "$normalized_caller_path")" || true + if [ -n "$repository" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) lies in repository ($repository) (parsed exec path)" + fi + printf '%s\n' "$repository" + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) lies in the main repository (parsed exec path)" + fi + printf '%s\n' "" + fi + return 1 + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($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 + local normalized_caller_path normalized_dir + normalized_caller_path="$(__runfiles_normalize_backslashes "$caller_path")" + local _rf_rd="${RUNFILES_DIR%/}" + _rf_rd="${_rf_rd%\\}" + normalized_dir="$(__runfiles_normalize_backslashes "$_rf_rd")" + if [ -n "$_RLOCATION_CASE_INSENSITIVE" ]; then + normalized_caller_path="$(__runfiles_tolower "$normalized_caller_path")" + normalized_dir="$(__runfiles_tolower "$normalized_dir")" + fi + case "$normalized_caller_path" in + "$normalized_dir"/*) + rlocation_path="${normalized_caller_path#"$normalized_dir"}" + rlocation_path="${rlocation_path#/}" + ;; + esac + if [ -z "$rlocation_path" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) does not lie under the runfiles directory ($normalized_dir)" + fi + local repository + repository="$(__runfiles_parse_exec_path_repo "$normalized_caller_path")" || true + if [ -n "$repository" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) lies in repository ($repository) (parsed exec path)" + fi + printf '%s\n' "$repository" + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($normalized_caller_path) lies in the main repository (parsed exec path)" + fi + printf '%s\n' "" + fi + return 0 + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($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.sh]: runfiles_current_repository($1): 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.sh]: runfiles_current_repository($1): ($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 repository="${rlocation_path%%/*}" + if [ "$repository" = "_main" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($rlocation_path) lies in the main repository" + fi + printf '%s\n' "" + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: runfiles_current_repository($1): ($rlocation_path) lies in repository ($repository)" + fi + printf '%s\n' "$repository" + fi +} + +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.sh]: rlocation($1): found under RUNFILES_DIR ($RUNFILES_DIR), return" + fi + printf '%s\n' "${RUNFILES_DIR}/$1" + elif [ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: 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. + local search_prefix escaped + case "$1" in + *" "*|*"$_RUNFILES_NL"*) + search_prefix=" $(__runfiles_encode_manifest_path "$1")" + search_prefix="$(__runfiles_gsub "$search_prefix" "$_RUNFILES_NL" '\n')" + escaped=true + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): using escaped search prefix ($search_prefix)" + fi + ;; + *) + search_prefix="$1" + escaped=false + ;; + esac + local result + result="$(__runfiles_find_line "$search_prefix" "$RUNFILES_MANIFEST_FILE")" || true + 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.sh]: rlocation($1): looking for prefix ($prefix)" + fi + case "$prefix" in + *" "*|*"$_RUNFILES_NL"*) + search_prefix=" $(__runfiles_encode_manifest_path "$prefix")" + search_prefix="$(__runfiles_gsub "$search_prefix" "$_RUNFILES_NL" '\n')" + escaped=true + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): using escaped search prefix ($search_prefix)" + fi + ;; + *) + search_prefix="$prefix" + escaped=false + ;; + esac + prefix_result="$(__runfiles_find_line "$search_prefix" "$RUNFILES_MANIFEST_FILE")" || true + if [ "$escaped" = true ] && [ -n "$prefix_result" ]; then + prefix_result="$(__runfiles_gsub "$prefix_result" '\n' "$_RUNFILES_NL")" + prefix_result="$(__runfiles_gsub "$prefix_result" '\b' '\')" + fi + [ -z "$prefix_result" ] && continue + local candidate="${prefix_result}${1#"${prefix}"}" + if [ -e "$candidate" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): found in manifest as ($candidate) via prefix ($prefix)" + fi + printf '%s\n' "$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.sh]: 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.sh]: rlocation($1): not found in manifest" + fi + printf '%s\n' "" + else + if [ "$escaped" = true ]; then + result="$(__runfiles_gsub "$result" '\n' "$_RUNFILES_NL")" + result="$(__runfiles_gsub "$result" '\b' '\')" + fi + if [ -e "$result" ]; then + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): found in manifest as ($result)" + fi + printf '%s\n' "$result" + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "INFO[runfiles.sh]: rlocation($1): found in manifest as ($result), but file does not exist" + fi + printf '%s\n' "" + fi + fi + else + if [ "${RUNFILES_LIB_DEBUG:-}" = 1 ]; then + echo >&2 "ERROR[runfiles.sh]: cannot look up runfile \"$1\" " \ + "(RUNFILES_DIR=\"${RUNFILES_DIR:-}\"," \ + "RUNFILES_MANIFEST_FILE=\"${RUNFILES_MANIFEST_FILE:-}\")" + fi + return 1 + fi +} + +# The repo mapping manifest may not exist with old versions of Bazel. +RUNFILES_REPO_MAPPING=$(runfiles_rlocation_checked _repo_mapping || echo "") +export RUNFILES_REPO_MAPPING diff --git a/shell/settings/BUILD.bazel b/shell/settings/BUILD.bazel new file mode 100644 index 0000000..675cad3 --- /dev/null +++ b/shell/settings/BUILD.bazel @@ -0,0 +1,7 @@ +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +bool_flag( + name = "experimental_use_shell_runfiles", + build_setting_default = False, + visibility = ["//visibility:public"], +) diff --git a/tests/runfiles/BUILD b/tests/runfiles/BUILD index 52d9867..67c35d8 100644 --- a/tests/runfiles/BUILD +++ b/tests/runfiles/BUILD @@ -2,9 +2,24 @@ load("@rules_shellcheck//:def.bzl", "shellcheck_test") load("//shell:sh_test.bzl", "sh_test") sh_test( - name = "runfiles_test", + name = "runfiles_bash_test", srcs = ["runfiles_test.bash"], - deps = ["//shell/runfiles"], + deps = ["//shell/runfiles:runfiles_bash"], +) + +sh_test( + name = "runfiles_sh_from_bash_test", + srcs = ["runfiles_test.bash"], + env = { + "RUNFILES_LIBRARY_FILE": "$(rlocationpath //shell/runfiles:runfiles_sh)", + }, + deps = ["//shell/runfiles:runfiles_sh"], +) + +sh_test( + name = "runfiles_sh_test", + srcs = ["runfiles_test.sh"], + deps = ["//shell/runfiles:runfiles_sh"], ) shellcheck_test( @@ -13,3 +28,10 @@ shellcheck_test( format = "gcc", severity = "warning", ) + +shellcheck_test( + name = "runfiles_sh_shellcheck_test", + data = ["//shell/runfiles:runfiles.sh"], + format = "gcc", + severity = "warning", +) diff --git a/tests/runfiles/runfiles_test.bash b/tests/runfiles/runfiles_test.bash index f738b3d..6255d91 100755 --- a/tests/runfiles/runfiles_test.bash +++ b/tests/runfiles/runfiles_test.bash @@ -46,6 +46,8 @@ msys*|mingw*|cygwin*) ;; esac +_RUNFILES_LIBRARY_FILE="${RUNFILES_LIBRARY_FILE:-bazel_tools/tools/bash/runfiles/runfiles.bash}" + function find_runfiles_lib() { # Unset existing definitions of the functions we want to test. if type rlocation >&/dev/null; then @@ -58,14 +60,14 @@ function find_runfiles_lib() { 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 + elif [[ -f "$0.runfiles/${_RUNFILES_LIBRARY_FILE}" ]]; 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" + if [[ -f "${RUNFILES_DIR:-/dev/null}/${_RUNFILES_LIBRARY_FILE}" ]]; then + echo "${RUNFILES_DIR}/${_RUNFILES_LIBRARY_FILE}" elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then - grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ + grep -m1 "^${_RUNFILES_LIBRARY_FILE} " \ "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2- else echo >&2 "ERROR: cannot find //shell/runfiles:runfiles.bash" diff --git a/tests/runfiles/runfiles_test.sh b/tests/runfiles/runfiles_test.sh new file mode 100755 index 0000000..d14a2e4 --- /dev/null +++ b/tests/runfiles/runfiles_test.sh @@ -0,0 +1,570 @@ +#!/bin/sh +# shellcheck disable=SC3043 +# +# 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 -eu + +NL=' +' + +_log_base() { + _prefix=$1 + shift + echo >&2 "${_prefix}[runfiles_test.sh ($(date "+%H:%M:%S %z"))] $*" +} + +fail() { + _log_base "FAILED" "$@" + exit 1 +} + +log_fail() { + _log_base "FAILED" "$@" +} + +log_info() { + _log_base "INFO" "$@" +} + +is_windows() { + [ -n "${SYSTEMROOT:-}" ] || [ -n "${COMSPEC:-}" ] +} + +find_runfiles_lib() { + if type rlocation >/dev/null 2>&1; then + unset -f rlocation + unset -f 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/_main/shell/runfiles/runfiles.sh" ]; then + export RUNFILES_DIR="$0.runfiles" + fi + fi + if [ -f "${RUNFILES_DIR:-/dev/null}/_main/shell/runfiles/runfiles.sh" ]; then + echo "${RUNFILES_DIR}/_main/shell/runfiles/runfiles.sh" + elif [ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]; then + _target="_main/shell/runfiles/runfiles.sh" + while IFS= read -r _line; do + case "$_line" in + "${_target} "*) + echo "${_line#"${_target} "}" + return 0 + ;; + esac + done < "$RUNFILES_MANIFEST_FILE" + echo >&2 "ERROR: cannot find //shell/runfiles:runfiles.sh" + exit 1 + else + echo >&2 "ERROR: cannot find //shell/runfiles:runfiles.sh" + exit 1 + fi +} + +test_rlocation_call_requires_no_envvars() { + export RUNFILES_DIR=mock/runfiles + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + . "$runfiles_lib_path" || fail +} + +test_rlocation_argument_validation() { + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + . "$runfiles_lib_path" + + if rlocation "../foo" >/dev/null 2>&1; then + fail + fi + if rlocation "foo/.." >/dev/null 2>&1; then + fail + fi + if rlocation "foo/../bar" >/dev/null 2>&1; then + fail + fi + if rlocation "./foo" >/dev/null 2>&1; then + fail + fi + if rlocation "foo/." >/dev/null 2>&1; then + fail + fi + if rlocation "foo/./bar" >/dev/null 2>&1; then + fail + fi + if rlocation "//foo" >/dev/null 2>&1; then + fail + fi + if rlocation "foo//" >/dev/null 2>&1; then + fail + fi + if rlocation "foo//bar" >/dev/null 2>&1; then + fail + fi + if rlocation "\\foo" >/dev/null 2>&1; then + fail + fi +} + +test_rlocation_abs_path() { + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE= + export RUNFILES_MANIFEST_ONLY= + . "$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 +} + +test_init_manifest_based_runfiles() { + local tmpdir + 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 ${NL}a" + mkdir -p "${tmpdir}/space newline${NL}backslash\\a" + touch "${tmpdir}/space newline${NL}backslash\\a/f i\\le" + fi + + export RUNFILES_DIR= + export RUNFILES_MANIFEST_FILE="$tmpdir/foo.runfiles_manifest" + . "$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/${NL}\\i" || echo failed)" = "$tmpdir/ \\nj k ${NL}a" ] || fail + [ "$(rlocation "space newline${NL}backslash\\_dir/f i\\le" || echo failed)" = "${tmpdir}/space newline${NL}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 ${NL}a" "${tmpdir}/space newline${NL}backslash\\a" + [ -z "$(rlocation "h/${NL}\\i" || echo failed)" ] || fail + [ -z "$(rlocation "space newline${NL}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 +} + +test_manifest_based_envvars() { + local tmpdir + 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" + . "$runfiles_lib_path" + + runfiles_export_envvars + [ "${RUNFILES_DIR:-}" = "$tmpdir/foo.runfiles" ] || fail + [ "${RUNFILES_MANIFEST_FILE:-}" = "$tmpdir/foo.runfiles_manifest" ] || fail +} + +test_init_directory_based_runfiles() { + local tmpdir + tmpdir="$(mktemp -d "$TEST_TMPDIR/tmp.XXXXXXXX")" + + export RUNFILES_DIR="${tmpdir}/mock/runfiles" + export RUNFILES_MANIFEST_FILE= + . "$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 +} + +test_directory_based_runfiles_with_repo_mapping_from_main() { + local tmpdir + 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" < "$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 + . "$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 +} + +test_manifest_based_runfiles_with_repo_mapping_from_other_repo() { + local tmpdir + 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 + . "$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 +} + +test_manifest_based_runfiles_with_repo_mapping_from_extension_repo() { + local tmpdir + 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 +my_module+/foo/runfile $tmpdir/my_module+/runfile +my_module++ext+repo1/foo/runfile $tmpdir/my_module++ext+repo1/runfile +repo2+/foo/runfile $tmpdir/repo2+/runfile +EOF + . "$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" + mkdir -p "$tmpdir/my_module+" + touch "$tmpdir/my_module+/runfile" + mkdir -p "$tmpdir/my_module++ext+repo1" + touch "$tmpdir/my_module++ext+repo1/runfile" + mkdir -p "$tmpdir/repo2+" + touch "$tmpdir/repo2+/runfile" + + [ "$(rlocation "my_module/foo/runfile" "my_module++ext+repo1" || echo failed)" = "$tmpdir/my_module+/runfile" ] || fail + [ "$(rlocation "repo1/foo/runfile" "my_module++ext+repo1" || echo failed)" = "$tmpdir/my_module++ext+repo1/runfile" ] || fail + [ "$(rlocation "repo2+/foo/runfile" "my_module++ext+repo1" || echo failed)" = "$tmpdir/repo2+/runfile" ] || fail +} + +test_directory_based_envvars() { + export RUNFILES_DIR=mock/runfiles + export RUNFILES_MANIFEST_FILE= + . "$runfiles_lib_path" + + runfiles_export_envvars + [ "${RUNFILES_DIR:-}" = "mock/runfiles" ] || fail + [ -z "${RUNFILES_MANIFEST_FILE:-}" ] || fail +} + +main() { + manifest_file="${RUNFILES_MANIFEST_FILE:-}" + dir="${RUNFILES_DIR:-}" + runfiles_lib_path=$(find_runfiles_lib) + + tests=" + test_rlocation_call_requires_no_envvars + test_rlocation_argument_validation + test_rlocation_abs_path + test_init_manifest_based_runfiles + test_manifest_based_envvars + test_init_directory_based_runfiles + test_directory_based_runfiles_with_repo_mapping_from_main + test_directory_based_runfiles_with_repo_mapping_from_other_repo + test_directory_based_runfiles_with_repo_mapping_from_extension_repo + test_manifest_based_runfiles_with_repo_mapping_from_main + test_manifest_based_runfiles_with_repo_mapping_from_other_repo + test_manifest_based_runfiles_with_repo_mapping_from_extension_repo + test_directory_based_envvars + " + failure=0 + for t in $tests; do + export RUNFILES_MANIFEST_FILE="$manifest_file" + export RUNFILES_DIR="$dir" + log_info "Running $t" + if ! ($t); then + log_fail "$t" + failure=1 + fi + done + return $failure +} + +main