24#ifndef GOBY_MIDDLEWARE_TOOL_INTERFACE_MODEL_H
25#define GOBY_MIDDLEWARE_TOOL_INTERFACE_MODEL_H
44#include <yaml-cpp/yaml.h>
92 std::string message, std::string location =
"")
94 issues.push_back({severity, std::move(code), std::move(message), std::move(location), {}});
97inline std::ostream& operator<<(std::ostream& os,
const Issue& issue)
105 os <<
" [" << issue.
code <<
"]";
108 return os <<
": " << issue.
message;
113 return std::any_of(issues.begin(), issues.end(),
114 [](
const Issue& i) { return i.severity == Issue::Severity::ERROR; });
124 std::map<Layer, int> next_;
129 Thread(std::string n,
bool k, std::set<std::string> b = std::set<std::string>())
155 std::map<std::string, std::shared_ptr<Thread>>
threads;
162 static const std::set<PubSubEntry> empty;
164 return it ==
publishes.end() ? empty : it->second;
169 static const std::set<PubSubEntry> empty;
171 return it ==
subscribes.end() ? empty : it->second;
182 std::string most_derived(
const std::string& thread)
const
184 auto it =
threads.find(thread);
185 return it ==
threads.end() ? thread : it->second->most_derived_name();
191 for (
auto& bound : binder ? binder(entry) :
std::vector<
PubSubEntry>{entry})
193 auto result = entries.insert(bound);
195 result.first->publish_index = indexer.
next(bound.layer);
205 std::vector<std::pair<std::shared_ptr<Thread>, YAML::Node>> new_thread_yaml;
206 if (
auto interthread_node = yaml[
"interthread"])
208 for (
const auto& thread_node : interthread_node[
"threads"])
210 auto thread_name = thread_node[
"name"].as<std::string>();
211 bool known = !thread_node[
"known"] || thread_node[
"known"].as<
bool>();
212 std::set<std::string> bases;
213 for (
const auto& base : thread_node[
"bases"]) bases.insert(base.as<std::string>());
215 auto it =
threads.find(thread_name);
218 .emplace(thread_name, std::make_shared<Thread>(thread_name, known, bases))
221 it->second->bases.insert(bases.begin(), bases.end());
222 new_thread_yaml.emplace_back(it->second, thread_node);
228 auto& thread = *thread_p.second;
229 bool is_direct_thread_subclass =
230 std::any_of(thread.bases.begin(), thread.bases.end(), [](
const std::string& b)
231 { return b.find(
"goby::middleware::SimpleThread") == 0; });
232 if (is_direct_thread_subclass)
234 for (
const auto& base : thread.bases)
236 auto base_it =
threads.find(base);
238 base_it->second->parent = &thread;
242 std::sort(new_thread_yaml.begin(), new_thread_yaml.end(),
243 [](
const auto& a,
const auto& b) { return a.first->name < b.first->name; });
244 for (
auto& thread_yaml : new_thread_yaml)
246 auto& thread = *thread_yaml.first;
247 for (
const auto& p : thread_yaml.second[
"publishes"])
248 insert(thread.interthread_publishes,
250 thread.most_derived_name()),
252 for (
const auto& s : thread_yaml.second[
"subscribes"])
253 insert(thread.interthread_subscribes,
255 thread.most_derived_name()),
266 for (
const auto& p : layer_node[
"publishes"])
270 insert(
publishes[layer], entry, indexer, binder);
272 for (
const auto& s : layer_node[
"subscribes"])
276 insert(
subscribes[layer], entry, indexer, binder);
283 for (
const auto& layer_p : *layer_map)
285 for (
const auto& e : layer_p.second)
289 std::make_shared<Thread>(e.thread, e.thread_is_known));
303using KeyValues = std::vector<std::pair<std::string, std::string>>;
310 std::optional<std::string>
user;
317 std::optional<std::string>
unit;
320 std::map<std::string, std::string>
vars;
333 std::optional<std::string>
exec;
335 std::optional<std::string>
args;
336 std::optional<std::string>
kill;
338 std::optional<std::string>
when;
342 std::optional<std::vector<std::string>>
hold;
357 std::optional<std::string>
args;
358 std::optional<std::string>
kill;
451 std::vector<std::shared_ptr<Bind>>
binds;
466 std::vector<std::shared_ptr<Bind>>
binds;
476 const std::map<std::string, std::string>& defines,
477 std::set<std::string>& unset)
480 for (std::size_t i = 0; i < text.size(); ++i)
482 if (text.compare(i, 3,
"$${") == 0)
488 if (text.compare(i, 2,
"${") != 0)
493 auto close = text.find(
'}', i + 2);
494 if (close == std::string::npos)
496 out += text.substr(i);
499 std::string expr = text.substr(i + 2, close - i - 2);
500 std::string name = expr, fallback;
501 bool has_fallback =
false;
502 auto sep = expr.find(
":-");
503 if (sep != std::string::npos)
505 name = expr.substr(0, sep);
506 fallback = expr.substr(sep + 2);
510 if (name ==
"instance" || name ==
"app")
512 out += text.substr(i, close - i + 1);
517 auto define_it = defines.find(name);
518 const char* env = std::getenv(name.c_str());
519 if (define_it != defines.end())
520 out += define_it->second;
523 else if (has_fallback)
528 out += text.substr(i, close - i + 1);
536 const std::string& code,
const std::string& location,
Issues& issues,
541 yaml = YAML::Load(text);
544 catch (
const std::exception& e)
547 "cannot parse " + path +
": " + e.what(), location);
552inline bool load_yaml(
const std::string& path,
const std::string& code,
const std::string& location,
553 Issues& issues, YAML::Node& yaml)
555 std::ifstream in(path);
562 std::stringstream ss;
564 return load_yaml_text(ss.str(), path, code, location, issues, yaml);
567inline std::vector<std::string>
read_list(
const YAML::Node& node)
569 std::vector<std::string> values;
570 if (node && node.IsSequence())
571 for (
const auto& v : node) values.push_back(v.as<std::string>());
572 else if (node && node.IsScalar())
573 values.push_back(node.as<std::string>());
599 else if (std::string(
"\\^$.|?+()[]{}").find(c) != std::string::npos)
600 re += std::string(
"\\") + c;
604 return std::regex(re);
619 return pos_ == text_.size();
625 while (pos_ < text_.size() && std::isspace(
static_cast<unsigned char>(text_[pos_]))) ++pos_;
630 if (pos_ < text_.size() && text_[pos_] == c)
637 bool expr(
long long& value)
650 else if (accept(
'-'))
660 bool term(
long long& value)
673 bool factor(
long long& value)
676 return expr(value) && accept(
')');
678 std::size_t start = pos_;
679 while (pos_ < text_.size() && std::isdigit(
static_cast<unsigned char>(text_[pos_]))) ++pos_;
680 if (pos_ == start || pos_ - start > 10)
682 value = std::stoll(text_.substr(start, pos_ - start));
692inline bool eval_numeric(
const std::string& text,
const std::vector<std::uint32_t>& instances,
693 std::set<std::uint32_t>& values, std::string& error)
695 static const std::regex range(
"\\s*([0-9]+)\\s*-\\s*([0-9]+)\\s*");
696 static const std::string instance_var =
"${instance}";
700 if (std::regex_match(text, m, range))
702 auto first = std::stoll(m[1]), last = std::stoll(m[2]);
703 if (!in_range(first) || !in_range(last) || last < first || last - first > 100000)
705 error =
"'" + text +
"' is not a valid range of group numbers";
708 for (
auto v = first; v <= last; ++v) values.insert(v);
712 bool uses_instance = text.find(instance_var) != std::string::npos;
713 if (uses_instance && instances.empty())
715 error =
"'" + text +
"' uses ${instance}, but its platform has no instances:";
718 for (
auto instance : uses_instance ? instances : std::vector<std::uint32_t>{0})
720 std::string expanded = text;
721 for (
auto pos = expanded.find(instance_var); pos != std::string::npos;
722 pos = expanded.find(instance_var, pos))
723 expanded.replace(pos, instance_var.size(), std::to_string(instance));
727 error =
"'" + text +
"' is not a group number, a range (a-b) or arithmetic on " +
731 values.insert(value);
736inline std::vector<std::shared_ptr<Bind>>
read_binds(
const YAML::Node& node,
737 const std::string& location,
Issues& issues)
739 std::vector<std::shared_ptr<Bind>> binds;
742 if (!node.IsSequence())
750 for (
const auto& entry : node)
752 auto bind = std::make_shared<Bind>();
753 bind->location = location +
"[" + std::to_string(index++) +
"]";
754 auto fail = [&](
const std::string&
msg)
759 fail(
"a bind must be a map");
762 static const std::set<std::string> keys{
"group",
"numeric",
"layer",
763 "dynamic",
"publishes",
"subscribes"};
765 for (
const auto& kv : entry)
767 if (!keys.count(kv.first.as<std::string>()))
769 fail(
"unknown key '" + kv.first.as<std::string>() +
"'");
776 if (entry[
"dynamic"])
778 bind->is_publication =
true;
779 auto select = entry[
"dynamic"];
782 fail(
"dynamic: selects a dynamic publication or subscription by layer, scheme "
788 bind->has_layer =
layer_from_name(select[
"layer"].as<std::string>(), bind->layer);
789 if (!bind->has_layer)
791 fail(
"unknown layer '" + select[
"layer"].as<std::string>() +
"'");
795 if (select[
"scheme"])
796 bind->select_scheme = select[
"scheme"].as<std::string>();
798 bind->select_type = select[
"type"].as<std::string>();
800 for (
auto kind : {
"publishes",
"subscribes"})
803 std::string(kind) ==
"publishes" ? bind->publishes : bind->subscribes;
804 for (
const auto& c : entry[kind])
806 if (!c.IsMap() || !c[
"group"])
808 fail(std::string(kind) +
" entries need group: (and type:, scheme: if "
809 "they differ from the dynamic entry's)");
813 concrete.push_back({c[
"group"].as<std::string>(),
814 c[
"scheme"] ? c[
"scheme"].as<std::string>() :
"",
815 c[
"type"] ? c[
"type"].as<std::string>() :
""});
820 if (bind->publishes.empty() && bind->subscribes.empty())
822 fail(
"a dynamic: bind needs publishes: or subscribes:");
828 if (!entry[
"group"] || !entry[
"numeric"])
830 fail(
"a bind needs group: and numeric:, or dynamic: with publishes: or "
834 bind->group_pattern =
glob_regex(entry[
"group"].as<std::string>());
835 bind->numerics =
read_list(entry[
"numeric"]);
838 bind->has_layer =
layer_from_name(entry[
"layer"].as<std::string>(), bind->layer);
839 if (!bind->has_layer)
841 fail(
"unknown layer '" + entry[
"layer"].as<std::string>() +
"'");
845 for (
const auto& numeric : bind->numerics)
847 std::set<std::uint32_t> values;
858 binds.push_back(bind);
864 std::vector<std::uint32_t> instances)
866 return [binds, instances](
PubSubEntry e) -> std::vector<PubSubEntry>
871 for (
const auto& bind : binds)
873 if (bind->has_layer && bind->layer != e.layer)
876 if (!bind->is_publication)
878 if (!e.numeric_runtime || !std::regex_match(e.group, bind->group_pattern))
880 for (
const auto& numeric : bind->numerics)
883 eval_numeric(numeric, instances, e.bound_numerics, error);
889 if (!e.unresolved() && !e.numeric_runtime)
891 if ((!bind->select_scheme.empty() && bind->select_scheme != e.scheme) ||
892 (!bind->select_type.empty() && bind->select_type != e.type))
894 const auto& concrete =
896 if (concrete.empty())
900 std::vector<PubSubEntry> bound;
901 for (
const auto& c : concrete)
905 if (!c.scheme.empty())
921 return kill ==
"goby_terminate" || kill ==
"SIGTERM" || kill ==
"SIGINT" || kill ==
"SIGKILL";
932 "env: must be a map of NAME: value", location);
935 for (
const auto& kv : node)
937 auto value = kv.second.as<std::string>();
939 if (value.find_first_of(
" ,;") != std::string::npos)
941 "env: " + kv.first.as<std::string>() +
942 " contains a space, comma or semicolon, which goby_launch cannot pass",
944 env.emplace_back(kv.first.as<std::string>(), value);
955 auto& launch = ref.
launch;
957 auto text = [&](
const char* key, std::optional<std::string>& value)
960 value = entry[key].as<std::string>();
963 if (entry[
"process"])
965 if (entry[
"application"] || entry[
"file"] || entry[
"plugins"] || entry[
"bind"])
968 "a process: entry is not a Goby application, so it has no application:, "
969 "file:, plugins: or bind:",
973 launch.process =
true;
974 launch.name = entry[
"process"].as<std::string>();
976 text(
"exec", launch.exec);
977 text(
"config", launch.config);
978 text(
"args", launch.args);
979 text(
"kill", launch.kill);
980 text(
"when", launch.when);
983 if (entry[
"enabled"])
984 launch.enabled = entry[
"enabled"].as<
bool>();
988 "kill: must be goby_terminate, SIGTERM, SIGINT or SIGKILL", ref.
location);
993 if (
auto expected = entry[
"expected"])
995 if (expected.IsScalar() && (expected.as<std::string>() ==
"false"))
996 launch.not_expected =
true;
1012 "launch: must be a map (flags:, defaults:)", location);
1015 for (
const auto& kv : node)
1017 auto key = kv.first.as<std::string>();
1018 if (key !=
"flags" && key !=
"defaults")
1020 "unknown key '" + key +
"'", location);
1023 settings.
flags = node[
"flags"].as<std::string>();
1024 if (
auto defaults = node[
"defaults"])
1026 for (
const auto& kv : defaults)
1028 auto key = kv.first.as<std::string>();
1029 if (key ==
"config")
1030 settings.
config = kv.second.as<std::string>();
1031 else if (key ==
"args")
1032 settings.
args = kv.second.as<std::string>();
1033 else if (key ==
"kill")
1034 settings.
kill = kv.second.as<std::string>();
1035 else if (key ==
"env")
1036 settings.
env =
read_env(kv.second, location +
".defaults.env", issues);
1039 "unknown key '" + key +
"' (defaults: takes config, args, kill, env)",
1040 location +
".defaults");
1044 "kill: must be goby_terminate, SIGTERM, SIGINT or SIGKILL",
1045 location +
".defaults");
1050inline std::string
with_instance(std::string text,
const std::vector<std::uint32_t>& instances)
1052 if (instances.size() != 1)
1054 static const std::string var =
"${instance}";
1055 for (
auto pos = text.find(var); pos != std::string::npos; pos = text.find(var, pos))
1056 text.replace(pos, var.size(), std::to_string(instances.front()));
1061 const std::vector<std::uint32_t>& instances)
1069 for (
auto& kv : settings.
env) kv.second =
with_instance(kv.second, instances);
1085 for (
const auto& kv : node)
1087 auto key = kv.first.as<std::string>();
1088 auto text = [&]() {
return kv.second.as<std::string>(); };
1089 if (key ==
"template")
1091 else if (key ==
"user")
1092 settings.
user = text();
1093 else if (key ==
"group")
1094 settings.
group = text();
1095 else if (key ==
"wanted_by")
1097 else if (key ==
"description")
1099 else if (key ==
"condition")
1101 else if (key ==
"unit")
1102 settings.
unit = text();
1103 else if (key ==
"service")
1105 else if (key ==
"install")
1107 else if (key ==
"skip")
1108 settings.
skip = kv.second.as<
bool>();
1109 else if (key ==
"vars" && kv.second.IsMap())
1111 for (
const auto& var : kv.second)
1112 settings.
vars[var.first.as<std::string>()] = var.second.as<std::string>();
1116 "unknown key '" + key +
"'", location);
1130 "systemd: must be a map (target:, ordered:, defaults:)", location);
1133 for (
const auto& kv : node)
1135 auto key = kv.first.as<std::string>();
1136 if (key ==
"target")
1137 systemd.
target = kv.second.as<std::string>();
1138 else if (key ==
"ordered")
1139 systemd.
ordered = kv.second.as<
bool>();
1140 else if (key ==
"defaults")
1144 "unknown key '" + key +
"'", location);
1150 const std::vector<std::uint32_t>& instances)
1171 for (
auto& kv : entry.
env) kv.second =
with_instance(kv.second, instances);
1175inline std::vector<RunRef>
read_run(
const YAML::Node& node,
bool legacy,
1176 const std::string& location,
Issues& issues)
1178 std::vector<RunRef> refs;
1179 if (!node.IsSequence())
1187 for (
const auto& entry : node)
1190 ref.
location = location +
"[" + std::to_string(index++) +
"]";
1192 if (entry.IsScalar())
1196 else if (entry.IsMap())
1198 static const std::set<std::string> legacy_keys{
"file",
"application",
"bind"};
1199 static const std::set<std::string> run_keys{
1200 "application",
"file",
"name",
"plugins",
"bind",
"process",
1201 "exec",
"config",
"args",
"env",
"kill",
"when",
1202 "enabled",
"hold",
"expected",
"systemd"};
1203 const auto& allowed = legacy ? legacy_keys : run_keys;
1204 bool bad_key =
false;
1205 for (
const auto& kv : entry)
1207 auto key = kv.first.as<std::string>();
1208 if (!allowed.count(key))
1211 "unknown key '" + key +
"'", ref.
location);
1219 ref.
file = entry[
"file"].as<std::string>();
1223 if (entry[
"application"])
1225 ref.
name = entry[
"application"].as<std::string>();
1231 if (entry[
"application"])
1232 ref.
application = entry[
"application"].as<std::string>();
1234 ref.
name = entry[
"name"].as<std::string>();
1242 legacy ?
"an interfaces entry that is a map must have file:"
1243 :
"a run entry must name an application:, a file: or a process:",
1251 "must be an application name or a map", ref.
location);
1254 refs.push_back(ref);
1262 std::vector<RunRef>& refs,
Issues& issues)
1264 if (node[
"run"] && node[
"interfaces"])
1267 "use run: or interfaces:, not both", location);
1271 refs =
read_run(node[
"run"],
false, location +
".run", issues);
1272 else if (node[
"interfaces"])
1273 refs =
read_run(node[
"interfaces"],
true, location +
".interfaces", issues);
1285 auto yaml = YAML::LoadFile(path);
1286 if (yaml.IsMap() && yaml[
"application"] && yaml[
"application"].IsScalar())
1287 return yaml[
"application"].as<std::string>();
1289 catch (
const std::exception&)
1301 const std::vector<std::string>&
dirs()
const {
return dirs_; }
1305 namespace fs = std::filesystem;
1306 if (fs::path(file).is_absolute() || fs::exists(file))
1307 return fs::exists(file) ? file :
"";
1308 for (
const auto& dir : dirs_)
1310 auto candidate = fs::path(dir) / file;
1311 if (fs::exists(candidate))
1312 return candidate.string();
1322 if (it == index_.end())
1324 const auto& files = it->second;
1325 if (files.size() > 1 && reported_shadowed_.insert(
application).second)
1328 for (
auto f = files.begin() + 1; f != files.end(); ++f)
1329 others += (others.empty() ?
"" :
", ") + *f;
1331 "application " +
application +
" is declared by " + files.front() +
1332 ", which is used, and by " + others);
1334 return files.front();
1340 for (
const auto& dir : dirs_) out += (out.empty() ?
"" :
", ") + dir;
1347 namespace fs = std::filesystem;
1349 for (
const auto& dir : dirs_)
1352 if (!fs::is_directory(dir, ec))
1354 std::vector<fs::path> files;
1355 for (fs::directory_iterator it(dir, ec), end; !ec && it != end; it.increment(ec))
1357 auto ext = it->path().extension().string();
1358 if (ext ==
".yml" || ext ==
".yaml")
1359 files.push_back(it->path());
1361 std::sort(files.begin(), files.end());
1362 for (
const auto& file : files)
1372 std::vector<std::string> dirs_;
1373 bool indexed_{
false};
1375 std::map<std::string, std::vector<std::string>> index_;
1376 std::set<std::string> reported_shadowed_;
1386 if (!node.IsSequence())
1389 "necessity: must be a sequence",
"necessity");
1393 static const std::vector<std::string> selectors{
"platform",
"module",
"application",
"thread",
1394 "layer",
"group",
"type"};
1396 for (
const auto& entry : node)
1398 std::string location =
"necessity[" + std::to_string(index++) +
"]";
1399 if (!entry.IsMap() || !entry[
"set"])
1402 "a necessity override needs set: required|recommended|optional", location);
1405 auto set_name = entry[
"set"].as<std::string>();
1406 if (set_name !=
"required" && set_name !=
"recommended" && set_name !=
"optional")
1409 "set: must be required, recommended or optional", location);
1412 std::map<std::string, std::regex> select;
1414 for (
const auto& kv : entry)
1416 auto key = kv.first.as<std::string>();
1419 if (std::find(selectors.begin(), selectors.end(), key) == selectors.end())
1422 "unknown key '" + key +
"'", location);
1428 select[key] = std::regex(kv.second.as<std::string>());
1430 catch (
const std::regex_error& e)
1433 key +
": is not a valid regex: " + e.what(), location);
1440 auto matches = [&](
const std::string& key,
const std::string& value)
1442 auto it = select.find(key);
1443 return it == select.end() || std::regex_match(value, it->second);
1447 auto apply = [&](std::set<PubSubEntry>& subs,
const Platform& platform,
1450 if (!(matches(
"platform", platform.name) || matches(
"platform", platform.base_name)) ||
1451 !matches(
"module", module.name) || !matches(
"application", app.name))
1453 std::set<PubSubEntry> updated;
1456 if (matches(
"thread", e.thread) && matches(
"layer",
layer_name(e.layer)) &&
1457 matches(
"group", e.group) && matches(
"type", e.type))
1460 e.necessity_overridden =
true;
1468 for (
auto& platform : deployment.
platforms)
1470 for (
auto& module : platform.modules)
1472 for (
auto& app_p :
module.applications)
1474 auto& app = app_p.second;
1475 for (
auto& thread_p : app.threads)
1476 apply(thread_p.second->interthread_subscribes, platform, module, app);
1477 for (
auto& layer_p : app.subscribes)
1478 apply(layer_p.second, platform, module, app);
1484 "this override matches no subscription", location);
1488inline std::vector<std::string>
search_dirs(
const std::string& deployment_path,
1491 namespace fs = std::filesystem;
1492 std::vector<std::string> dirs;
1493 auto deployment_dir = fs::path(deployment_path).parent_path().string();
1494 dirs.push_back(deployment_dir.empty() ?
"." : deployment_dir);
1496 if (
const char* env = std::getenv(
"GOBY_INTERFACE_PATH"))
1498 std::stringstream ss(env);
1499 for (std::string dir; std::getline(ss, dir,
':');)
1501 dirs.push_back(dir);
1505 dirs.push_back(
".");
1507 std::vector<std::string> unique;
1508 std::set<std::string> seen;
1509 for (
const auto& dir : dirs)
1512 auto canonical = fs::weakly_canonical(dir, ec).string();
1513 if (seen.insert(ec ? dir : canonical).second)
1514 unique.push_back(dir);
1528 deployment.
file = path;
1530 auto defines = params.defines;
1531 if (!params.goby_interfaces_dir.empty() && !defines.count(
"GOBY_INTERFACES_DIR"))
1532 defines[
"GOBY_INTERFACES_DIR"] = params.goby_interfaces_dir;
1534 std::ifstream in(path);
1537 add_issue(issues, Issue::Severity::ERROR,
"deployment-missing",
"cannot open " + path);
1540 std::stringstream raw;
1542 std::set<std::string> unset;
1543 YAML::Node deploy_yaml;
1544 if (!detail::load_yaml_text(detail::expand_variables(raw.str(), defines, unset), path,
1545 "deployment",
"", issues, deploy_yaml))
1547 for (
const auto& name : unset)
1548 add_issue(issues, Issue::Severity::INFO,
"variable-unset",
1550 "} has no value here, so it is left for the shell (set it with -D " + name +
1554 std::map<std::string, detail::PlatformRef> platform_refs;
1556 if (!deploy_yaml.IsMap() || !deploy_yaml[
"deployment"])
1558 if (deploy_yaml.IsMap() && deploy_yaml[
"application"])
1560 deployment.
name = deploy_yaml[
"application"].as<std::string>() +
"_stub_deployment";
1563 ref.location =
"application";
1564 platform_refs[
"goby_platform"].modules.push_back({
"nomodule", {ref},
true});
1568 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema",
1569 "must specify 'deployment: name' in a deployment file, or give a single "
1576 deployment.
name = deploy_yaml[
"deployment"].as<std::string>();
1578 auto platforms_node = deploy_yaml[
"platforms"];
1579 if (!platforms_node || !platforms_node.IsSequence())
1581 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema",
1582 "platforms: must be a sequence",
"platforms");
1586 int platform_index = 0;
1587 for (
const auto& platform : platforms_node)
1589 std::string location =
"platforms[" + std::to_string(platform_index++) +
"]";
1590 if (!platform[
"name"])
1592 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema",
1593 "each platform must have name:", location);
1596 auto platform_name = platform[
"name"].as<std::string>();
1597 auto& platform_ref = platform_refs[platform_name];
1598 platform_ref.location = location;
1599 platform_ref.binds = detail::read_binds(platform[
"bind"], location +
".bind", issues);
1600 platform_ref.launch_settings =
1601 detail::read_launch_settings(platform[
"launch"], location +
".launch", issues);
1602 platform_ref.systemd =
1603 detail::read_platform_systemd(platform[
"systemd"], location +
".systemd", issues);
1604 if (
auto instances_node = platform[
"instances"])
1606 for (
const auto& instance : detail::read_list(instances_node))
1608 std::set<std::uint32_t> values;
1610 if (!detail::eval_numeric(instance, {}, values, error))
1611 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema", error,
1612 location +
".instances");
1613 platform_ref.instances.insert(platform_ref.instances.end(), values.begin(),
1618 if (
auto modules_node = platform[
"modules"])
1620 int module_index = 0;
1621 for (
const auto& module : modules_node)
1623 std::string module_location =
1624 location +
".modules[" + std::to_string(module_index++) +
"]";
1626 if (!module[
"name"] ||
1627 !detail::read_applications(module, module_location, module_ref.
run, issues))
1629 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema",
1630 "each module must have name: and a run: (or interfaces:) list",
1634 module_ref.
name =
module["name"].as<std::string>();
1635 platform_ref.modules.push_back(module_ref);
1641 if (!detail::read_applications(platform, location, module_ref.run, issues))
1643 add_issue(issues, Issue::Severity::ERROR,
"deployment-schema",
1644 "a platform must have modules:, or a run: (or interfaces:) list",
1648 platform_ref.modules = {module_ref};
1653 auto locate = [&](
const detail::RunRef& ref,
const std::string& application,
1654 const std::string& file) -> std::string
1660 add_issue(issues, Issue::Severity::ERROR,
"interface-missing",
1661 "cannot find " + file +
" (searched: " + finder.
searched() +
")",
1667 add_issue(issues, Issue::Severity::ERROR,
"interface-missing",
1668 "no interface file declares application " + application +
1669 " (searched: " + finder.
searched() +
")",
1674 auto load_interface = [&](
const std::string& file,
const detail::RunRef& ref,
1675 YAML::Node& yaml) ->
bool
1677 if (!detail::load_yaml(file,
"interface", ref.
location, issues, yaml))
1679 if (!yaml.IsMap() || !yaml[
"application"] || !yaml[
"application"].IsScalar())
1681 add_issue(issues, Issue::Severity::ERROR,
"interface-invalid",
1682 file +
" has no application:", ref.
location);
1688 auto merge = [&](
Application& app,
const YAML::Node& yaml,
const std::string& file,
1695 catch (
const std::exception& e)
1697 add_issue(issues, Issue::Severity::ERROR,
"interface-invalid", file +
": " + e.what(),
1702 auto load_platform = [&](
const std::string& name,
const detail::PlatformRef& platform_ref,
1703 const std::vector<std::uint32_t>& instances,
Platform& platform)
1705 platform.name = name;
1706 platform.launch_settings = detail::for_instance(platform_ref.launch_settings, instances);
1707 platform.systemd = platform_ref.systemd;
1708 platform.systemd.defaults = detail::for_instance(platform_ref.systemd.defaults, instances);
1709 for (
const auto& module_ref : platform_ref.modules)
1712 module.name = module_ref.name;
1713 module.not_a_module = module_ref.not_a_module;
1714 for (
const auto& ref : module_ref.run)
1716 auto launch = detail::for_instance(ref.
launch, instances);
1717 launch.module =
module.name;
1720 platform.launch.push_back(launch);
1726 if (file.empty() || !load_interface(file, ref, yaml))
1730 if (launch.name.empty())
1731 launch.name = std::filesystem::path(ref.
file).stem().string();
1732 platform.launch.push_back(launch);
1736 std::string app_name =
1737 ref.
name.empty() ? yaml[
"application"].as<std::string>() : ref.
name;
1738 launch.application = yaml[
"application"].as<std::string>();
1739 launch.name = app_name;
1742 bool already_launched = std::any_of(
1743 platform.launch.begin(), platform.launch.end(), [&](
const LaunchEntry& e)
1744 { return e.module == launch.module && e.name == launch.name; });
1745 if (!already_launched)
1746 platform.launch.push_back(launch);
1748 auto it =
module.applications.find(app_name);
1749 if (it == module.applications.end())
1751 it =
module.applications.emplace(app_name, Application(app_name)).first;
1755 add_issue(issues, Issue::Severity::WARNING,
"application-duplicate",
1756 "application " + app_name +
" is listed more than once on " +
1757 platform.name + (module.not_a_module ?
"" :
"/" +
module.name) +
1758 "; the entries were merged (give each a distinct name:, or
"
1759 "list plugins under plugins:)
",
1762 auto binds = ref.binds;
1763 binds.insert(binds.end(), platform_ref.binds.begin(), platform_ref.binds.end());
1764 auto binder = detail::make_binder(binds, instances);
1765 merge(it->second, yaml, file, ref, binder);
1767 for (const auto& plugin : ref.plugins)
1769 bool is_file = std::filesystem::path(plugin).extension() == ".yml
" ||
1770 std::filesystem::path(plugin).extension() == ".yaml
";
1771 auto plugin_file = locate(ref, is_file ? "" : plugin, is_file ? plugin : "");
1772 YAML::Node plugin_yaml;
1773 if (!plugin_file.empty() && load_interface(plugin_file, ref, plugin_yaml))
1774 merge(it->second, plugin_yaml, plugin_file, ref, binder);
1777 platform.modules.push_back(std::move(module));
1779 std::stable_sort(platform.modules.begin(), platform.modules.end(),
1780 [](const Module& a, const Module& b) { return a.name < b.name; });
1783 for (const auto& platform_p : platform_refs)
1785 const auto& ref = platform_p.second;
1786 if (ref.instances.empty() || !params.expand_instances)
1789 platform.base_name = platform_p.first;
1790 load_platform(platform_p.first, ref, ref.instances, platform);
1791 deployment.platforms.push_back(std::move(platform));
1795 for (auto instance : ref.instances)
1798 platform.base_name = platform_p.first;
1799 load_platform(platform_p.first + std::to_string(instance), ref, {instance},
1801 deployment.platforms.push_back(std::move(platform));
1805 std::stable_sort(deployment.platforms.begin(), deployment.platforms.end(),
1806 [](const Platform& a, const Platform& b) { return a.name < b.name; });
1808 detail::apply_necessity_overrides(deploy_yaml["necessity
"], deployment, issues);
1810 std::vector<std::shared_ptr<detail::Bind>> all_binds;
1811 for (const auto& platform_p : platform_refs)
1813 const auto& ref = platform_p.second;
1814 all_binds.insert(all_binds.end(), ref.binds.begin(), ref.binds.end());
1815 for (const auto& module_ref : ref.modules)
1816 for (const auto& run_ref : module_ref.run)
1817 all_binds.insert(all_binds.end(), run_ref.binds.begin(), run_ref.binds.end());
1819 for (const auto& bind : all_binds)
1821 if (bind->uses == 0)
1822 add_issue(issues, Issue::Severity::WARNING, "bind-unused
",
1823 "this bind matches no dynamic publication or subscription
", bind->location);
1829} // namespace interface
1831} // namespace middleware
static constexpr std::uint32_t maximum_valid_group
detail namespace with internal helper functions
The global namespace for the Goby project.
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::FieldOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyFieldOptions >, 11, false > field
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::MessageOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyMessageOptions >, 11, false > msg
How one run: entry is started by goby_launch.
std::optional< std::string > args
std::optional< std::vector< std::string > > hold
std::optional< std::string > exec
std::optional< std::string > config
std::optional< std::string > kill
std::string std::string name
std::optional< std::vector< std::string > > expected
std::optional< std::string > when
One publication or subscription as written to (and read from) an interface file.