Prefer the runfiles manifest over the runfiles directory (#68)

`runfiles_rlocation_checked` looked up a path in the runfiles directory
before consulting the manifest. Whether the runfiles directory is
populated is a property of how the action or test is executed and is not
known at analysis time, so the directory may exist while holding the
stale contents of a previous execution. Consult the manifest first and
only fall back to the runfiles directory if no manifest exists.

This also fixes `runfiles_current_repository` on Windows with
`--enable_runfiles`. That mode is the only one in which both
`RUNFILES_DIR` and `RUNFILES_MANIFEST_FILE` are set: Windows does not
sandbox, so the runfiles tree used at runtime contains the `MANIFEST`
file that `runfiles_export_envvars` promotes to
`RUNFILES_MANIFEST_FILE`. `rlocation` then returned a path inside the
runfiles tree, but the manifest maps rlocation paths to the locations of
the original files, so the caller could never be the target of a
manifest entry and `runfiles_current_repository` reported every caller
as belonging to the main repository. With the manifest taking
precedence, callers are manifest targets again and the lookup succeeds.

`runfiles_current_repository` had no test coverage at all, which is why
this went unnoticed. Cover all three combinations of the two envvars:
directory only, manifest only, and both set at the same time. The last
case also asserts that a stale copy in the runfiles directory is never
preferred over the manifest.

Future work (https://github.com/bazelbuild/bazel/pull/30674) will allow
the directory to be used again in both functions, assuming it's
materialized.
diff --git a/shell/runfiles/runfiles.bash b/shell/runfiles/runfiles.bash
index 3dfe3e2..b6bbdfe 100644
--- a/shell/runfiles/runfiles.bash
+++ b/shell/runfiles/runfiles.bash
@@ -374,12 +374,11 @@
   # 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
+  # The manifest takes precedence over the runfiles directory: whether the directory is populated
+  # is a property of the execution of the action or test, which is not known at analysis time, so
+  # the directory may exist but contain the stale contents of a previous execution. If the manifest
+  # exists, it is always authoritative.
+  if [[ -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
@@ -483,6 +482,11 @@
         echo ""
       fi
     fi
+  elif [[ -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"
   else
     if [[ "${RUNFILES_LIB_DEBUG:-}" == 1 ]]; then
       echo >&2 "ERROR[runfiles.bash]: cannot look up runfile \"$1\" " \
diff --git a/tests/runfiles/runfiles_test.bash b/tests/runfiles/runfiles_test.bash
index f738b3d..fd60eee 100755
--- a/tests/runfiles/runfiles_test.bash
+++ b/tests/runfiles/runfiles_test.bash
@@ -512,6 +512,97 @@
   [[ "$(rlocation "repo2+/foo/runfile" "my_module++ext+repo1" || echo failed)" == "$tmpdir/repo2+/runfile" ]] || fail
 }
 
+# Writes a script that reports the repository it lies in via the given function.
+# The function must not be defined by more than one script in a single test, as
+# runfiles_current_repository resolves the path of the script that defines it.
+function write_current_repository_lib() {
+  local -r path="$1"
+  local -r func="$2"
+  local -r result="${3:-}"
+  mkdir -p "$(dirname "$path")"
+  if [[ -n "$result" ]]; then
+    # A stale copy that must never be sourced.
+    cat > "$path" << EOF
+function $func() {
+  echo "$result"
+}
+EOF
+  else
+    cat > "$path" << EOF
+function $func() {
+  runfiles_current_repository 1
+}
+EOF
+  fi
+}
+
+function test_current_repository_directory_based() {
+  local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
+
+  export RUNFILES_DIR="${tmpdir}/mock/runfiles"
+  export RUNFILES_MANIFEST_FILE=
+  write_current_repository_lib "$RUNFILES_DIR/protobuf+3.19.2/foo/lib.sh" repo_of_other
+  write_current_repository_lib "$RUNFILES_DIR/_main/bar/lib.sh" repo_of_main
+  source "$runfiles_lib_path"
+
+  source "$(rlocation "protobuf+3.19.2/foo/lib.sh" "")" || fail
+  [[ "$(repo_of_other || echo failed)" == "protobuf+3.19.2" ]] || fail
+
+  source "$(rlocation "_main/bar/lib.sh" "")" || fail
+  [[ "$(repo_of_main || echo failed)" == "" ]] || fail
+}
+
+function test_current_repository_manifest_based() {
+  local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
+
+  export RUNFILES_DIR=
+  export RUNFILES_MANIFEST_FILE="$tmpdir/foo.runfiles_manifest"
+  write_current_repository_lib "$tmpdir/protobuf+3.19.2/foo/lib.sh" repo_of_other
+  write_current_repository_lib "$tmpdir/_main/bar/lib.sh" repo_of_main
+  cat > "$RUNFILES_MANIFEST_FILE" << EOF
+protobuf+3.19.2/foo/lib.sh $tmpdir/protobuf+3.19.2/foo/lib.sh
+_main/bar/lib.sh $tmpdir/_main/bar/lib.sh
+EOF
+  source "$runfiles_lib_path"
+
+  source "$(rlocation "protobuf+3.19.2/foo/lib.sh" "")" || fail
+  [[ "$(repo_of_other || echo failed)" == "protobuf+3.19.2" ]] || fail
+
+  source "$(rlocation "_main/bar/lib.sh" "")" || fail
+  [[ "$(repo_of_main || echo failed)" == "" ]] || fail
+}
+
+# Both envvars are set at the same time e.g. on Windows with --enable_runfiles,
+# where the runfiles directory contains the MANIFEST file that
+# runfiles_export_envvars promotes to RUNFILES_MANIFEST_FILE. The manifest maps
+# rlocation paths to the locations of the *original* files, so a caller that had
+# been looked up in the runfiles directory could never be found in it.
+function test_current_repository_directory_and_manifest_based() {
+  local tmpdir="$(mktemp -d $TEST_TMPDIR/tmp.XXXXXXXX)"
+
+  export RUNFILES_DIR="${tmpdir}/mock/runfiles"
+  export RUNFILES_MANIFEST_FILE="$RUNFILES_DIR/MANIFEST"
+  write_current_repository_lib "$tmpdir/protobuf+3.19.2/foo/lib.sh" repo_of_other
+  write_current_repository_lib "$tmpdir/_main/bar/lib.sh" repo_of_main
+  # The runfiles directory may hold stale contents, so the manifest wins.
+  mkdir -p "$RUNFILES_DIR"
+  write_current_repository_lib "$RUNFILES_DIR/protobuf+3.19.2/foo/lib.sh" repo_of_other stale
+  write_current_repository_lib "$RUNFILES_DIR/_main/bar/lib.sh" repo_of_main stale
+  cat > "$RUNFILES_MANIFEST_FILE" << EOF
+protobuf+3.19.2/foo/lib.sh $tmpdir/protobuf+3.19.2/foo/lib.sh
+_main/bar/lib.sh $tmpdir/_main/bar/lib.sh
+EOF
+  source "$runfiles_lib_path"
+
+  [[ "$(rlocation "protobuf+3.19.2/foo/lib.sh" "" || echo failed)" == "$tmpdir/protobuf+3.19.2/foo/lib.sh" ]] || fail
+  source "$(rlocation "protobuf+3.19.2/foo/lib.sh" "")" || fail
+  [[ "$(repo_of_other || echo failed)" == "protobuf+3.19.2" ]] || fail
+
+  [[ "$(rlocation "_main/bar/lib.sh" "" || echo failed)" == "$tmpdir/_main/bar/lib.sh" ]] || fail
+  source "$(rlocation "_main/bar/lib.sh" "")" || fail
+  [[ "$(repo_of_main || echo failed)" == "" ]] || fail
+}
+
 function test_directory_based_envvars() {
   export RUNFILES_DIR=mock/runfiles
   export RUNFILES_MANIFEST_FILE=