1
1
#include " common/find_resource.h"
2
2
3
3
#include < cstdlib>
4
+ #include < utility>
5
+ #include < vector>
6
+
4
7
#include < spruce.hh>
5
8
9
+ #include " drake/common/drake_marker.h"
6
10
#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 "
9
13
10
14
using std::string;
11
15
12
16
namespace dairlib {
13
17
14
- using Result = drake:: FindResourceResult;
18
+ using Result = FindResourceResult;
15
19
using drake::optional;
16
20
using drake::nullopt;
17
21
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
+
18
105
// Returns true iff the path is relative (not absolute).
19
106
bool IsRelativePath (const string& path) {
107
+ // TODO(jwnimmer-tri) Prevent .. escape?
20
108
return !path.empty () && (path[0 ] != ' /' );
21
109
}
22
110
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
+
23
136
// 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".
24
140
optional<string> CheckCandidateDir (const spruce::path& candidate_dir) {
25
141
// If we found the sentinel, we win.
26
142
spruce::path candidate_file = candidate_dir;
@@ -31,13 +147,33 @@ optional<string> CheckCandidateDir(const spruce::path& candidate_dir) {
31
147
return nullopt;
32
148
}
33
149
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
+ }
34
169
35
170
// Returns the directory that contains our sentinel file, searching from the
36
171
// 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".
38
175
optional<string> FindSentinelDir () {
39
176
spruce::path candidate_dir = spruce::dir::getcwd ();
40
- std::cout << candidate_dir.getStr () << std::endl;
41
177
int num_attempts = 0 ;
42
178
while (true ) {
43
179
DRAKE_THROW_UNLESS (num_attempts < 1000 ); // Insanity fail-fast.
@@ -59,30 +195,84 @@ optional<string> FindSentinelDir() {
59
195
}
60
196
}
61
197
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));
74
222
}
75
223
76
224
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.
77
231
if (!IsRelativePath (resource_path)) {
78
232
return Result::make_error (
79
233
std::move (resource_path),
80
234
" resource_path is not a relative path" );
81
235
}
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
+ }
86
276
}
87
277
88
278
// Nothing found.
@@ -94,6 +284,4 @@ std::string FindResourceOrThrow(std::string resource_path) {
94
284
return FindResource (std::move (resource_path)).get_absolute_path_or_throw ();
95
285
}
96
286
97
-
98
-
99
287
} // namespace drake
0 commit comments