Skip to content

Commit 2628644

Browse files
committed
Added and incorporated find resource utilities. See cassie_utils.h for a good way to construct models.
1 parent 3d8ee29 commit 2628644

10 files changed

+351
-51
lines changed

.dairlib-find_resource-sentinel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file is used as a sentinel to anchor the implementation of FindResource.

common/BUILD.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
cc_library(
4+
name = "common",
5+
hdrs = [
6+
"find_resource.h"
7+
],
8+
srcs = ["find_resource.cc"],
9+
deps = [
10+
"@drake//common",
11+
]
12+
)

common/find_resource.cc

Lines changed: 211 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,142 @@
11
#include "common/find_resource.h"
22

33
#include <cstdlib>
4+
#include <utility>
5+
#include <vector>
6+
47
#include <spruce.hh>
58

9+
#include "drake/common/drake_marker.h"
610
#include "drake/common/drake_throw.h"
7-
#include "drake/common/drake_optional.h"
8-
11+
#include "drake/common/find_loaded_library.h"
12+
#include "drake/common/never_destroyed.h"
913

1014
using std::string;
1115

1216
namespace dairlib {
1317

14-
using Result = drake::FindResourceResult;
18+
using Result = FindResourceResult;
1519
using drake::optional;
1620
using drake::nullopt;
1721

22+
optional<string>
23+
Result::get_absolute_path() const {
24+
return absolute_path_;
25+
}
26+
27+
string
28+
Result::get_absolute_path_or_throw() const {
29+
// If we have a path, return it.
30+
const auto& optional_path = get_absolute_path();
31+
if (optional_path) { return *optional_path; }
32+
33+
// Otherwise, throw the error message.
34+
const auto& optional_error = get_error_message();
35+
DRAKE_ASSERT(optional_error != nullopt);
36+
throw std::runtime_error(*optional_error);
37+
}
38+
39+
optional<string>
40+
Result::get_error_message() const {
41+
// If an error has been set, return it.
42+
if (error_message_ != nullopt) {
43+
DRAKE_ASSERT(absolute_path_ == nullopt);
44+
return error_message_;
45+
}
46+
47+
// If successful, return no-error.
48+
if (absolute_path_ != nullopt) {
49+
return nullopt;
50+
}
51+
52+
// Both optionals are null; we are empty; return a default error message.
53+
DRAKE_ASSERT(resource_path_.empty());
54+
return string("No resource was requested (empty result)");
55+
}
56+
57+
string Result::get_resource_path() const {
58+
return resource_path_;
59+
}
60+
61+
Result Result::make_success(string resource_path, string absolute_path) {
62+
DRAKE_THROW_UNLESS(!resource_path.empty());
63+
DRAKE_THROW_UNLESS(!absolute_path.empty());
64+
65+
Result result;
66+
result.resource_path_ = std::move(resource_path);
67+
result.absolute_path_ = std::move(absolute_path);
68+
result.CheckInvariants();
69+
return result;
70+
}
71+
72+
Result Result::make_error(string resource_path, string error_message) {
73+
DRAKE_THROW_UNLESS(!resource_path.empty());
74+
DRAKE_THROW_UNLESS(!error_message.empty());
75+
76+
Result result;
77+
result.resource_path_ = std::move(resource_path);
78+
result.error_message_ = std::move(error_message);
79+
result.CheckInvariants();
80+
return result;
81+
}
82+
83+
Result Result::make_empty() {
84+
Result result;
85+
result.CheckInvariants();
86+
return result;
87+
}
88+
89+
void Result::CheckInvariants() {
90+
if (resource_path_.empty()) {
91+
// For our "empty" state, both success and error must be empty.
92+
DRAKE_DEMAND(absolute_path_ == nullopt);
93+
DRAKE_DEMAND(error_message_ == nullopt);
94+
} else {
95+
// For our "non-empty" state, we must have exactly one of success or error.
96+
DRAKE_DEMAND((absolute_path_ == nullopt) != (error_message_ == nullopt));
97+
}
98+
// When non-nullopt, the path and error cannot be the empty string.
99+
DRAKE_DEMAND((absolute_path_ == nullopt) || !absolute_path_->empty());
100+
DRAKE_DEMAND((error_message_ == nullopt) || !error_message_->empty());
101+
}
102+
103+
namespace {
104+
18105
// Returns true iff the path is relative (not absolute).
19106
bool IsRelativePath(const string& path) {
107+
// TODO(jwnimmer-tri) Prevent .. escape?
20108
return !path.empty() && (path[0] != '/');
21109
}
22110

111+
// Returns the absolute_path iff the `$dirpath/$relpath` exists, else nullopt.
112+
// As a convenience to callers, if `dirpath` is nullopt, the result is nullopt.
113+
// (To inquire about an empty `dirpath`, pass the empty string, not nullopt.)
114+
optional<string> FileExists(
115+
const optional<string>& dirpath, const string& relpath) {
116+
DRAKE_ASSERT(IsRelativePath(relpath));
117+
if (!dirpath) { return nullopt; }
118+
const spruce::path dir_query(*dirpath);
119+
if (!dir_query.isDir()) { return nullopt; }
120+
const spruce::path file_query(dir_query.getStr() + '/' + relpath);
121+
if (!file_query.exists()) { return nullopt; }
122+
return file_query.getStr();
123+
}
124+
125+
// Given a path like /foo/bar, returns the path /foo/bar/drake.
126+
// Iff the path is nullopt, then the result is nullopt.
127+
optional<string> AppendDrakeTo(const optional<string>& path) {
128+
if (path) {
129+
spruce::path result = *path;
130+
result.append("drake");
131+
return result.getStr();
132+
}
133+
return nullopt;
134+
}
135+
23136
// Returns candidate_dir iff it exists and contains our sentinel file.
137+
// Candidate paths will already end with "drake" as their final path element,
138+
// or possibly a related name like "drake2"; that is, they will contain files
139+
// named like "common/foo.txt", not "drake/common/foo.txt".
24140
optional<string> CheckCandidateDir(const spruce::path& candidate_dir) {
25141
// If we found the sentinel, we win.
26142
spruce::path candidate_file = candidate_dir;
@@ -31,13 +147,33 @@ optional<string> CheckCandidateDir(const spruce::path& candidate_dir) {
31147
return nullopt;
32148
}
33149

150+
// Returns a sentinel directory appropriate when running under `bazel test`.
151+
optional<string> GetTestRunfilesDir() {
152+
// These environment variables are documented at:
153+
// https://docs.bazel.build/versions/master/test-encyclopedia.html#initial-conditions
154+
// We check TEST_TMPDIR as a sanity check that we're being called by Bazel.
155+
// Other than TEST_SRCDIR below, its the only other non-standard environment
156+
// variable that Bazel is required to set when running a test.
157+
if (::getenv("TEST_TMPDIR") == nullptr) {
158+
// Not running under `bazel test`.
159+
return nullopt;
160+
}
161+
char* test_srcdir = ::getenv("TEST_SRCDIR");
162+
if (test_srcdir == nullptr) {
163+
// Apparently running under `bazel test`, but no runfiles tree is set?
164+
// Maybe TEST_TMPDIR was something other than Bazel; ignore it.
165+
return nullopt;
166+
}
167+
return CheckCandidateDir(*AppendDrakeTo(string(test_srcdir)));
168+
}
34169

35170
// Returns the directory that contains our sentinel file, searching from the
36171
// current directory and working up through all transitive parent directories
37-
// up to "/".
172+
// up to "/". Candidate paths will already end with "drake" as their final
173+
// path element, or possibly a related name like "drake2"; that is, they will
174+
// contain files named like "common/foo.txt", not "drake/common/foo.txt".
38175
optional<string> FindSentinelDir() {
39176
spruce::path candidate_dir = spruce::dir::getcwd();
40-
std::cout << candidate_dir.getStr() << std::endl;
41177
int num_attempts = 0;
42178
while (true) {
43179
DRAKE_THROW_UNLESS(num_attempts < 1000); // Insanity fail-fast.
@@ -59,30 +195,84 @@ optional<string> FindSentinelDir() {
59195
}
60196
}
61197

62-
// Returns the absolute_path iff the `$dirpath/$relpath` exists, else nullopt.
63-
// As a convenience to callers, if `dirpath` is nullopt, the result is nullopt.
64-
// (To inquire about an empty `dirpath`, pass the empty string, not nullopt.)
65-
optional<string> FileExists(
66-
const optional<string>& dirpath, const string& relpath) {
67-
DRAKE_ASSERT(IsRelativePath(relpath));
68-
if (!dirpath) { return nullopt; }
69-
const spruce::path dir_query(*dirpath);
70-
if (!dir_query.isDir()) { return nullopt; }
71-
const spruce::path file_query(dir_query.getStr() + '/' + relpath);
72-
if (!file_query.exists()) { return nullopt; }
73-
return file_query.getStr();
198+
} // namespace
199+
200+
const char* const kDrakeResourceRootEnvironmentVariableName =
201+
"DRAKE_RESOURCE_ROOT";
202+
203+
// Saves search directorys path in a persistent variable.
204+
// This function is only accessible from this file and should not
205+
// be used outside of `GetResourceSearchPaths()` and
206+
// `AddResourceSearchPath()`.
207+
namespace {
208+
std::vector<string>& GetMutableResourceSearchPaths() {
209+
static drake::never_destroyed<std::vector<string>> search_paths;
210+
return search_paths.access();
211+
}
212+
} // namespace
213+
214+
std::vector<string> GetResourceSearchPaths() {
215+
return GetMutableResourceSearchPaths();
216+
}
217+
218+
void AddResourceSearchPath(string search_path) {
219+
// Throw an error if path is relative.
220+
DRAKE_THROW_UNLESS(!IsRelativePath(search_path));
221+
GetMutableResourceSearchPaths().push_back(std::move(search_path));
74222
}
75223

76224
Result FindResource(string resource_path) {
225+
// Check if resource_path is well-formed: a relative path that starts with
226+
// "drake" as its first directory name. A valid example would look like:
227+
// "drake/common/test/find_resource_test_data.txt". Requiring strings passed
228+
// to drake::FindResource to start with "drake" is redundant, but preserves
229+
// compatibility with the original semantics of this function; if we want to
230+
// offer a function that takes paths without "drake", we can use a new name.
77231
if (!IsRelativePath(resource_path)) {
78232
return Result::make_error(
79233
std::move(resource_path),
80234
"resource_path is not a relative path");
81235
}
82-
std::cout << *FindSentinelDir() << " ** " << resource_path << std::endl;
83-
if (auto absolute_path = FileExists(FindSentinelDir(), resource_path)) {
84-
return Result::make_success(
85-
std::move(resource_path), std::move(*absolute_path));
236+
237+
// Collect a list of (priority-ordered) directories to check. Candidate
238+
// paths will already end with "drake" as their final path element, or
239+
// possibly a related name like "drake2"; that is, they will contain files
240+
// named like "common/foo.txt", not "drake/common/foo.txt".
241+
std::vector<optional<string>> candidate_dirs;
242+
243+
// (1) Add the current directory
244+
candidate_dirs.emplace_back(spruce::dir::getcwd().getStr());
245+
246+
// (2) Add the list of paths given programmatically. Paths are added only
247+
// if the sentinel file can be found.
248+
for (const auto& search_path : GetMutableResourceSearchPaths()) {
249+
spruce::path candidate_dir(*AppendDrakeTo(search_path));
250+
candidate_dirs.emplace_back(CheckCandidateDir(candidate_dir));
251+
}
252+
253+
// (3) Find resources during `bazel test` execution.
254+
candidate_dirs.emplace_back(GetTestRunfilesDir());
255+
256+
// (4) Search in cwd (and its parent, grandparent, etc.) to find Drake's
257+
// resource-root sentinel file.
258+
candidate_dirs.emplace_back(FindSentinelDir());
259+
260+
// Make sure that candidate_dirs are not relative paths. This could cause
261+
// bugs, but in theory it should never happen as the code above should
262+
// guard against it.
263+
for (const auto& candidate_dir : candidate_dirs) {
264+
if (candidate_dir && IsRelativePath(candidate_dir.value())) {
265+
string error_message = "path is not absolute: " + candidate_dir.value();
266+
return Result::make_error(std::move(resource_path), error_message);
267+
}
268+
}
269+
270+
// See which (if any) candidate contains the requested resource.
271+
for (const auto& candidate_dir : candidate_dirs) {
272+
if (auto absolute_path = FileExists(candidate_dir, resource_path)) {
273+
return Result::make_success(
274+
std::move(resource_path), std::move(*absolute_path));
275+
}
86276
}
87277

88278
// Nothing found.
@@ -94,6 +284,4 @@ std::string FindResourceOrThrow(std::string resource_path) {
94284
return FindResource(std::move(resource_path)).get_absolute_path_or_throw();
95285
}
96286

97-
98-
99287
} // namespace drake

0 commit comments

Comments
 (0)