Merge lp:unity-scopes-api/staging into lp:unity-scopes-api
- staging
- Merge into trunk
Proposed by
Paweł Stołowski
Status: | Merged |
---|---|
Approved by: | Marcus Tomlinson |
Approved revision: | 305 |
Merged at revision: | 351 |
Proposed branch: | lp:unity-scopes-api/staging |
Merge into: | lp:unity-scopes-api |
Diff against target: |
492 lines (+202/-97) 11 files modified
include/unity/scopes/internal/Utils.h (+5/-0) scoperegistry/scoperegistry.cpp (+1/-51) src/scopes/internal/JsonCppNode.cpp (+10/-0) src/scopes/internal/Utils.cpp (+83/-0) src/scopes/internal/zmq_middleware/ZmqObject.cpp (+1/-4) test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.cpp (+5/-1) test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.ini.in (+1/-1) test/gtest/scopes/internal/SettingsDB/SettingsDB_test.cpp (+7/-3) test/gtest/scopes/internal/Utils/Utils_test.cpp (+86/-0) test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp (+2/-37) unity-scopes.map (+1/-0) |
To merge this branch: | bzr merge lp:unity-scopes-api/staging |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marcus Tomlinson (community) | Approve | ||
Review via email:
|
Commit message
Merged devel.
Description of the change
Merged devel.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'include/unity/scopes/internal/Utils.h' |
2 | --- include/unity/scopes/internal/Utils.h 2015-04-10 03:55:25 +0000 |
3 | +++ include/unity/scopes/internal/Utils.h 2015-09-21 09:13:55 +0000 |
4 | @@ -22,6 +22,8 @@ |
5 | #include <string> |
6 | #include <sstream> |
7 | |
8 | +#include <boost/filesystem.hpp> |
9 | + |
10 | namespace unity |
11 | { |
12 | |
13 | @@ -57,6 +59,9 @@ |
14 | |
15 | void make_directories(std::string const& path_name, mode_t mode); |
16 | |
17 | +std::vector<std::string> split_exec_args(std::string const& id, std::string const& custom_exec); |
18 | +std::string convert_exec_rel_to_abs(std::string const& id, boost::filesystem::path const& scope_dir, std::string const& custom_exec); |
19 | + |
20 | } // namespace internal |
21 | |
22 | } // namespace scopes |
23 | |
24 | === modified file 'scoperegistry/scoperegistry.cpp' |
25 | --- scoperegistry/scoperegistry.cpp 2015-07-01 11:02:46 +0000 |
26 | +++ scoperegistry/scoperegistry.cpp 2015-09-21 09:13:55 +0000 |
27 | @@ -259,30 +259,6 @@ |
28 | } |
29 | } |
30 | |
31 | -string get_first_component(string const& id, string const& custom_exec) |
32 | -{ |
33 | - if (custom_exec.empty()) |
34 | - { |
35 | - throw unity::InvalidArgumentException("Invalid scope runner executable for scope: " + id); |
36 | - } |
37 | - |
38 | - wordexp_t exp; |
39 | - string result; |
40 | - |
41 | - // Split command into program and args |
42 | - if (wordexp(custom_exec.c_str(), &exp, WRDE_NOCMD) == 0) |
43 | - { |
44 | - util::ResourcePtr<wordexp_t*, decltype(&wordfree)> free_guard(&exp, wordfree); |
45 | - result = exp.we_wordv[0]; |
46 | - } |
47 | - else |
48 | - { |
49 | - throw unity::InvalidArgumentException("Invalid scope runner executable for scope: " + id); |
50 | - } |
51 | - |
52 | - return result; |
53 | -} |
54 | - |
55 | // For each scope, open the config file for the scope, create the metadata info from the config, |
56 | // and add an entry to the RegistryObject. |
57 | // If the scope uses settings, also parse the settings file and add the settings to the metadata. |
58 | @@ -435,33 +411,7 @@ |
59 | |
60 | try |
61 | { |
62 | - auto custom_exec = sc.scope_runner(); |
63 | - filesystem::path program(get_first_component( scope.first, custom_exec)); |
64 | - if (program.is_relative()) |
65 | - { |
66 | - // First look inside the arch-specific directory |
67 | - if (filesystem::exists(scope_dir / DEB_HOST_MULTIARCH / program)) |
68 | - { |
69 | - // Join the full command, not just the program path |
70 | - exec_data.custom_exec = (scope_dir / DEB_HOST_MULTIARCH / custom_exec).native(); |
71 | - } |
72 | - // Next try in the non arch-aware directory |
73 | - else if (filesystem::exists(scope_dir / program)) |
74 | - { |
75 | - // Join the full command, not just the program path |
76 | - exec_data.custom_exec = (scope_dir / custom_exec).native(); |
77 | - } |
78 | - else |
79 | - { |
80 | - throw unity::InvalidArgumentException( |
81 | - "Nonexistent scope runner '" + custom_exec |
82 | - + "' executable for scope: " + scope.first); |
83 | - } |
84 | - } |
85 | - else |
86 | - { |
87 | - exec_data.custom_exec = custom_exec; |
88 | - } |
89 | + exec_data.custom_exec = convert_exec_rel_to_abs(scope.first, scope_dir, sc.scope_runner()); |
90 | } |
91 | catch (NotFoundException const&) |
92 | { |
93 | |
94 | === modified file 'src/scopes/internal/JsonCppNode.cpp' |
95 | --- src/scopes/internal/JsonCppNode.cpp 2015-07-13 08:44:43 +0000 |
96 | +++ src/scopes/internal/JsonCppNode.cpp 2015-09-21 09:13:55 +0000 |
97 | @@ -279,6 +279,11 @@ |
98 | |
99 | JsonNodeInterface::SPtr JsonCppNode::get_node(std::string const& node_name) const |
100 | { |
101 | + if (!root_) |
102 | + { |
103 | + throw unity::LogicException("Current node is empty"); |
104 | + } |
105 | + |
106 | if (!root_.isMember(node_name)) |
107 | { |
108 | throw unity::LogicException("Node " + node_name + " does not exist"); |
109 | @@ -290,6 +295,11 @@ |
110 | |
111 | JsonNodeInterface::SPtr JsonCppNode::get_node(unsigned int node_index) const |
112 | { |
113 | + if (!root_) |
114 | + { |
115 | + throw unity::LogicException("Current node is empty"); |
116 | + } |
117 | + |
118 | if (root_.type() != Json::arrayValue) |
119 | { |
120 | throw unity::LogicException("Root node is not an array"); |
121 | |
122 | === modified file 'src/scopes/internal/Utils.cpp' |
123 | --- src/scopes/internal/Utils.cpp 2015-04-10 03:55:25 +0000 |
124 | +++ src/scopes/internal/Utils.cpp 2015-09-21 09:13:55 +0000 |
125 | @@ -19,12 +19,14 @@ |
126 | #include <unity/scopes/internal/Utils.h> |
127 | #include <unity/scopes/ScopeExceptions.h> |
128 | #include <unity/UnityExceptions.h> |
129 | +#include <unity/util/ResourcePtr.h> |
130 | |
131 | #include <boost/filesystem.hpp> |
132 | |
133 | #include <iomanip> |
134 | #include <locale> |
135 | #include <mutex> |
136 | +#include <wordexp.h> |
137 | |
138 | #include <sys/stat.h> |
139 | |
140 | @@ -190,6 +192,87 @@ |
141 | } |
142 | } |
143 | |
144 | +vector<string> split_exec_args(string const& id, string const& custom_exec) |
145 | +{ |
146 | + if (custom_exec.empty()) |
147 | + { |
148 | + throw unity::InvalidArgumentException("Invalid empty executable for scope: '" + id + "'"); |
149 | + } |
150 | + |
151 | + wordexp_t exp; |
152 | + vector<string> result; |
153 | + |
154 | + // Split command into program and args |
155 | + if (wordexp(custom_exec.c_str(), &exp, WRDE_NOCMD) == 0) |
156 | + { |
157 | + util::ResourcePtr<wordexp_t*, decltype(&wordfree)> free_guard(&exp, wordfree); |
158 | + for (size_t i = 0; i < exp.we_wordc; ++i ) |
159 | + { |
160 | + auto argument = string(exp.we_wordv[i]); |
161 | + if(argument.find_first_of(' ') != std::string::npos) |
162 | + { |
163 | + // This argument contains spaces, enclose it in quotation marks |
164 | + result.emplace_back("\"" + argument + "\""); |
165 | + } |
166 | + else |
167 | + { |
168 | + result.emplace_back(argument); |
169 | + } |
170 | + } |
171 | + } |
172 | + else |
173 | + { |
174 | + throw unity::InvalidArgumentException("Invalid executable for scope: '" + id + "'"); |
175 | + } |
176 | + |
177 | + return result; |
178 | +} |
179 | + |
180 | +string convert_exec_rel_to_abs(string const& id, boost::filesystem::path const& scope_dir, string const& custom_exec) |
181 | +{ |
182 | + string result; |
183 | + |
184 | + // Loop through each argument of the scope runner command and ensure all path args are absolute |
185 | + auto custom_exec_args = split_exec_args(id, custom_exec); |
186 | + for (auto custom_exec_arg : custom_exec_args) |
187 | + { |
188 | + boost::filesystem::path argument(custom_exec_arg); |
189 | + if (argument.is_relative()) |
190 | + { |
191 | + // First look inside the arch-specific directory |
192 | + if (boost::filesystem::exists(scope_dir / DEB_HOST_MULTIARCH / argument)) |
193 | + { |
194 | + // Append the argument as a relative path |
195 | + result += (scope_dir / DEB_HOST_MULTIARCH / argument).native() + " "; |
196 | + } |
197 | + // Next try in the non arch-aware directory |
198 | + else if (boost::filesystem::exists(scope_dir / argument)) |
199 | + { |
200 | + // Append the argument as a relative path |
201 | + result += (scope_dir / argument).native() + " "; |
202 | + } |
203 | + // If this is the first argument (program name) it must exist, throw here |
204 | + else if (result.empty()) |
205 | + { |
206 | + throw unity::InvalidArgumentException( |
207 | + "Nonexistent scope runner executable: '" + custom_exec_arg |
208 | + + "' for scope: '" + id + "'"); |
209 | + } |
210 | + // Otherwise just append the argument as is |
211 | + else |
212 | + { |
213 | + result += custom_exec_arg + " "; |
214 | + } |
215 | + } |
216 | + else |
217 | + { |
218 | + result += custom_exec_arg + " "; |
219 | + } |
220 | + } |
221 | + result.resize(result.size() - 1); |
222 | + return result; |
223 | +} |
224 | + |
225 | } // namespace internal |
226 | |
227 | } // namespace scopes |
228 | |
229 | === modified file 'src/scopes/internal/zmq_middleware/ZmqObject.cpp' |
230 | --- src/scopes/internal/zmq_middleware/ZmqObject.cpp 2015-08-04 04:56:01 +0000 |
231 | +++ src/scopes/internal/zmq_middleware/ZmqObject.cpp 2015-09-21 09:13:55 +0000 |
232 | @@ -250,16 +250,13 @@ |
233 | |
234 | ZmqObjectProxy::TwowayOutParams ZmqObjectProxy::invoke_twoway__(capnp::MessageBuilder& request, int64_t timeout) |
235 | { |
236 | - RequestMode mode; |
237 | std::string endpoint; |
238 | { |
239 | lock_guard<mutex> lock(shared_mutex); |
240 | - mode = mode_; |
241 | endpoint = endpoint_; |
242 | + assert(mode_ == RequestMode::Twoway); |
243 | } |
244 | |
245 | - assert(mode == RequestMode::Twoway); |
246 | - |
247 | zmqpp::socket s(*mw_base()->context(), zmqpp::socket_type::request); |
248 | // Allow some linger time so we don't hang indefinitely if the other end disappears. |
249 | s.set(zmqpp::socket_option::linger, 100); |
250 | |
251 | === modified file 'test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.cpp' |
252 | --- test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.cpp 2014-12-05 03:05:37 +0000 |
253 | +++ test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.cpp 2015-09-21 09:13:55 +0000 |
254 | @@ -25,6 +25,9 @@ |
255 | #include <unity/scopes/Runtime.h> |
256 | #include <unity/scopes/PreviewReply.h> |
257 | |
258 | +#include <boost/filesystem.hpp> |
259 | + |
260 | +using namespace boost; |
261 | using namespace std; |
262 | using namespace unity::scopes; |
263 | |
264 | @@ -102,10 +105,11 @@ |
265 | std::string scope_config = argc > 2 ? argv[2] : "FAIL"; |
266 | std::string rt_config = argc > 3 ? argv[3] : "FAIL"; |
267 | std::string unused_arg_2 = argc > 4 ? argv[4] : "FAIL"; |
268 | + filesystem::path abs_path_arg(argc > 5 ? argv[5] : "FAIL"); |
269 | |
270 | // In order to test that custom exec splitting works, we check here that our arbitrary arguments |
271 | // have been delivered to us as expected. |
272 | - if (unused_arg_1 == "unused arg 1" && unused_arg_2 == "unused arg 2") |
273 | + if (unused_arg_1 == "unused arg 1" && unused_arg_2 == "unused arg 2" && filesystem::exists(abs_path_arg)) |
274 | { |
275 | MyScope scope; |
276 | auto runtime = Runtime::create_scope_runtime("testscopeB", rt_config); |
277 | |
278 | === modified file 'test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.ini.in' |
279 | --- test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.ini.in 2014-12-08 15:17:04 +0000 |
280 | +++ test/gtest/scopes/Registry/scopes/testscopeB/testscopeB.ini.in 2015-09-21 09:13:55 +0000 |
281 | @@ -6,5 +6,5 @@ |
282 | Icon = data/scope-B.Icon |
283 | SearchHint = scope-B.SearchHint |
284 | HotKey = scope-B.HotKey |
285 | -ScopeRunner = testscopeB "unused arg 1" %S %R unused' 'arg' '2 |
286 | +ScopeRunner = testscopeB "unused arg 1" %S %R unused' 'arg' '2 ./testscopeB.ini |
287 | IdleTimeout = 2 |
288 | |
289 | === modified file 'test/gtest/scopes/internal/SettingsDB/SettingsDB_test.cpp' |
290 | --- test/gtest/scopes/internal/SettingsDB/SettingsDB_test.cpp 2015-07-13 14:43:24 +0000 |
291 | +++ test/gtest/scopes/internal/SettingsDB/SettingsDB_test.cpp 2015-09-21 09:13:55 +0000 |
292 | @@ -44,7 +44,7 @@ |
293 | int fd = ::open(db_name.c_str(), O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); |
294 | if (fd == -1) |
295 | { |
296 | - FAIL() << "Couldn't open file " << db_name << " " << errno; |
297 | + FAIL() << "Couldn't open file " << db_name << ", errno: " << errno; |
298 | } |
299 | |
300 | struct flock fl; |
301 | @@ -56,7 +56,7 @@ |
302 | |
303 | if (::fcntl(fd, F_SETLKW, &fl) != 0) |
304 | { |
305 | - FAIL() << "Couldn't get write lock for " << db_name << " " << errno; |
306 | + FAIL() << "Couldn't get write lock for " << db_name << ", errno: " << errno; |
307 | } |
308 | |
309 | string pth = string(TEST_SRC_DIR) + "/" + src; |
310 | @@ -71,7 +71,11 @@ |
311 | char buf[1024]; |
312 | while ((n = ::read(fd2, buf, 1024)) > 0) |
313 | { |
314 | - ::write(fd, buf, n); |
315 | + int rc = ::write(fd, buf, n); |
316 | + if (rc != n) |
317 | + { |
318 | + FAIL() << "Write returned " << rc << ", expected " << n << " (" << db_name << ", errno: " << errno << ")"; |
319 | + } |
320 | } |
321 | ::close(fd2); |
322 | ::close(fd); |
323 | |
324 | === modified file 'test/gtest/scopes/internal/Utils/Utils_test.cpp' |
325 | --- test/gtest/scopes/internal/Utils/Utils_test.cpp 2015-04-10 03:55:25 +0000 |
326 | +++ test/gtest/scopes/internal/Utils/Utils_test.cpp 2015-09-21 09:13:55 +0000 |
327 | @@ -132,3 +132,89 @@ |
328 | |
329 | chmod(child.c_str(), 0777); // Don't leave the dir behind without permissions. |
330 | } |
331 | + |
332 | +TEST(Utils, split_exec_args) |
333 | +{ |
334 | + // Test empty executable |
335 | + try |
336 | + { |
337 | + split_exec_args("test", ""); |
338 | + FAIL(); |
339 | + } |
340 | + catch (std::exception const& e) |
341 | + { |
342 | + EXPECT_STREQ("unity::InvalidArgumentException: Invalid empty executable for scope: 'test'", e.what()); |
343 | + } |
344 | + |
345 | + // Test invalid executable |
346 | + try |
347 | + { |
348 | + split_exec_args("test", "\""); |
349 | + FAIL(); |
350 | + } |
351 | + catch (std::exception const& e) |
352 | + { |
353 | + EXPECT_STREQ("unity::InvalidArgumentException: Invalid executable for scope: 'test'", e.what()); |
354 | + } |
355 | + |
356 | + // Test argument splitting |
357 | + auto exec_args = split_exec_args("test", "/path\\ to/exec' 'file arg \"arg 2\" arg' '3 arg\\ 4"); |
358 | + ASSERT_EQ(5, exec_args.size()); |
359 | + EXPECT_STREQ("\"/path to/exec file\"", exec_args[0].c_str()); |
360 | + EXPECT_STREQ("arg", exec_args[1].c_str()); |
361 | + EXPECT_STREQ("\"arg 2\"", exec_args[2].c_str()); |
362 | + EXPECT_STREQ("\"arg 3\"", exec_args[3].c_str()); |
363 | + EXPECT_STREQ("\"arg 4\"", exec_args[4].c_str()); |
364 | +} |
365 | + |
366 | +TEST(Utils, convert_exec_rel_to_abs) |
367 | +{ |
368 | + // Test empty executable |
369 | + try |
370 | + { |
371 | + convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), ""); |
372 | + FAIL(); |
373 | + } |
374 | + catch (std::exception const& e) |
375 | + { |
376 | + EXPECT_STREQ("unity::InvalidArgumentException: Invalid empty executable for scope: 'test'", e.what()); |
377 | + } |
378 | + |
379 | + // Test invalid executable |
380 | + try |
381 | + { |
382 | + convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "\""); |
383 | + FAIL(); |
384 | + } |
385 | + catch (std::exception const& e) |
386 | + { |
387 | + EXPECT_STREQ("unity::InvalidArgumentException: Invalid executable for scope: 'test'", e.what()); |
388 | + } |
389 | + |
390 | + // Test nonexistent executable |
391 | + try |
392 | + { |
393 | + convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "noexec"); |
394 | + FAIL(); |
395 | + } |
396 | + catch (std::exception const& e) |
397 | + { |
398 | + EXPECT_STREQ("unity::InvalidArgumentException: Nonexistent scope runner executable: 'noexec' for scope: 'test'", e.what()); |
399 | + } |
400 | + |
401 | + // Test absolute executable |
402 | + auto abs_exec = convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "/bin/bash"); |
403 | + EXPECT_STREQ("/bin/bash", abs_exec.c_str()); |
404 | + |
405 | + // Test absolute executable w/ args |
406 | + abs_exec = convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "/bin/bash arg \"arg 2\" arg' '3 arg\\ 4"); |
407 | + EXPECT_STREQ("/bin/bash arg \"arg 2\" \"arg 3\" \"arg 4\"", abs_exec.c_str()); |
408 | + |
409 | + // Test relative executable (w/ ./) |
410 | + abs_exec = convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "./Utils_test"); |
411 | + EXPECT_TRUE(boost::filesystem::exists(abs_exec)); |
412 | + |
413 | + // Test relative executable (w/o ./) |
414 | + abs_exec = convert_exec_rel_to_abs("test", boost::filesystem::path(TEST_DIR), "Utils_test"); |
415 | + EXPECT_TRUE(boost::filesystem::exists(abs_exec)); |
416 | +} |
417 | |
418 | === modified file 'test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp' |
419 | --- test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp 2015-07-22 03:54:35 +0000 |
420 | +++ test/gtest/scopes/internal/zmq_middleware/RegistryI/RegistryI_test.cpp 2015-09-21 09:13:55 +0000 |
421 | @@ -28,6 +28,7 @@ |
422 | #include <unity/scopes/internal/ScopeMetadataImpl.h> |
423 | #include <unity/scopes/internal/ScopeImpl.h> |
424 | #include <unity/scopes/internal/UniqueID.h> |
425 | +#include <unity/scopes/internal/Utils.h> |
426 | #include <unity/scopes/internal/zmq_middleware/ZmqRegistry.h> |
427 | #include <unity/scopes/SearchMetadata.h> |
428 | #include <unity/scopes/ScopeExceptions.h> |
429 | @@ -465,42 +466,6 @@ |
430 | return count_child_procs() - start_process_count; |
431 | } |
432 | |
433 | - string convert_custom_exec(fs::path const& scope_dir, string const& custom_exec) |
434 | - { |
435 | - string result; |
436 | - |
437 | - vector<string> split; |
438 | - boost::split(split, custom_exec, boost::is_space()); |
439 | - fs::path program(split.front()); |
440 | - if (program.is_relative()) |
441 | - { |
442 | - // First look inside the arch-specific directory |
443 | - if (fs::exists(scope_dir / DEB_HOST_MULTIARCH / program)) |
444 | - { |
445 | - // Join the full command, not just the program path |
446 | - result = (scope_dir / DEB_HOST_MULTIARCH / custom_exec).native(); |
447 | - } |
448 | - // Next try in the non arch-aware directory |
449 | - else if (fs::exists(scope_dir / program)) |
450 | - { |
451 | - // Join the full command, not just the program path |
452 | - result = (scope_dir / custom_exec).native(); |
453 | - } |
454 | - else |
455 | - { |
456 | - throw unity::InvalidArgumentException( |
457 | - "Nonexistent scope runner '" + custom_exec |
458 | - + "' executable for scope"); |
459 | - } |
460 | - } |
461 | - else |
462 | - { |
463 | - result = custom_exec; |
464 | - } |
465 | - |
466 | - return result; |
467 | - } |
468 | - |
469 | ScopeProxy start_testscopeB() |
470 | { |
471 | std::string test_scope_id = "testscopeB"; |
472 | @@ -519,7 +484,7 @@ |
473 | |
474 | RegistryObject::ScopeExecData exec_data; |
475 | exec_data.scope_id = test_scope_id; |
476 | - exec_data.custom_exec = convert_custom_exec(REGISTRY_TEST_DIR "/scopes/testscopeB", sc.scope_runner()); |
477 | + exec_data.custom_exec = convert_exec_rel_to_abs(test_scope_id, REGISTRY_TEST_DIR "/scopes/testscopeB", sc.scope_runner()); |
478 | exec_data.runtime_config = runtime_ini; |
479 | exec_data.scope_config = test_scope_config; |
480 | exec_data.timeout_ms = process_timeout; |
481 | |
482 | === modified file 'unity-scopes.map' |
483 | --- unity-scopes.map 2015-04-10 03:55:25 +0000 |
484 | +++ unity-scopes.map 2015-09-21 09:13:55 +0000 |
485 | @@ -13,6 +13,7 @@ |
486 | virtual?thunk?to?unity::scopes::[!iu]*; |
487 | vtable?for?unity::scopes::utility::[!i]*; |
488 | vtable?for?unity::scopes::[!iu]*; |
489 | + unity::scopes::internal::convert_exec_rel_to_abs*; |
490 | unity::scopes::internal::Executor::*; |
491 | unity::scopes::internal::IniSettingsSchema::*; |
492 | unity::scopes::internal::JsonSettingsSchema::*; |
ACK