Add support for `--incompatible_compact_repo_mapping_manifest` (#32)
Along the way, fix two more bugs:
* gracefully handle the case of a missing repo mapping manifest
* consistently escape everything that ends up in a grep pattern as a
literal
Work towards https://github.com/bazelbuild/bazel/issues/26262
diff --git a/shell/runfiles/runfiles.bash b/shell/runfiles/runfiles.bash
index 2efdbfb..f618b2c 100644
--- a/shell/runfiles/runfiles.bash
+++ b/shell/runfiles/runfiles.bash
@@ -117,6 +117,13 @@
}
export -f __runfiles_maybe_grep
+# Escape the argument for use in a grep regex.
+# This is used to escape paths that may contain special characters.
+function __runfiles_escape_grep() {
+ echo -n "$1" | sed 's/[.[\*^$]/\\&/g'
+}
+export -f __runfiles_escape_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
@@ -161,7 +168,21 @@
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)
+ # The repo mapping manifest may compactly represent the mapping for source
+ # repos of the form module++ext+<something> with rows of the form
+ # module++ext+*,module,module+. We don't want to rely on the particular
+ # separator char, but do assume that it is not a valid character in a
+ # user-specified repository name.
+ # If the source repo name does not match the sed pattern, then the
+ # alternative in the regex below will have identical branches, which is
+ # fine. The ^ is duplicated in each branch since grep doesn't support
+ # empty subexpressions.
+ local -r source_repo_prefix="$(echo -n "$source_repo" | sed 's/\(.*[^-a-zA-Z0-9_.]\)[-a-zA-Z0-9_.]\{1,\}/\1*/')"
+ local -r escaped_pattern="\(^$(__runfiles_escape_grep "$source_repo")\|^$(__runfiles_escape_grep "$source_repo_prefix")\),$(__runfiles_escape_grep "$target_repo_apparent_name"),"
+ if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then
+ echo >&2 "INFO[runfiles.bash]: rlocation($1): matching pattern on repo mapping manifest ($escaped_pattern)"
+ fi
+ local -r target_repo=$(__runfiles_maybe_grep -m1 "$escaped_pattern" "$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
@@ -248,7 +269,7 @@
# 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')"
+ local -r escaped_caller_path="$(__runfiles_escape_grep "$normalized_caller_path")"
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
@@ -379,7 +400,7 @@
# Escape the search prefix for use in the grep regex below *after*
# determining the trim length.
local result
- result=$(__runfiles_maybe_grep -m1 "^$(echo -n "$search_prefix" | sed 's/[.[\*^$]/\\&/g') " "${RUNFILES_MANIFEST_FILE}" | cut -b "${trim_length}-")
+ result=$(__runfiles_maybe_grep -m1 "^$(__runfiles_escape_grep "$search_prefix") " "${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
@@ -408,7 +429,7 @@
fi
# The extra space below is added because cut counts from 1.
trim_length=$(echo -n "$search_prefix " | wc -c | tr -d ' ')
- prefix_result=$(__runfiles_maybe_grep -m1 "^$(echo -n "$search_prefix" | sed 's/[.[\*^$]/\\&/g') " "${RUNFILES_MANIFEST_FILE}" | cut -b "${trim_length}"-)
+ prefix_result=$(__runfiles_maybe_grep -m1 "^$(__runfiles_escape_grep "$search_prefix") " "${RUNFILES_MANIFEST_FILE}" | cut -b "${trim_length}"-)
if [[ "$escaped" = true ]]; then
prefix_result="${prefix_result//\\n/$'\n'}"
prefix_result="${prefix_result//\\b/\\}"
@@ -469,5 +490,6 @@
}
export -f runfiles_rlocation_checked
-RUNFILES_REPO_MAPPING=$(runfiles_rlocation_checked _repo_mapping 2> /dev/null)
+# 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/tests/runfiles/runfiles_test.bash b/tests/runfiles/runfiles_test.bash
index e50925b..fb61b86 100755
--- a/tests/runfiles/runfiles_test.bash
+++ b/tests/runfiles/runfiles_test.bash
@@ -329,6 +329,44 @@
[[ "$(rlocation "config.json" "protobuf+3.19.2" || echo failed)" == "$RUNFILES_DIR/config.json" ]] || fail
}
+function test_directory_based_runfiles_with_repo_mapping_from_extension_repo() {
+ local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
+
+ export RUNFILES_DIR=${tmpdir}/mock/runfiles
+ mkdir -p "$RUNFILES_DIR"
+ cat > "$RUNFILES_DIR/_repo_mapping" <<EOF
+,config.json,config.json+1.2.3
+,my_module,_main
+,my_protobuf,protobuf+3.19.2
+,my_workspace,_main
+my_module++ex+*,my_module,my_module+
+my_module++ext+*,my_module,my_module+
+my_module++ext+*,repo1,my_module++ext+repo1
+my_module++ext1+*,my_module,my_module+
+EOF
+ export RUNFILES_MANIFEST_FILE=
+ source "$runfiles_lib_path"
+
+ mkdir -p "$RUNFILES_DIR/_main/bar"
+ touch "$RUNFILES_DIR/_main/bar/runfile"
+ mkdir -p "$RUNFILES_DIR/protobuf+3.19.2/bar/dir/de eply/nes ted"
+ touch "$RUNFILES_DIR/protobuf+3.19.2/bar/dir/file"
+ touch "$RUNFILES_DIR/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"
+ mkdir -p "$RUNFILES_DIR/protobuf+3.19.2/foo"
+ touch "$RUNFILES_DIR/protobuf+3.19.2/foo/runfile"
+ touch "$RUNFILES_DIR/config.json"
+ mkdir -p "$RUNFILES_DIR/my_module+/foo"
+ touch "$RUNFILES_DIR/my_module+/foo/runfile"
+ mkdir -p "$RUNFILES_DIR/my_module++ext+repo1/foo"
+ touch "$RUNFILES_DIR/my_module++ext+repo1/foo/runfile"
+ mkdir -p "$RUNFILES_DIR/repo2+/foo"
+ touch "$RUNFILES_DIR/repo2+/foo/runfile"
+
+ [[ "$(rlocation "my_module/foo/runfile" "my_module++ext+repo1" || echo failed)" == "$RUNFILES_DIR/my_module+/foo/runfile" ]] || fail
+ [[ "$(rlocation "repo1/foo/runfile" "my_module++ext+repo1" || echo failed)" == "$RUNFILES_DIR/my_module++ext+repo1/foo/runfile" ]] || fail
+ [[ "$(rlocation "repo2+/foo/runfile" "my_module++ext+repo1" || echo failed)" == "$RUNFILES_DIR/repo2+/foo/runfile" ]] || fail
+}
+
function test_manifest_based_runfiles_with_repo_mapping_from_main() {
local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
@@ -427,6 +465,53 @@
[[ "$(rlocation "config.json" "protobuf+3.19.2" || echo failed)" == "$tmpdir/config.json" ]] || fail
}
+function test_manifest_based_runfiles_with_repo_mapping_from_extension_repo() {
+ local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
+
+ cat > "$tmpdir/foo.repo_mapping" <<EOF
+,config.json,config.json+1.2.3
+,my_module,_main
+,my_protobuf,protobuf+3.19.2
+,my_workspace,_main
+my_module++ex+*,my_module,my_module+
+my_module++ext+*,my_module,my_module+
+my_module++ext+*,repo1,my_module++ext+repo1
+my_module++ext1+*,my_module,my_module+
+EOF
+ export RUNFILES_DIR=
+ export RUNFILES_MANIFEST_FILE="$tmpdir/foo.runfiles_manifest"
+ cat > "$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
+ 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"
+ 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
+}
+
function test_directory_based_envvars() {
export RUNFILES_DIR=mock/runfiles
export RUNFILES_MANIFEST_FILE=