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();
101 pythonScriptArgumentsVector =
103 pythonPathEntriesVector =
simox::alg::split(pythonPathEntriesString,
":",
true,
true);
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.");
183 std::stringstream originalPythonPath;
184 if (
char* pp = getenv(
"PYTHONPATH"))
186 originalPythonPath << pp;
188 std::vector<std::string> pythonPath =
simox::alg::split(originalPythonPath.str(),
":");
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());
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);
279 args.insert(args.end(),
290 os <<
"\nVenv path: \t" << paths.
venvPath;
292 os <<
"\nWorking directory: \t" << paths.
workingDir;
301 namespace fs = std::filesystem;
303 armarxPackagePath = findArmarXPackagePath(properties);
304 pythonPackagePath = findPythonPackagePath(properties);
305 pythonScriptPath = findPythonScriptPath(properties);
309 std::string cmd = std::string(
"cd ") +
310 reinterpret_cast<const char*
>(pythonPackagePath.u8string().c_str()) +
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());
331 venvPath = findVenvPath(properties);
333 pythonBinPath = findPythonBinaryPath(properties);
337 workingDir = pythonScriptPath.parent_path();
352 std::stringstream ss;
354 throw armarx::LocalException(ss.str());
375 if (std::optional<path> p = checkCandidateFiles(candidates))
381 std::stringstream ss;
382 ss <<
"Could not find python script '" << properties.
pythonScriptPath <<
"' "
385 <<
"Checked candidates: ";
386 for (
const path& candidate : candidates)
388 ss <<
"\n" << candidate;
390 throw armarx::LocalException(ss.str());
404 venvPath = getDedicatedVenvPath(properties);
408 venvPath = getSharedVenvPath(properties);
413 std::vector<path> candidates{
414 getDedicatedVenvPath(properties),
415 getSharedVenvPath(properties),
417 if (std::optional<path> p = checkCandidateDirectories(candidates))
419 venvPath = p.value();
423 std::stringstream ss;
424 ss <<
"Could not find dedicated or shared venv '" << properties.
venvName <<
"' "
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 "
459 ss <<
"Expected path: \n" << venvPath;
460 throw armarx::LocalException(ss.str());
470 const path pythonBinPath = venvPath /
"bin" /
"python";
472 if (std::filesystem::is_regular_file(pythonBinPath))
474 return pythonBinPath;
478 std::stringstream ss;
479 ss <<
"Could not find python binary "
481 <<
" venv '" << properties.
venvName <<
"' ";
493 ss <<
"Expected path: \n" << pythonBinPath;
494 throw armarx::LocalException(ss.str());
502 return pythonPackagePath / properties.
venvName;
512 std::optional<PythonApplicationManager::Paths::path>
514 const std::vector<PythonApplicationManager::Paths::path>& candidates)
516 return checkCandidatePaths(
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)
524 return checkCandidatePaths(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())