Disable runfiles on Windows.

This adds a new configuration option that allows disabling the creation of symlink forest for runfiles.
On Windows, symlink forest is disabled by default; only the runfiles manifest is created.

For shell tests, a function 'rlocation' is provided that converts from runfiles location to a real location.

Work towards #1212.

--
MOS_MIGRATED_REVID=125439553
diff --git a/scripts/bootstrap/compile.sh b/scripts/bootstrap/compile.sh
index a241213..f0a797e 100755
--- a/scripts/bootstrap/compile.sh
+++ b/scripts/bootstrap/compile.sh
@@ -208,7 +208,12 @@
 cat <<'EOF' >${ARCHIVE_DIR}/_embedded_binaries/build-runfiles${EXE_EXT}
 #!/bin/sh
 win_arg='--windows_compatible'
-if [ $1 == $win_arg ];
+manifest_arg='--manifest_only'
+if [ $1 == $win_arg ] || [ $1 == $manifest_arg ];
+then
+     shift
+fi
+if [ $1 == $win_arg ] || [ $1 == $manifest_arg ];
 then
      shift
 fi
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
index 98540d2..61ac369 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RunfilesSupport.java
@@ -312,7 +312,8 @@
                 outputManifest,
                 /*filesetTree=*/ false,
                 config.getShExecutable(),
-                config.getLocalShellEnvironment()));
+                config.getLocalShellEnvironment(),
+                config.runfilesEnabled()));
     return outputManifest;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
index 5ebe26f..ef2fde9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeAction.java
@@ -43,11 +43,11 @@
   private final boolean filesetTree;
   private final PathFragment shExecutable;
   private final ImmutableMap<String, String> shellEnviroment;
+  private final boolean enableRunfiles;
 
   /**
    * Creates SymlinkTreeAction instance.
-   *
-   * @param owner action owner
+   *  @param owner action owner
    * @param inputManifest the input runfiles manifest
    * @param artifactMiddleman the middleman artifact representing all the files the symlinks
    *                          point to (on Windows we need to know if the target of a "symlink" is
@@ -56,7 +56,7 @@
    *                       (must have "MANIFEST" base name). Symlink tree root
    *                       will be set to the artifact's parent directory.
    * @param filesetTree true if this is fileset symlink tree,
-   *                    false if this is a runfiles symlink tree.
+   * @param enableRunfiles true is the actual symlink tree needs to be created.
    */
   public SymlinkTreeAction(
       ActionOwner owner,
@@ -65,7 +65,8 @@
       Artifact outputManifest,
       boolean filesetTree,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment) {
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles) {
     super(owner, computeInputs(inputManifest, artifactMiddleman), ImmutableList.of(outputManifest));
     Preconditions.checkArgument(outputManifest.getPath().getBaseName().equals("MANIFEST"));
     this.inputManifest = inputManifest;
@@ -73,6 +74,7 @@
     this.filesetTree = filesetTree;
     this.shExecutable = shExecutable;
     this.shellEnviroment = shellEnvironment;
+    this.enableRunfiles = enableRunfiles;
   }
 
   private static ImmutableList<Artifact> computeInputs(
@@ -129,6 +131,7 @@
     actionExecutionContext
         .getExecutor()
         .getContext(SymlinkTreeActionContext.class)
-        .createSymlinks(this, actionExecutionContext, shExecutable, shellEnviroment);
+        .createSymlinks(
+            this, actionExecutionContext, shExecutable, shellEnviroment, enableRunfiles);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
index fa9182a..11c1417 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/SymlinkTreeActionContext.java
@@ -31,6 +31,7 @@
       SymlinkTreeAction action,
       ActionExecutionContext actionExecutionContext,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment)
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles)
       throws ActionExecutionException, InterruptedException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index c37bac4..0499743 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -855,6 +855,14 @@
             + "static globally defined ones")
     public boolean useDynamicConfigurations;
 
+    @Option(
+      name = "experimental_enable_runfiles",
+      defaultValue = "auto",
+      category = "undocumented",
+      help = "Enable runfiles; off on Windows, on on other platforms"
+    )
+    public TriState enableRunfiles;
+
     @Override
     public FragmentOptions getHost(boolean fallback) {
       Options host = (Options) getDefault();
@@ -2350,6 +2358,17 @@
     return options.cpu;
   }
 
+  public boolean runfilesEnabled() {
+    switch (options.enableRunfiles) {
+      case YES:
+        return true;
+      case NO:
+        return false;
+      default:
+        return OS.getCurrent() != OS.WINDOWS;
+    }
+  }
+
   /**
    * Returns true if the configuration performs static linking.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
index 0e47c2b..6a53d7d 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeHelper.java
@@ -89,7 +89,9 @@
    */
   public void createSymlinksUsingCommand(Path execRoot,
       BuildConfiguration config, BinTools binTools) throws CommandException {
-    List<String> argv = getSpawnArgumentList(execRoot, binTools, config.getShExecutable());
+    List<String> argv =
+        getSpawnArgumentList(
+            execRoot, binTools, config.getShExecutable(), config.runfilesEnabled());
 
     CommandBuilder builder = new CommandBuilder();
     builder.addArgs(argv);
@@ -105,19 +107,25 @@
    * block for undetermined period of time. If it is interrupted during
    * that wait, ExecException will be thrown but interrupted bit will be
    * preserved.
-   *  @param action action instance that requested symlink tree creation
+   * @param action action instance that requested symlink tree creation
    * @param actionExecutionContext Services that are in the scope of the action.
    * @param shExecutable
+   * @param enableRunfiles
    */
   public void createSymlinks(
       AbstractAction action,
       ActionExecutionContext actionExecutionContext,
       BinTools binTools,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment)
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles)
       throws ExecException, InterruptedException {
-    List<String> args = getSpawnArgumentList(
-        actionExecutionContext.getExecutor().getExecRoot(), binTools, shExecutable);
+    List<String> args =
+        getSpawnArgumentList(
+            actionExecutionContext.getExecutor().getExecRoot(),
+            binTools,
+            shExecutable,
+            enableRunfiles);
     try (ResourceHandle handle =
         ResourceManager.instance().acquireResources(action, RESOURCE_SET)) {
       actionExecutionContext.getExecutor().getSpawnActionContext(action.getMnemonic()).exec(
@@ -130,7 +138,7 @@
    * Returns the complete argument list build-runfiles has to be called with.
    */
   private List<String> getSpawnArgumentList(
-      Path execRoot, BinTools binTools, PathFragment shExecutable) {
+      Path execRoot, BinTools binTools, PathFragment shExecutable, boolean enableRunfiles) {
     PathFragment path = binTools.getExecPath(BUILD_RUNFILES);
     Preconditions.checkNotNull(path, BUILD_RUNFILES + " not found in embedded tools");
 
@@ -156,6 +164,10 @@
       args.add("--windows_compatible");
     }
 
+    if (!enableRunfiles) {
+      args.add("--manifest_only");
+    }
+
     args.add(inputManifest.getPathString());
     args.add(symlinkTreeRoot.getPathString());
 
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
index e34a6b4..dc54714 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SymlinkTreeStrategy.java
@@ -48,7 +48,8 @@
       SymlinkTreeAction action,
       ActionExecutionContext actionExecutionContext,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment)
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles)
       throws ActionExecutionException, InterruptedException {
     Executor executor = actionExecutionContext.getExecutor();
     try (AutoProfiler p =
@@ -64,7 +65,12 @@
               action.isFilesetTree(), helper.getSymlinkTreeRoot());
         } else {
           helper.createSymlinks(
-              action, actionExecutionContext, binTools, shExecutable, shellEnvironment);
+              action,
+              actionExecutionContext,
+              binTools,
+              shExecutable,
+              shellEnvironment,
+              enableRunfiles);
         }
       } catch (ExecException e) {
         throw e.toActionExecutionException(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java b/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java
index 3790799..fc736ae 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/NativeLibs.java
@@ -169,7 +169,8 @@
             outputManifest,
             false,
             ruleContext.getConfiguration().getShExecutable(),
-            ruleContext.getConfiguration().getLocalShellEnvironment()));
+            ruleContext.getConfiguration().getLocalShellEnvironment(),
+            ruleContext.getConfiguration().runfilesEnabled()));
     return outputManifest;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
index 72ff4e8..2dd01c3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
@@ -71,8 +71,14 @@
       throws ExecException, InterruptedException {
     Path runfilesDir = null;
     try {
-      runfilesDir = TestStrategy.getLocalRunfilesDirectory(action, actionExecutionContext, binTools,
-          action.getShExecutable(), action.getLocalShellEnvironment());
+      runfilesDir =
+          TestStrategy.getLocalRunfilesDirectory(
+              action,
+              actionExecutionContext,
+              binTools,
+              action.getShExecutable(),
+              action.getLocalShellEnvironment(),
+              action.isEnableRunfiles());
     } catch (ExecException e) {
       throw new TestExecException(e.getMessage());
     }
@@ -161,6 +167,9 @@
     vars.put("TEST_TMPDIR", tmpDir.getPathString());
     vars.put("TEST_WORKSPACE", action.getRunfilesPrefix());
     vars.put("XML_OUTPUT_FILE", resolvedPaths.getXmlOutputPath().getPathString());
+    if (!action.isEnableRunfiles()) {
+      vars.put("RUNFILES_MANIFEST_ONLY", "1");
+    }
 
     return vars;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/rules/test/TestRunnerAction.java
index 687d622..473ec92 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/TestRunnerAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/TestRunnerAction.java
@@ -562,6 +562,10 @@
     return configuration.getLocalShellEnvironment();
   }
 
+  public boolean isEnableRunfiles() {
+    return configuration.runfilesEnabled();
+  }
+
   /**
    * The same set of paths as the parent test action, resolved against a given exec root.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
index e1bd9de..68cefe1 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
@@ -357,7 +357,8 @@
       ActionExecutionContext actionExecutionContext,
       BinTools binTools,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment)
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles)
       throws ExecException, InterruptedException {
     TestTargetExecutionSettings execSettings = testAction.getExecutionSettings();
 
@@ -380,8 +381,14 @@
     long startTime = Profiler.nanoTimeMaybe();
     synchronized (execSettings.getInputManifest()) {
       Profiler.instance().logSimpleTask(startTime, ProfilerTask.WAIT, testAction);
-      updateLocalRunfilesDirectory(testAction, runfilesDir, actionExecutionContext, binTools,
-          shExecutable, shellEnvironment);
+      updateLocalRunfilesDirectory(
+          testAction,
+          runfilesDir,
+          actionExecutionContext,
+          binTools,
+          shExecutable,
+          shellEnvironment,
+          enableRunfiles);
     }
 
     return runfilesDir;
@@ -399,7 +406,8 @@
       ActionExecutionContext actionExecutionContext,
       BinTools binTools,
       PathFragment shExecutable,
-      ImmutableMap<String, String> shellEnvironment)
+      ImmutableMap<String, String> shellEnvironment,
+      boolean enableRunfiles)
       throws ExecException, InterruptedException {
     Executor executor = actionExecutionContext.getExecutor();
 
@@ -423,7 +431,12 @@
             runfilesDir.relativeTo(executor.getExecRoot()), /* filesetTree= */
             false)
         .createSymlinks(
-            testAction, actionExecutionContext, binTools, shExecutable, shellEnvironment);
+            testAction,
+            actionExecutionContext,
+            binTools,
+            shExecutable,
+            shellEnvironment,
+            enableRunfiles);
 
     executor.getEventHandler().handle(Event.progress(testAction.getProgressMessage()));
   }
diff --git a/src/main/tools/build-runfiles.cc b/src/main/tools/build-runfiles.cc
index ae45200..d48df3c 100644
--- a/src/main/tools/build-runfiles.cc
+++ b/src/main/tools/build-runfiles.cc
@@ -111,9 +111,11 @@
 
 class RunfilesCreator {
  public:
-  explicit RunfilesCreator(const std::string &output_base, bool windows_compatible)
+  explicit RunfilesCreator(const std::string &output_base,
+                           bool windows_compatible, bool manifest_only)
       : output_base_(output_base),
         windows_compatible_(windows_compatible),
+        manifest_only_(manifest_only),
         output_filename_("MANIFEST"),
         temp_filename_(output_filename_ + ".tmp") {
     SetupOutputBase();
@@ -207,8 +209,10 @@
            output_filename_.c_str());
     }
 
-    ScanTreeAndPrune(".");
-    CreateFiles();
+    if (!manifest_only_) {
+      ScanTreeAndPrune(".");
+      CreateFiles();
+    }
 
     // rename output file into place
     if (rename(temp_filename_.c_str(), output_filename_.c_str()) != 0) {
@@ -428,6 +432,7 @@
  private:
   std::string output_base_;
   bool windows_compatible_;
+  bool manifest_only_;
   std::string output_filename_;
   std::string temp_filename_;
 
@@ -441,6 +446,7 @@
   bool allow_relative = false;
   bool use_metadata = false;
   bool windows_compatible = false;
+  bool manifest_only = false;
 
   while (argc >= 1) {
     if (strcmp(argv[0], "--allow_relative") == 0) {
@@ -452,6 +458,10 @@
     } else if (strcmp(argv[0], "--windows_compatible") == 0) {
       windows_compatible = true;
       argc--; argv++;
+    } else if (strcmp(argv[0], "--manifest_only") == 0) {
+      manifest_only = true;
+      argc--;
+      argv++;
     } else {
       break;
     }
@@ -477,7 +487,8 @@
     manifest_file = std::string(cwd_buf) + '/' + manifest_file;
   }
 
-  RunfilesCreator runfiles_creator(output_base_dir, windows_compatible);
+  RunfilesCreator runfiles_creator(output_base_dir, windows_compatible,
+                                   manifest_only);
   runfiles_creator.ReadManifest(manifest_file, allow_relative, use_metadata);
   runfiles_creator.CreateRunfiles();
 
diff --git a/src/test/shell/bazel/bazel_windows_cpp_test.sh b/src/test/shell/bazel/bazel_windows_cpp_test.sh
index 2a523cc..4bff4c7 100755
--- a/src/test/shell/bazel/bazel_windows_cpp_test.sh
+++ b/src/test/shell/bazel/bazel_windows_cpp_test.sh
@@ -18,7 +18,7 @@
 #
 
 # Load test environment
-source $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test-setup.sh \
+source $(rlocation io_bazel/src/test/shell/bazel/test-setup.sh) \
   || { echo "test-setup.sh not found!" >&2; exit 1; }
 
 if ! is_windows; then
diff --git a/src/test/shell/bazel/test-setup.sh b/src/test/shell/bazel/test-setup.sh
index 05f265f..77b23bb 100755
--- a/src/test/shell/bazel/test-setup.sh
+++ b/src/test/shell/bazel/test-setup.sh
@@ -32,15 +32,6 @@
 # Here we unset variable that were set by the invoking Blaze instance
 unset JAVA_RUNFILES
 
-function is_windows() {
-  # On windows, the shell test actually running on msys
-  if [ "${PLATFORM}" == "msys_nt-6.1" ]; then
-    true
-  else
-    false
-  fi
-}
-
 function setup_bazelrc() {
   # enable batch mode when running on windows
   if is_windows; then
diff --git a/src/test/shell/bazel/testenv.sh b/src/test/shell/bazel/testenv.sh
index 8733ead..43c14fd 100755
--- a/src/test/shell/bazel/testenv.sh
+++ b/src/test/shell/bazel/testenv.sh
@@ -21,24 +21,39 @@
 BAZEL_RUNFILES="$TEST_SRCDIR/io_bazel"
 
 # Load the unit-testing framework
-source "${BAZEL_RUNFILES}/src/test/shell/unittest.bash" || \
+source "$(rlocation io_bazel/src/test/shell/unittest.bash)" || \
   { echo "Failed to source unittest.bash" >&2; exit 1; }
 
 # WORKSPACE file
 workspace_file="${BAZEL_RUNFILES}/WORKSPACE"
 
 # Bazel
-bazel_tree="${BAZEL_RUNFILES}/src/test/shell/bazel/doc-srcs.zip"
-bazel="${BAZEL_RUNFILES}/src/bazel"
+bazel_tree="$(rlocation io_bazel/src/test/shell/bazel/doc-srcs.zip)"
+bazel="$(rlocation io_bazel/src/bazel)"
 bazel_data="${BAZEL_RUNFILES}"
 
+# Windows
+PLATFORM="$(uname -s | tr 'A-Z' 'a-z')"
+function is_windows() {
+  # On windows, the shell test actually running on msys
+  if [ "${PLATFORM}" == "msys_nt-6.1" ]; then
+    true
+  else
+    false
+  fi
+}
+
 # Java
-jdk_dir="${TEST_SRCDIR}/local_jdk"
-langtools="${BAZEL_RUNFILES}/src/test/shell/bazel/langtools.jar"
+if is_windows; then
+  jdk_dir="$(cygpath -m $(cd $(rlocation local_jdk/bin/java.exe)/../..; pwd))"
+else
+  jdk_dir="${TEST_SRCDIR}/local_jdk"
+fi
+langtools="$(rlocation io_bazel/src/test/shell/bazel/langtools.jar)"
 
 # Tools directory location
-tools_dir="${BAZEL_RUNFILES}/tools"
-langtools_dir="${BAZEL_RUNFILES}/third_party/java/jdk/langtools"
+tools_dir="$(dirname $(rlocation io_bazel/tools/BUILD))"
+langtools_dir="$(dirname $(rlocation io_bazel/third_party/java/jdk/langtools/BUILD))"
 EXTRA_BAZELRC="build --ios_sdk_version=8.4"
 
 # Java tooling
@@ -80,7 +95,6 @@
 python_server="${BAZEL_RUNFILES}/src/test/shell/bazel/testing_server.py"
 
 # Third-party
-PLATFORM="$(uname -s | tr 'A-Z' 'a-z')"
 MACHINE_TYPE="$(uname -m)"
 MACHINE_IS_64BIT='no'
 if [ "${MACHINE_TYPE}" = 'amd64' -o "${MACHINE_TYPE}" = 'x86_64' ]; then
@@ -152,7 +166,7 @@
 
 # Copy the examples of the base workspace
 function copy_examples() {
-  EXAMPLE="$BAZEL_RUNFILES/examples"
+  EXAMPLE="$(cd $(dirname $(rlocation io_bazel/examples/cpp/BUILD))/..; pwd)"
   cp -RL ${EXAMPLE} .
   chmod -R +w .
 }
diff --git a/src/test/shell/unittest.bash b/src/test/shell/unittest.bash
index c7ff53f..70e311f 100644
--- a/src/test/shell/unittest.bash
+++ b/src/test/shell/unittest.bash
@@ -525,7 +525,8 @@
 }
 
 # Multi-platform timestamp function
-if [ "$(uname -s | tr 'A-Z' 'a-z')" = "linux" ]; then
+UNAME=$(uname -s | tr 'A-Z' 'a-z')
+if [ "$UNAME" = "linux" ] || [ "$UNAME" = "msys_nt-6.1" ]; then
     function timestamp() {
       echo $(($(date +%s%N)/1000000))
     }
diff --git a/tools/test/test-setup.sh b/tools/test/test-setup.sh
index 6fa814a..150b9fa 100755
--- a/tools/test/test-setup.sh
+++ b/tools/test/test-setup.sh
@@ -28,12 +28,36 @@
 export GTEST_TMP_DIR="${TEST_TMPDIR}"
 
 DIR="$TEST_SRCDIR"
+RUNFILES_MANIFEST_FILE=$DIR/MANIFEST
+
+if [ -z "$RUNFILES_MANIFEST_ONLY" ]; then
+  function rlocation() {
+    if [[ "$1" = /* ]]; then
+      echo $1
+    else
+      echo "$(dirname $RUNFILES_MANIFEST_FILE)/$1"
+    fi
+  }
+else
+  function rlocation() {
+    if [[ "$1" = /* ]]; then
+      echo $1
+    else
+      echo $(grep "^$1 " $MANIFEST_FILE | awk '{ print $2 }')
+    fi
+  }
+fi
+
+export -f rlocation
+export RUNFILES_MANIFEST_FILE
 
 if [ ! -z "$TEST_WORKSPACE" ]
 then
   DIR="$DIR"/"$TEST_WORKSPACE"
 fi
 
+
+
 # normal commands are run in the exec-root where they have access to
 # the entire source tree. By chdir'ing to the runfiles root, tests only
 # have direct access to their declared dependencies.
@@ -48,8 +72,18 @@
 # If the test is at the top of the tree, we have to add '.' to $PATH,
 PATH=".:$PATH"
 
+
+TEST_NAME=$1
+shift
+
+if [[ "$TEST_NAME" = /* ]]; then
+  EXE="${TEST_NAME}"
+else
+  EXE="$(rlocation $TEST_WORKSPACE/$TEST_NAME)"
+fi
+
 exitCode=0
-"$@" || exitCode=$?
+"${EXE}" "$@" || exitCode=$?
 
 if [ -n "${XML_OUTPUT_FILE-}" -a ! -f "${XML_OUTPUT_FILE-}" ]; then
   # Create a default XML output file if the test runner hasn't generated it
@@ -63,8 +97,8 @@
   cat <<EOF >${XML_OUTPUT_FILE}
 <?xml version="1.0" encoding="UTF-8"?>
 <testsuites>
-  <testsuite name="$1" tests="1" failures="0" errors="$errors">
-    <testcase name="$1" status="run">$error_msg</testcase>
+  <testsuite name="$TEST_NAME" tests="1" failures="0" errors="$errors">
+    <testcase name="$TEST_NAME" status="run">$error_msg</testcase>
   </testsuite>
 </testsuites>
 EOF