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 std::string simplified;
45 if(simplifyWhitespace)
56 throw error(
"Substitution error: {}", e.
what());
62 std::string expansion =
evaluate(value);
70 if(expansion ==
"1" || expansion ==
"true" || expansion ==
"True")
73 if(expansion ==
"0" || expansion ==
"false" || expansion ==
"False")
76 throw error(
"Unknown truth value '%s'", expansion.c_str());
81 const char* if_cond = e->Attribute(
"if");
82 const char* unless_cond = e->Attribute(
"unless");
84 if(if_cond && unless_cond)
86 throw error(
"both if= and unless= specified, don't know what to do");
104 auto it =
m_args.find(name);
107 else if(
override || it->second == UNSET_MARKER)
122 : m_rootContext(this)
123 , m_anonGen(
std::random_device()())
125 const char* ROS_NAMESPACE = getenv(
"ROS_NAMESPACE");
158 TiXmlDocument document(filename);
160 TiXmlBase::SetCondenseWhiteSpace(
false);
162 if(!document.LoadFile())
181 TiXmlDocument document;
183 TiXmlBase::SetCondenseWhiteSpace(
false);
185 document.Parse(input.c_str());
204 const char* name = element->Attribute(
"rosmon-name");
208 const char*
windowTitle = element->Attribute(
"rosmon-window-title");
216 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
218 TiXmlElement* e = n->ToElement();
227 if(e->ValueStr() ==
"arg")
235 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
237 TiXmlElement* e = n->ToElement();
246 if(e->ValueStr() ==
"node")
248 else if(e->ValueStr() ==
"param")
250 else if(e->ValueStr() ==
"rosparam")
252 else if(e->ValueStr() ==
"group")
254 const char* ns = e->Attribute(
"ns");
263 else if(e->ValueStr() ==
"include")
265 else if(e->ValueStr() ==
"env")
267 else if(e->ValueStr() ==
"remap")
274 const char* name = element->Attribute(
"name");
275 const char* pkg = element->Attribute(
"pkg");
276 const char* type = element->Attribute(
"type");
277 const char* args = element->Attribute(
"args");
278 const char* ns = element->Attribute(
"ns");
279 const char* respawn = element->Attribute(
"respawn");
280 const char* respawnDelay = element->Attribute(
"respawn_delay");
281 const char* required = element->Attribute(
"required");
282 const char* launchPrefix = element->Attribute(
"launch-prefix");
283 const char* coredumpsEnabled = element->Attribute(
"enable-coredumps");
284 const char* cwd = element->Attribute(
"cwd");
285 const char* clearParams = element->Attribute(
"clear_params");
286 const char* stopTimeout = element->Attribute(
"rosmon-stop-timeout");
287 const char* memoryLimit = element->Attribute(
"rosmon-memory-limit");
288 const char* cpuLimit = element->Attribute(
"rosmon-cpu-limit");
291 if(!name || !pkg || !type)
293 throw ctx.
error(
"name, pkg, type are mandatory for node elements!");
299 std::string fullNamespace = ctx.
prefix().substr(0, ctx.
prefix().length()-1);
311 return n->namespaceString() == fullNamespace && n->name() == node->name();
316 throw ctx.
error(
"node name '{}' is not unique", node->name());
325 seconds = boost::lexical_cast<
double>(ctx.
evaluate(stopTimeout));
327 catch(boost::bad_lexical_cast&)
329 throw ctx.
error(
"bad rosmon-stop-timeout value '{}'", stopTimeout);
332 throw ctx.
error(
"negative rosmon-stop-timeout value '{}'", stopTimeout);
334 node->setStopTimeout(seconds);
341 uint64_t memoryLimitByte;
343 std::tie(memoryLimitByte, ok) =
parseMemory(static_cast<std::string>(memoryLimit));
346 throw ctx.
error(
"{} cannot be parsed as a memory limit", memoryLimit);
349 node->setMemoryLimit(memoryLimitByte);
361 cpuLimitPct = boost::lexical_cast<
double>(ctx.
evaluate(cpuLimit));
363 catch(boost::bad_lexical_cast&)
365 throw ctx.
error(
"bad rosmon-cpu-limit value '{}'", cpuLimit);
369 throw ctx.
error(
"negative rosmon-cpu-limit value'{}'", cpuLimit);
371 node->setCPULimit(cpuLimitPct);
379 node->addExtraArguments(ctx.
evaluate(args));
381 if(!fullNamespace.empty())
382 node->setNamespace(fullNamespace);
386 node->setRespawn(ctx.
parseBool(respawn, element->Row()));
393 seconds = boost::lexical_cast<
double>(ctx.
evaluate(respawnDelay));
395 catch(boost::bad_lexical_cast&)
397 throw ctx.
error(
"bad respawn_delay value '{}'", respawnDelay);
404 if(required && ctx.
parseBool(required, element->Row()))
406 node->setRequired(
true);
409 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
411 TiXmlElement* e = n->ToElement();
420 if(e->ValueStr() ==
"param")
422 else if(e->ValueStr() ==
"rosparam")
424 else if(e->ValueStr() ==
"remap")
426 else if(e->ValueStr() ==
"env")
436 node->setLaunchPrefix(ctx.
evaluate(launchPrefix));
439 node->setCoredumpsEnabled(ctx.
parseBool(coredumpsEnabled, element->Row()));
442 node->setWorkingDirectory(ctx.
evaluate(cwd));
445 node->setClearParams(ctx.
parseBool(clearParams, element->Row()));
454 std::string fullValueLowercase = boost::algorithm::to_lower_copy(fullValue);
455 if(fullValueLowercase ==
"true")
457 else if(fullValueLowercase ==
"false")
461 try {
return boost::lexical_cast<
int>(fullValue); }
462 catch(boost::bad_lexical_cast&) {}
464 try {
return boost::lexical_cast<
float>(fullValue); }
465 catch(boost::bad_lexical_cast&) {}
473 const char* name = element->Attribute(
"name");
474 const char* value = element->Attribute(
"value");
475 const char*
command = element->Attribute(
"command");
476 const char* textfile = element->Attribute(
"textfile");
477 const char* binfile = element->Attribute(
"binfile");
478 const char* type = element->Attribute(
"type");
482 throw ctx.
error(
"name is mandatory for param elements");
485 int numCommands = (value ? 1 : 0) + (command ? 1 : 0) + (textfile ? 1 : 0) + (binfile ? 1 : 0);
488 throw ctx.
error(
"<param> tags need exactly one of value=, command=, textfile=, binfile= attributes.");
491 std::string fullName = ctx.
evaluate(name);
494 throw ctx.
error(
"param name is empty");
504 ctx.
warning(
"leading slashes in <param> names are ignored inside <node> contexts for roslaunch compatibility.");
505 fullName = fullName.substr(1);
507 else if(fullName[0] ==
'~')
510 fullName = fullName.substr(1);
513 fullName = ctx.
prefix() + fullName;
516 std::string errorStr;
519 throw ctx.
error(
"Expanded parameter name '{}' is invalid: {}",
524 std::string fullType;
531 if(fullType ==
"yaml")
533 std::string fullValue = ctx.
evaluate(value,
false);
538 n = YAML::Load(fullValue);
540 catch(YAML::ParserException& e)
542 throw ctx.
error(
"Invalid YAML: {}", e.what());
562 std::string fullFile = ctx.
evaluate(binfile);
564 m_paramJobs[fullName] = std::async(std::launch::deferred,
566 std::ifstream stream(fullFile, std::ios::binary | std::ios::ate);
568 throw ctx.
error(
"Could not open file '{}'", fullFile);
570 std::vector<char> data(stream.tellg(), 0);
571 stream.seekg(0, std::ios::beg);
573 stream.read(data.data(), data.size());
576 return {data.data(),
static_cast<int>(data.size())};
590 auto computeString = std::make_shared<std::future<std::string>>();
595 std::string fullCommand = ctx.
evaluate(command);
598 *computeString = std::async(std::launch::deferred,
599 [=]() -> std::string {
600 std::stringstream buffer;
603 if(pipe(pipe_fd) != 0)
604 throw ctx.
error(
"Could not create pipe: {}", strerror(errno));
608 throw ctx.
error(
"Could not fork: {}", strerror(errno));
614 if(pipe_fd[1] != STDOUT_FILENO)
616 dup2(pipe_fd[1], STDOUT_FILENO);
620 char* argp[] = {strdup(
"sh"), strdup(
"-c"), strdup(fullCommand.c_str()),
nullptr};
623 throw ctx.
error(
"Could not execvp '{}': {}", fullCommand, strerror(errno));
629 memset(&timeout, 0,
sizeof(timeout));
631 timeout.tv_usec = 500 * 1000;
637 FD_SET(pipe_fd[0], &fds);
639 int ret = select(pipe_fd[0]+1, &fds,
nullptr,
nullptr, &timeout);
641 throw ctx.
error(
"Could not select(): {}", strerror(errno));
645 fmt::print(
"Still loading parameter '{}'...\n", fullName);
652 ret = read(pipe_fd[0], buf,
sizeof(buf)-1);
654 throw ctx.
error(
"Could not read: {}", strerror(errno));
665 if(waitpid(pid, &status, 0) < 0)
666 throw ctx.
error(
"Could not waitpid(): {}", strerror(errno));
668 if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
670 throw ctx.
error(
"<param> command failed (exit status {})",
684 std::string fullFile = ctx.
evaluate(textfile);
686 *computeString = std::async(std::launch::deferred,
687 [=]() -> std::string {
688 std::ifstream stream(fullFile);
690 throw ctx.
error(
"Could not open file '{}'", fullFile);
692 std::stringstream buffer;
693 buffer << stream.rdbuf();
701 throw ctx.
error(
"<param> needs either command, value, binfile, or textfile");
704 if(fullType ==
"yaml")
708 std::string yamlString = computeString->get();
713 n = YAML::Load(yamlString);
715 catch(YAML::ParserException& e)
717 throw ctx.
error(
"Read invalid YAML from process or file: {}",
722 return {fullName, n};
728 m_paramJobs[fullName] = std::async(std::launch::deferred,
748 return boost::lexical_cast<
int>(value);
749 else if(type ==
"double")
750 return boost::lexical_cast<
double>(value);
751 else if(type ==
"bool" || type ==
"boolean")
753 std::string value_lowercase = boost::algorithm::to_lower_copy(value);
754 if(value_lowercase ==
"true")
756 else if(value_lowercase ==
"false")
760 throw ctx.
error(
"invalid boolean value '{}'", value);
763 else if(type ==
"str" || type ==
"string")
767 throw ctx.
error(
"invalid param type '{}'", type);
770 catch(boost::bad_lexical_cast& e)
772 throw ctx.
error(
"could not convert param value '{}' to type '{}'",
780 const char*
command = element->Attribute(
"command");
782 if(!command || strcmp(command,
"load") == 0)
784 const char* file = element->Attribute(
"file");
785 std::string fullFile;
787 std::string contents;
791 std::ifstream stream(fullFile);
793 throw ctx.
error(
"Could not open file '{}'", fullFile);
795 std::stringstream buffer;
796 buffer << stream.rdbuf();
798 contents = buffer.str();
802 if(
const char* t = element->GetText())
811 const char* subst_value = element->Attribute(
"subst_value");
812 if(subst_value && ctx.
parseBool(subst_value, element->Row()))
813 contents = ctx.
evaluate(contents,
false);
818 n = YAML::Load(contents);
820 catch(YAML::ParserException& e)
822 throw ctx.
error(
"Could not parse YAML: {}", e.what());
825 const char* ns = element->Attribute(
"ns");
829 const char* name = element->Attribute(
"param");
842 throw ctx.
error(
"error while parsing rosparam input file {}: {}",
848 throw ctx.
error(
"error while parsing YAML input from launch file: {}",
855 throw ctx.
error(
"Unsupported rosparam command '{}'", command);
862 case YAML::NodeType::Map:
864 for(YAML::const_iterator it = n.begin(); it != n.end(); ++it)
866 loadYAMLParams(ctx, it->second, prefix +
"/" + it->first.as<std::string>());
870 case YAML::NodeType::Sequence:
871 case YAML::NodeType::Scalar:
881 throw ctx.
error(
"invalid yaml node type");
888 const char* file = element->Attribute(
"file");
889 const char* ns = element->Attribute(
"ns");
890 const char* passAllArgs = element->Attribute(
"pass_all_args");
891 const char* clearParams = element->Attribute(
"clear_params");
894 throw ctx.
error(
"<include> file attribute is mandatory");
896 if(clearParams && ctx.
parseBool(clearParams, element->Row()))
898 throw ctx.
error(
"<include clear_params=\"true\" /> is not supported and probably a bad idea.");
901 std::string fullFile = ctx.
evaluate(file);
910 if(!passAllArgs || !ctx.
parseBool(passAllArgs, element->Row()))
914 for(TiXmlNode* n = element->FirstChild(); n; n = n->NextSibling())
916 TiXmlElement* e = n->ToElement();
923 if(e->ValueStr() ==
"arg")
925 const char* name = e->Attribute(
"name");
926 const char* value = e->Attribute(
"value");
927 const char* defaultValue = e->Attribute(
"default");
930 throw ctx.
error(
"<arg> inside include needs a name attribute");
932 if(!value && defaultValue)
936 "You are using <arg> inside an <include> tag with the " 937 "default=XY attribute - which is superfluous. " 938 "Use value=XY instead for less confusion. " 939 "Attribute name: {}",
942 value = defaultValue;
946 throw ctx.
error(
"<arg> inside include needs name and value");
952 TiXmlDocument document(fullFile);
953 if(!document.LoadFile())
954 throw ctx.
error(
"Could not load launch file '{}': {}", fullFile, document.ErrorDesc());
958 parse(document.RootElement(), &childCtx);
963 const char* name = element->Attribute(
"name");
964 const char* value = element->Attribute(
"value");
965 const char* def = element->Attribute(
"default");
968 throw ctx.
error(
"<arg> needs name attribute");
972 std::string fullValue = ctx.
evaluate(value);
973 ctx.
setArg(name, fullValue,
true);
977 std::string fullValue = ctx.
evaluate(def);
978 ctx.
setArg(name, fullValue,
false);
982 ctx.
setArg(name, UNSET_MARKER,
false);
988 const char* name = element->Attribute(
"name");
989 const char* value = element->Attribute(
"value");
992 throw ctx.
error(
"<env> needs name, value attributes");
999 const char* from = element->Attribute(
"from");
1000 const char* to = element->Attribute(
"to");
1003 throw ctx.
error(
"remap needs 'from' and 'to' arguments");
1016 snprintf(buf,
sizeof(buf),
"%08X", r);
1018 auto name = base +
"_" + buf;
1026 template<
class Iterator>
1029 for(
size_t j = 0; j < i; ++j)
1047 const int NUM_THREADS = std::thread::hardware_concurrency();
1049 std::vector<std::thread> threads(NUM_THREADS);
1053 bool caughtExceptionFlag =
false;
1056 for(
int i = 0; i < NUM_THREADS; ++i)
1058 threads[i] = std::thread([
this,i,NUM_THREADS,&mutex,&caughtException,&caughtExceptionFlag]() {
1070 std::lock_guard<std::mutex> guard(mutex);
1085 std::lock_guard<std::mutex> guard(mutex);
1093 std::lock_guard<std::mutex> guard(mutex);
1094 caughtException = e;
1095 caughtExceptionFlag =
true;
1101 for(
auto& t : threads)
1104 if(caughtExceptionFlag)
1105 throw caughtException;
void setEnvironment(const std::string &name, const std::string &value)
virtual const char * what() const noexcept
uint64_t m_defaultMemoryLimit
bool shouldSkip(TiXmlElement *e)
void parseNode(TiXmlElement *element, ParseContext ctx)
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)
ParseContext enterScope(const std::string &prefix)
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 evaluateParameters()
void parseEnv(TiXmlElement *element, ParseContext &ctx)
const std::string & prefix() const
void parseROSParam(TiXmlElement *element, ParseContext ctx)
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
std::map< std::string, std::string > m_anonNames
std::string anonName(const std::string &base)
void parseArgument(TiXmlElement *element, ParseContext &ctx)
std::map< std::string, std::string > m_args
ROSLIB_DECL std::string command(const std::string &cmd)
double m_defaultStopTimeout
std::map< std::string, std::string > m_environment
std::string evaluate(const std::string &tpl, bool simplifyWhitespace=true)
void parse(const std::string &filename, bool onlyArguments=false)
void setCurrentElement(TiXmlElement *e)
void parseInclude(TiXmlElement *element, ParseContext ctx)
static XmlRpc::XmlRpcValue autoXmlRpcValue(const std::string &fullValue)
void warning(const char *fmt, const Args &...args) const
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)
void parseParam(TiXmlElement *element, ParseContext ctx, ParamContext paramContext=PARAM_GENERAL)
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
std::string m_rosmonNodeName
void parseRemap(TiXmlElement *element, ParseContext &ctx)
bool parseBool(const std::string &value, int line)
std::tuple< uint64_t, bool > parseMemory(const std::string &memory)
std::map< std::string, std::string > m_remappings
void setDefaultStopTimeout(double timeout)
const std::map< std::string, std::string > & remappings()
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)
bool isOnlyWhitespace(const std::string &input)
Check if string is whitespace only (includes ' ')