24#ifndef GOBY_MIDDLEWARE_TOOL_INTERFACE_LAUNCH_H
25#define GOBY_MIDDLEWARE_TOOL_INTERFACE_LAUNCH_H
51 std::string
name()
const {
return platform->
name + (
module.empty() ? "" : "." + module); }
70inline std::string
replace_all(std::string text,
const std::string& from,
const std::string& to)
72 for (
auto pos = text.find(from); pos != std::string::npos; pos = text.find(from, pos))
74 text.replace(pos, from.size(), to);
82 auto word = command.substr(0, command.find(
' '));
83 auto slash = word.rfind(
'/');
84 return slash == std::string::npos ? word : word.substr(slash + 1);
89 return unit.module.empty() || e.module == unit.module;
96 if (!unit.module.empty() &&
module.name != unit.module)
98 auto it =
module.applications.find(e.name);
99 if (it != module.applications.end())
119 for (
const std::string op : {
" =~ ",
" == ",
" != "})
121 auto pos = when.find(op);
122 if (pos == std::string::npos)
124 auto lhs = when.substr(0, pos), rhs = when.substr(pos + op.size());
126 return "echo \"" + lhs +
"\" | grep -qE \"" + rhs +
"\"";
127 return "test \"" + lhs +
"\" " + (op ==
" == " ?
"=" :
"!=") +
" \"" + rhs +
"\"";
137 if (c ==
'"' || c ==
'\\')
146 return !e.process && (e.application ==
"gobyd" || e.name ==
"gobyd");
154 std::vector<LaunchUnit> units;
155 for (
const auto& platform : deployment.
platforms)
157 if (!platform.has_modules())
159 units.push_back({&platform,
""});
162 for (
const auto& module : platform.modules) units.push_back({&platform, module.name});
172 std::vector<std::string> clients;
175 if (!e.enabled || e.process || !detail::in_unit(e, unit))
177 const auto* app = detail::application(unit, e);
178 if ((app && detail::requires_intervehicle(*app)) ||
179 e.application ==
"goby_intervehicle_portal")
180 clients.push_back(e.name);
189 std::vector<std::string> names;
192 if (!e.enabled || e.process || e.not_expected || &e == &coroner ||
193 !detail::in_unit(e, unit))
195 names.push_back(e.name);
203inline std::vector<std::string>
expand_auto(
const std::vector<std::string>& given,
204 const std::vector<std::string>& derived)
206 std::vector<std::string> out;
207 for (
const auto& item : given)
209 const auto& add = item ==
"auto" ? derived : std::vector<std::string>{item};
210 for (
const auto& name : add)
212 if (std::find(out.begin(), out.end(), name) == out.end())
224 std::vector<LaunchLine> lines;
227 if (!detail::in_unit(e, unit))
234 auto pick = [&](
const std::optional<std::string>& own,
235 const std::optional<std::string>& fallback) -> std::string
241 auto app = [&](
const std::string& text)
242 {
return detail::replace_all(text,
"${app}", e.name); };
244 std::string command = app(e.exec ? *e.exec : (e.process ? e.name : e.application));
245 for (
const auto& part : {pick(e.config, settings.config), pick(e.args, settings.args)})
248 command +=
" " + app(part);
254 for (
const auto& client : detail::expand_auto(*e.hold,
auto_hold(unit)))
255 clients += (clients.empty() ?
"" :
" ") + std::string(
"required_client: \"") +
257 if (!clients.empty())
258 command +=
" --hold '" + clients +
"'";
262 for (
const auto& name : detail::expand_auto(*e.expected,
auto_expected(unit, e)))
263 command +=
" --expected_name " + name;
267 command =
"/bin/bash -c \"if " +
268 detail::escape_double_quoted(detail::when_command(*e.when)) +
"; then exec " +
269 detail::escape_double_quoted(command) +
"; else exec sleep infinity; fi\"";
272 line.
kill = pick(e.kill, settings.kill);
273 if (line.
kill.empty() && e.process)
274 line.
kill =
"SIGTERM";
277 line.
env = settings.env;
278 for (
const auto& kv : e.env)
280 auto it = std::find_if(line.
env.begin(), line.
env.end(),
281 [&](
const std::pair<std::string, std::string>& existing)
282 { return existing.first == kv.first; });
283 if (it == line.
env.end())
284 line.
env.push_back(kv);
286 it->second = kv.second;
288 for (
auto& kv : line.
env) kv.second = app(kv.second);
289 lines.push_back(line);
297 std::vector<std::string> directives;
299 directives.push_back(
"kill=" + line.
kill);
300 if (detail::first_word_basename(line.
command) != line.
name)
301 directives.push_back(
"name=" + line.
name);
302 for (
const auto& kv : line.
env) directives.push_back(
"env=" + kv.first +
"=" + kv.second);
305 if (!directives.empty())
308 for (std::size_t i = 0; i < directives.size(); ++i) text += (i ?
", " :
"") + directives[i];
317 std::stringstream ss;
319 if (flags.find(
"-p ") == std::string::npos && flags.find(
"-p\t") == std::string::npos)
320 flags += (flags.empty() ?
"" :
" ") + std::string(
"-p ") + unit.
name();
321 ss <<
"#!/usr/bin/env -S goby_launch " << flags <<
"\n";
322 ss <<
"# Generated by 'goby interface launch' from " << deployment_file <<
" for "
323 << unit.
name() <<
": edit that file rather than this one.\n";
325 ss << (line.entry->enabled ?
"" :
"# (enabled: false) ") <<
launch_line_text(line) <<
"\n";
336 bool seen_gobyd = std::none_of(
337 unit.platform->launch.begin(), unit.platform->launch.end(), [&](
const LaunchEntry& e)
338 { return e.enabled && detail::in_unit(e, unit) && detail::is_gobyd(e); });
341 const auto& e = *line.entry;
345 if (text.find(
'#') != std::string::npos)
346 add_issue(issues, Issue::Severity::ERROR,
"launch-hash",
347 e.name +
": goby_launch drops everything after a '#' on a line",
350 if (text[0] ==
'[' && line.command.find(
']') != std::string::npos)
351 add_issue(issues, Issue::Severity::ERROR,
"launch-bracket",
352 e.name +
": goby_launch would read the command's ']' as the end of the "
353 "line's [directives]",
355 if (detail::is_gobyd(e))
357 else if (!e.process && !seen_gobyd)
358 add_issue(issues, Issue::Severity::WARNING,
"launch-order",
359 e.name +
" is launched before gobyd on " + unit.name(), e.location);
361 if (e.hold && std::find(e.hold->begin(), e.hold->end(),
"auto") == e.hold->end())
363 for (
const auto& client :
auto_hold(unit))
365 if (std::find(e.hold->begin(), e.hold->end(), client) == e.hold->end())
366 add_issue(issues, Issue::Severity::WARNING,
"hold-incomplete",
367 "the hold leaves out " + client +
368 ", which needs its intervehicle subscriptions in place "
369 "first (hold: auto adds it)",
374 std::find(e.expected->begin(), e.expected->end(),
"auto") == e.expected->end())
377 std::set<std::string> given(e.expected->begin(), e.expected->end()),
378 actual(launched.begin(), launched.end());
379 std::string missing, extra;
380 for (
const auto& name : actual)
381 if (!given.count(name))
382 missing += (missing.empty() ?
"" :
", ") + name;
383 for (
const auto& name : given)
384 if (!actual.count(name))
385 extra += (extra.empty() ?
"" :
", ") + name;
386 if (!missing.empty() || !extra.empty())
387 add_issue(issues, Issue::Severity::INFO,
"coroner-expected",
388 "the expected list " +
389 (missing.empty() ?
"" :
"leaves out " + missing) +
390 (!missing.empty() && !extra.empty() ?
" and " :
"") +
391 (extra.empty() ?
"" :
"names " + extra +
", not launched here"),
410 std::vector<ParsedLaunchLine> lines;
411 std::stringstream in(text);
413 for (std::string raw; std::getline(in, raw);)
416 auto line = raw.substr(0, raw.find(
'#'));
417 auto first = line.find_first_not_of(
" \t");
418 if (first == std::string::npos)
420 line = line.substr(first, line.find_last_not_of(
" \t\r") - first + 1);
426 auto close = line.find(
']');
427 std::string directives = line.substr(1, close - 1);
428 line = close == std::string::npos ?
"" : line.substr(close + 1);
429 line = line.substr(std::min(line.size(), line.find_first_not_of(
" \t")));
430 for (
auto& c : directives)
432 if (c ==
',' || c ==
';')
435 std::stringstream ds(directives);
436 for (std::string directive; ds >> directive;)
438 auto eq = directive.find(
'=');
439 auto key = directive.substr(0, eq);
440 auto value = eq == std::string::npos ?
"" : directive.substr(eq + 1);
443 else if (key ==
"name")
445 else if (key ==
"env")
447 auto env_eq = value.find(
'=');
448 parsed.
env.emplace_back(value.substr(0, env_eq),
449 env_eq == std::string::npos ?
""
450 : value.substr(env_eq + 1));
455 if (parsed.
name.empty())
456 parsed.
name = detail::first_word_basename(line);
457 lines.push_back(parsed);
474 const std::string& launch_text)
477 std::vector<LaunchLine> expected;
480 if (line.entry->enabled)
481 expected.push_back(line);
485 auto default_kill = [](
int gobyd_count)
486 {
return gobyd_count == 1 ?
"goby_terminate" :
"SIGTERM"; };
487 int file_gobyds = std::count_if(parsed.begin(), parsed.end(), [](
const ParsedLaunchLine& l)
488 { return detail::first_word_basename(l.command) ==
"gobyd"; });
489 int deployment_gobyds = std::count_if(expected.begin(), expected.end(), [](
const LaunchLine& l)
490 { return detail::is_gobyd(*l.entry); });
494 std::sort(env.begin(), env.end());
496 for (
const auto& kv : env) text += (text.empty() ?
"" :
" ") + kv.first +
"=" + kv.second;
497 return text.empty() ?
"(none)" : text;
500 std::vector<LaunchDifference> differences;
501 for (
const auto& p : parsed)
503 auto it = std::find_if(expected.begin(), expected.end(),
504 [&](
const LaunchLine& l) { return l.name == p.name; });
505 if (it == expected.end())
507 differences.push_back(
508 {
"launched-only", p.name, p.line_number,
509 "line " + std::to_string(p.line_number) +
" has no entry in the deployment"});
512 auto file_kill = p.kill.empty() ? default_kill(file_gobyds) : p.kill;
513 auto deployment_kill = it->kill.empty() ? default_kill(deployment_gobyds) : it->kill;
514 if (file_kill != deployment_kill)
515 differences.push_back(
516 {
"kill", p.name, p.line_number,
517 file_kill +
" in the launch file, " + deployment_kill +
" in the deployment"});
518 if (env_text(p.env) != env_text(it->env))
519 differences.push_back({
"env", p.name, p.line_number,
520 env_text(p.env) +
" in the launch file, " + env_text(it->env) +
521 " in the deployment"});
523 for (
const auto& l : expected)
525 if (std::none_of(parsed.begin(), parsed.end(),
527 differences.push_back(
528 {
"not-launched", l.
name, 0,
529 "in the deployment (" + l.entry->location +
") but not in the launch file"});
detail namespace with internal helper functions
The global namespace for the Goby project.
How one run: entry is started by goby_launch.