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> 44 if(
const char*
stopTimeout = e->Attribute(
"rosmon-stop-timeout"))
51 catch(boost::bad_lexical_cast&)
61 if(
const char*
memoryLimit = e->Attribute(
"rosmon-memory-limit"))
63 uint64_t memoryLimitByte;
74 if(
const char*
cpuLimit = e->Attribute(
"rosmon-cpu-limit"))
81 catch(boost::bad_lexical_cast&)
100 std::string simplified;
101 if(simplifyWhitespace)
112 throw error(
"Substitution error: {}", e.
what());
118 std::string expansion =
evaluate(value);
126 if(expansion ==
"1" || expansion ==
"true" || expansion ==
"True")
129 if(expansion ==
"0" || expansion ==
"false" || expansion ==
"False")
132 throw error(
"Unknown truth value '%s'", expansion.c_str());
137 const char* if_cond = e->Attribute(
"if");
138 const char* unless_cond = e->Attribute(
"unless");
140 if(if_cond && unless_cond)
142 throw error(
"both if= and unless= specified, don't know what to do");
160 auto it =
m_args.find(name);
163 else if(
override || it->second == UNSET_MARKER)
192 : m_rootContext(this)
193 , m_anonGen(
std::random_device()())
195 const char* ROS_NAMESPACE = getenv(
"ROS_NAMESPACE");
228 TiXmlDocument document(filename);
230 TiXmlBase::SetCondenseWhiteSpace(
false);
232 if(!document.LoadFile())
251 TiXmlDocument document;
253 TiXmlBase::SetCondenseWhiteSpace(
false);
255 document.Parse(input.c_str());
274 const char* name = element->Attribute(
"rosmon-name");
278 const char*
windowTitle = element->Attribute(
"rosmon-window-title");
282 const char*
disableUI = element->Attribute(
"rosmon-disable-ui");
292 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
294 TiXmlElement* e = n->ToElement();
303 if(e->ValueStr() ==
"arg")
311 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
313 TiXmlElement* e = n->ToElement();
322 if(e->ValueStr() ==
"node")
324 else if(e->ValueStr() ==
"param")
326 else if(e->ValueStr() ==
"rosparam")
328 else if(e->ValueStr() ==
"group")
332 if(
const char* ns = e->Attribute(
"ns"))
339 else if(e->ValueStr() ==
"include")
341 else if(e->ValueStr() ==
"env")
343 else if(e->ValueStr() ==
"remap")
350 const char* name = element->Attribute(
"name");
351 const char* pkg = element->Attribute(
"pkg");
352 const char* type = element->Attribute(
"type");
353 const char* args = element->Attribute(
"args");
354 const char* ns = element->Attribute(
"ns");
355 const char* respawn = element->Attribute(
"respawn");
356 const char* respawnDelay = element->Attribute(
"respawn_delay");
357 const char* required = element->Attribute(
"required");
358 const char* launchPrefix = element->Attribute(
"launch-prefix");
359 const char* cwd = element->Attribute(
"cwd");
360 const char* clearParams = element->Attribute(
"clear_params");
361 const char* output = element->Attribute(
"output");
363 if(!name || !pkg || !type)
365 throw attr_ctx.
error(
"name, pkg, type are mandatory for node elements!");
374 std::string fullNamespace = ctx.
prefix().substr(0, ctx.
prefix().length()-1);
389 return n->namespaceString() == fullNamespace && n->name() == node->name();
394 throw ctx.
error(
"node name '{}' is not unique", node->name());
404 node->addExtraArguments(ctx.
evaluate(args));
406 if(!fullNamespace.empty())
407 node->setNamespace(fullNamespace);
411 node->setRespawn(attr_ctx.
parseBool(respawn, element->Row()));
418 seconds = boost::lexical_cast<
double>(attr_ctx.
evaluate(respawnDelay));
420 catch(boost::bad_lexical_cast&)
422 throw ctx.
error(
"bad respawn_delay value '{}'", respawnDelay);
429 if(required && attr_ctx.
parseBool(required, element->Row()))
431 node->setRequired(
true);
435 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
437 TiXmlElement* e = n->ToElement();
446 if(e->ValueStr() ==
"rosparam")
451 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
453 TiXmlElement* e = n->ToElement();
462 if(e->ValueStr() ==
"param")
464 else if(e->ValueStr() ==
"remap")
466 else if(e->ValueStr() ==
"env")
476 node->setLaunchPrefix(attr_ctx.
evaluate(launchPrefix));
479 node->setWorkingDirectory(attr_ctx.
evaluate(cwd));
482 node->setClearParams(attr_ctx.
parseBool(clearParams, element->Row()));
486 node->setStdoutDisplayed(
false);
491 std::string outputStr = attr_ctx.
evaluate(output);
492 if(outputStr ==
"screen")
493 node->setStdoutDisplayed(
true);
494 else if(outputStr ==
"log")
495 node->setStdoutDisplayed(
false);
497 throw ctx.
error(
"Invalid output attribute value: '{}'", outputStr);
507 std::string fullValueLowercase = boost::algorithm::to_lower_copy(fullValue);
508 if(fullValueLowercase ==
"true")
510 else if(fullValueLowercase ==
"false")
514 try {
return boost::lexical_cast<
int>(fullValue); }
515 catch(boost::bad_lexical_cast&) {}
517 try {
return boost::lexical_cast<
double>(fullValue); }
518 catch(boost::bad_lexical_cast&) {}
526 const char* name = element->Attribute(
"name");
527 const char* value = element->Attribute(
"value");
528 const char*
command = element->Attribute(
"command");
529 const char* textfile = element->Attribute(
"textfile");
530 const char* binfile = element->Attribute(
"binfile");
531 const char* type = element->Attribute(
"type");
535 throw ctx.
error(
"name is mandatory for param elements");
538 int numCommands = (value ? 1 : 0) + (command ? 1 : 0) + (textfile ? 1 : 0) + (binfile ? 1 : 0);
541 throw ctx.
error(
"<param> tags need exactly one of value=, command=, textfile=, binfile= attributes.");
544 std::string fullName = ctx.
evaluate(name);
547 throw ctx.
error(
"param name is empty");
557 ctx.
warning(
"leading slashes in <param> names are ignored inside <node> contexts for roslaunch compatibility.");
558 fullName = fullName.substr(1);
560 else if(fullName[0] ==
'~')
563 fullName = fullName.substr(1);
566 fullName = ctx.
prefix() + fullName;
569 std::string errorStr;
572 throw ctx.
error(
"Expanded parameter name '{}' is invalid: {}",
577 std::string fullType;
584 if(fullType ==
"yaml")
586 std::string fullValue = ctx.
evaluate(value,
false);
591 n = YAML::Load(fullValue);
593 catch(YAML::ParserException& e)
595 throw ctx.
error(
"Invalid YAML: {}", e.what());
623 std::string fullFile = ctx.
evaluate(binfile);
625 m_paramJobs[fullName] = std::async(std::launch::deferred,
627 std::ifstream stream(fullFile, std::ios::binary | std::ios::ate);
629 throw ctx.
error(
"Could not open file '{}'", fullFile);
631 std::vector<char> data(stream.tellg(), 0);
632 stream.seekg(0, std::ios::beg);
634 stream.read(data.data(), data.size());
637 return {data.data(),
static_cast<int>(data.size())};
651 auto computeString = std::make_shared<std::future<std::string>>();
657 #if !ROS_VERSION_MINIMUM(1,13,0) 661 "On ROS versions prior to Lunar, roslaunch does not respect the " 662 "type attribute on <param> tags with command= or textfile= " 663 "actions. However, rosmon does support it and will create the " 664 "properly typed parameter {}.",
673 std::string fullCommand = ctx.
evaluate(command);
676 *computeString = std::async(std::launch::deferred,
677 [=]() -> std::string {
678 std::stringstream buffer;
681 if(pipe(pipe_fd) != 0)
682 throw ctx.
error(
"Could not create pipe: {}", strerror(errno));
686 throw ctx.
error(
"Could not fork: {}", strerror(errno));
692 if(pipe_fd[1] != STDOUT_FILENO)
694 dup2(pipe_fd[1], STDOUT_FILENO);
698 char* argp[] = {strdup(
"sh"), strdup(
"-c"), strdup(fullCommand.c_str()),
nullptr};
701 throw ctx.
error(
"Could not execvp '{}': {}", fullCommand, strerror(errno));
707 memset(&timeout, 0,
sizeof(timeout));
709 timeout.tv_usec = 500 * 1000;
715 FD_SET(pipe_fd[0], &fds);
717 int ret = select(pipe_fd[0]+1, &fds,
nullptr,
nullptr, &timeout);
719 throw ctx.
error(
"Could not select(): {}", strerror(errno));
723 fmt::print(
"Still loading parameter '{}'...\n", fullName);
730 ret = read(pipe_fd[0], buf,
sizeof(buf)-1);
732 throw ctx.
error(
"Could not read: {}", strerror(errno));
743 if(waitpid(pid, &status, 0) < 0)
744 throw ctx.
error(
"Could not waitpid(): {}", strerror(errno));
746 if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
748 throw ctx.
error(
"<param> command failed (exit status {})",
762 std::string fullFile = ctx.
evaluate(textfile);
764 *computeString = std::async(std::launch::deferred,
765 [=]() -> std::string {
766 std::ifstream stream(fullFile);
768 throw ctx.
error(
"Could not open file '{}'", fullFile);
770 std::stringstream buffer;
771 buffer << stream.rdbuf();
779 throw ctx.
error(
"<param> needs either command, value, binfile, or textfile");
782 if(fullType ==
"yaml")
786 std::string yamlString = computeString->get();
791 n = YAML::Load(yamlString);
793 catch(YAML::ParserException& e)
795 throw ctx.
error(
"Read invalid YAML from process or file: {}",
800 return {fullName, n};
806 m_paramJobs[fullName] = std::async(std::launch::deferred,
827 else if(type ==
"double")
829 else if(type ==
"bool" || type ==
"boolean")
831 std::string value_lowercase = boost::algorithm::to_lower_copy(
835 if(value_lowercase ==
"true")
837 else if(value_lowercase ==
"false")
840 throw ctx.
error(
"invalid boolean value '{}'", value);
842 else if(type ==
"str" || type ==
"string")
846 throw ctx.
error(
"invalid param type '{}'", type);
849 catch(boost::bad_lexical_cast& e)
851 throw ctx.
error(
"could not convert param value '{}' to type '{}'",
859 const char*
command = element->Attribute(
"command");
861 if(!command || strcmp(command,
"load") == 0)
863 const char* file = element->Attribute(
"file");
864 std::string fullFile;
866 std::string contents;
870 std::ifstream stream(fullFile);
872 throw ctx.
error(
"Could not open file '{}'", fullFile);
874 std::stringstream buffer;
875 buffer << stream.rdbuf();
877 contents = buffer.str();
881 if(
const char* t = element->GetText())
890 const char* subst_value = element->Attribute(
"subst_value");
891 if(subst_value && ctx.
parseBool(subst_value, element->Row()))
892 contents = ctx.
evaluate(contents,
false);
897 n = YAML::Load(contents);
899 catch(YAML::ParserException& e)
901 throw ctx.
error(
"Could not parse YAML: {}", e.what());
905 const char* ns = element->Attribute(
"ns");
909 const char* name = element->Attribute(
"param");
922 throw ctx.
error(
"error while parsing rosparam input file {}: {}",
928 throw ctx.
error(
"error while parsing YAML input from launch file: {}",
935 throw ctx.
error(
"Unsupported rosparam command '{}'", command);
942 case YAML::NodeType::Map:
945 for(YAML::const_iterator it = n.begin(); it != n.end(); ++it)
947 if(it->first.as<std::string>() ==
"<<")
954 for(YAML::const_iterator it = n.begin(); it != n.end(); ++it)
956 auto key = it->first.as<std::string>();
960 if(!key.empty() && key[0] ==
'/')
963 loadYAMLParams(ctx, it->second, prefix +
"/" + it->first.as<std::string>());
969 case YAML::NodeType::Sequence:
970 case YAML::NodeType::Scalar:
978 case YAML::NodeType::Null:
985 throw ctx.
error(
"invalid yaml node type");
992 const char* file = element->Attribute(
"file");
993 const char* ns = element->Attribute(
"ns");
994 const char* passAllArgs = element->Attribute(
"pass_all_args");
995 const char* clearParams = element->Attribute(
"clear_params");
998 throw ctx.
error(
"<include> file attribute is mandatory");
1000 if(clearParams && ctx.
parseBool(clearParams, element->Row()))
1002 throw ctx.
error(
"<include clear_params=\"true\" /> is not supported and probably a bad idea.");
1005 std::string fullFile = ctx.
evaluate(file);
1015 if(!passAllArgs || !ctx.
parseBool(passAllArgs, element->Row()))
1019 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
1021 TiXmlElement* e = n->ToElement();
1028 if(e->ValueStr() ==
"arg")
1030 const char* name = e->Attribute(
"name");
1031 const char* value = e->Attribute(
"value");
1032 const char* defaultValue = e->Attribute(
"default");
1035 throw ctx.
error(
"<arg> inside include needs a name attribute");
1037 if(!value && defaultValue)
1041 "You are using <arg> inside an <include> tag with the " 1042 "default=XY attribute - which is superfluous. " 1043 "Use value=XY instead for less confusion. " 1044 "Attribute name: {}",
1047 value = defaultValue;
1051 throw ctx.
error(
"<arg> inside include needs name and value");
1057 TiXmlDocument document(fullFile);
1058 if(!document.LoadFile())
1059 throw ctx.
error(
"Could not load launch file '{}': {}", fullFile, document.ErrorDesc());
1063 parse(document.RootElement(), &childCtx);
1068 const char* name = element->Attribute(
"name");
1069 const char* value = element->Attribute(
"value");
1070 const char* def = element->Attribute(
"default");
1073 throw ctx.
error(
"<arg> needs name attribute");
1077 std::string fullValue = ctx.
evaluate(value);
1078 ctx.
setArg(name, fullValue,
true);
1082 std::string fullValue = ctx.
evaluate(def);
1083 ctx.
setArg(name, fullValue,
false);
1087 ctx.
setArg(name, UNSET_MARKER,
false);
1093 const char* name = element->Attribute(
"name");
1094 const char* value = element->Attribute(
"value");
1097 throw ctx.
error(
"<env> needs name, value attributes");
1104 const char* from = element->Attribute(
"from");
1105 const char* to = element->Attribute(
"to");
1108 throw ctx.
error(
"remap needs 'from' and 'to' arguments");
1115 return fmt::format(
"{:08X}",
m_anonGen());
1118 template<
class Iterator>
1121 for(
size_t j = 0; j < i; ++j)
1139 const int NUM_THREADS = std::thread::hardware_concurrency();
1141 std::vector<std::thread> threads(NUM_THREADS);
1145 bool caughtExceptionFlag =
false;
1148 for(
int i = 0; i < NUM_THREADS; ++i)
1150 threads[i] = std::thread([
this,i,NUM_THREADS,&mutex,&caughtException,&caughtExceptionFlag]() {
1162 std::lock_guard<std::mutex> guard(mutex);
1177 std::lock_guard<std::mutex> guard(mutex);
1185 std::lock_guard<std::mutex> guard(mutex);
1186 caughtException = e;
1187 caughtExceptionFlag =
true;
1193 for(
auto& t : threads)
1196 if(caughtExceptionFlag)
1197 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 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)
std::string anonName(const std::string &base)
const std::map< std::string, std::string > environment() const
void setArg(const std::string &name, const std::string &value, bool override)
void setDefaultCPULimit(double CPULimit)
std::shared_ptr< Node > Ptr
std::map< std::string, ParameterFuture > m_paramJobs
ROSCPP_DECL bool validate(const std::string &name, std::string &error)
virtual const char * what() const noexceptoverride
ROSCPP_DECL std::string clean(const std::string &name)
void setWarningOutput(std::ostream *warningStream)
void parseROSParam(TiXmlElement *element, ParseContext &ctx)
bool coredumpsEnabled() const
void evaluateParameters()
void parseEnv(TiXmlElement *element, ParseContext &ctx)
std::string convertWhitespace(const std::string &input)
Convert any whitespace to space characters.
const std::string & prefix() const
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
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)
void setCurrentElement(TiXmlElement *e)
void parseInclude(TiXmlElement *element, ParseContext ctx)
uint64_t memoryLimit() const
void setOutputAttrMode(OutputAttr mode)
static XmlRpc::XmlRpcValue autoXmlRpcValue(const std::string &fullValue)
void warning(const char *fmt, const Args &...args) const
void parseParam(TiXmlElement *element, ParseContext &ctx, ParamContext paramContext=PARAM_GENERAL)
void parseTopLevelAttributes(TiXmlElement *element)
ParseException error(const char *fmt, const Args &...args) const
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)
std::string windowTitle() const
ParseContext m_rootContext
std::string m_windowTitle
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
double stopTimeout() const
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 ' ')