diff options
author | Laszlo Csomor <laszlocsomor@google.com> | 2018-04-30 03:29:21 -0700 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-04-30 03:30:49 -0700 |
commit | 819bf38d97e6eef3c823bdae3ffcdb013d6d83e3 (patch) | |
tree | f18d8d0cc5e34061c5c46514b18b9e5608f3d566 /tools/cpp | |
parent | eb35e41242e1f9fb0ae1061623272c891237c8a8 (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/BUILD | 12 | ||||
-rw-r--r-- | tools/cpp/runfiles/BUILD | 40 | ||||
-rw-r--r-- | tools/cpp/runfiles/BUILD.tools | 10 | ||||
-rw-r--r-- | tools/cpp/runfiles/runfiles.cc | 354 | ||||
-rw-r--r-- | tools/cpp/runfiles/runfiles.h | 185 | ||||
-rw-r--r-- | tools/cpp/runfiles/runfiles_test.cc | 493 |
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 |