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.
Conception/drake-master/common/find_runfiles.cc

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