33#include <boost/regex.hpp>
35#include <IceUtil/UUID.h>
37#include <SimoxUtility/algorithm/string/string_tools.h>
57 "Either the name of the application if its directory is part of the PATH environment "
58 "variable of the system or it is located in an ArmarX-package, or the relative or "
59 "absolute path to the application.")
60 .setCaseInsensitive(
false);
63 "Comma separated list of command line parameters. "
64 "Commas in double-quoted strings are ignored.");
68 "Set this property if the application is located in an ArmarX-package.");
72 "If set, this path is used as working directory for the external app. This overrides "
73 "BinaryPathAsWorkingDirectory. ${HOME} for env vars, $C{RobotAPI:BINARY_DIR} for "
74 "CMakePackageFinder vars");
77 "BinaryPathAsWorkingDirectory",
79 "If true the path of the binary is set as the working directory.");
81 "RestartInCaseOfCrash",
83 "Whether the application should be restarted in case it crashed.");
86 "DisconnectInCaseOfCrash",
88 "Whether this component should disconnect as long as the application is not running.");
90 "FakeObjectStartDelay",
92 "Delay in ms after which the fake armarx object is started on which other apps can "
93 "depend. Not used if property FakeObjectDelayedStartKeyword is used.");
95 "FakeObjectDelayedStartKeyword",
97 "If not empty, the start up of the fake armarx object will be delayed until this "
98 "keyword is found in the stdout of the subprocess.");
102 "Delay ins ms before the subprocess is killed after sending the stop signal");
106 "If true, PYTHONUNBUFFERED=1 is added to the environment variables.");
109 "If true, all outputs from the subprocess are printed with "
110 "ARMARX_LOG, otherwise with std::cout");
114 "Comma-seperated list of env-var assignment, e.g. MYVAR=1,ADDPATH=/tmp");
118 "Comma-seperated list of Ice Object dependencies. The external app will only be "
119 "started after all dependencies have been found.");
127 const boost::regex re(
",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)");
129 boost::match_results<std::string::const_iterator> what;
130 boost::match_flag_type flags = boost::match_default;
131 std::string::const_iterator s = argsStr.begin();
132 std::string::const_iterator e = argsStr.end();
134 while (boost::regex_search(s, e, what, re, flags))
136 int pos = what.position();
137 auto arg = argsStr.substr(begin, pos);
142 std::string::difference_type l = what.length();
143 std::string::difference_type p = what.position();
147 std::string lastArg = argsStr.substr(begin);
148 if (!lastArg.empty())
150 args.push_back(lastArg);
157 getProperty(redirectToArmarXLog,
"RedirectToArmarXLog");
158 getProperty(restartWhenCrash,
"RestartInCaseOfCrash");
159 getProperty(disconnectWhenCrash,
"DisconnectInCaseOfCrash");
161 updateLogSenderComponentName();
166 std::filesystem::path appPath(application);
167 ARMARX_INFO <<
"Application absolute path: " << appPath;
170 args.insert(args.begin(), application);
175 envVars.push_back(
"PYTHONUNBUFFERED=1");
181 for (
const auto& var : addEnvVrs)
186 envVars.push_back(envVarResolved);
190 for (
auto& var : envVars)
193 if (
split.size() >= 2)
196 setenv(
split.at(0).c_str(),
split.at(1).c_str(), 1);
200 getProperty(startUpKeyword,
"FakeObjectDelayedStartKeyword");
201 starterUUID =
"ExternalApplicationManagerStarter" + IceUtil::generateUUID();
202 depObjUUID =
"ExternalApplicationManagerDependency" + IceUtil::generateUUID();
235 ExternalApplicationManager::updateLogSenderComponentName()
256 std::string workingDir;
260 ? std::filesystem::path(application).parent_path().string()
261 : std::filesystem::current_path().string();
263 catch (std::exception& e)
265 ARMARX_ERROR <<
"caught exception in line " << __LINE__ <<
" what:\n" << e.what();
274 std::filesystem::current_path(workingDir);
276 catch (std::exception& e)
279 <<
" when setting the current working directory to '" << workingDir
280 <<
"'. except.what:\n"
284 ARMARX_INFO <<
"Using '" << workingDir <<
"' as working directory";
293 std::filesystem::path applicationPath =
296 if (applicationPath.is_absolute())
298 if (!std::filesystem::exists(applicationPath))
300 throw LocalException() << applicationPath <<
" does not exist.";
302 return applicationPath;
310 const std::filesystem::path path =
311 (finder.
getBinaryDir() / applicationPath).lexically_normal();
312 if (!std::filesystem::exists(path))
314 throw LocalException()
315 << path <<
" does not exist in ArmarX-package " << finder.
getName();
321 throw LocalException() << finder.
getName() <<
" is not an ArmarX-package";
326 std::string applicationPathStr = applicationPath.string();
330 applicationPath = applicationPathStr;
332 if (!std::filesystem::exists(applicationPath))
334 const std::string path = boost::process::search_path(applicationPath);
337 throw LocalException()
338 << applicationPath <<
" does not represent an executable.";
347 return applicationPath;
378 ExternalApplicationManager::waitForApplication()
384 wait_for_exit(*childProcess);
387 if (restartWhenCrash && !appStoppedOnPurpose)
394 isAppRunning =
false;
395 if (this->disconnectWhenCrash)
411 depObj->setParent(
this);
414 ARMARX_INFO <<
"Adding dummy app with name " << depObjUUID;
421 starter->setParent(
this);
428 ExternalApplicationManager::setupStream(StreamMetaData&
meta)
432 meta.read = [&,
this](
const boost::system::error_code& error, std::size_t length)
436 std::ostringstream ss;
437 ss << &
meta.input_buffer;
438 std::string s = ss.str();
440 std::filesystem::path appPath(application);
441 if (redirectToArmarXLog)
448 std::cout << s << std::flush;
451 if (!startUpKeyword.empty() && !depObj)
458 boost::asio::async_read_until(meta.pend, meta.input_buffer,
"\n", meta.read);
460 else if (error == boost::asio::error::not_found)
462 std::cout <<
"Did not receive ending character!" << std::endl;
465 boost::asio::async_read_until(meta.pend, meta.input_buffer,
"\n", meta.read);
466 auto run = [&]() { meta.io_service.run(); };
468 std::thread{run}.detach();
472 ExternalApplicationManager::startApplication()
474 appStoppedOnPurpose =
false;
476 Ice::StringSeq managedObjects = this->
getArmarXManager()->getManagedObjectNames();
477 bool dummyAlreadyAdded =
false;
478 for (std::string s : managedObjects)
482 dummyAlreadyAdded =
true;
486 outMetaData.reset(
new StreamMetaData());
487 errMetaData.reset(
new StreamMetaData());
490 outMetaData->sink = boost::iostreams::file_descriptor_sink(
491 outMetaData->pipe.sink, boost::iostreams::close_handle);
492 errMetaData->sink = boost::iostreams::file_descriptor_sink(
493 errMetaData->pipe.sink, boost::iostreams::close_handle);
502 ARMARX_INFO <<
"Commandline: " << simox::alg::join(args,
" ");
503 childProcess.reset(
new boost::process::child(boost::process::execute(
504 boost::process::initializers::set_args(args),
505 boost::process::initializers::start_in_dir(workingDir),
506 boost::process::initializers::bind_stdout(outMetaData->sink),
507 boost::process::initializers::bind_stderr(errMetaData->sink),
508 boost::process::initializers::inherit_env(),
510 boost::process::initializers::on_exec_setup(
511 [](boost::process::executor&) { ::prctl(PR_SET_PDEATHSIG, SIGKILL); }))));
516 setupStream(*outMetaData);
517 setupStream(*errMetaData);
522 waitTask =
new RunningTask<ExternalApplicationManager>(
523 this, &ExternalApplicationManager::waitForApplication);
527 if (!dummyAlreadyAdded && startUpKeyword.empty())
536 ExternalApplicationManager::stopApplication()
541 int processId = (*childProcess).pid;
542 ARMARX_INFO <<
"Stopping application with PID " << processId <<
" with SIGINT";
543 appStoppedOnPurpose =
true;
551 ::killpg(processId, SIGINT);
557 boost::process::terminate(*childProcess);
561 isAppRunning =
false;
566 ExternalApplicationManager::cleanUp()
572 errMetaData->io_service.stop();
573 outMetaData->io_service.stop();
578 ExternalApplicationManager::waitForProcessToFinish(
int pid,
int timeoutMS)
588 ::kill(pid, 0) != -1)
593 if (::kill(pid, 0) == -1)
603 ExternalApplicationManager::StreamMetaData::StreamMetaData() :
608 ExternalApplicationManager::StreamMetaData::StreamMetaData(
609 const ExternalApplicationManager::StreamMetaData&
data) :
static ApplicationPtr getInstance()
Retrieve shared pointer to the application object.
static void ResolveHomePath(std::string &path)
Resolves a path like ~/myfile.txt or $HOME/myfile.txt to /home/user/myfile.txt.
static bool ReplaceEnvVars(std::string &string)
ReplaceEnvVars replaces environment variables in a string with their values, if the env.
The CMakePackageFinder class provides an interface to the CMake Package finder capabilities.
std::string getBinaryDir() const
std::string getName() const
Returns the name of the given package.
bool packageFound() const
Returns whether or not this package was found with cmake.
static bool ReplaceCMakePackageFinderVars(std::string &string)
Replaces occurrences like $C{PACKAGE_NAME:VAR_NAME} with their CMakePackageFinder value.
ComponentPropertyDefinitions(std::string prefix, bool hasObjectNameParameter=true)
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Property< PropertyType > getProperty(const std::string &name)
ExternalApplicationManagerPropertyDefinitions(std::string prefix)
void onInitComponent() override
void terminateApplication(const Ice::Current &) override
virtual void addApplicationArguments(Ice::StringSeq &args)
void onDisconnectComponent() override
virtual std::string deriveWorkingDir() const
std::string getPathToApplication(const Ice::Current &) override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
bool isApplicationRunning(const Ice::Current &) override
friend struct ExternalApplicationManagerDependency
void onConnectComponent() override
void addDependencyObject()
void onExitComponent() override
void restartApplication(const Ice::Current &) override
friend struct ExternalApplicationManagerStarter
virtual std::string deriveApplicationPath() const
static void SetComponentName(const std::string &componentName)
void setTag(const LogTag &tag)
bool usingProxy(const std::string &name, const std::string &endpoints="")
Registers a proxy for retrieval after initialization and adds it to the dependency list.
std::string getName() const
Retrieve name of object.
ArmarXManagerPtr getArmarXManager() const
Returns the ArmarX manager used to add and remove components.
std::string prefix
Prefix of the properties such as namespace, domain, component name, etc.
PropertyDefinition< PropertyType > & defineOptionalProperty(const std::string &name, PropertyType defaultValue, const std::string &description="", PropertyDefinitionBase::PropertyConstness constness=PropertyDefinitionBase::eConstant)
PropertyDefinition< PropertyType > & defineRequiredProperty(const std::string &name, const std::string &description="", PropertyDefinitionBase::PropertyConstness constness=PropertyDefinitionBase::eConstant)
bool hasProperty(const std::string &name)
Property< PropertyType > getProperty(const std::string &name)
Property creation and retrieval.
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
static void MSSleep(int durationMS)
lock the calling thread for a given duration (like usleep(...) but using Timeserver time)
static std::string expand(const std::string &input)
Return the expanded form of the comma-separated assignment list without modifying the process environ...
#define ARMARX_INFO
The normal logging level.
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
#define ARMARX_LOG_S
This macro creates a new temporary instance which can then be used to log data using the << operator.
#define ARMARX_VERBOSE
The logging level for verbose information.
T getValue(nlohmann::json &userConfig, nlohmann::json &defaultConfig, const std::string &entryName)
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< std::string > split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
std::vector< std::string > Split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
bool Contains(const ContainerType &container, const ElementType &searchElement)
Vertex source(const detail::edge_base< Directed, Vertex > &e, const PCG &)
bool empty(const std::string &s)