BEGIN_PUBLIC
Copybara import of the project:

--
82e44f3c47 by Fabian Meumertzheim <fabian@meumertzhe.im>:

Copy over runfiles library from Bazel

The runfiles library can be maintained independently of Bazel releases and bazel_tools can refer to it via an alias.

--
885d333d55 by Fabian Meumertzheim <fabian@meumertzhe.im>:

Switch to new include path and namespace

--
b8cd815963 by Fabian Meumertzheim <fabian@meumertzhe.im>:

Fix CI

--
41b7d48ed8 by Fabian Meumertzheim <fabian@meumertzhe.im>:

Silence warnings

--
b97a30e8bb by Fabian Meumertzheim <fabian@meumertzhe.im>:

Remove per_file_copt

END_PUBLIC

PiperOrigin-RevId: 697942902
Change-Id: I10a6c9baf0464722fca026db99cf572acfd159f1
This commit is contained in:
Fabian Meumertzheim 2024-11-19 02:58:15 -08:00 committed by Copybara-Service
parent 4db02b76c9
commit 8395ec0172
9 changed files with 1657 additions and 2 deletions

2
.bazelrc Normal file
View File

@ -0,0 +1,2 @@
# Required by abseil-cpp.
common --cxxopt=-std=c++14

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
bazel-*
MODULE.bazel.lock
/.ijwb/
/.clwb/

View File

@ -15,5 +15,6 @@ use_repo(cc_configure, "local_config_cc", "local_config_cc_toolchains")
register_toolchains("@local_config_cc_toolchains//:all")
bazel_dep(name = "rules_shell", version = "0.2.0", dev_dependency = True)
bazel_dep(name = "googletest", version = "1.15.2", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)
bazel_dep(name = "stardoc", version = "0.7.0", dev_dependency = True)

View File

@ -50,3 +50,10 @@ http_archive(
strip_prefix = "protobuf-27.0",
url = "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.tar.gz",
)
http_archive(
name = "googletest",
integrity = "sha256-e0K01u1IgQxTYsJloX+uvpDcI3PIheUhZDnTeSfwKSY=",
strip_prefix = "googletest-1.15.2",
url = "https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz",
)

View File

@ -1,7 +1,12 @@
load("//cc:cc_library.bzl", "cc_library")
licenses(["notice"])
alias(
cc_library(
name = "runfiles",
actual = "@bazel_tools//tools/cpp/runfiles",
srcs = ["runfiles.cc"],
hdrs = ["runfiles.h"],
include_prefix = "rules_cc/cc/runfiles",
strip_include_prefix = ".",
visibility = ["//visibility:public"],
)

482
cc/runfiles/runfiles.cc Normal file
View File

@ -0,0 +1,482 @@
// 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 "rules_cc/cc/runfiles/runfiles.h"
#ifdef _WIN32
#include <windows.h>
#else // not _WIN32
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif // _WIN32
#include <fstream>
#include <functional>
#include <map>
#include <sstream>
#include <vector>
#include <utility>
#ifdef _WIN32
#include <memory>
#endif // _WIN32
namespace rules_cc {
namespace cc {
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 char* prefix) {
if (!prefix || !*prefix) {
return true;
}
if (s.empty()) {
return false;
}
return s.find(prefix) == 0;
}
bool contains(const string& s, const char* substr) {
if (!substr || !*substr) {
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();
}
bool IsReadableFile(const string& path) {
return std::ifstream(path).is_open();
}
bool IsDirectory(const string& path) {
#ifdef _WIN32
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
}
bool PathsFrom(const std::string& argv0, std::string runfiles_manifest_file,
std::string runfiles_dir, std::string* out_manifest,
std::string* out_directory);
bool PathsFrom(const std::string& argv0, std::string runfiles_manifest_file,
std::string runfiles_dir,
std::function<bool(const std::string&)> is_runfiles_manifest,
std::function<bool(const std::string&)> is_runfiles_directory,
std::string* out_manifest, std::string* out_directory);
bool ParseManifest(const string& path, map<string, string>* result,
string* error);
bool ParseRepoMapping(const string& path,
map<pair<string, string>, string>* result, string* error);
} // namespace
Runfiles* Runfiles::Create(const string& argv0,
const string& runfiles_manifest_file,
const string& runfiles_dir,
const string& source_repository, string* error) {
string manifest, directory;
if (!PathsFrom(argv0, runfiles_manifest_file, runfiles_dir, &manifest,
&directory)) {
if (error) {
std::ostringstream err;
err << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): cannot find runfiles (argv0=\"" << argv0 << "\")";
*error = err.str();
}
return nullptr;
}
vector<pair<string, string> > envvars = {
{"RUNFILES_MANIFEST_FILE", manifest},
{"RUNFILES_DIR", directory},
// TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
// pick up RUNFILES_DIR.
{"JAVA_RUNFILES", directory}};
map<string, string> runfiles;
if (!manifest.empty()) {
if (!ParseManifest(manifest, &runfiles, error)) {
return nullptr;
}
}
map<pair<string, string>, string> mapping;
if (!ParseRepoMapping(
RlocationUnchecked("_repo_mapping", runfiles, directory), &mapping,
error)) {
return nullptr;
}
return new Runfiles(std::move(runfiles), std::move(directory),
std::move(mapping), std::move(envvars),
string(source_repository));
}
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 _WIN32
DWORD size = ::GetEnvironmentVariableA(key.c_str(), nullptr, 0);
if (size == 0) {
return string(); // unset or empty envvar
}
std::unique_ptr<char[]> value(new char[size]);
::GetEnvironmentVariableA(key.c_str(), value.get(), size);
return value.get();
#else
char* result = getenv(key.c_str());
return (result == nullptr) ? string() : string(result);
#endif
}
// Replaces \s, \n, and \b with their respective characters.
string Unescape(const string& path) {
string result;
result.reserve(path.size());
for (size_t i = 0; i < path.size(); ++i) {
if (path[i] == '\\' && i + 1 < path.size()) {
switch (path[i + 1]) {
case 's': {
result.push_back(' ');
break;
}
case 'n': {
result.push_back('\n');
break;
}
case 'b': {
result.push_back('\\');
break;
}
default: {
result.push_back(path[i]);
result.push_back(path[i + 1]);
break;
}
}
++i;
} else {
result.push_back(path[i]);
}
}
return result;
}
string Runfiles::Rlocation(const string& path) const {
return Rlocation(path, source_repository_);
}
string Runfiles::Rlocation(const string& path,
const string& source_repo) const {
if (path.empty() || starts_with(path, "../") || contains(path, "/..") ||
starts_with(path, "./") || contains(path, "/./") ||
ends_with(path, "/.") || contains(path, "//")) {
return string();
}
if (IsAbsolute(path)) {
return path;
}
string::size_type first_slash = path.find_first_of('/');
if (first_slash == string::npos) {
return RlocationUnchecked(path, runfiles_map_, directory_);
}
string target_apparent = path.substr(0, first_slash);
auto target =
repo_mapping_.find(std::make_pair(source_repo, target_apparent));
if (target == repo_mapping_.cend()) {
return RlocationUnchecked(path, runfiles_map_, directory_);
}
return RlocationUnchecked(target->second + path.substr(first_slash),
runfiles_map_, directory_);
}
string Runfiles::RlocationUnchecked(const string& path,
const map<string, string>& runfiles_map,
const string& directory) {
const auto exact_match = runfiles_map.find(path);
if (exact_match != runfiles_map.end()) {
return exact_match->second;
}
if (!runfiles_map.empty()) {
// If path references a runfile that lies under a directory that itself is a
// runfile, then only the directory is listed in the manifest. Look up all
// prefixes of path in the manifest and append the relative path from the
// prefix to the looked up path.
std::size_t prefix_end = path.size();
while ((prefix_end = path.find_last_of('/', prefix_end - 1)) !=
string::npos) {
const string prefix = path.substr(0, prefix_end);
const auto prefix_match = runfiles_map.find(prefix);
if (prefix_match != runfiles_map.end()) {
return prefix_match->second + "/" + path.substr(prefix_end + 1);
}
}
}
if (!directory.empty()) {
return directory + "/" + path;
}
return "";
}
namespace {
bool 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()) {
std::string source;
std::string target;
if (line[0] == ' ') {
// The link path contains escape sequences for spaces and backslashes.
string::size_type idx = line.find(' ', 1);
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;
}
source = Unescape(line.substr(1, idx - 1));
target = Unescape(line.substr(idx + 1));
} else {
string::size_type idx = line.find(' ');
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;
}
source = line.substr(0, idx);
target = line.substr(idx + 1);
}
(*result)[source] = target;
std::getline(stm, line);
++line_count;
}
return true;
}
bool ParseRepoMapping(const string& path,
map<pair<string, string>, string>* result,
string* error) {
std::ifstream stm(path);
if (!stm.is_open()) {
return true;
}
string line;
std::getline(stm, line);
size_t line_count = 1;
while (!line.empty()) {
string::size_type first_comma = line.find_first_of(',');
if (first_comma == string::npos) {
if (error) {
std::ostringstream err;
err << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): bad repository mapping entry in \"" << path << "\" line #"
<< line_count << ": \"" << line << "\"";
*error = err.str();
}
return false;
}
string::size_type second_comma = line.find_first_of(',', first_comma + 1);
if (second_comma == string::npos) {
if (error) {
std::ostringstream err;
err << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): bad repository mapping entry in \"" << path << "\" line #"
<< line_count << ": \"" << line << "\"";
*error = err.str();
}
return false;
}
string source = line.substr(0, first_comma);
string target_apparent =
line.substr(first_comma + 1, second_comma - (first_comma + 1));
string target = line.substr(second_comma + 1);
(*result)[std::make_pair(source, target_apparent)] = target;
std::getline(stm, line);
++line_count;
}
return true;
}
} // namespace
namespace testing {
bool TestOnly_PathsFrom(const string& argv0, string mf, string dir,
function<bool(const string&)> is_runfiles_manifest,
function<bool(const string&)> is_runfiles_directory,
string* out_manifest, string* out_directory) {
return PathsFrom(argv0, mf, dir, is_runfiles_manifest, is_runfiles_directory,
out_manifest, out_directory);
}
bool TestOnly_IsAbsolute(const string& path) { return IsAbsolute(path); }
} // namespace testing
Runfiles* Runfiles::Create(const std::string& argv0,
const std::string& runfiles_manifest_file,
const std::string& runfiles_dir,
std::string* error) {
return Runfiles::Create(argv0, runfiles_manifest_file, runfiles_dir, "",
error);
}
Runfiles* Runfiles::Create(const string& argv0, const string& source_repository,
string* error) {
return Runfiles::Create(argv0, GetEnv("RUNFILES_MANIFEST_FILE"),
GetEnv("RUNFILES_DIR"), source_repository, error);
}
Runfiles* Runfiles::Create(const string& argv0, string* error) {
return Runfiles::Create(argv0, "", error);
}
Runfiles* Runfiles::CreateForTest(const string& source_repository,
std::string* error) {
return Runfiles::Create(std::string(), GetEnv("RUNFILES_MANIFEST_FILE"),
GetEnv("TEST_SRCDIR"), source_repository, error);
}
Runfiles* Runfiles::CreateForTest(std::string* error) {
return Runfiles::CreateForTest("", error);
}
namespace {
bool PathsFrom(const string& argv0, string mf, string dir, string* out_manifest,
string* out_directory) {
return PathsFrom(
argv0, mf, dir, [](const string& path) { return IsReadableFile(path); },
[](const string& path) { return IsDirectory(path); }, out_manifest,
out_directory);
}
bool PathsFrom(const string& argv0, string mf, string dir,
function<bool(const string&)> is_runfiles_manifest,
function<bool(const string&)> is_runfiles_directory,
string* out_manifest, string* out_directory) {
out_manifest->clear();
out_directory->clear();
bool mfValid = is_runfiles_manifest(mf);
bool dirValid = is_runfiles_directory(dir);
if (!argv0.empty() && !mfValid && !dirValid) {
mf = argv0 + ".runfiles/MANIFEST";
dir = argv0 + ".runfiles";
mfValid = is_runfiles_manifest(mf);
dirValid = is_runfiles_directory(dir);
if (!mfValid) {
mf = argv0 + ".runfiles_manifest";
mfValid = is_runfiles_manifest(mf);
}
}
if (!mfValid && !dirValid) {
return false;
}
if (!mfValid) {
mf = dir + "/MANIFEST";
mfValid = is_runfiles_manifest(mf);
if (!mfValid) {
mf = dir + "_manifest";
mfValid = is_runfiles_manifest(mf);
}
}
if (!dirValid &&
(ends_with(mf, ".runfiles_manifest") || ends_with(mf, "/MANIFEST"))) {
static const size_t kSubstrLen = 9; // "_manifest" or "/MANIFEST"
dir = mf.substr(0, mf.size() - kSubstrLen);
dirValid = is_runfiles_directory(dir);
}
if (mfValid) {
*out_manifest = mf;
}
if (dirValid) {
*out_directory = dir;
}
return true;
}
} // namespace
} // namespace runfiles
} // namespace cc
} // namespace rules_cc

264
cc/runfiles/runfiles.h Normal file
View File

@ -0,0 +1,264 @@
// 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:
// 1. Depend on this runfiles library from your build rule:
//
// cc_binary(
// name = "my_binary",
// ...
// deps = ["@rules_cc//cc/runfiles"],
// )
//
// 2. Include the runfiles library.
//
// #include "rules_cc/cc/runfiles/runfiles.h"
//
// using rules_cc::cc::runfiles::Runfiles;
//
// 3. Create a Runfiles object and use rlocation to look up runfile paths:
//
// int main(int argc, char** argv) {
// std::string error;
// std::unique_ptr<Runfiles> runfiles(
// Runfiles::Create(argv[0], BAZEL_CURRENT_REPOSITORY, &error));
//
// // Important:
// // If this is a test, use
// // Runfiles::CreateForTest(BAZEL_CURRENT_REPOSITORY, &error).
//
// if (runfiles == nullptr) {
// ... // error handling
// }
// std::string path =
// runfiles->Rlocation("my_workspace/path/to/my/data.txt");
// ...
//
// The code above creates a Runfiles object and retrieves a runfile path.
// The BAZEL_CURRENT_REPOSITORY macro is available in every target that
// depends on the runfiles library.
//
// The Runfiles::Create function uses the runfiles manifest and the
// runfiles directory from the RUNFILES_MANIFEST_FILE and RUNFILES_DIR
// environment variables. If not present, the function looks for the
// manifest and directory near argv[0], the path of the main program.
//
// 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], BAZEL_CURRENT_REPOSITORY, &error));
//
// std::string path = runfiles->Rlocation("path/to/binary"));
// if (!path.empty()) {
// ... // create "args" argument vector for execv
// const auto envvars = runfiles->EnvVars();
// pid_t child = fork();
// if (child) {
// int status;
// waitpid(child, &status, 0);
// } else {
// for (const auto i : envvars) {
// setenv(i.first.c_str(), i.second.c_str(), 1);
// }
// execv(args[0], args);
// }
#ifndef RULES_CC_CC_RUNFILES_RUNFILES_H_
#define RULES_CC_CC_RUNFILES_RUNFILES_H_ 1
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace rules_cc {
namespace cc {
namespace runfiles {
class Runfiles {
public:
virtual ~Runfiles() {}
// Returns a new `Runfiles` instance.
//
// Use this from within `cc_test` rules.
//
// Returns nullptr on error. If `error` is provided, the method prints an
// error message into it.
//
// This method looks at the RUNFILES_MANIFEST_FILE and TEST_SRCDIR
// environment variables.
//
// If source_repository is not provided, it defaults to the main repository
// (also known as the workspace).
static Runfiles* CreateForTest(std::string* error = nullptr);
static Runfiles* CreateForTest(const std::string& source_repository,
std::string* error = nullptr);
// Returns a new `Runfiles` instance.
//
// Use this from `cc_binary` or `cc_library` rules. You may pass an empty
// `argv0` if `argv[0]` from the `main` method is unknown.
//
// Returns nullptr on error. If `error` is provided, the method prints an
// error message into it.
//
// This method looks at the RUNFILES_MANIFEST_FILE and RUNFILES_DIR
// environment variables. If either is empty, the method looks for the
// manifest or directory using the other environment variable, or using argv0
// (unless it's empty).
//
// If source_repository is not provided, it defaults to the main repository
// (also known as the workspace).
static Runfiles* Create(const std::string& argv0,
std::string* error = nullptr);
static Runfiles* Create(const std::string& argv0,
const std::string& source_repository,
std::string* error = nullptr);
// Returns a new `Runfiles` instance.
//
// Use this from any `cc_*` rule if you want to manually specify the paths to
// the runfiles manifest and/or runfiles directory. You may pass an empty
// `argv0` if `argv[0]` from the `main` method is unknown.
//
// This method is the same as `Create(argv0, error)`, except it uses
// `runfiles_manifest_file` and `runfiles_dir` as the corresponding
// environment variable values, instead of looking up the actual environment
// variables.
static Runfiles* Create(const std::string& argv0,
const std::string& runfiles_manifest_file,
const std::string& runfiles_dir,
std::string* error = nullptr);
static Runfiles* Create(const std::string& argv0,
const std::string& runfiles_manifest_file,
const std::string& runfiles_dir,
const std::string& source_repository,
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 exist. The caller should verify the path's
// existence.
//
// The function may return an empty string if it cannot find a runfile.
//
// Args:
// path: runfiles-root-relative path of the runfile; must not be empty and
// must not contain uplevel references.
// source_repository: if provided, overrides the source repository set when
// this Runfiles instance was created.
// 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
std::string Rlocation(const std::string& path) const;
std::string Rlocation(const std::string& path,
const std::string& source_repository) const;
// Returns environment variables for subprocesses.
//
// The caller should set the returned key-value pairs in the environment of
// subprocesses, so that those subprocesses can also access runfiles (in case
// they are also Bazel-built binaries).
const std::vector<std::pair<std::string, std::string> >& EnvVars() const {
return envvars_;
}
// Returns a new Runfiles instance that by default uses the provided source
// repository as a default for all calls to Rlocation.
//
// The current instance remains valid.
std::unique_ptr<Runfiles> WithSourceRepository(
const std::string& source_repository) const {
return std::unique_ptr<Runfiles>(new Runfiles(
runfiles_map_, directory_, repo_mapping_, envvars_, source_repository));
}
private:
Runfiles(
std::map<std::string, std::string> runfiles_map, std::string directory,
std::map<std::pair<std::string, std::string>, std::string> repo_mapping,
std::vector<std::pair<std::string, std::string> > envvars,
std::string source_repository)
: runfiles_map_(std::move(runfiles_map)),
directory_(std::move(directory)),
repo_mapping_(std::move(repo_mapping)),
envvars_(std::move(envvars)),
source_repository_(std::move(source_repository)) {}
Runfiles(const Runfiles&) = delete;
Runfiles(Runfiles&&) = delete;
Runfiles& operator=(const Runfiles&) = delete;
Runfiles& operator=(Runfiles&&) = delete;
static std::string RlocationUnchecked(
const std::string& path,
const std::map<std::string, std::string>& runfiles_map,
const std::string& directory);
const std::map<std::string, std::string> runfiles_map_;
const std::string directory_;
const std::map<std::pair<std::string, std::string>, std::string>
repo_mapping_;
const std::vector<std::pair<std::string, std::string> > envvars_;
const std::string source_repository_;
};
// 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.
//
// Computes the path of the runfiles manifest and the runfiles directory.
//
// If the method finds both a valid manifest and valid directory according to
// `is_runfiles_manifest` and `is_runfiles_directory`, then the method sets
// the corresponding values to `out_manifest` and `out_directory` and returns
// true.
//
// If the method only finds a valid manifest or a valid directory, but not
// both, then it sets the corresponding output variable (`out_manifest` or
// `out_directory`) to the value while clearing the other output variable. The
// method still returns true in this case.
//
// If the method cannot find either a valid manifest or valid directory, it
// clears both output variables and returns false.
bool TestOnly_PathsFrom(
const std::string& argv0, std::string runfiles_manifest_file,
std::string runfiles_dir,
std::function<bool(const std::string&)> is_runfiles_manifest,
std::function<bool(const std::string&)> is_runfiles_directory,
std::string* out_manifest, std::string* out_directory);
// 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 cc
} // namespace rules_cc
#endif // RULES_CC_CC_RUNFILES_RUNFILES_H_

12
tests/runfiles/BUILD Normal file
View File

@ -0,0 +1,12 @@
load("//cc:cc_test.bzl", "cc_test")
licenses(["notice"])
cc_test(
name = "runfiles_test",
srcs = ["runfiles_test.cc"],
deps = [
"//cc/runfiles",
"@googletest//:gtest_main",
],
)

View File

@ -0,0 +1,880 @@
// 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 "rules_cc/cc/runfiles/runfiles.h"
#ifdef _WIN32
#include <windows.h>
#endif // _WIN32
#include <fstream>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#define RUNFILES_TEST_TOSTRING_HELPER(x) #x
#define RUNFILES_TEST_TOSTRING(x) RUNFILES_TEST_TOSTRING_HELPER(x)
#define LINE_AS_STRING() RUNFILES_TEST_TOSTRING(__LINE__)
namespace rules_cc {
namespace cc {
namespace runfiles {
namespace {
using rules_cc::cc::runfiles::testing::TestOnly_IsAbsolute;
using rules_cc::cc::runfiles::testing::TestOnly_PathsFrom;
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_; }
string DirName() const {
string::size_type pos = path_.find_last_of('/');
return pos == string::npos ? "" : path_.substr(0, pos);
}
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_;
};
void AssertEnvvars(const Runfiles& runfiles,
const string& expected_manifest_file,
const string& expected_directory);
static string GetTemp();
};
void RunfilesTest::AssertEnvvars(const Runfiles& runfiles,
const string& expected_manifest_file,
const string& expected_directory) {
vector<pair<string, string> > expected = {
{"RUNFILES_MANIFEST_FILE", expected_manifest_file},
{"RUNFILES_DIR", expected_directory},
{"JAVA_RUNFILES", expected_directory}};
ASSERT_EQ(runfiles.EnvVars(), expected);
}
string RunfilesTest::GetTemp() {
#ifdef _WIN32
DWORD size = ::GetEnvironmentVariableA("TEST_TMPDIR", nullptr, 0);
if (size == 0) {
return string(); // unset or empty envvar
}
unique_ptr<char[]> value(new char[size]);
::GetEnvironmentVariableA("TEST_TMPDIR", value.get(), size);
return value.get();
#else
char* result = getenv("TEST_TMPDIR");
return result != nullptr ? string(result) : 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(RunfilesTest::GetTemp());
if (tmp.empty()) {
cerr << "WARNING: " << __FILE__ << "(" << __LINE__
<< "): $TEST_TMPDIR is empty" << endl;
return nullptr;
}
string path(tmp + "/" + name);
string::size_type i = 0;
#ifdef _WIN32
while ((i = name.find_first_of("/\\", i + 1)) != string::npos) {
string d = tmp + "\\" + name.substr(0, i);
if (!CreateDirectoryA(d.c_str(), nullptr)) {
cerr << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): failed to create directory \"" << d << "\"" << endl;
return nullptr;
}
}
#else
while ((i = name.find_first_of('/', i + 1)) != string::npos) {
string d = tmp + "/" + name.substr(0, i);
if (mkdir(d.c_str(), 0777)) {
cerr << "ERROR: " << __FILE__ << "(" << __LINE__
<< "): failed to create directory \"" << d << "\"" << endl;
return nullptr;
}
}
#endif
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_AS_STRING() ".runfiles_manifest", {"a/b c/d"}));
ASSERT_TRUE(mf != nullptr);
string argv0(mf->Path().substr(
0, mf->Path().size() - string(".runfiles_manifest").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"", &error));
ASSERT_TRUE(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"), "");
AssertEnvvars(*r, mf->Path(), "");
}
TEST_F(RunfilesTest,
CreatesManifestBasedRunfilesFromManifestInRunfilesDirectory) {
unique_ptr<MockFile> mf(MockFile::Create(
"foo" LINE_AS_STRING() ".runfiles/MANIFEST", {"a/b c/d"}));
ASSERT_TRUE(mf != nullptr);
string argv0(mf->Path().substr(
0, mf->Path().size() - string(".runfiles/MANIFEST").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("a/b"), "c/d");
EXPECT_EQ(r->Rlocation("foo"), argv0 + ".runfiles/foo");
AssertEnvvars(*r, mf->Path(), argv0 + ".runfiles");
}
TEST_F(RunfilesTest, CreatesManifestBasedRunfilesFromEnvvar) {
unique_ptr<MockFile> mf(MockFile::Create(
"foo" LINE_AS_STRING() ".runfiles_manifest", {"a/b c/d"}));
ASSERT_TRUE(mf != nullptr);
string error;
unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", mf->Path(),
"non-existent-runfiles_dir", &error));
ASSERT_TRUE(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"), "");
AssertEnvvars(*r, mf->Path(), "");
}
TEST_F(RunfilesTest, CannotCreateManifestBasedRunfilesDueToBadManifest) {
unique_ptr<MockFile> mf(MockFile::Create(
"foo" LINE_AS_STRING() ".runfiles_manifest", {"a b", "nospace"}));
ASSERT_TRUE(mf != nullptr);
string error;
unique_ptr<Runfiles> r(
Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
EXPECT_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, ManifestBasedRunfilesRlocationAndEnvVars) {
unique_ptr<MockFile> mf(
MockFile::Create("foo" LINE_AS_STRING() ".runfiles_manifest",
{
"a/b c/d",
"e/f target path with spaces",
" h/\\si j k",
" dir\\swith\\sspaces l/m",
" h/\\n\\s\\bi j k \\n\\b",
"not_escaped with\\backslash and spaces",
}));
ASSERT_TRUE(mf != nullptr);
string error;
unique_ptr<Runfiles> r(
Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
ASSERT_TRUE(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");
EXPECT_EQ(r->Rlocation("a/b/file"), "c/d/file");
EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file"), "c/d/deeply/nested/file");
EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file with spaces"),
"c/d/deeply/nested/file with spaces");
EXPECT_EQ(r->Rlocation("e/f"), "target path with spaces");
EXPECT_EQ(r->Rlocation("e/f/file"), "target path with spaces/file");
EXPECT_EQ(r->Rlocation("h/ i"), "j k");
EXPECT_EQ(r->Rlocation("h/\n \\i"), "j k \n\\");
EXPECT_EQ(r->Rlocation("dir with spaces"), "l/m");
EXPECT_EQ(r->Rlocation("dir with spaces/file"), "l/m/file");
EXPECT_EQ(r->Rlocation("not_escaped"), "with\\backslash and spaces");
}
TEST_F(RunfilesTest, DirectoryBasedRunfilesRlocationAndEnvVars) {
unique_ptr<MockFile> dummy(
MockFile::Create("foo" LINE_AS_STRING() ".runfiles/dummy", {"a/b c/d"}));
ASSERT_TRUE(dummy != nullptr);
string dir = dummy->DirName();
string error;
unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", "", dir, &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("a/b"), dir + "/a/b");
EXPECT_EQ(r->Rlocation("c/d"), dir + "/c/d");
EXPECT_EQ(r->Rlocation(""), "");
EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
EXPECT_EQ(r->Rlocation("foo/"), dir + "/foo/");
EXPECT_EQ(r->Rlocation("foo/bar"), dir + "/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");
AssertEnvvars(*r, "", dir);
}
TEST_F(RunfilesTest, ManifestAndDirectoryBasedRunfilesRlocationAndEnvVars) {
unique_ptr<MockFile> mf(MockFile::Create(
"foo" LINE_AS_STRING() ".runfiles/MANIFEST", {"a/b c/d"}));
ASSERT_TRUE(mf != nullptr);
string dir = mf->DirName();
string error;
unique_ptr<Runfiles> r(
Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("a/b"), "c/d");
EXPECT_EQ(r->Rlocation("c/d"), dir + "/c/d");
EXPECT_EQ(r->Rlocation(""), "");
EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
EXPECT_EQ(r->Rlocation("foo/"), dir + "/foo/");
EXPECT_EQ(r->Rlocation("foo/bar"), dir + "/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");
EXPECT_EQ(r->Rlocation("a/b/file"), "c/d/file");
EXPECT_EQ(r->Rlocation("a/b/deeply/nested/file"), "c/d/deeply/nested/file");
AssertEnvvars(*r, mf->Path(), dir);
}
TEST_F(RunfilesTest, ManifestBasedRunfilesEnvVars) {
unique_ptr<MockFile> mf(
MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles_manifest")));
ASSERT_TRUE(mf != nullptr);
string error;
unique_ptr<Runfiles> r(
Runfiles::Create("ignore-argv0", mf->Path(), "", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
AssertEnvvars(*r, mf->Path(), "");
}
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_AS_STRING() ".runfiles/dummy")));
string argv0(mf->Path().substr(
0, mf->Path().size() - string(".runfiles/dummy").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"", &error));
ASSERT_TRUE(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");
AssertEnvvars(*r, "", argv0 + ".runfiles");
}
TEST_F(RunfilesTest, CreatesDirectoryBasedRunfilesFromEnvvar) {
// We create a directory as a side-effect of creating a mock file.
unique_ptr<MockFile> mf(
MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/dummy")));
string dir = mf->DirName();
string error;
unique_ptr<Runfiles> r(Runfiles::Create("ignore-argv0", "", dir, &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("a/b"), dir + "/a/b");
EXPECT_EQ(r->Rlocation("foo"), dir + "/foo");
EXPECT_EQ(r->Rlocation("/Foo"), "/Foo");
EXPECT_EQ(r->Rlocation("c:/Foo"), "c:/Foo");
EXPECT_EQ(r->Rlocation("c:\\Foo"), "c:\\Foo");
AssertEnvvars(*r, "", dir);
}
TEST_F(RunfilesTest, FailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined) {
unique_ptr<MockFile> mf(
MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/MANIFEST")));
ASSERT_TRUE(mf != nullptr);
string error;
unique_ptr<Runfiles> r(
Runfiles::Create("ignore-argv0", mf->Path(), "whatever", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
// We create a directory as a side-effect of creating a mock file.
mf.reset(MockFile::Create(string("foo" LINE_AS_STRING() ".runfiles/dummy")));
r.reset(Runfiles::Create("ignore-argv0", "", mf->DirName(), &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
r.reset(Runfiles::Create("ignore-argv0", /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"", &error));
EXPECT_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_AS_STRING() "/..")));
EXPECT_TRUE(mf == nullptr);
}
{
unique_ptr<MockFile> mf(MockFile::Create(string("/Foo" LINE_AS_STRING())));
EXPECT_TRUE(mf == nullptr);
}
{
unique_ptr<MockFile> mf(
MockFile::Create(string("C:/Foo" LINE_AS_STRING())));
EXPECT_TRUE(mf == nullptr);
}
string path;
{
unique_ptr<MockFile> mf(
MockFile::Create(string("foo" LINE_AS_STRING() "/bar1/qux")));
ASSERT_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_AS_STRING() "/bar2/qux"), vector<string>()));
ASSERT_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_AS_STRING() "/bar3/qux"),
{"hello world", "you are beautiful"}));
ASSERT_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:\\"));
}
TEST_F(RunfilesTest, PathsFromEnvVars) {
string mf, dir, rm;
// Both envvars have a valid value.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
[](const string& path) { return path == "mock1.runfiles/MANIFEST"; },
[](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "mock1.runfiles/MANIFEST");
EXPECT_EQ(dir, "mock2.runfiles");
// RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good and there's a
// runfiles manifest in the runfiles directory.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
[](const string& path) { return path == "mock2.runfiles/MANIFEST"; },
[](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "mock2.runfiles/MANIFEST");
EXPECT_EQ(dir, "mock2.runfiles");
// RUNFILES_MANIFEST_FILE is invalid but RUNFILES_DIR is good, but there's no
// runfiles manifest in the runfiles directory.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1.runfiles/MANIFEST", "mock2.runfiles",
[](const string& path) { return false; },
[](const string& path) { return path == "mock2.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "");
EXPECT_EQ(dir, "mock2.runfiles");
// RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, and it is in
// a valid-looking runfiles directory.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1.runfiles/MANIFEST", "mock2",
[](const string& path) { return path == "mock1.runfiles/MANIFEST"; },
[](const string& path) { return path == "mock1.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "mock1.runfiles/MANIFEST");
EXPECT_EQ(dir, "mock1.runfiles");
// RUNFILES_DIR is invalid but RUNFILES_MANIFEST_FILE is good, but it is not
// in any valid-looking runfiles directory.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return path == "mock1/MANIFEST"; },
[](const string& path) { return false; }, &mf, &dir));
EXPECT_EQ(mf, "mock1/MANIFEST");
EXPECT_EQ(dir, "");
// Both envvars are invalid, but there's a manifest in a runfiles directory
// next to argv0, however there's no other content in the runfiles directory.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return path == "argv0.runfiles/MANIFEST"; },
[](const string& path) { return false; }, &mf, &dir));
EXPECT_EQ(mf, "argv0.runfiles/MANIFEST");
EXPECT_EQ(dir, "");
// Both envvars are invalid, but there's a manifest next to argv0. There's
// no runfiles tree anywhere.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return path == "argv0.runfiles_manifest"; },
[](const string& path) { return false; }, &mf, &dir));
EXPECT_EQ(mf, "argv0.runfiles_manifest");
EXPECT_EQ(dir, "");
// Both envvars are invalid, but there's a valid manifest next to argv0, and a
// valid runfiles directory (without a manifest in it).
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return path == "argv0.runfiles_manifest"; },
[](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "argv0.runfiles_manifest");
EXPECT_EQ(dir, "argv0.runfiles");
// Both envvars are invalid, but there's a valid runfiles directory next to
// argv0, though no manifest in it.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return false; },
[](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "");
EXPECT_EQ(dir, "argv0.runfiles");
// Both envvars are invalid, but there's a valid runfiles directory next to
// argv0 with a valid manifest in it.
EXPECT_TRUE(TestOnly_PathsFrom(
"argv0", "mock1/MANIFEST", "mock2",
[](const string& path) { return path == "argv0.runfiles/MANIFEST"; },
[](const string& path) { return path == "argv0.runfiles"; }, &mf, &dir));
EXPECT_EQ(mf, "argv0.runfiles/MANIFEST");
EXPECT_EQ(dir, "argv0.runfiles");
}
TEST_F(RunfilesTest, ManifestBasedRlocationWithRepoMapping_fromMain) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".repo_mapping",
{",config.json,config.json+1.2.3", ",my_module,_main",
",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
"protobuf+3.19.2,config.json,config.json+1.2.3",
"protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
ASSERT_TRUE(rm != nullptr);
unique_ptr<MockFile> mf(MockFile::Create(
"foo" + uid + ".runfiles_manifest",
{"_repo_mapping " + rm->Path(), "config.json /etc/config.json",
"protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
"_main/bar/runfile /the/path/./to/other//other runfile.txt",
"protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory"}));
ASSERT_TRUE(mf != nullptr);
string argv0(mf->Path().substr(
0, mf->Path().size() - string(".runfiles_manifest").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
/*source_repository=*/"", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
"/the/path/./to/other//other runfile.txt");
EXPECT_EQ(r->Rlocation("my_workspace/bar/runfile"),
"/the/path/./to/other//other runfile.txt");
EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"),
"C:/Actual Path\\protobuf\\runfile");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"), "E:\\Actual Path\\Directory");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"),
"E:\\Actual Path\\Directory/file");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
"E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"), "");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), "");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"), "");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le"), "");
EXPECT_EQ(r->Rlocation("_main/bar/runfile"),
"/the/path/./to/other//other runfile.txt");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
"C:/Actual Path\\protobuf\\runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
"E:\\Actual Path\\Directory");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
"E:\\Actual Path\\Directory/file");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
"E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("config.json"), "/etc/config.json");
EXPECT_EQ(r->Rlocation("_main"), "");
EXPECT_EQ(r->Rlocation("my_module"), "");
EXPECT_EQ(r->Rlocation("protobuf"), "");
}
TEST_F(RunfilesTest, ManifestBasedRlocationWithRepoMapping_fromOtherRepo) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".repo_mapping",
{",config.json,config.json+1.2.3", ",my_module,_main",
",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
"protobuf+3.19.2,config.json,config.json+1.2.3",
"protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
ASSERT_TRUE(rm != nullptr);
unique_ptr<MockFile> mf(MockFile::Create(
"foo" + uid + ".runfiles_manifest",
{"_repo_mapping " + rm->Path(), "config.json /etc/config.json",
"protobuf+3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
"_main/bar/runfile /the/path/./to/other//other runfile.txt",
"protobuf+3.19.2/bar/dir E:\\Actual Path\\Directory"}));
ASSERT_TRUE(mf != nullptr);
string argv0(mf->Path().substr(
0, mf->Path().size() - string(".runfiles_manifest").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
"protobuf+3.19.2", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
"C:/Actual Path\\protobuf\\runfile");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), "E:\\Actual Path\\Directory");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
"E:\\Actual Path\\Directory/file");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
"E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("my_module/bar/runfile"), "");
EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"), "");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"), "");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"), "");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"), "");
EXPECT_EQ(r->Rlocation("_main/bar/runfile"),
"/the/path/./to/other//other runfile.txt");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
"C:/Actual Path\\protobuf\\runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
"E:\\Actual Path\\Directory");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
"E:\\Actual Path\\Directory/file");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
"E:\\Actual Path\\Directory/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("config.json"), "/etc/config.json");
EXPECT_EQ(r->Rlocation("_main"), "");
EXPECT_EQ(r->Rlocation("my_module"), "");
EXPECT_EQ(r->Rlocation("protobuf"), "");
}
TEST_F(RunfilesTest, DirectoryBasedRlocationWithRepoMapping_fromMain) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
{",config.json,config.json+1.2.3", ",my_module,_main",
",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
"protobuf+3.19.2,config.json,config.json+1.2.3",
"protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
ASSERT_TRUE(rm != nullptr);
string dir = rm->DirName();
string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
/*source_repository=*/"", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("my_module/bar/runfile"), dir + "/_main/bar/runfile");
EXPECT_EQ(r->Rlocation("my_workspace/bar/runfile"),
dir + "/_main/bar/runfile");
EXPECT_EQ(r->Rlocation("my_protobuf/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir"),
dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
dir + "/protobuf/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi+le"),
dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
}
TEST_F(RunfilesTest, DirectoryBasedRlocationWithRepoMapping_fromOtherRepo) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
{",config.json,config.json+1.2.3", ",my_module,_main",
",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
"protobuf+3.19.2,config.json,config.json+1.2.3",
"protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
ASSERT_TRUE(rm != nullptr);
string dir = rm->DirName();
string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
"protobuf+3.19.2", &error));
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
dir + "/my_module/bar/runfile");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
}
TEST_F(RunfilesTest,
DirectoryBasedRlocationWithRepoMapping_fromOtherRepo_withSourceRepo) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".runfiles/_repo_mapping",
{",config.json,config.json+1.2.3", ",my_module,_main",
",my_protobuf,protobuf+3.19.2", ",my_workspace,_main",
"protobuf+3.19.2,config.json,config.json+1.2.3",
"protobuf+3.19.2,protobuf,protobuf+3.19.2"}));
ASSERT_TRUE(rm != nullptr);
string dir = rm->DirName();
string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
/*source_repository=*/"", &error));
r = r->WithSourceRepository("protobuf+3.19.2");
ASSERT_TRUE(r != nullptr);
EXPECT_TRUE(error.empty());
EXPECT_EQ(r->Rlocation("protobuf/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir"), dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("protobuf/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("my_module/bar/runfile"),
dir + "/my_module/bar/runfile");
EXPECT_EQ(r->Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi+le"),
dir + "/my_protobuf/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("_main/bar/runfile"), dir + "/_main/bar/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/foo/runfile"),
dir + "/protobuf+3.19.2/foo/runfile");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir"),
dir + "/protobuf+3.19.2/bar/dir");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/file"),
dir + "/protobuf+3.19.2/bar/dir/file");
EXPECT_EQ(r->Rlocation("protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le"),
dir + "/protobuf+3.19.2/bar/dir/de eply/nes ted/fi+le");
EXPECT_EQ(r->Rlocation("config.json"), dir + "/config.json");
}
TEST_F(RunfilesTest, InvalidRepoMapping) {
string uid = LINE_AS_STRING();
unique_ptr<MockFile> rm(
MockFile::Create("foo" + uid + ".runfiles/_repo_mapping", {"a,b"}));
ASSERT_TRUE(rm != nullptr);
string dir = rm->DirName();
string argv0(dir.substr(0, dir.size() - string(".runfiles").size()));
string error;
unique_ptr<Runfiles> r(Runfiles::Create(argv0, /*runfiles_manifest_file=*/"",
/*runfiles_dir=*/"",
/*source_repository=*/"", &error));
EXPECT_EQ(r, nullptr);
EXPECT_TRUE(error.find("bad repository mapping") != string::npos);
}
} // namespace
} // namespace runfiles
} // namespace cc
} // namespace rules_cc