aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/cpp
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2018-04-30 03:29:21 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-04-30 03:30:49 -0700
commit819bf38d97e6eef3c823bdae3ffcdb013d6d83e3 (patch)
treef18d8d0cc5e34061c5c46514b18b9e5608f3d566 /tools/cpp
parenteb35e41242e1f9fb0ae1061623272c891237c8a8 (diff)
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
Diffstat (limited to 'tools/cpp')
-rw-r--r--tools/cpp/BUILD12
-rw-r--r--tools/cpp/runfiles/BUILD40
-rw-r--r--tools/cpp/runfiles/BUILD.tools10
-rw-r--r--tools/cpp/runfiles/runfiles.cc354
-rw-r--r--tools/cpp/runfiles/runfiles.h185
-rw-r--r--tools/cpp/runfiles/runfiles_test.cc493
6 files changed, 1093 insertions, 1 deletions
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index 57007d324a..baed0148fd 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -360,7 +360,17 @@ filegroup(
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 0000000000..9afbd01ef5
--- /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 0000000000..8f0a9cf143
--- /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 0000000000..2f25b53ae5
--- /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 0000000000..2fad6df3dc
--- /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 0000000000..2177e02afc
--- /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