27#include <SimoxUtility/algorithm/string.h>
36 const simox::meta::EnumNames<PythonApplicationManager::VenvType>
45 const std::string& prefix)
48 armarxPackageName, prefix +
"10_ArmarXPackageName",
"Name of the ArmarX package.");
50 prefix +
"11_ArmarXPythonPackagesDir",
51 "Directory where python packages are located (relative to the root of the "
55 prefix +
"20_PythonPackageName",
56 "Name of the Python package in the ArmarXPackage/python/ directory.");
58 prefix +
"21_PythonScriptPath",
59 "Path to the python script (inside the python package).");
61 prefix +
"22_PythonScriptArgs",
62 "Whitespace separated list of arguments.");
64 prefix +
"23_PythonPathEntries",
65 "Colon-separated list of paths to add to the PYTHONPATH.");
66 defs->optional(
pythonPoetry, prefix +
"24_PythonPoetry",
"Use python poetry.");
68 defs->optional(
venvName, prefix +
"30_venv.Name",
"Name of the virtual environment.");
71 ss <<
"Type of the virtual environment."
73 <<
" \tDerive automatically."
75 <<
"\tSearch inside the python package root directory."
77 <<
" \tSearch in the shared_envs directory.";
84 prefix +
"40_WorkingDirectory",
85 "If set, this path is used as working directory for the python script (overriding "
86 "BinaryPathAsWorkingDirectory)."
87 "\n${HOME} for env vars, $C{RobotAPI:BINARY_DIR} for CMakePackageFinder vars");
95 if (props.
getProperty<std::string>(prefix +
"40_WorkingDirectory").isSet())
98 props.
getProperty<std::string>(
"WorkingDirectory").getValueAndReplaceAllVars();
112 properties.defineProperties(defs,
"py.");
114 defs->defineOptionalProperty<
bool>(
115 "BinaryPathAsWorkingDirectory",
117 "If true the path of the binary is set as the working directory.");
118 defs->defineOptionalProperty<
bool>(
119 "RestartInCaseOfCrash",
121 "Whether the application should be restarted in case it crashed.");
123 defs->defineOptionalProperty<
bool>(
124 "DisconnectInCaseOfCrash",
126 "Whether this component should disconnect as long as the application is not running.");
127 defs->defineOptionalProperty<
int>(
128 "FakeObjectStartDelay",
130 "Delay in ms after which the fake armarx object is started on which other apps can "
131 "depend. Not used if property FakeObjectDelayedStartKeyword is used.");
132 defs->defineOptionalProperty<std::string>(
133 "FakeObjectDelayedStartKeyword",
135 "If not empty, the start up of the fake armarx object will be delayed until this "
136 "keyword is found in the stdout of the subprocess.");
137 defs->defineOptionalProperty<
int>(
140 "Delay ins ms before the subprocess is killed after sending the stop signal");
141 defs->defineOptionalProperty<
bool>(
144 "If true, PYTHONUNBUFFERED=1 is added to the environment variables.");
145 defs->defineOptionalProperty<
bool>(
"RedirectToArmarXLog",
147 "If true, all outputs from the subprocess are printed "
148 "with ARMARX_LOG, otherwise with std::cout");
149 defs->defineOptionalProperty<Ice::StringSeq>(
152 "Comma-seperated list of env-var assignment, e.g. MYVAR=1,ADDPATH=/tmp");
153 defs->defineOptionalProperty<Ice::StringSeq>(
156 "Comma-seperated list of Ice Object dependencies. The external app will only be "
157 "started after all dependencies have been found.");
162 std::filesystem::path
168 std::filesystem::path armarxCoreRootPath = finder.
getPackageDir();
169 std::filesystem::path armarxCliPath = armarxCoreRootPath /
"etc" /
"python";
170 return armarxCliPath;
178 properties.read(*
this,
"py.");
179 paths.derive(properties);
183 std::stringstream originalPythonPath;
184 if (
char* pp = getenv(
"PYTHONPATH"))
186 originalPythonPath << pp;
188 std::vector<std::string> pythonPath = simox::alg::split(originalPythonPath.str(),
":");
190 std::stringstream message;
204 std::find(pythonPath.begin(), pythonPath.end(), armarxCliPath.string());
205 it != pythonPath.end())
207 message <<
"(Removed '" << *it <<
"' from PYTHONPATH.)";
208 pythonPath.erase(it);
211 pythonPath.push_back(this->paths.pythonPackagePath.string());
212 pythonPath.push_back(properties.pythonPathEntriesString);
214 const std::string newPythonpath = simox::alg::join(pythonPath,
":");
216 ARMARX_INFO <<
"Setting PYTHONPATH:\n\t" << newPythonpath
217 <<
"\nOriginal PYTHONPATH:\n\t" << originalPythonPath.str() <<
"\n"
220 setenv(
"PYTHONPATH", newPythonpath.c_str(), 1);
264 return paths.workingDir;
270 return paths.pythonBinPath;
277 args.push_back(paths.pythonScriptPath);
279 args.insert(args.end(),
280 properties.pythonScriptArgumentsVector.begin(),
281 properties.pythonScriptArgumentsVector.end());
287 os <<
"ArmarX package path: \t" << paths.armarxPackagePath;
288 os <<
"\nPython package path: \t" << paths.pythonPackagePath;
289 os <<
"\nPython script path: \t" << paths.pythonScriptPath;
290 os <<
"\nVenv path: \t" << paths.venvPath;
291 os <<
"\nPython binary path: \t" << paths.pythonBinPath;
292 os <<
"\nWorking directory: \t" << paths.workingDir;
301 namespace fs = std::filesystem;
307 if (properties.pythonPoetry)
309 std::string cmd = std::string(
"cd ") +
311 ";poetry env info -p --ansi";
313 std::array<char, 128> buffer;
317 std::unique_ptr<FILE, int(*)(FILE*)> pipe(popen(cmd.c_str(),
"r"), pclose);
320 throw std::runtime_error(
"popen() failed!");
322 while (fgets(buffer.data(), buffer.size(), pipe.get()) !=
nullptr)
324 std::string s = buffer.data();
325 s.erase(std::remove(s.begin(), s.end(),
'\n'), s.end());
335 if (properties.workingDir.empty())
352 std::stringstream ss;
353 ss <<
"Could not find ArmarX package '" << properties.armarxPackageName <<
"'.";
354 throw armarx::LocalException(ss.str());
364 properties.pythonPackageName;
372 properties.pythonScriptPath,
381 std::stringstream ss;
382 ss <<
"Could not find python script '" << properties.pythonScriptPath <<
"' "
383 <<
"in python package '" << properties.pythonPackageName <<
"' "
384 <<
"in ArmarX package '" << properties.armarxPackageName <<
"'. \n"
385 <<
"Checked candidates: ";
386 for (
const path& candidate : candidates)
388 ss <<
"\n" << candidate;
390 throw armarx::LocalException(ss.str());
401 switch (properties.venvType)
413 std::vector<path> candidates{
423 std::stringstream ss;
424 ss <<
"Could not find dedicated or shared venv '" << properties.venvName <<
"' "
425 <<
"in python package '" << properties.pythonPackageName <<
"' "
426 <<
"in ArmarX package '" << properties.armarxPackageName <<
"'. \n"
427 <<
"Checked candidates: ";
428 for (
const path& candidate : candidates)
430 ss <<
"\n" << candidate;
432 throw armarx::LocalException(ss.str());
438 if (std::filesystem::is_directory(
venvPath))
444 std::stringstream ss;
445 ss <<
"Could not find "
446 << simox::alg::to_lower(
VenvTypeNames.to_name(properties.venvType)) <<
" venv '"
447 << properties.venvName <<
"' ";
448 switch (properties.venvType)
453 ss <<
"in python package '" << properties.pythonPackageName <<
"' ";
458 ss <<
"in ArmarX package '" << properties.armarxPackageName <<
"'. \n";
459 ss <<
"Expected path: \n" <<
venvPath;
460 throw armarx::LocalException(ss.str());
478 std::stringstream ss;
479 ss <<
"Could not find python binary "
480 <<
"in " << simox::alg::to_lower(
VenvTypeNames.to_name(properties.venvType))
481 <<
" venv '" << properties.venvName <<
"' ";
482 switch (properties.venvType)
487 ss <<
"in python package '" << properties.pythonPackageName <<
"' ";
492 ss <<
"in ArmarX package '" << properties.armarxPackageName <<
"'. \n";
494 throw armarx::LocalException(ss.str());
512 std::optional<PythonApplicationManager::Paths::path>
514 const std::vector<PythonApplicationManager::Paths::path>& candidates)
517 candidates, [](
const path& p) {
return std::filesystem::is_regular_file(p); });
520 std::optional<PythonApplicationManager::Paths::path>
522 const std::vector<PythonApplicationManager::Paths::path>& candidates)
525 [](
const path& p) {
return std::filesystem::is_directory(p); });
528 std::optional<PythonApplicationManager::Paths::path>
530 const std::vector<PythonApplicationManager::Paths::path>& candidates,
533 auto it = std::find_if(candidates.begin(), candidates.end(), existsFn);
534 if (it != candidates.end())
The CMakePackageFinder class provides an interface to the CMake Package finder capabilities.
bool packageFound() const
Returns whether or not this package was found with cmake.
std::string getPackageDir() const
Returns the top level path of a source package.
Default component property definition container.
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
void onInitComponent() override
void onDisconnectComponent() override
void onConnectComponent() override
void onExitComponent() override
Abstract PropertyUser class.
Property< PropertyType > getProperty(const std::string &name)
Property creation and retrieval.
static const simox::meta::EnumNames< VenvType > VenvTypeNames
void onInitComponent() override
std::string deriveApplicationPath() const override
void onDisconnectComponent() override
std::string deriveWorkingDir() const override
void onConnectComponent() override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void onExitComponent() override
void addApplicationArguments(Ice::StringSeq &args) override
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_INFO
The normal logging level.
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::ostream & operator<<(std::ostream &os, const PythonApplicationManager::Paths &paths)
std::filesystem::path getArmarXCliPath()
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
static std::optional< path > checkCandidatePaths(const std::vector< path > &candidates, std::function< bool(path)> existsFn)
path findArmarXPackagePath(const Properties &properties) const
path getSharedVenvPath(const Properties &properties) const
static std::optional< path > checkCandidateDirectories(const std::vector< path > &candidates)
void derive(const Properties &properties)
std::filesystem::path path
path findVenvPath(const Properties &properties) const
path findPythonPackagePath(const Properties &properties) const
path findPythonBinaryPath(const Properties &properties) const
std::string sharedVenvsDir
static std::optional< path > checkCandidateFiles(const std::vector< path > &candidates)
path getDedicatedVenvPath(const Properties &properties) const
path findPythonScriptPath(const Properties &properties) const
std::string pythonPathEntriesString
void read(PropertyUser &props, const std::string &prefix="")
std::string armarxPythonPackagesDir
std::string pythonPackageName
std::string armarxPackageName
std::vector< std::string > pythonScriptArgumentsVector
std::vector< std::string > pythonPathEntriesVector
std::string pythonScriptPath
void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string &prefix="")
std::string pythonScriptArgumentsString
Whitespace separated list of arguments.