c++,runfiles: move runfiles library

Move the half-done C++ runfiles library to
`//tools/cpp/runfiles`. (The Python and
Bash runfiles libraries are already under
`//tools/<language>/runfiles`.)

See https://github.com/bazelbuild/bazel/issues/4460

Change-Id: I1006f7f81462ea0e4b1de1adcdba89e386d4f9e7

Closes #5107.

Change-Id: I1006f7f81462ea0e4b1de1adcdba89e386d4f9e7
PiperOrigin-RevId: 194763392
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index 57007d32..baed014 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -360,7 +360,17 @@
 
 filegroup(
     name = "srcs",
-    srcs = glob(["**"]),
+    srcs = glob(["**"]) + [
+        "//tools/cpp/runfiles:srcs",
+    ],
+)
+
+filegroup(
+    name = "embedded_tools",
+    srcs = glob(["**"]) + [
+        "//tools/cpp/runfiles:embedded_tools",
+    ],
+    visibility = ["//tools:__pkg__"],
 )
 
 filegroup(
diff --git a/tools/cpp/runfiles/BUILD b/tools/cpp/runfiles/BUILD
new file mode 100644
index 0000000..9afbd01
--- /dev/null
+++ b/tools/cpp/runfiles/BUILD
@@ -0,0 +1,40 @@
+package(default_visibility = ["//visibility:private"])
+
+filegroup(
+    name = "srcs",
+    srcs = glob(
+        ["**"],
+        exclude = [
+            ".*",
+            "*~",
+        ],
+    ),
+    visibility = ["//tools/cpp:__pkg__"],
+)
+
+filegroup(
+    name = "embedded_tools",
+    srcs = [
+        "BUILD.tools",
+        "runfiles.cc",
+    ],
+    visibility = ["//tools/cpp:__pkg__"],
+)
+
+cc_library(
+    name = "runfiles",
+    testonly = 1,
+    srcs = ["runfiles.cc"],
+    hdrs = ["runfiles.h"],
+)
+
+cc_test(
+    name = "runfiles_test",
+    srcs = ["runfiles_test.cc"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":runfiles",
+        "//src/main/cpp/util:file",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/tools/cpp/runfiles/BUILD.tools b/tools/cpp/runfiles/BUILD.tools
new file mode 100644
index 0000000..8f0a9cf
--- /dev/null
+++ b/tools/cpp/runfiles/BUILD.tools
@@ -0,0 +1,10 @@
+# This package will host the C++ runfiles library when it's finally released.
+
+# TODO(laszlocsomor): uncomment the cc_library below when the C++ runfiles library is ready to be
+# released.
+# cc_library(
+#     name = "runfiles",
+#     srcs = ["runfiles.cc"],
+#     hdrs = ["runfiles.h"],
+#     visibility = ["//visibility:public"],
+# )
diff --git a/tools/cpp/runfiles/runfiles.cc b/tools/cpp/runfiles/runfiles.cc
new file mode 100644
index 0000000..2f25b53
--- /dev/null
+++ b/tools/cpp/runfiles/runfiles.cc
@@ -0,0 +1,354 @@
+// 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.
+#include "tools/cpp/runfiles/runfiles.h"
+
+#ifdef COMPILER_MSVC
+#include <windows.h>
+#else  // not COMPILER_MSVC
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif  // COMPILER_MSVC
+
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
+#ifdef COMPILER_MSVC
+#include <memory>
+#endif  // COMPILER_MSVC
+
+namespace bazel {
+namespace tools {
+namespace cpp {
+namespace runfiles {
+
+using std::function;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace {
+
+bool starts_with(const string& s, const string& prefix) {
+  if (prefix.empty()) {
+    return true;
+  }
+  if (s.empty()) {
+    return false;
+  }
+  return s.find(prefix) == 0;
+}
+
+bool contains(const string& s, const string& substr) {
+  if (substr.empty()) {
+    return true;
+  }
+  if (s.empty()) {
+    return false;
+  }
+  return s.find(substr) != string::npos;
+}
+
+bool ends_with(const string& s, const string& suffix) {
+  if (suffix.empty()) {
+    return true;
+  }
+  if (s.empty()) {
+    return false;
+  }
+  return s.rfind(suffix) == s.size() - suffix.size();
+}
+
+class RunfilesImpl : public Runfiles {
+ public:
+  static Runfiles* Create(const string& argv0,
+                          function<string(const string&)> env_lookup,
+                          string* error);
+
+  string Rlocation(const string& path) const override;
+
+  // Returns the runtime-location of a given runfile.
+  //
+  // This method assumes that the caller already validated the `path`. See
+  // Runfiles::Rlocation for requirements.
+  virtual string RlocationChecked(const string& path) const = 0;
+
+ protected:
+  RunfilesImpl() {}
+  virtual ~RunfilesImpl() {}
+};
+
+// Runfiles implementation that parses a runfiles-manifest to look up runfiles.
+class ManifestBased : public RunfilesImpl {
+ public:
+  // Returns a new `ManifestBased` instance.
+  // Reads the file at `manifest_path` to build a map of the runfiles.
+  // Returns nullptr upon failure.
+  static ManifestBased* Create(const string& manifest_path, string* error);
+
+  vector<pair<string, string> > EnvVars() const override;
+  string RlocationChecked(const string& path) const override;
+
+ private:
+  ManifestBased(const string& manifest_path, map<string, string>&& runfiles_map)
+      : manifest_path_(manifest_path), runfiles_map_(runfiles_map) {}
+
+  ManifestBased(const ManifestBased&) = delete;
+  ManifestBased(ManifestBased&&) = delete;
+  ManifestBased& operator=(const ManifestBased&) = delete;
+  ManifestBased& operator=(ManifestBased&&) = delete;
+
+  string RunfilesDir() const;
+  static bool ParseManifest(const string& path, map<string, string>* result,
+                            string* error);
+
+  const string manifest_path_;
+  const map<string, string> runfiles_map_;
+};
+
+// Runfiles implementation that appends runfiles paths to the runfiles root.
+class DirectoryBased : public RunfilesImpl {
+ public:
+  DirectoryBased(string runfiles_path)
+      : runfiles_path_(std::move(runfiles_path)) {}
+  vector<pair<string, string> > EnvVars() const override;
+  string RlocationChecked(const string& path) const override;
+
+ private:
+  DirectoryBased(const DirectoryBased&) = delete;
+  DirectoryBased(DirectoryBased&&) = delete;
+  DirectoryBased& operator=(const DirectoryBased&) = delete;
+  DirectoryBased& operator=(DirectoryBased&&) = delete;
+
+  const string runfiles_path_;
+};
+
+bool IsReadableFile(const string& path) {
+  return std::ifstream(path).is_open();
+}
+
+bool IsDirectory(const string& path) {
+#ifdef COMPILER_MSVC
+  DWORD attrs = GetFileAttributesA(path.c_str());
+  return (attrs != INVALID_FILE_ATTRIBUTES) &&
+         (attrs & FILE_ATTRIBUTE_DIRECTORY);
+#else
+  struct stat buf;
+  return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode);
+#endif
+}
+
+Runfiles* RunfilesImpl::Create(const string& argv0,
+                               function<string(const string&)> env_lookup,
+                               string* error) {
+  string manifest(std::move(env_lookup("RUNFILES_MANIFEST_FILE")));
+  if (!manifest.empty()) {
+    return ManifestBased::Create(manifest, error);
+  }
+
+  string directory(std::move(env_lookup("RUNFILES_DIR")));
+  if (!directory.empty()) {
+    return new DirectoryBased(directory);
+  }
+
+  manifest = argv0 + ".runfiles_manifest";
+  if (IsReadableFile(manifest)) {
+    return CreateManifestBased(manifest, error);
+  }
+
+  manifest = argv0 + ".runfiles/MANIFEST";
+  if (IsReadableFile(manifest)) {
+    return CreateManifestBased(manifest, error);
+  }
+
+  directory = argv0 + ".runfiles";
+  if (IsDirectory(directory)) {
+    return CreateDirectoryBased(std::move(directory), error);
+  }
+
+  if (error) {
+    std::ostringstream err;
+    err << "ERROR: " << __FILE__ << "(" << __LINE__
+        << "): cannot find runfiles (argv0=\"" << argv0 << "\")";
+    *error = err.str();
+  }
+  return nullptr;
+}
+
+bool IsAbsolute(const string& path) {
+  if (path.empty()) {
+    return false;
+  }
+  char c = path.front();
+  return (c == '/' && (path.size() < 2 || path[1] != '/')) ||
+         (path.size() >= 3 &&
+          ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) &&
+          path[1] == ':' && (path[2] == '\\' || path[2] == '/'));
+}
+
+string GetEnv(const string& key) {
+#ifdef COMPILER_MSVC
+  DWORD size = ::GetEnvironmentVariableA(key.c_str(), NULL, 0);
+  if (size == 0) {
+    return std::move(string());  // unset or empty envvar
+  }
+  std::unique_ptr<char[]> value(new char[size]);
+  ::GetEnvironmentVariableA(key.c_str(), value.get(), size);
+  return move(string(value.get()));
+#else
+  char* result = getenv(key.c_str());
+  return std::move((result == NULL) ? string() : string(result));
+#endif
+}
+
+string RunfilesImpl::Rlocation(const string& path) const {
+  if (path.empty() || starts_with(path, "../") || contains(path, "/..") ||
+      starts_with(path, "./") || contains(path, "/./") ||
+      ends_with(path, "/.") || contains(path, "//")) {
+    return std::move(string());
+  }
+  if (IsAbsolute(path)) {
+    return path;
+  }
+  return RlocationChecked(path);
+}
+
+ManifestBased* ManifestBased::Create(const string& manifest_path,
+                                     string* error) {
+  map<string, string> runfiles;
+  return ParseManifest(manifest_path, &runfiles, error)
+             ? new ManifestBased(manifest_path, std::move(runfiles))
+             : nullptr;
+}
+
+string ManifestBased::RlocationChecked(const string& path) const {
+  const auto value = runfiles_map_.find(path);
+  return std::move(value == runfiles_map_.end() ? string() : value->second);
+}
+
+vector<pair<string, string> > ManifestBased::EnvVars() const {
+  return std::move(vector<pair<string, string> >(
+      {std::make_pair("RUNFILES_MANIFEST_FILE", manifest_path_),
+       // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
+       // pick up RUNFILES_DIR.
+       std::make_pair("JAVA_RUNFILES", RunfilesDir())}));
+}
+
+string ManifestBased::RunfilesDir() const {
+  const auto pos1 = manifest_path_.size() - 9;   // "_MANIFEST"
+  const auto pos2 = manifest_path_.size() - 18;  // ".runfiles_manifest"
+  if (manifest_path_.rfind("/MANIFEST") == pos1 ||
+      manifest_path_.rfind("\\MANIFEST") == pos1 ||
+      manifest_path_.rfind(".runfiles_manifest") == pos2) {
+    return std::move(manifest_path_.substr(0, pos1));  // remove ".MANIFEST"
+  } else {
+    return std::move(string());
+  }
+}
+
+bool ManifestBased::ParseManifest(const string& path,
+                                  map<string, string>* result, string* error) {
+  std::ifstream stm(path);
+  if (!stm.is_open()) {
+    if (error) {
+      std::ostringstream err;
+      err << "ERROR: " << __FILE__ << "(" << __LINE__
+          << "): cannot open runfiles manifest \"" << path << "\"";
+      *error = err.str();
+    }
+    return false;
+  }
+  string line;
+  std::getline(stm, line);
+  size_t line_count = 1;
+  while (!line.empty()) {
+    string::size_type idx = line.find_first_of(' ');
+    if (idx == string::npos) {
+      if (error) {
+        std::ostringstream err;
+        err << "ERROR: " << __FILE__ << "(" << __LINE__
+            << "): bad runfiles manifest entry in \"" << path << "\" line #"
+            << line_count << ": \"" << line << "\"";
+        *error = err.str();
+      }
+      return false;
+    }
+    (*result)[line.substr(0, idx)] = line.substr(idx + 1);
+    std::getline(stm, line);
+    ++line_count;
+  }
+  return true;
+}
+
+string DirectoryBased::RlocationChecked(const string& path) const {
+  return std::move(runfiles_path_ + "/" + path);
+}
+
+vector<pair<string, string> > DirectoryBased::EnvVars() const {
+  return std::move(vector<pair<string, string> >(
+      {std::make_pair("RUNFILES_DIR", runfiles_path_),
+       // TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
+       // pick up RUNFILES_DIR.
+       std::make_pair("JAVA_RUNFILES", runfiles_path_)}));
+}
+
+}  // namespace
+
+namespace testing {
+
+Runfiles* TestOnly_CreateRunfiles(const std::string& argv0,
+                                  function<string(const string&)> env_lookup,
+                                  string* error) {
+  return RunfilesImpl::Create(argv0, env_lookup, error);
+}
+
+bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); }
+
+}  // namespace testing
+
+Runfiles* Runfiles::Create(const string& argv0, string* error) {
+  return RunfilesImpl::Create(
+      argv0,
+      [](const string& key) {
+        if (key == "RUNFILES_MANIFEST_FILE" || key == "RUNFILES_DIR") {
+          string val(GetEnv(key));
+          return std::move(val);
+        } else {
+          return std::move(string());
+        }
+      },
+      error);
+}
+
+Runfiles* Runfiles::CreateManifestBased(const string& manifest_path,
+                                        string* error) {
+  return ManifestBased::Create(manifest_path, error);
+}
+
+Runfiles* Runfiles::CreateDirectoryBased(const string& directory_path,
+                                         string* error) {
+  // Note: `error` is intentionally unused because we don't expect any errors
+  // here. We expect an `error` pointer so that we may use it in the future if
+  // need be, without having to change the API.
+  return new DirectoryBased(directory_path);
+}
+
+}  // namespace runfiles
+}  // namespace cpp
+}  // namespace tools
+}  // namespace bazel
diff --git a/tools/cpp/runfiles/runfiles.h b/tools/cpp/runfiles/runfiles.h
new file mode 100644
index 0000000..2fad6df
--- /dev/null
+++ b/tools/cpp/runfiles/runfiles.h
@@ -0,0 +1,185 @@
+// 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 C++ binaries and tests.
+//
+// Usage:
+//
+//   #include "tools/cpp/runfiles/runfiles.h"
+//
+//   using bazel::tools::cpp::runfiles::Runfiles;
+//
+//   int main(int argc, char** argv) {
+//     std::string error;
+//     std::unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
+//     if (runfiles == nullptr) {
+//       ...  // error handling
+//     }
+//     std::string path(runfiles->Rlocation("io_bazel/src/bazel"));
+//     std::ifstream data(path);
+//     if (data.is_open()) {
+//       ...  // use the runfile
+//
+// The code above creates a manifest- or directory-based implementations
+// depending on it finding a runfiles manifest or -directory near argv[0] or
+// finding appropriate environment variables that tell it where to find the
+// manifest or directory. See `Runfiles::Create` for more info.
+//
+// If you want to explicitly create a manifest- or directory-based
+// implementation, you can do so as follows:
+//
+//   std::unique_ptr<Runfiles> runfiles1(
+//       Runfiles::CreateManifestBased(path/to/foo.runfiles/MANIFEST", &error));
+//
+//   std::unique_ptr<Runfiles> runfiles2(
+//       Runfiles::CreateDirectoryBased(path/to/foo.runfiles", &error));
+//
+// If you want to start child processes that also need runfiles, you need to set
+// the right environment variables for them:
+//
+//   std::unique_ptr<Runfiles> runfiles(Runfiles::Create(argv[0], &error));
+//
+//   for (const auto i : runfiles->EnvVars()) {
+//     setenv(i.first, i.second, 1);
+//   }
+//   std::string path(runfiles->Rlocation("path/to/binary"));
+//   if (!path.empty()) {
+//     pid_t child = fork();
+//     ...
+
+#ifndef TOOLS_CPP_RUNFILES_RUNFILES_H_
+#define TOOLS_CPP_RUNFILES_RUNFILES_H_ 1
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace bazel {
+namespace tools {
+namespace cpp {
+namespace runfiles {
+
+class Runfiles {
+ public:
+  virtual ~Runfiles() {}
+
+  // Returns a new `Runfiles` instance.
+  //
+  // The returned object is either:
+  // - manifest-based, meaning it looks up runfile paths from a manifest file,
+  //   or
+  // - directory-based, meaning it looks up runfile paths under a given
+  //   directory path
+  //
+  // This method:
+  // 1. checks the RUNFILES_MANIFEST_FILE or RUNFILES_DIR environment variables;
+  //    if either is non-empty, returns a manifest- or directory-based Runfiles
+  //    object; otherwise
+  // 2. checks if there's a runfiles manifest (argv0 + ".runfiles_manifest") or
+  //    runfiles directory (argv0 + ".runfiles") next to this binary; if so,
+  //    returns a manifest- or directory-based Runfiles object; otherwise
+  // 3. returns nullptr.
+  //
+  // The manifest-based Runfiles object eagerly reads and caches the whole
+  // manifest file upon instantiation; this may be relevant for performance
+  // consideration.
+  //
+  // Returns nullptr on error. If `error` is provided, the method prints an
+  // error message into it.
+  static Runfiles* Create(const std::string& argv0,
+                          std::string* error = nullptr);
+
+  // Returns a new manifest-based `Runfiles` instance.
+  // Returns nullptr on error. If `error` is provided, the method prints an
+  // error message into it.
+  static Runfiles* CreateManifestBased(const std::string& manifest_path,
+                                       std::string* error = nullptr);
+
+  // Returns a new directory-based `Runfiles` instance.
+  // Returns nullptr on error. If `error` is provided, the method prints an
+  // error message into it.
+  static Runfiles* CreateDirectoryBased(const std::string& directory_path,
+                                        std::string* error = nullptr);
+
+  // Returns the runtime path of a runfile.
+  //
+  // Runfiles are data-dependencies of Bazel-built binaries and tests.
+  //
+  // The returned path may not be valid. The caller should check the path's
+  // validity and that the path exists.
+  //
+  // The function may return an empty string. In that case the caller can be
+  // sure that the Runfiles object does not know about this data-dependency.
+  //
+  // Args:
+  //   path: runfiles-root-relative path of the runfile; must not be empty and
+  //     must not contain uplevel references.
+  // Returns:
+  //   the path to the runfile, which the caller should check for existence, or
+  //   an empty string if the method doesn't know about this runfile
+  virtual std::string Rlocation(const std::string& path) const = 0;
+
+  // Returns environment variables for subprocesses.
+  //
+  // The caller should set the returned key-value pairs in the environment of
+  // subprocesses in case those subprocesses are also Bazel-built binaries that
+  // need to use runfiles.
+  virtual std::vector<std::pair<std::string, std::string> > EnvVars() const = 0;
+
+ protected:
+  Runfiles() {}
+
+ private:
+  Runfiles(const Runfiles&) = delete;
+  Runfiles(Runfiles&&) = delete;
+  Runfiles& operator=(const Runfiles&) = delete;
+  Runfiles& operator=(Runfiles&&) = delete;
+};
+
+// The "testing" namespace contains functions that allow unit testing the code.
+// Do not use these outside of runfiles_test.cc, they are only part of the
+// public API for the benefit of the tests.
+// These functions and their interface may change without notice.
+namespace testing {
+
+// For testing only.
+//
+// Create a new Runfiles instance, looking up environment variables using
+// `env_lookup`.
+//
+// Args:
+//   argv0: name of the binary; if this string is not empty, then the function
+//     looks for a runfiles manifest or directory next to this
+//   env_lookup: a function that returns envvar values if an envvar is known, or
+//     empty string otherwise
+Runfiles* TestOnly_CreateRunfiles(
+    const std::string& argv0,
+    std::function<std::string(const std::string&)> env_lookup,
+    std::string* error);
+
+// For testing only.
+// Returns true if `path` is an absolute Unix or Windows path.
+// For Windows paths, this function does not regard drive-less absolute paths
+// (i.e. absolute-on-current-drive, e.g. "\foo\bar") as absolute and returns
+// false for these.
+bool TestOnly_IsAbsolute(const std::string& path);
+
+}  // namespace testing
+}  // namespace runfiles
+}  // namespace cpp
+}  // namespace tools
+}  // namespace bazel
+
+#endif  // TOOLS_CPP_RUNFILES_RUNFILES_H_
diff --git a/tools/cpp/runfiles/runfiles_test.cc b/tools/cpp/runfiles/runfiles_test.cc
new file mode 100644
index 0000000..2177e02
--- /dev/null
+++ b/tools/cpp/runfiles/runfiles_test.cc
@@ -0,0 +1,493 @@
+// 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.
+
+#include "tools/cpp/runfiles/runfiles.h"
+
+#ifdef COMPILER_MSVC
+#include <windows.h>
+#endif  // COMPILER_MSVC
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "src/main/cpp/util/file.h"
+
+#define _T(x) #x
+#define T(x) _T(x)
+#define LINE() T(__LINE__)
+
+namespace bazel {
+namespace tools {
+namespace cpp {
+namespace runfiles {
+namespace {
+
+using bazel::tools::cpp::runfiles::testing::TestOnly_CreateRunfiles;
+using bazel::tools::cpp::runfiles::testing::TestOnly_IsAbsolute;
+using std::cerr;
+using std::endl;
+using std::function;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+class RunfilesTest : public ::testing::Test {
+ protected:
+  // Create a temporary file that is deleted with the destructor.
+  class MockFile {
+   public:
+    // Create an empty file with the given name under $TEST_TMPDIR.
+    static MockFile* Create(const string& name);
+
+    // Create a file with the given name and contents under $TEST_TMPDIR.
+    // The method ensures to create all parent directories, so `name` is allowed
+    // to contain directory components.
+    static MockFile* Create(const string& name, const vector<string>& lines);
+
+    ~MockFile();
+    const string& Path() const { return path_; }
+
+   private:
+    MockFile(const string& path) : path_(path) {}
+    MockFile(const MockFile&) = delete;
+    MockFile(MockFile&&) = delete;
+    MockFile& operator=(const MockFile&) = delete;
+    MockFile& operator=(MockFile&&) = delete;
+
+    const string path_;
+  };
+
+  static string GetTemp();
+
+  static function<string(const string&)> kEnvWithTestSrcdir;
+};
+
+function<string(const string&)> RunfilesTest::kEnvWithTestSrcdir =
+    [](const string& key) {
+      if (key == "TEST_SRCDIR") {
+        return string("always ignored");
+      } else {
+        return string();
+      }
+    };
+
+string RunfilesTest::GetTemp() {
+#ifdef COMPILER_MSVC
+  DWORD size = ::GetEnvironmentVariableA("TEST_TMPDIR", NULL, 0);
+  if (size == 0) {
+    return std::move(string());  // unset or empty envvar
+  }
+  unique_ptr<char[]> value(new char[size]);
+  ::GetEnvironmentVariableA("TEST_TMPDIR", value.get(), size);
+  return std::move(string(value.get()));
+#else
+  char* result = getenv("TEST_TMPDIR");
+  return result != NULL ? std::move(string(result)) : std::move(string());
+#endif
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(const string& name) {
+  return Create(name, vector<string>());
+}
+
+RunfilesTest::MockFile* RunfilesTest::MockFile::Create(
+    const string& name, const vector<string>& lines) {
+  if (name.find("..") != string::npos || TestOnly_IsAbsolute(name)) {
+    cerr << "WARNING: " << __FILE__ << "(" << __LINE__ << "): bad name: \""
+         << name << "\"" << endl;
+    return nullptr;
+  }
+
+  string tmp(std::move(RunfilesTest::GetTemp()));
+  if (tmp.empty()) {
+    cerr << "WARNING: " << __FILE__ << "(" << __LINE__
+         << "): $TEST_TMPDIR is empty" << endl;
+    return nullptr;
+  }
+  string path(tmp + "/" + name);
+  string dirname = blaze_util::Dirname(path);
+  if (!blaze_util::MakeDirectories(dirname, 0777)) {
+    cerr << "WARNING: " << __FILE__ << "(" << __LINE__ << "): MakeDirectories("
+         << dirname << ") failed" << endl;
+    return nullptr;
+  }
+
+  auto stm = std::ofstream(path);
+  for (auto i : lines) {
+    stm << i << std::endl;
+  }
+  return new MockFile(path);
+}
+
+RunfilesTest::MockFile::~MockFile() { std::remove(path_.c_str()); }
+
+TEST_F(RunfilesTest, CreatesManifestBasedRunfilesFromManifestNextToBinary) {
+  unique_ptr<MockFile> mf(
+      MockFile::Create("foo" LINE() ".runfiles_manifest", {"a/b c/d"}));
+  EXPECT_TRUE(mf != nullptr);
+  string argv0(mf->Path().substr(
+      0, mf->Path().size() - string(".runfiles_manifest").size()));
+
+  string error;
+  unique_ptr<Runfiles> r(
+      TestOnly_CreateRunfiles(argv0, kEnvWithTestSrcdir, &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+  EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+  // We know it's manifest-based because it returns empty string for unknown
+  // paths.
+  EXPECT_EQ(r->Rlocation("unknown"), "");
+}
+
+TEST_F(RunfilesTest,
+       CreatesManifestBasedRunfilesFromManifestInRunfilesDirectory) {
+  unique_ptr<MockFile> mf(
+      MockFile::Create("foo" LINE() ".runfiles/MANIFEST", {"a/b c/d"}));
+  EXPECT_TRUE(mf != nullptr);
+  string argv0(mf->Path().substr(
+      0, mf->Path().size() - string(".runfiles/MANIFEST").size()));
+
+  string error;
+  unique_ptr<Runfiles> r(
+      TestOnly_CreateRunfiles(argv0, kEnvWithTestSrcdir, &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+  EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+  // We know it's manifest-based because it returns empty string for unknown
+  // paths.
+  EXPECT_EQ(r->Rlocation("unknown"), "");
+}
+
+TEST_F(RunfilesTest, CreatesManifestBasedRunfilesFromEnvvar) {
+  unique_ptr<MockFile> mf(
+      MockFile::Create("foo" LINE() ".runfiles_manifest", {"a/b c/d"}));
+  EXPECT_TRUE(mf != nullptr);
+
+  string error;
+  unique_ptr<Runfiles> r(TestOnly_CreateRunfiles(
+      "ignore-argv0",
+      [&mf](const string& key) {
+        if (key == "RUNFILES_MANIFEST_FILE") {
+          return mf->Path();
+        } else if (key == "RUNFILES_DIR") {
+          return string("ignored when RUNFILES_MANIFEST_FILE has a value");
+        } else if (key == "TEST_SRCDIR") {
+          return string("always ignored");
+        } else {
+          return string();
+        }
+      },
+      &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+  EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+  // We know it's manifest-based because it returns empty string for unknown
+  // paths.
+  EXPECT_EQ(r->Rlocation("unknown"), "");
+}
+
+TEST_F(RunfilesTest, CannotCreateManifestBasedRunfilesDueToBadManifest) {
+  unique_ptr<MockFile> mf(
+      MockFile::Create("foo" LINE() ".runfiles_manifest", {"a b", "nospace"}));
+  EXPECT_TRUE(mf != nullptr);
+
+  string error;
+  unique_ptr<Runfiles> r(Runfiles::CreateManifestBased(mf->Path(), &error));
+  ASSERT_EQ(r, nullptr);
+  EXPECT_NE(error.find("bad runfiles manifest entry"), string::npos);
+  EXPECT_NE(error.find("line #2: \"nospace\""), string::npos);
+}
+
+TEST_F(RunfilesTest, ManifestBasedRunfilesRlocation) {
+  unique_ptr<MockFile> mf(
+      MockFile::Create("foo" LINE() ".runfiles_manifest", {"a/b c/d"}));
+  EXPECT_TRUE(mf != nullptr);
+
+  string error;
+  unique_ptr<Runfiles> r(Runfiles::CreateManifestBased(mf->Path(), &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+  EXPECT_EQ(r->Rlocation("a/b"), "c/d");
+  EXPECT_EQ(r->Rlocation("c/d"), "");
+  EXPECT_EQ(r->Rlocation(""), "");
+  EXPECT_EQ(r->Rlocation("foo"), "");
+  EXPECT_EQ(r->Rlocation("foo/"), "");
+  EXPECT_EQ(r->Rlocation("foo/bar"), "");
+  EXPECT_EQ(r->Rlocation("../foo"), "");
+  EXPECT_EQ(r->Rlocation("foo/.."), "");
+  EXPECT_EQ(r->Rlocation("foo/../bar"), "");
+  EXPECT_EQ(r->Rlocation("./foo"), "");
+  EXPECT_EQ(r->Rlocation("foo/."), "");
+  EXPECT_EQ(r->Rlocation("foo/./bar"), "");
+  EXPECT_EQ(r->Rlocation("//foo"), "");
+  EXPECT_EQ(r->Rlocation("foo//"), "");
+  EXPECT_EQ(r->Rlocation("foo//bar"), "");
+  EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+  EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+  EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+}
+
+TEST_F(RunfilesTest, DirectoryBasedRunfilesRlocation) {
+  string error;
+  unique_ptr<Runfiles> r(Runfiles::CreateDirectoryBased("whatever", &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  EXPECT_EQ(r->Rlocation("a/b"), "whatever/a/b");
+  EXPECT_EQ(r->Rlocation("c/d"), "whatever/c/d");
+  EXPECT_EQ(r->Rlocation(""), "");
+  EXPECT_EQ(r->Rlocation("foo"), "whatever/foo");
+  EXPECT_EQ(r->Rlocation("foo/"), "whatever/foo/");
+  EXPECT_EQ(r->Rlocation("foo/bar"), "whatever/foo/bar");
+  EXPECT_EQ(r->Rlocation("../foo"), "");
+  EXPECT_EQ(r->Rlocation("foo/.."), "");
+  EXPECT_EQ(r->Rlocation("foo/../bar"), "");
+  EXPECT_EQ(r->Rlocation("./foo"), "");
+  EXPECT_EQ(r->Rlocation("foo/."), "");
+  EXPECT_EQ(r->Rlocation("foo/./bar"), "");
+  EXPECT_EQ(r->Rlocation("//foo"), "");
+  EXPECT_EQ(r->Rlocation("foo//"), "");
+  EXPECT_EQ(r->Rlocation("foo//bar"), "");
+  EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+  EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+  EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+}
+
+TEST_F(RunfilesTest, ManifestBasedRunfilesEnvVars) {
+  const vector<string> suffixes({"/MANIFEST", ".runfiles_manifest",
+                                 "runfiles_manifest", ".runfiles", ".manifest",
+                                 ".txt"});
+  for (vector<string>::size_type i = 0; i < suffixes.size(); ++i) {
+    unique_ptr<MockFile> mf(
+        MockFile::Create(string("foo" LINE()) + suffixes[i]));
+    EXPECT_TRUE(mf != nullptr) << " (suffix=\"" << suffixes[i] << "\")";
+
+    string error;
+    unique_ptr<Runfiles> r(Runfiles::CreateManifestBased(mf->Path(), &error));
+    ASSERT_NE(r, nullptr) << " (suffix=\"" << suffixes[i] << "\")";
+    EXPECT_TRUE(error.empty());
+
+    // The object can compute the runfiles directory when i=0 and i=1, but not
+    // when i>1 because the manifest file's name doesn't end in a well-known
+    // way.
+    const string expected_runfiles_dir(
+        i < 2 ? mf->Path().substr(0, mf->Path().size() - 9 /* "_manifest" */)
+              : "");
+    vector<pair<string, string> > expected(
+        {{"RUNFILES_MANIFEST_FILE", mf->Path()},
+         {"JAVA_RUNFILES", expected_runfiles_dir}});
+    EXPECT_EQ(r->EnvVars(), expected) << " (suffix=\"" << suffixes[i] << "\")";
+  }
+}
+
+TEST_F(RunfilesTest, CreatesDirectoryBasedRunfilesFromDirectoryNextToBinary) {
+  // We create a directory as a side-effect of creating a mock file.
+  unique_ptr<MockFile> mf(
+      MockFile::Create(string("foo" LINE() ".runfiles/dummy")));
+  string argv0(mf->Path().substr(
+      0, mf->Path().size() - string(".runfiles/dummy").size()));
+
+  string error;
+  unique_ptr<Runfiles> r(
+      TestOnly_CreateRunfiles(argv0, kEnvWithTestSrcdir, &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  EXPECT_EQ(r->Rlocation("a/b"), argv0 + ".runfiles/a/b");
+  // We know it's directory-based because it returns some result for unknown
+  // paths.
+  EXPECT_EQ(r->Rlocation("unknown"), argv0 + ".runfiles/unknown");
+}
+
+TEST_F(RunfilesTest, CreatesDirectoryBasedRunfilesFromEnvvar) {
+  string error;
+  unique_ptr<Runfiles> r(
+      TestOnly_CreateRunfiles("ignore-argv0",
+                              [](const string& key) {
+                                if (key == "RUNFILES_DIR") {
+                                  return string("runfiles/dir");
+                                } else if (key == "TEST_SRCDIR") {
+                                  return string("always ignored");
+                                } else {
+                                  return string();
+                                }
+                              },
+                              &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  EXPECT_EQ(r->Rlocation("a/b"), "runfiles/dir/a/b");
+  EXPECT_EQ(r->Rlocation("foo"), "runfiles/dir/foo");
+  EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
+  EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
+  EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
+}
+
+TEST_F(RunfilesTest, DirectoryBasedRunfilesEnvVars) {
+  string error;
+  unique_ptr<Runfiles> r(
+      Runfiles::CreateDirectoryBased("runfiles/dir", &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  vector<pair<string, string> > expected(
+      {{"RUNFILES_DIR", "runfiles/dir"}, {"JAVA_RUNFILES", "runfiles/dir"}});
+  EXPECT_EQ(r->EnvVars(), expected);
+}
+
+TEST_F(RunfilesTest, FailsToCreateManifestBasedBecauseManifestDoesNotExist) {
+  string error;
+  unique_ptr<Runfiles> r(
+      Runfiles::CreateManifestBased("non-existent-file", &error));
+  ASSERT_EQ(r, nullptr);
+  EXPECT_NE(error.find("cannot open runfiles manifest"), string::npos);
+}
+
+TEST_F(RunfilesTest, FailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined) {
+  unique_ptr<MockFile> mf(MockFile::Create(string("foo" LINE())));
+  EXPECT_TRUE(mf != nullptr);
+
+  string error;
+  unique_ptr<Runfiles> r(
+      TestOnly_CreateRunfiles("ignore-argv0",
+                              [&mf](const string& key) {
+                                if (key == "RUNFILES_MANIFEST_FILE") {
+                                  return mf->Path();
+                                } else if (key == "RUNFILES_DIR") {
+                                  return string("whatever");
+                                } else if (key == "TEST_SRCDIR") {
+                                  return string("always ignored");
+                                } else {
+                                  return string();
+                                }
+                              },
+                              &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  r.reset(TestOnly_CreateRunfiles("ignore-argv0",
+                                  [](const string& key) {
+                                    if (key == "RUNFILES_DIR") {
+                                      return string("whatever");
+                                    } else if (key == "TEST_SRCDIR") {
+                                      return string("always ignored");
+                                    } else {
+                                      return string();
+                                    }
+                                  },
+                                  &error));
+  ASSERT_NE(r, nullptr);
+  EXPECT_TRUE(error.empty());
+
+  r.reset(TestOnly_CreateRunfiles("ignore-argv0", kEnvWithTestSrcdir, &error));
+  ASSERT_EQ(r, nullptr);
+  EXPECT_NE(error.find("cannot find runfiles"), string::npos);
+}
+
+TEST_F(RunfilesTest, MockFileTest) {
+  {
+    unique_ptr<MockFile> mf(MockFile::Create(string("foo" LINE() "/..")));
+    EXPECT_TRUE(mf == nullptr);
+  }
+
+  {
+    unique_ptr<MockFile> mf(MockFile::Create(string("/Foo" LINE())));
+    EXPECT_TRUE(mf == nullptr);
+  }
+
+  {
+    unique_ptr<MockFile> mf(MockFile::Create(string("C:/Foo" LINE())));
+    EXPECT_TRUE(mf == nullptr);
+  }
+
+  string path;
+  {
+    unique_ptr<MockFile> mf(MockFile::Create(string("foo" LINE() "/bar1/qux")));
+    EXPECT_TRUE(mf != nullptr);
+    path = mf->Path();
+
+    std::ifstream stm(path);
+    EXPECT_TRUE(stm.good());
+    string actual;
+    stm >> actual;
+    EXPECT_TRUE(actual.empty());
+  }
+  {
+    std::ifstream stm(path);
+    EXPECT_FALSE(stm.good());
+  }
+
+  {
+    unique_ptr<MockFile> mf(
+        MockFile::Create(string("foo" LINE() "/bar2/qux"), vector<string>()));
+    EXPECT_TRUE(mf != nullptr);
+    path = mf->Path();
+
+    std::ifstream stm(path);
+    EXPECT_TRUE(stm.good());
+    string actual;
+    stm >> actual;
+    EXPECT_TRUE(actual.empty());
+  }
+  {
+    std::ifstream stm(path);
+    EXPECT_FALSE(stm.good());
+  }
+
+  {
+    unique_ptr<MockFile> mf(
+        MockFile::Create(string("foo" LINE() "/bar3/qux"),
+                         {"hello world", "you are beautiful"}));
+    EXPECT_TRUE(mf != nullptr);
+    path = mf->Path();
+
+    std::ifstream stm(path);
+    EXPECT_TRUE(stm.good());
+    string actual;
+    std::getline(stm, actual);
+    EXPECT_EQ("hello world", actual);
+    std::getline(stm, actual);
+    EXPECT_EQ("you are beautiful", actual);
+    std::getline(stm, actual);
+    EXPECT_EQ("", actual);
+  }
+  {
+    std::ifstream stm(path);
+    EXPECT_FALSE(stm.good());
+  }
+}
+
+TEST_F(RunfilesTest, IsAbsolute) {
+  EXPECT_FALSE(TestOnly_IsAbsolute("foo"));
+  EXPECT_FALSE(TestOnly_IsAbsolute("foo/bar"));
+  EXPECT_FALSE(TestOnly_IsAbsolute("\\foo"));
+  EXPECT_TRUE(TestOnly_IsAbsolute("c:\\foo"));
+  EXPECT_TRUE(TestOnly_IsAbsolute("c:/foo"));
+  EXPECT_TRUE(TestOnly_IsAbsolute("/foo"));
+  EXPECT_TRUE(TestOnly_IsAbsolute("x:\\foo"));
+  EXPECT_FALSE(TestOnly_IsAbsolute("::\\foo"));
+  EXPECT_FALSE(TestOnly_IsAbsolute("x\\foo"));
+  EXPECT_FALSE(TestOnly_IsAbsolute("x:"));
+  EXPECT_TRUE(TestOnly_IsAbsolute("x:\\"));
+}
+
+}  // namespace
+}  // namespace runfiles
+}  // namespace cpp
+}  // namespace tools
+}  // namespace bazel