mirror of https://github.com/bazelbuild/rules_cc
483 lines
14 KiB
C++
483 lines
14 KiB
C++
// 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 <algorithm>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#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
|