forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
6.9 KiB
209 lines
6.9 KiB
#include "drake/common/find_runfiles.h"
|
|
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
|
|
#ifdef __APPLE__
|
|
#include <mach-o/dyld.h>
|
|
#endif
|
|
|
|
#include <filesystem>
|
|
|
|
#include "tools/cpp/runfiles/runfiles.h"
|
|
#include <fmt/format.h>
|
|
|
|
#include "drake/common/drake_assert.h"
|
|
#include "drake/common/never_destroyed.h"
|
|
#include "drake/common/text_logging.h"
|
|
|
|
using bazel::tools::cpp::runfiles::Runfiles;
|
|
|
|
namespace drake {
|
|
namespace {
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
// Replace `nullptr` with `"nullptr",` or else just return `arg` unchanged.
|
|
const char* nullable_to_string(const char* arg) {
|
|
return arg ? arg : "nullptr";
|
|
}
|
|
|
|
// Either a bazel_tools Runfiles object xor an error string.
|
|
struct RunfilesSingleton {
|
|
std::unique_ptr<Runfiles> runfiles;
|
|
std::string runfiles_dir;
|
|
std::string error;
|
|
};
|
|
|
|
// Create a bazel_tools Runfiles object xor an error string. This is memoized
|
|
// by GetSingletonRunfiles (i.e., this is only called once per process).
|
|
RunfilesSingleton Create() {
|
|
const char* mechanism{};
|
|
RunfilesSingleton result;
|
|
std::string bazel_error;
|
|
|
|
// Chose a mechanism based on environment variables.
|
|
if (std::getenv("TEST_SRCDIR")) {
|
|
// When running under bazel test, use the test heuristics.
|
|
mechanism = "TEST_SRCDIR";
|
|
result.runfiles.reset(Runfiles::CreateForTest(&bazel_error));
|
|
} else if ((std::getenv("RUNFILES_MANIFEST_FILE") != nullptr) ||
|
|
(std::getenv("RUNFILES_DIR") != nullptr)) {
|
|
// When running with some RUNFILES_* env already set, just use that.
|
|
mechanism = "RUNFILES_{MANIFEST_FILE,DIR}";
|
|
result.runfiles.reset(Runfiles::Create({}, &bazel_error));
|
|
} else {
|
|
// When running from the user's command line, use argv0.
|
|
mechanism = "argv0";
|
|
#ifdef __APPLE__
|
|
std::string argv0;
|
|
argv0.resize(4096);
|
|
uint32_t buf_size = argv0.size();
|
|
int error = _NSGetExecutablePath(&argv0.front(), &buf_size);
|
|
if (error) {
|
|
throw std::runtime_error("Error from _NSGetExecutablePath");
|
|
}
|
|
argv0 = argv0.c_str(); // Remove trailing nil bytes.
|
|
drake::log()->debug("_NSGetExecutablePath = {}", argv0);
|
|
#else
|
|
const std::string& argv0 = fs::read_symlink({
|
|
"/proc/self/exe"}).string();
|
|
drake::log()->debug("readlink(/proc/self/exe) = {}", argv0);
|
|
#endif
|
|
result.runfiles.reset(Runfiles::Create(argv0, &bazel_error));
|
|
}
|
|
drake::log()->debug("FindRunfile mechanism = {}", mechanism);
|
|
drake::log()->debug("cwd = \"{}\"", fs::current_path().string());
|
|
|
|
// If there were runfiles, identify the RUNFILES_DIR.
|
|
if (result.runfiles) {
|
|
for (const auto& key_value : result.runfiles->EnvVars()) {
|
|
if (key_value.first == "RUNFILES_DIR") {
|
|
// N.B. We must normalize the path; otherwise the path may include
|
|
// `parent/./path` if the binary was run using `./bazel-bin/target` vs
|
|
// `bazel-bin/target`.
|
|
// TODO(eric.cousineau): Show this in Drake itself. This behavior was
|
|
// encountered in Anzu issue 5653, in a Python binary.
|
|
fs::path path = key_value.second;
|
|
path = fs::absolute(path);
|
|
path = path.lexically_normal();
|
|
result.runfiles_dir = path.string();
|
|
break;
|
|
}
|
|
}
|
|
// If we didn't find it, something was very wrong.
|
|
if (result.runfiles_dir.empty()) {
|
|
bazel_error = "RUNFILES_DIR was not provided by the Runfiles object";
|
|
result.runfiles.reset();
|
|
} else if (!fs::is_directory({result.runfiles_dir})) {
|
|
bazel_error = fmt::format(
|
|
"RUNFILES_DIR '{}' does not exist", result.runfiles_dir);
|
|
result.runfiles.reset();
|
|
}
|
|
}
|
|
|
|
// Report any error.
|
|
if (!result.runfiles) {
|
|
result.runfiles_dir.clear();
|
|
result.error = fmt::format(
|
|
"{} (created using {} with TEST_SRCDIR={} and "
|
|
"RUNFILES_MANIFEST_FILE={} and RUNFILES_DIR={})",
|
|
bazel_error, mechanism,
|
|
nullable_to_string(std::getenv("TEST_SRCDIR")),
|
|
nullable_to_string(std::getenv("RUNFILES_MANIFEST_FILE")),
|
|
nullable_to_string(std::getenv("RUNFILES_DIR")));
|
|
drake::log()->debug("FindRunfile error: {}", result.error);
|
|
}
|
|
|
|
// Sanity check our return value.
|
|
if (result.runfiles.get() == nullptr) {
|
|
DRAKE_DEMAND(result.runfiles_dir.empty());
|
|
DRAKE_DEMAND(result.error.length() > 0);
|
|
} else {
|
|
DRAKE_DEMAND(result.runfiles_dir.length() > 0);
|
|
DRAKE_DEMAND(result.error.empty());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Returns the RunfilesSingleton for the current process, latch-initializing it
|
|
// first if necessary.
|
|
const RunfilesSingleton& GetRunfilesSingleton() {
|
|
static const never_destroyed<RunfilesSingleton> result{Create()};
|
|
return result.access();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool HasRunfiles() {
|
|
return GetRunfilesSingleton().runfiles.get() != nullptr;
|
|
}
|
|
|
|
RlocationOrError FindRunfile(const std::string& resource_path) {
|
|
const auto& singleton = GetRunfilesSingleton();
|
|
|
|
// Check for HasRunfiles.
|
|
RlocationOrError result;
|
|
if (!singleton.runfiles) {
|
|
DRAKE_DEMAND(!singleton.error.empty());
|
|
result.error = singleton.error;
|
|
return result;
|
|
}
|
|
|
|
// Check the user input.
|
|
if (resource_path.empty()) {
|
|
result.error = "Resource path must not be empty";
|
|
return result;
|
|
}
|
|
if (resource_path[0] == '/') {
|
|
result.error = fmt::format(
|
|
"Resource path '{}' must not be an absolute path", resource_path);
|
|
return result;
|
|
}
|
|
|
|
// Locate the file on the manifest and in the directory.
|
|
const std::string by_man = singleton.runfiles->Rlocation(resource_path);
|
|
const std::string by_dir = singleton.runfiles_dir + "/" + resource_path;
|
|
const bool by_man_ok = fs::is_regular_file({by_man});
|
|
const bool by_dir_ok = fs::is_regular_file({by_dir});
|
|
drake::log()->debug(
|
|
"FindRunfile found by-manifest '{}' ({}) and by-directory '{}' ({})",
|
|
by_man, by_man_ok ? "good" : "bad", by_dir, by_dir_ok ? "good" : "bad");
|
|
|
|
if (by_man_ok && by_dir_ok) {
|
|
// We must always return the directory-based result (not the manifest
|
|
// result) because the result itself may be a file that contains embedded
|
|
// relative pathnames. The manifest result might actually be in the source
|
|
// tree, not the runfiles directory, and in that case relative paths may
|
|
// not work (e.g., when the relative path refers to a genfile).
|
|
result.abspath = by_dir;
|
|
return result;
|
|
}
|
|
|
|
// Report an error.
|
|
const char* detail{};
|
|
if (!by_man_ok && !by_dir_ok) {
|
|
detail =
|
|
"but the file does not exist at that location "
|
|
"nor is it on the manifest";
|
|
} else if (!by_man_ok && by_dir_ok) {
|
|
detail =
|
|
"and the file exists at that location "
|
|
"but it is not on the manifest";
|
|
} else {
|
|
DRAKE_DEMAND(by_man_ok && !by_dir_ok);
|
|
detail =
|
|
"and it is on the manifest"
|
|
"but the file does not exist at that location";
|
|
}
|
|
result.error = fmt::format(
|
|
"Sought '{}' in runfiles directory '{}' {}; "
|
|
"perhaps a 'data = []' dependency is missing.",
|
|
resource_path, singleton.runfiles_dir, detail);
|
|
return result;
|
|
}
|
|
|
|
} // namespace drake
|