20 #include <boost/regex.hpp> 21 #include <boost/algorithm/string/case_conv.hpp> 22 #include <boost/algorithm/string/trim.hpp> 23 #include <boost/lexical_cast.hpp> 25 #include <yaml-cpp/yaml.h> 38 const bool global_prefix = (!prefix.empty() && prefix.front() ==
'/');
46 if(
const char*
stopTimeout = e->Attribute(
"rosmon-stop-timeout"))
53 catch(boost::bad_lexical_cast&)
63 if(
const char*
memoryLimit = e->Attribute(
"rosmon-memory-limit"))
65 uint64_t memoryLimitByte;
76 if(
const char*
cpuLimit = e->Attribute(
"rosmon-cpu-limit"))
83 catch(boost::bad_lexical_cast&)
102 std::string simplified;
103 if(simplifyWhitespace)
114 throw error(
"Substitution error: {}", e.
what());
120 std::string expansion =
evaluate(value);
128 if(expansion ==
"1" || expansion ==
"true" || expansion ==
"True")
131 if(expansion ==
"0" || expansion ==
"false" || expansion ==
"False")
134 throw error(
"Unknown truth value '%s'", expansion.c_str());
139 const char* if_cond = e->Attribute(
"if");
140 const char* unless_cond = e->Attribute(
"unless");
142 if(if_cond && unless_cond)
144 throw error(
"both if= and unless= specified, don't know what to do");
162 auto it =
m_args.find(name);
165 else if(
override || it->second == UNSET_MARKER)
194 : m_rootContext(this)
195 , m_anonGen(
std::random_device()())
197 const char* ROS_NAMESPACE = getenv(
"ROS_NAMESPACE");
230 TiXmlDocument document(filename);
232 TiXmlBase::SetCondenseWhiteSpace(
false);
234 if(!document.LoadFile())
253 TiXmlDocument document;
255 TiXmlBase::SetCondenseWhiteSpace(
false);
257 document.Parse(input.c_str());
280 if(node->spawnDelay().isZero())
282 node->setSpawnDelay(currentDelay);
283 currentDelay += autoIncrementSpawnDelay;
290 const char* name = element->Attribute(
"rosmon-name");
294 const char*
windowTitle = element->Attribute(
"rosmon-window-title");
298 const char*
disableUI = element->Attribute(
"rosmon-disable-ui");
308 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
310 TiXmlElement* e = n->ToElement();
319 if(e->ValueStr() ==
"arg")
327 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
329 TiXmlElement* e = n->ToElement();
338 if(e->ValueStr() ==
"node")
340 else if(e->ValueStr() ==
"param")
342 else if(e->ValueStr() ==
"rosparam")
344 else if(e->ValueStr() ==
"group")
348 if(
const char* ns = e->Attribute(
"ns"))
355 else if(e->ValueStr() ==
"include")
357 else if(e->ValueStr() ==
"env")
359 else if(e->ValueStr() ==
"remap")
366 const char* name = element->Attribute(
"name");
367 const char* pkg = element->Attribute(
"pkg");
368 const char* type = element->Attribute(
"type");
369 const char* args = element->Attribute(
"args");
370 const char* ns = element->Attribute(
"ns");
371 const char* respawn = element->Attribute(
"respawn");
372 const char* respawnDelay = element->Attribute(
"respawn_delay");
373 const char* nRespawnsAllowed = element->Attribute(
"rosmon-restart-warn-threshold");
374 const char* required = element->Attribute(
"required");
375 const char* launchPrefix = element->Attribute(
"launch-prefix");
376 const char* cwd = element->Attribute(
"cwd");
377 const char* clearParams = element->Attribute(
"clear_params");
378 const char* output = element->Attribute(
"output");
379 const char* spawnDelay = element->Attribute(
"rosmon-spawn-delay");
381 if(!name || !pkg || !type)
383 throw attr_ctx.
error(
"name, pkg, type are mandatory for node elements!");
392 std::string fullNamespace = ctx.
prefix().substr(0, ctx.
prefix().length()-1);
407 return n->namespaceString() == fullNamespace && n->name() == node->name();
412 throw ctx.
error(
"node name '{}' is not unique", node->name());
422 node->addExtraArguments(ctx.
evaluate(args));
424 if(!fullNamespace.empty())
425 node->setNamespace(fullNamespace);
429 node->setRespawn(attr_ctx.
parseBool(respawn, element->Row()));
436 seconds = boost::lexical_cast<
double>(attr_ctx.
evaluate(respawnDelay));
438 catch(boost::bad_lexical_cast&)
440 throw ctx.
error(
"bad respawn_delay value '{}'", respawnDelay);
446 if (nRespawnsAllowed)
451 n_respawns = boost::lexical_cast<
int>(attr_ctx.
evaluate(nRespawnsAllowed));
453 catch(boost::bad_lexical_cast&)
455 throw ctx.
error(
"bad rosmon-restart-warn-threshold value '{}'", nRespawnsAllowed);
458 node->setNumRespawnsAllowed(n_respawns);
467 seconds = boost::lexical_cast<
double>(attr_ctx.
evaluate(spawnDelay));
469 catch(boost::bad_lexical_cast&)
471 throw ctx.
error(
"bad spawn_delay value '{}'", spawnDelay);
477 if(required && attr_ctx.
parseBool(required, element->Row()))
479 node->setRequired(
true);
483 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
485 TiXmlElement* e = n->ToElement();
494 if(e->ValueStr() ==
"rosparam")
499 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
501 TiXmlElement* e = n->ToElement();
510 if(e->ValueStr() ==
"param")
512 else if(e->ValueStr() ==
"remap")
514 else if(e->ValueStr() ==
"env")
524 node->setLaunchPrefix(attr_ctx.
evaluate(launchPrefix));
527 node->setWorkingDirectory(attr_ctx.
evaluate(cwd));
530 node->setClearParams(attr_ctx.
parseBool(clearParams, element->Row()));
534 node->setStdoutDisplayed(
false);
539 std::string outputStr = attr_ctx.
evaluate(output);
540 if(outputStr ==
"screen")
541 node->setStdoutDisplayed(
true);
542 else if(outputStr ==
"log")
543 node->setStdoutDisplayed(
false);
545 throw ctx.
error(
"Invalid output attribute value: '{}'", outputStr);
555 std::string fullValueLowercase = boost::algorithm::to_lower_copy(fullValue);
556 if(fullValueLowercase ==
"true")
558 else if(fullValueLowercase ==
"false")
562 try {
return boost::lexical_cast<
int>(fullValue); }
563 catch(boost::bad_lexical_cast&) {}
565 try {
return boost::lexical_cast<
double>(fullValue); }
566 catch(boost::bad_lexical_cast&) {}
574 const char* name = element->Attribute(
"name");
575 const char* value = element->Attribute(
"value");
576 const char*
command = element->Attribute(
"command");
577 const char* textfile = element->Attribute(
"textfile");
578 const char* binfile = element->Attribute(
"binfile");
579 const char* type = element->Attribute(
"type");
583 throw ctx.
error(
"name is mandatory for param elements");
586 int numCommands = (value ? 1 : 0) + (command ? 1 : 0) + (textfile ? 1 : 0) + (binfile ? 1 : 0);
589 throw ctx.
error(
"<param> tags need exactly one of value=, command=, textfile=, binfile= attributes.");
592 std::string fullName = ctx.
evaluate(name);
595 throw ctx.
error(
"param name is empty");
605 ctx.
warning(
"leading slashes in <param> names are ignored inside <node> contexts for roslaunch compatibility.");
606 fullName = fullName.substr(1);
608 else if(fullName[0] ==
'~')
611 fullName = fullName.substr(1);
614 fullName = ctx.
prefix() + fullName;
617 std::string errorStr;
620 throw ctx.
error(
"Expanded parameter name '{}' is invalid: {}",
625 std::string fullType;
632 if(fullType ==
"yaml")
634 std::string fullValue = ctx.
evaluate(value,
false);
639 n = YAML::Load(fullValue);
641 catch(YAML::ParserException& e)
643 throw ctx.
error(
"Invalid YAML: {}", e.what());
671 std::string fullFile = ctx.
evaluate(binfile);
673 m_paramJobs[fullName] = std::async(std::launch::deferred,
675 std::ifstream stream(fullFile, std::ios::binary | std::ios::ate);
677 throw ctx.
error(
"Could not open file '{}'", fullFile);
679 std::vector<char> data(stream.tellg(), 0);
680 stream.seekg(0, std::ios::beg);
682 stream.read(data.data(), data.size());
685 return {data.data(),
static_cast<int>(data.size())};
699 auto computeString = std::make_shared<std::future<std::string>>();
705 #if !ROS_VERSION_MINIMUM(1,13,0) 709 "On ROS versions prior to Lunar, roslaunch does not respect the " 710 "type attribute on <param> tags with command= or textfile= " 711 "actions. However, rosmon does support it and will create the " 712 "properly typed parameter {}.",
721 std::string fullCommand = ctx.
evaluate(command);
724 *computeString = std::async(std::launch::deferred,
725 [=]() -> std::string {
726 std::stringstream buffer;
729 if(pipe(pipe_fd) != 0)
730 throw ctx.
error(
"Could not create pipe: {}", strerror(errno));
734 throw ctx.
error(
"Could not fork: {}", strerror(errno));
740 if(pipe_fd[1] != STDOUT_FILENO)
742 dup2(pipe_fd[1], STDOUT_FILENO);
746 char* argp[] = {strdup(
"sh"), strdup(
"-c"), strdup(fullCommand.c_str()),
nullptr};
749 throw ctx.
error(
"Could not execvp '{}': {}", fullCommand, strerror(errno));
755 memset(&timeout, 0,
sizeof(timeout));
757 timeout.tv_usec = 500 * 1000;
763 FD_SET(pipe_fd[0], &fds);
765 int ret = select(pipe_fd[0]+1, &fds,
nullptr,
nullptr, &timeout);
767 throw ctx.
error(
"Could not select(): {}", strerror(errno));
771 fmt::print(
"Still loading parameter '{}'...\n", fullName);
778 ret = read(pipe_fd[0], buf,
sizeof(buf)-1);
780 throw ctx.
error(
"Could not read: {}", strerror(errno));
791 if(waitpid(pid, &status, 0) < 0)
792 throw ctx.
error(
"Could not waitpid(): {}", strerror(errno));
794 if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
796 throw ctx.
error(
"<param> command failed (exit status {})",
810 std::string fullFile = ctx.
evaluate(textfile);
812 *computeString = std::async(std::launch::deferred,
813 [=]() -> std::string {
814 std::ifstream stream(fullFile);
816 throw ctx.
error(
"Could not open file '{}'", fullFile);
818 std::stringstream buffer;
819 buffer << stream.rdbuf();
827 throw ctx.
error(
"<param> needs either command, value, binfile, or textfile");
830 if(fullType ==
"yaml")
834 std::string yamlString = computeString->get();
839 n = YAML::Load(yamlString);
841 catch(YAML::ParserException& e)
843 throw ctx.
error(
"Read invalid YAML from process or file: {}",
848 return {fullName, n};
854 m_paramJobs[fullName] = std::async(std::launch::deferred,
875 else if(type ==
"double")
877 else if(type ==
"bool" || type ==
"boolean")
879 std::string value_lowercase = boost::algorithm::to_lower_copy(
883 if(value_lowercase ==
"true")
885 else if(value_lowercase ==
"false")
888 throw ctx.
error(
"invalid boolean value '{}'", value);
890 else if(type ==
"str" || type ==
"string")
894 throw ctx.
error(
"invalid param type '{}'", type);
897 catch(boost::bad_lexical_cast& e)
899 throw ctx.
error(
"could not convert param value '{}' to type '{}'",
907 const char*
command = element->Attribute(
"command");
909 if(!command || strcmp(command,
"load") == 0)
911 const char* file = element->Attribute(
"file");
912 std::string fullFile;
914 std::string contents;
918 std::ifstream stream(fullFile);
920 throw ctx.
error(
"Could not open file '{}'", fullFile);
922 std::stringstream buffer;
923 buffer << stream.rdbuf();
925 contents = buffer.str();
929 if(
const char* t = element->GetText())
938 const char* subst_value = element->Attribute(
"subst_value");
939 if(subst_value && ctx.
parseBool(subst_value, element->Row()))
940 contents = ctx.
evaluate(contents,
false);
945 n = YAML::Load(contents);
947 catch(YAML::ParserException& e)
949 throw ctx.
error(
"Could not parse YAML: {}", e.what());
953 const char* ns = element->Attribute(
"ns");
957 const char* name = element->Attribute(
"param");
970 throw ctx.
error(
"error while parsing rosparam input file {}: {}",
976 throw ctx.
error(
"error while parsing YAML input from launch file: {}",
983 throw ctx.
error(
"Unsupported rosparam command '{}'", command);
990 case YAML::NodeType::Map:
993 for(YAML::const_iterator it = n.begin(); it != n.end(); ++it)
995 if(it->first.as<std::string>() ==
"<<")
1002 for(YAML::const_iterator it = n.begin(); it != n.end(); ++it)
1004 auto key = it->first.as<std::string>();
1008 if(!key.empty() && key[0] ==
'/')
1011 loadYAMLParams(ctx, it->second, prefix +
"/" + it->first.as<std::string>());
1017 case YAML::NodeType::Sequence:
1018 case YAML::NodeType::Scalar:
1026 case YAML::NodeType::Null:
1033 throw ctx.
error(
"invalid yaml node type");
1040 const char* file = element->Attribute(
"file");
1041 const char* ns = element->Attribute(
"ns");
1042 const char* passAllArgs = element->Attribute(
"pass_all_args");
1043 const char* clearParams = element->Attribute(
"clear_params");
1044 const char* importArgs = element->Attribute(
"rosmon-import-args");
1047 throw ctx.
error(
"<include> file attribute is mandatory");
1049 if(clearParams && ctx.
parseBool(clearParams, element->Row()))
1051 throw ctx.
error(
"<include clear_params=\"true\" /> is not supported and probably a bad idea.");
1054 std::string fullFile = ctx.
evaluate(file);
1064 if(!passAllArgs || !ctx.
parseBool(passAllArgs, element->Row()))
1068 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
1070 TiXmlElement* e = n->ToElement();
1077 if(e->ValueStr() ==
"arg")
1079 const char* name = e->Attribute(
"name");
1080 const char* value = e->Attribute(
"value");
1081 const char* defaultValue = e->Attribute(
"default");
1084 throw ctx.
error(
"<arg> inside include needs a name attribute");
1086 if(!value && defaultValue)
1090 "You are using <arg> inside an <include> tag with the " 1091 "default=XY attribute - which is superfluous. " 1092 "Use value=XY instead for less confusion. " 1093 "Attribute name: {}",
1096 value = defaultValue;
1100 throw ctx.
error(
"<arg> inside include needs name and value");
1106 TiXmlDocument document(fullFile);
1107 if(!document.LoadFile())
1108 throw ctx.
error(
"Could not load launch file '{}': {}", fullFile, document.ErrorDesc());
1112 parse(document.RootElement(), &childCtx);
1115 if(importArgs && ctx.
parseBool(importArgs, element->Row()))
1118 ctx.
setArg(pair.first, pair.second,
true);
1124 const char* name = element->Attribute(
"name");
1125 const char* value = element->Attribute(
"value");
1126 const char* def = element->Attribute(
"default");
1129 throw ctx.
error(
"<arg> needs name attribute");
1133 std::string fullValue = ctx.
evaluate(value);
1134 ctx.
setArg(name, fullValue,
true);
1138 std::string fullValue = ctx.
evaluate(def);
1139 ctx.
setArg(name, fullValue,
false);
1143 ctx.
setArg(name, UNSET_MARKER,
false);
1149 const char* name = element->Attribute(
"name");
1150 const char* value = element->Attribute(
"value");
1153 throw ctx.
error(
"<env> needs name, value attributes");
1160 const char* from = element->Attribute(
"from");
1161 const char* to = element->Attribute(
"to");
1164 throw ctx.
error(
"remap needs 'from' and 'to' arguments");
1171 return fmt::format(
"{:08X}",
m_anonGen());
1174 template<
class Iterator>
1177 for(
size_t j = 0; j < i; ++j)
1195 const int NUM_THREADS = std::thread::hardware_concurrency();
1197 std::vector<std::thread> threads(NUM_THREADS);
1201 bool caughtExceptionFlag =
false;
1204 for(
int i = 0; i < NUM_THREADS; ++i)
1206 threads[i] = std::thread([
this,i,NUM_THREADS,&mutex,&caughtException,&caughtExceptionFlag]() {
1218 std::lock_guard<std::mutex> guard(mutex);
1233 std::lock_guard<std::mutex> guard(mutex);
1241 std::lock_guard<std::mutex> guard(mutex);
1242 caughtException = e;
1243 caughtExceptionFlag =
true;
1249 for(
auto& t : threads)
1252 if(caughtExceptionFlag)
1253 throw caughtException;
void setEnvironment(const std::string &name, const std::string &value)
virtual const char * what() const noexcept
std::map< std::string, std::string > m_anonNames
bool shouldSkip(TiXmlElement *e)
std::mt19937_64 m_anonGen
std::string windowTitle() const
std::string simplifyWhitespace(const std::string &input)
Compress any sequence of whitespace to single spaces.
void setFilename(const std::string &filename)
std::string generateAnonHash()
ParseContext enterScope(const std::string &prefix)
uint64_t memoryLimit() const
std::string anonName(const std::string &base)
void setArg(const std::string &name, const std::string &value, bool override)
virtual const char * what() const noexcept override
void setDefaultCPULimit(double CPULimit)
std::shared_ptr< Node > Ptr
ParseException error(const char *fmt, const Args &... args) const
std::map< std::string, ParameterFuture > m_paramJobs
ROSCPP_DECL bool validate(const std::string &name, std::string &error)
ROSCPP_DECL std::string clean(const std::string &name)
void setWarningOutput(std::ostream *warningStream)
void parseROSParam(TiXmlElement *element, ParseContext &ctx)
void parseInclude(TiXmlElement *element, ParseContext &ctx)
const std::map< std::string, std::string > & arguments() const
void evaluateParameters()
void parseEnv(TiXmlElement *element, ParseContext &ctx)
std::string convertWhitespace(const std::string &input)
Convert any whitespace to space characters.
std::vector< std::future< YAMLResult > > m_yamlParamJobs
void parseString(const std::string &input, bool onlyArguments=false)
void setDefaultMemoryLimit(uint64_t memoryLimit)
const char * UNSET_MARKER
void parseArgument(TiXmlElement *element, ParseContext &ctx)
std::map< std::string, std::string > m_args
const std::string & prefix() const
ROSLIB_DECL std::string command(const std::string &cmd)
std::map< std::string, std::string > m_environment
std::string evaluate(const std::string &tpl, bool simplifyWhitespace=true)
void setCPULimit(double limit)
void parse(const std::string &filename, bool onlyArguments=false)
double stopTimeout() const
bool coredumpsEnabled() const
void setCurrentElement(TiXmlElement *e)
void setOutputAttrMode(OutputAttr mode)
void applyAutoIncrementSpawnDelayToAll(const ros::WallDuration &autoIncrementSpawnDelay)
static XmlRpc::XmlRpcValue autoXmlRpcValue(const std::string &fullValue)
void parseParam(TiXmlElement *element, ParseContext &ctx, ParamContext paramContext=PARAM_GENERAL)
void warning(const char *fmt, const Args &... args) const
void parseTopLevelAttributes(TiXmlElement *element)
XmlRpc::XmlRpcValue yamlToXmlRpc(const ParseContext &ctx, const YAML::Node &n)
void setArgument(const std::string &name, const std::string &value)
std::string parseSubstitutionArgs(const std::string &input, ParseContext &context)
std::vector< Node::Ptr > m_nodes
void setRemap(const std::string &from, const std::string &to)
ParseContext m_rootContext
const std::map< std::string, std::string > environment() const
std::string m_windowTitle
void setNodeLogDir(const std::string &logDir)
void parseNode(TiXmlElement *element, ParseContext &ctx)
void setMemoryLimit(uint64_t limit)
std::string m_rosmonNodeName
void setStopTimeout(double timeout)
std::string strip(const std::string &input)
void parseRemap(TiXmlElement *element, ParseContext &ctx)
bool parseBool(const std::string &value, int line)
std::tuple< uint64_t, bool > parseMemory(const std::string &memory)
OutputAttr m_outputAttrMode
std::map< std::string, std::string > m_remappings
void setDefaultStopTimeout(double timeout)
const std::map< std::string, std::string > & remappings()
std::ostream * m_warningOutput
void safeAdvance(Iterator &it, const Iterator &end, size_t i)
XmlRpc::XmlRpcValue paramToXmlRpc(const ParseContext &ctx, const std::string &value, const std::string &type="")
void loadYAMLParams(const ParseContext &ctx, const YAML::Node &n, const std::string &prefix)
void parseScopeAttributes(TiXmlElement *e, ParseContext &attr_ctx)
bool isOnlyWhitespace(const std::string &input)
Check if string is whitespace only (includes ' ')