CMakePackageFinder.cpp
Go to the documentation of this file.
1/*
2* This file is part of ArmarX.
3*
4* Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5*
6* ArmarX is free software; you can redistribute it and/or modify
7* it under the terms of the GNU General Public License version 2 as
8* published by the Free Software Foundation.
9*
10* ArmarX is distributed in the hope that it will be useful, but
11* WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17*
18* @package ArmarX::
19* @author Mirko Waechter ( mirko.waechter at kit dot edu)
20* @date 2014
21* @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22* GNU General Public License
23*/
24
25#include "CMakePackageFinder.h"
26
27#include <cstdlib>
28#include <ctime>
29#include <filesystem>
30#include <memory>
31#include <mutex>
32#include <sstream>
33
34#include <boost/algorithm/string/regex.hpp>
35#include <boost/interprocess/managed_shared_memory.hpp>
36#include <boost/interprocess/sync/file_lock.hpp>
37#include <boost/interprocess/sync/interprocess_condition.hpp>
38#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
39#include <boost/interprocess/sync/scoped_lock.hpp>
40#include <boost/interprocess/sync/sharable_lock.hpp>
41#include <boost/regex.hpp>
42#include <boost/thread/thread_time.hpp>
43
44#include <SimoxUtility/algorithm/string/string_tools.h>
45
52
55
56#define SCRIPT_PATH "ArmarXCore/core/system/cmake/FindPackageX.cmake"
57#define CACHE_PATH "/ArmarXCMakeCache_" + (getenv("USER") ? getenv("USER") : "DUMMY_USER")
58
59extern char** environ;
60
61namespace armarx
62{
63 std::string
65 {
66 char* homePathC = getenv("HOME");
67 std::string homePath;
68 if (homePathC)
69 {
70 homePath = homePathC;
71 }
72 return homePath;
73 }
74
75 std::shared_ptr<boost::interprocess::file_lock>
76 getFileLock(std::string lockName, bool verbose = false)
77 {
78 std::string path =
79 remove_trailing_separator(fs::temp_directory_path().string() + CACHE_PATH);
80 if (verbose)
81 {
82 std::cout << "getFileLock( path = " << path << ")" << std::endl;
83 }
84 if (!std::filesystem::exists(path))
85 {
86 if (verbose)
87 {
88 std::cout << "path does not exist" << std::endl;
89 }
90 if (!std::filesystem::create_directories(path))
91 {
92 if (verbose)
93 {
94 std::cout << "failed to create directories" << std::endl;
95 }
96 return std::shared_ptr<boost::interprocess::file_lock>();
97 }
98 }
99 path += "/" + lockName;
100 if (verbose)
101 {
102 std::cout << "lock file = " << path << std::endl;
103 }
104 if (!std::filesystem::exists(path))
105 {
106 if (verbose)
107 {
108 std::cout << "touch " << path << std::endl;
109 }
110 //touch file
111 std::ofstream file(path);
112 file.close();
113 }
114 if (verbose)
115 {
116 std::cout << "build lock with " << path << std::endl;
117 }
118 return std::shared_ptr<boost::interprocess::file_lock>(
119 new boost::interprocess::file_lock(path.c_str()));
120 }
121
122 std::shared_ptr<boost::interprocess::file_lock>
123 CreateAndCheckFileLock(const std::string& name)
124 {
126 std::shared_ptr<boost::interprocess::file_lock> lock(getFileLock(name, false));
127 if (!lock)
128 {
129 ARMARX_WARNING << "Failed to create file lock! retrying...";
130 lock = getFileLock(name, true);
131 ARMARX_CHECK_NOT_NULL(lock) << "Failed to get file lock '" + name + "'";
132 }
133 return lock;
134 }
135
136 const std::shared_ptr<boost::interprocess::file_lock>&
138 {
140 static auto lock = CreateAndCheckFileLock(".cachelock");
142 return lock;
143 }
144
145 const std::shared_ptr<boost::interprocess::file_lock>&
147 {
149 static auto lock = CreateAndCheckFileLock(".cmakelock");
151 return lock;
152 }
153
154 std::mutex&
156 {
157 static std::mutex mx;
158 return mx;
159 }
160
162 std::shared_ptr<boost::interprocess::scoped_lock<boost::interprocess::file_lock>>;
163
166 {
167 std::unique_lock lock(CMakeMutex());
168
169 ScopedFileLockPtr lockPtr(
170 new boost::interprocess::scoped_lock<boost::interprocess::file_lock>(
171 *CMakeFileLock(), boost::get_system_time() + boost::posix_time::milliseconds(50)));
172 while (!lockPtr->owns())
173 {
174 lockPtr->timed_lock(boost::get_system_time() + boost::posix_time::milliseconds(50));
175 }
176 return lockPtr;
177 }
178
179 bool
180 readCMakeCache(const std::string& packageName, std::string& packageContent)
181 {
182 auto start = IceUtil::Time::now();
183 std::string path = std::filesystem::temp_directory_path().string() + CACHE_PATH;
184 path += "/" + packageName;
185 if (!std::filesystem::exists(path))
186 {
187 return false;
188 }
189 else
190 {
191
192 const auto writeTime = std::filesystem::last_write_time(path);
193 const auto now = decltype(writeTime)::clock::now();
194 long age = std::chrono::duration_cast<std::chrono::seconds>(now - writeTime).count();
195 // ARMARX_INFO_S << VAROUT(packageName) << VAROUT(age) << VAROUT(std::asctime(std::localtime(&writeTime)));
196 boost::interprocess::sharable_lock<boost::interprocess::file_lock> e_lock(
197 *CacheFileLock());
198 packageContent = RapidXmlReader::ReadFileContents(path);
199 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
200 if (dura > 10000)
201 {
202 ARMARX_INFO_S << packageName << " from cache locked for " << dura;
203 }
204 if (age < 10)
205 {
206 return true;
207 }
208 else
209 {
210 return false;
211 }
212 }
213 }
214
215 bool
216 updateCMakeCache(const std::string& packageName, const std::string& packageContent)
217 {
218 auto start = IceUtil::Time::now();
219 std::string path = std::filesystem::temp_directory_path().string() + CACHE_PATH;
220 if (!std::filesystem::exists(path))
221 {
222 if (!std::filesystem::create_directories(path))
223 {
224 return false;
225 }
226 }
227 path = path + "/" + packageName;
228 boost::interprocess::scoped_lock<boost::interprocess::file_lock> e_lock(*CacheFileLock());
229 // std::string existingPackageContent = RapidXmlReader::ReadFileContents(path);
230 // if (existingPackageContent != packageContent)
231 {
232 std::ofstream file(path);
233 file << packageContent;
234 }
235 // ARMARX_IMPORTANT_S << "Updateing cmakecache " << VAROUT(packageName) << VAROUT(path);
236 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
237 if (dura > 10000)
238 {
239 ARMARX_INFO_S << packageName << " update cache locked for " << dura;
240 }
241 return true;
242 }
243
244 boost::interprocess::interprocess_upgradable_mutex* memoryMutex = nullptr;
245 std::shared_ptr<boost::interprocess::managed_shared_memory> sharedMemorySegment;
247
249 const std::filesystem::path& packagePath,
250 bool suppressStdErr,
251 bool usePackagePathOnlyAsHint) :
252 found(false), packageName(simox::alg::trim_copy(packageName))
253 {
254 if (this->packageName.empty())
255 {
256 ARMARX_WARNING << "CMakePackageFinder: Package name must not be empty";
257 }
258 static std::string scriptPath;
259 {
260
261 std::unique_lock lock(cmakePackageMutex);
262
263 if (scriptPath.empty())
264 {
265 // Somehow this call fails sometimes if many components are started seperatly
266 // So try on fail again with another path
267 auto start = IceUtil::Time::now();
268 auto list = FindPackageIncludePathList("ArmarXCore");
269 auto dura = (IceUtil::Time::now() - start);
270
271 if (dura.toMilliSeconds() > 10000)
272 {
273 ARMARX_INFO_S << "Cmakefinder for initial core search took long - Duration: "
274 << dura.toMilliSeconds();
275 }
276
277 if (!ArmarXDataPath::getAbsolutePath(SCRIPT_PATH, scriptPath, list, false))
278 {
279 ARMARX_WARNING_S
280 << "Finding FindPackageX.cmake failed - trying again with different path";
281
282 if (!ArmarXDataPath::getAbsolutePath(std::string("../source/") + SCRIPT_PATH,
283 scriptPath))
284 {
285 return;
286 }
287 }
288 }
289 }
290 // ARMARX_INFO_S << scriptPath;
291 int result;
292
293 auto start = IceUtil::Time::now();
294 std::string resultStr;
295 std::string tmpDir = getArmarXCMakeTempDir();
296 try
297 {
298 if (!packagePath.empty())
299 {
300 resultStr = ExecCommand("cd " + tmpDir + "; cmake -DPACKAGE=" + this->packageName +
301 " -DPACKAGEBUILDPATH" +
302 (usePackagePathOnlyAsHint ? "Hint" : "") + "=" +
303 packagePath.string() + " -P " + scriptPath,
304 result,
305 suppressStdErr);
306 }
307 else if (!readCMakeCache(this->packageName, resultStr))
308 {
309 resultStr = ExecCommand("cd " + tmpDir + "; cmake -DPACKAGE=" + this->packageName +
310 " -P " + scriptPath,
311 result,
312 suppressStdErr);
313 updateCMakeCache(this->packageName, resultStr);
314 }
315 }
316 catch (...)
317 {
318 }
319
320
321 auto dura = (IceUtil::Time::now() - start);
322
323 if (dura.toMilliSeconds() > 10000)
324 {
325 ARMARX_INFO_S << "Cmakefinder took long for package " << packagePath
326 << " - Duration: " << dura.toMilliSeconds();
327 }
328
329 std::vector<std::string> resultList;
330 resultList = extractVariables(resultStr);
331
332 // ARMARX_INFO_S << resultList;
333 }
334
335 std::vector<std::string>
337 {
338 // _CreateSharedMutex();
339
340 std::string output = FindPackageIncludePaths(packageName);
341 std::vector<std::string> result;
342 boost::split_regex(result, output, boost::regex("-I"));
343
344 for (size_t i = 0; i < result.size(); i++)
345 {
346 simox::alg::trim(result[i]);
347
348 if (result[i].empty())
349 {
350 result.erase(result.begin() + i);
351 i--;
352 }
353 }
354
355 return result;
356 }
357
358 std::string
360 {
361 // boost::interprocess::scoped_lock<boost::interprocess::file_lock> e_lock(*CMakeFileLock());
362 auto lock = lockCMake();
363 auto start = IceUtil::Time::now();
364 try
365 {
366
367 std::stringstream str;
368 str << "cmake --find-package -DNAME=" << packageName
369 << " -DLANGUAGE=CXX -DCOMPILER_ID=GNU -DMODE=LINK";
370 int result;
371 std::string output = ExecCommand(str.str(), result);
372
373 if (result != 0)
374 {
375 return "";
376 }
377
378
379 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
380 if (dura > 10)
381 {
382 // ARMARX_INFO_S << packageName << " locked for " << dura;
383 }
384 return output;
385 }
386 catch (...)
387 {
388 return "";
389 }
390 }
391
392 std::string
394 {
395 auto start = IceUtil::Time::now();
396 // std::cout << start.toDateTime() << " Waiting for lock;" << std::endl;
397 auto lock = lockCMake();
398 // auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
399 // if(dura > 1000)
400 // ARMARX_IMPORTANT_S << " waited for " << dura;
401 start = IceUtil::Time::now();
402 try
403 {
404 std::string tmpDir = getArmarXCMakeTempDir();
405 std::stringstream str;
406 str << "cd " + tmpDir + ";cmake --find-package -DNAME=" << packageName
407 << " -DLANGUAGE=CXX -DCOMPILER_ID=GNU -DMODE=COMPILE";
408 int result;
409 std::string output = ExecCommand(str.str(), result);
410
411 if (result != 0)
412 {
413 ARMARX_IMPORTANT_S << packageName << " search failed - output: " << output;
414 return "";
415 }
416
417
418 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
419 if (dura > 10)
420 {
421 // ARMARX_INFO_S << packageName << " locked for " << dura;
422 }
423 return output;
424 }
425 catch (...)
426 {
427
428
429 return "";
430 }
431 }
432
433 std::string
434 CMakePackageFinder::ExecCommand(std::string command, int& result, bool suppressStdErr)
435 {
436 auto start = IceUtil::Time::now();
437 if (suppressStdErr)
438 {
439 command += " 2>/dev/null";
440 }
441 ARMARX_DEBUG << "Cmd: " << command;
442 FILE* fp = popen(command.c_str(), "r");
443 char line[50];
444 std::string output;
445
446 while (fgets(line, sizeof line, fp))
447 {
448 output += line;
449 }
450
451 result = pclose(fp) / 256;
452 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
453 if (dura > 1000)
454 {
455 ARMARX_INFO_S << "ExecCommand took " << dura << " \n command: " << command;
456 }
457 return output;
458 }
459
460 std::vector<std::string>
462 {
463 std::vector<std::string> result;
464 using namespace std::filesystem;
465
466 char** env;
467
468 env = environ;
469
470 for (; *env; ++env)
471 {
472 const std::string envVar(*env);
473 ARMARX_DEBUG << "Retrieved environment variable " << envVar;
474
475 const std::vector<std::string> elements = simox::alg::split(envVar, "=");
476 if (not(elements.size() == 2))
477 {
478 continue;
479 }
480
481 const std::string& envVarName = elements.front();
482 const std::string& envVarValue = elements.back();
483
484 if (simox::alg::ends_with(envVarName, "_DIR"))
485 {
486 const std::string packageName = simox::alg::remove_suffix(envVarName, "_DIR");
487 auto pckFinder = CMakePackageFinder{packageName, envVarValue, true};
488
489 ARMARX_DEBUG << "Package name: " << packageName;
490 ARMARX_DEBUG << "Path: " << envVarValue;
491
492 // check if this is really a package
493 if (pckFinder.packageFound() && !pckFinder.getBuildDir().empty())
494 {
495 ARMARX_DEBUG << "Found package " << packageName;
496 result.push_back(packageName);
497 }
498 }
499 }
500
501 return result;
502 }
503
504 std::string
506 {
507 return packageName;
508 }
509
510 bool
512 {
513 return found;
514 }
515
516 const std::map<std::string, std::string>&
518 {
519 return vars;
520 }
521
522 std::string
523 CMakePackageFinder::getVar(const std::string& varName) const
524 {
525 std::map<std::string, std::string>::const_iterator it = vars.find(varName);
526
527 if (it != vars.end())
528 {
529 return it->second;
530 }
531
532 return "";
533 }
534
535 std::vector<std::string>
537 {
538 auto depListString = getVar("DEPENDENCIES");
539 std::vector<std::string> resultList = armarx::Split(depListString, ";", true, true);
540
541 return resultList;
542 }
543
544 std::map<std::string, std::string>
546 {
547 // is of type "package1:package1Path;package2:packagePath2"
548 auto depListString = getVar("PACKAGE_DEPENDENCY_PATHS");
549 std::vector<std::string> resultList = simox::alg::split(depListString, ";");
550 std::map<std::string, std::string> resultMap;
551
552 for (auto& depPairString : resultList)
553 {
554 std::vector<std::string> depPair = simox::alg::split(depPairString, ":");
555
556 if (depPair.size() < 2)
557 {
558 continue;
559 }
560
561 resultMap[depPair.at(0)] = depPair.at(1);
562 }
563
564 return resultMap;
565 }
566
567 bool
568 CMakePackageFinder::_ParseString(const std::string& input,
569 std::string& varName,
570 std::string& content)
571 {
572 // ARMARX_INFO_S << "input: " << input;
573 const boost::regex e("\\-\\- ([a-zA-Z0-9_]+):(.+)");
574 boost::match_results<std::string::const_iterator> what;
575
576 bool found = boost::regex_search(input, what, e);
577
578 for (size_t i = 1; i < what.size(); i++)
579 {
580
581 if (i == 1)
582 {
583 varName = what[i];
584 }
585 else if (i == 2)
586 {
587 content = what[i];
588 }
589
590 // ARMARX_INFO_S << VAROUT(varName);
591 }
592
593 simox::alg::trim(varName);
594 simox::alg::trim(content);
595 return found;
596 }
597
598 void
602
603 std::vector<std::string>
604 CMakePackageFinder::extractVariables(const std::string& cmakeVarString)
605 {
606 std::vector<std::string> resultList = simox::alg::split(cmakeVarString, "\n");
607
608 for (size_t i = 0; i < resultList.size(); i++)
609 {
610 simox::alg::trim(resultList[i]);
611
612 if (resultList[i].empty())
613 {
614 resultList.erase(resultList.begin() + i);
615 i--;
616 }
617 else
618 {
619 std::string var;
620 std::string content;
621
622 if (_ParseString(resultList[i], var, content))
623 {
624 found = true;
625 vars[var] = content;
626 }
627 }
628 }
629
630 return resultList;
631 }
632
633 std::vector<std::string>
635 {
636 auto depListString = getIncludePaths();
637 std::vector<std::string> resultList = simox::alg::split(depListString, ";");
638 return resultList;
639 }
640
641 std::string
643 {
644 const std::string tmpDir = "/tmp";
645 std::string result = tmpDir;
646 char* username = getenv("USER");
647 if (username)
648 {
649 std::string usernameString = std::string(username);
650 simox::alg::trim(usernameString);
651 result += "/armarxcmake-" + usernameString;
652 if (!std::filesystem::exists(result))
653 {
654 if (!std::filesystem::create_directories(result))
655 {
656 result = tmpDir;
657 }
658 }
659 }
660 return result;
661 }
662
663 bool
665 {
666 const boost::regex e("\\$C\\{([a-zA-Z0-9_\\-]+):([a-zA-Z0-9_\\-]+)\\}");
667 boost::match_results<std::string::const_iterator> what;
668 bool found = boost::regex_search(string, what, e);
669 std::map<std::string, CMakePackageFinder> finders;
670 if (found)
671 {
672 for (size_t i = 1; i < what.size(); i += 3)
673 {
674 std::string package = what[i];
675 auto it = finders.find(package);
676 if (it == finders.end())
677 {
678 it =
679 finders
680 .insert(std::make_pair(
681 package, CMakePackageFinderCache::GlobalCache.findPackage(package)))
682 .first;
683 // it = finders.find(package);
684 }
685 std::string var = what[i + 1];
686
687
688 auto envVar = it->second.getVar(var);
689 string = boost::regex_replace(string, e, std::string(envVar));
690 ARMARX_DEBUG << "Replacing '" << var << "' with '" << std::string(envVar) << "'";
691 }
692 }
693 return found;
694 }
695
696 std::string
698 {
699 return getVar("EXECUTABLE");
700 }
701
702 std::vector<std::string>
704 {
705 namespace fs = std::filesystem;
706
707 const fs::path componentReportFilename =
708 fs::path(getBuildDir()) / "component_executables_report.txt";
709 if (fs::exists(componentReportFilename))
710 {
711 std::ifstream componentReportFile(componentReportFilename);
712 if (componentReportFile.bad())
713 {
714 ARMARX_WARNING << "Could not load file: " << componentReportFilename;
715 return {};
716 }
717
718 const std::string content(std::istreambuf_iterator<char>{componentReportFile}, {});
719 return simox::alg::split(content, "\n");
720 }
721
722 // TODO legacy mode. Remove after full migration
723 if (vars.count("EXECUTABLE") > 0)
724 {
725 return simox::alg::split(getVar("EXECUTABLE"), " ");
726 }
727
728 ARMARX_WARNING << "No component executables available. Check if `" << packageName
729 << "/build/component_executables_report.txt` is generated properly and "
730 "EXECUTABLE variable (legacy).";
731 return {};
732 }
733} // namespace armarx
#define CACHE_PATH
char ** environ
std::string str(const T &t)
static CMakePackageFinderCache GlobalCache
static std::vector< std::string > FindPackageIncludePathList(const std::string &packageName)
Static function to find the include path with cmake.
std::vector< std::string > getDependencies() const
std::vector< std::string > getComponentExecutableNames() const
const std::map< std::string, std::string > & getVars() const
static std::string getArmarXCMakeTempDir()
return the path where the temporary cmake files are stored that are automically created by cmake.
std::map< std::string, std::string > getDependencyPaths() const
static std::string FindPackageLibs(const std::string &packageName)
std::string getVar(const std::string &varName) const
Returns the content of a CMake variable.
static std::string ExecCommand(std::string command, int &result, bool suppressStdErr=false)
std::string getName() const
Returns the name of the given package.
CMakePackageFinder(const std::string &packageName, const std::filesystem::path &packagePath="", bool suppressStdErr=false, bool usePackagePathOnlyAsHint=false)
The package with name packageName is searched with cmake during construction of this class.
std::string getBuildDir() const
std::vector< std::string > getIncludePathList() const
Return the include paths in a vector.
bool packageFound() const
Returns whether or not this package was found with cmake.
std::map< std::string, std::string > vars
static bool _ParseString(const std::string &input, std::string &varName, std::string &content)
static bool ReplaceCMakePackageFinderVars(std::string &string)
Replaces occurrences like $C{PACKAGE_NAME:VAR_NAME} with their CMakePackageFinder value.
std::vector< std::string > extractVariables(const std::string &cmakeVarString)
static std::string FindPackageIncludePaths(const std::string &packageName)
static std::vector< std::string > FindAllArmarXSourcePackages()
std::string getIncludePaths() const
Returns the include paths separated by semi-colons.
static std::string ReadFileContents(const std::string &path)
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO_S
Definition Logging.h:202
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_IMPORTANT_S
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:210
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
This file offers overloads of toIce() and fromIce() functions for STL container types.
fs::path remove_trailing_separator(fs::path p)
Definition filesystem.h:34
std::shared_ptr< boost::interprocess::file_lock > getFileLock(std::string lockName, bool verbose=false)
std::vector< std::string > Split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
const std::shared_ptr< boost::interprocess::file_lock > & CacheFileLock()
std::mutex & CMakeMutex()
std::mutex cmakePackageMutex
ScopedFileLockPtr lockCMake()
std::shared_ptr< boost::interprocess::file_lock > CreateAndCheckFileLock(const std::string &name)
std::shared_ptr< boost::interprocess::scoped_lock< boost::interprocess::file_lock > > ScopedFileLockPtr
std::shared_ptr< boost::interprocess::managed_shared_memory > sharedMemorySegment
const std::shared_ptr< boost::interprocess::file_lock > & CMakeFileLock()
std::string getHomeDir()
bool readCMakeCache(const std::string &packageName, std::string &packageContent)
bool updateCMakeCache(const std::string &packageName, const std::string &packageContent)
boost::interprocess::interprocess_upgradable_mutex * memoryMutex
Definition Impl.cpp:41
#define ARMARX_TRACE
Definition trace.h:77