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
58extern char** environ;
59
60namespace armarx
61{
62 std::string
64 {
65 char* homePathC = getenv("HOME");
66 std::string homePath;
67 if (homePathC)
68 {
69 homePath = homePathC;
70 }
71 return homePath;
72 }
73
74 std::string
76 {
77 const char* user = getenv("USER");
78 std::string dir = fs::temp_directory_path().string() + "/ArmarXCMakeCache_" +
79 (user ? user : "DUMMY_USER");
80
81 // Processes of different workspaces must not share cached package locations. As
82 // ARMARX_WORKSPACE holds the path of the workspace, only its name is appended.
83 const char* workspace = getenv("ARMARX_WORKSPACE");
84 if (workspace)
85 {
86 const std::string workspaceName =
87 remove_trailing_separator(workspace).filename().string();
88 if (not workspaceName.empty())
89 {
90 dir += "_" + workspaceName;
91 }
92 }
93
94 return dir;
95 }
96
97 std::shared_ptr<boost::interprocess::file_lock>
98 getFileLock(std::string lockName, bool verbose = false)
99 {
100 std::string path = remove_trailing_separator(getCMakeCacheDir());
101 if (verbose)
102 {
103 std::cout << "getFileLock( path = " << path << ")" << std::endl;
104 }
105 if (!std::filesystem::exists(path))
106 {
107 if (verbose)
108 {
109 std::cout << "path does not exist" << std::endl;
110 }
111 if (!std::filesystem::create_directories(path))
112 {
113 if (verbose)
114 {
115 std::cout << "failed to create directories" << std::endl;
116 }
117 return std::shared_ptr<boost::interprocess::file_lock>();
118 }
119 }
120 path += "/" + lockName;
121 if (verbose)
122 {
123 std::cout << "lock file = " << path << std::endl;
124 }
125 if (!std::filesystem::exists(path))
126 {
127 if (verbose)
128 {
129 std::cout << "touch " << path << std::endl;
130 }
131 //touch file
132 std::ofstream file(path);
133 file.close();
134 }
135 if (verbose)
136 {
137 std::cout << "build lock with " << path << std::endl;
138 }
139 return std::shared_ptr<boost::interprocess::file_lock>(
140 new boost::interprocess::file_lock(path.c_str()));
141 }
142
143 std::shared_ptr<boost::interprocess::file_lock>
144 CreateAndCheckFileLock(const std::string& name)
145 {
147 std::shared_ptr<boost::interprocess::file_lock> lock(getFileLock(name, false));
148 if (!lock)
149 {
150 ARMARX_WARNING << "Failed to create file lock! retrying...";
151 lock = getFileLock(name, true);
152 ARMARX_CHECK_NOT_NULL(lock) << "Failed to get file lock '" + name + "'";
153 }
154 return lock;
155 }
156
157 const std::shared_ptr<boost::interprocess::file_lock>&
159 {
161 static auto lock = CreateAndCheckFileLock(".cachelock");
163 return lock;
164 }
165
166 const std::shared_ptr<boost::interprocess::file_lock>&
168 {
170 static auto lock = CreateAndCheckFileLock(".cmakelock");
172 return lock;
173 }
174
175 std::mutex&
177 {
178 static std::mutex mx;
179 return mx;
180 }
181
183 std::shared_ptr<boost::interprocess::scoped_lock<boost::interprocess::file_lock>>;
184
187 {
188 std::unique_lock lock(CMakeMutex());
189
190 ScopedFileLockPtr lockPtr(
191 new boost::interprocess::scoped_lock<boost::interprocess::file_lock>(
192 *CMakeFileLock(), boost::get_system_time() + boost::posix_time::milliseconds(50)));
193 while (!lockPtr->owns())
194 {
195 lockPtr->timed_lock(boost::get_system_time() + boost::posix_time::milliseconds(50));
196 }
197 return lockPtr;
198 }
199
200 bool
201 readCMakeCache(const std::string& packageName, std::string& packageContent)
202 {
203 auto start = IceUtil::Time::now();
204 std::string path = getCMakeCacheDir();
205 path += "/" + packageName;
206 if (!std::filesystem::exists(path))
207 {
208 return false;
209 }
210 else
211 {
212
213 const auto writeTime = std::filesystem::last_write_time(path);
214 const auto now = decltype(writeTime)::clock::now();
215 long age = std::chrono::duration_cast<std::chrono::seconds>(now - writeTime).count();
216 // ARMARX_INFO_S << VAROUT(packageName) << VAROUT(age) << VAROUT(std::asctime(std::localtime(&writeTime)));
217 boost::interprocess::sharable_lock<boost::interprocess::file_lock> e_lock(
218 *CacheFileLock());
219 packageContent = RapidXmlReader::ReadFileContents(path);
220 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
221 if (dura > 10000)
222 {
223 ARMARX_INFO_S << packageName << " from cache locked for " << dura;
224 }
225 if (age < 10)
226 {
227 return true;
228 }
229 else
230 {
231 return false;
232 }
233 }
234 }
235
236 bool
237 updateCMakeCache(const std::string& packageName, const std::string& packageContent)
238 {
239 auto start = IceUtil::Time::now();
240 std::string path = getCMakeCacheDir();
241 if (!std::filesystem::exists(path))
242 {
243 if (!std::filesystem::create_directories(path))
244 {
245 return false;
246 }
247 }
248 path = path + "/" + packageName;
249 boost::interprocess::scoped_lock<boost::interprocess::file_lock> e_lock(*CacheFileLock());
250 // std::string existingPackageContent = RapidXmlReader::ReadFileContents(path);
251 // if (existingPackageContent != packageContent)
252 {
253 std::ofstream file(path);
254 file << packageContent;
255 }
256 // ARMARX_IMPORTANT_S << "Updateing cmakecache " << VAROUT(packageName) << VAROUT(path);
257 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
258 if (dura > 10000)
259 {
260 ARMARX_INFO_S << packageName << " update cache locked for " << dura;
261 }
262 return true;
263 }
264
265 boost::interprocess::interprocess_upgradable_mutex* memoryMutex = nullptr;
266 std::shared_ptr<boost::interprocess::managed_shared_memory> sharedMemorySegment;
268
270 const std::filesystem::path& packagePath,
271 bool suppressStdErr,
272 bool usePackagePathOnlyAsHint) :
273 found(false), packageName(simox::alg::trim_copy(packageName))
274 {
275 if (this->packageName.empty())
276 {
277 ARMARX_WARNING << "CMakePackageFinder: Package name must not be empty";
278 }
279 static std::string scriptPath;
280 {
281
282 std::unique_lock lock(cmakePackageMutex);
283
284 if (scriptPath.empty())
285 {
286 // Somehow this call fails sometimes if many components are started seperatly
287 // So try on fail again with another path
288 auto start = IceUtil::Time::now();
289 auto list = FindPackageIncludePathList("ArmarXCore");
290 auto dura = (IceUtil::Time::now() - start);
291
292 if (dura.toMilliSeconds() > 10000)
293 {
294 ARMARX_INFO_S << "Cmakefinder for initial core search took long - Duration: "
295 << dura.toMilliSeconds();
296 }
297
298 if (!ArmarXDataPath::getAbsolutePath(SCRIPT_PATH, scriptPath, list, false))
299 {
300 ARMARX_WARNING_S
301 << "Finding FindPackageX.cmake failed - trying again with different path";
302
303 if (!ArmarXDataPath::getAbsolutePath(std::string("../source/") + SCRIPT_PATH,
304 scriptPath))
305 {
306 return;
307 }
308 }
309 }
310 }
311 // ARMARX_INFO_S << scriptPath;
312 int result;
313
314 auto start = IceUtil::Time::now();
315 std::string resultStr;
316 std::string tmpDir = getArmarXCMakeTempDir();
317 try
318 {
319 if (!packagePath.empty())
320 {
321 resultStr = ExecCommand("cd " + tmpDir + "; cmake -DPACKAGE=" + this->packageName +
322 " -DPACKAGEBUILDPATH" +
323 (usePackagePathOnlyAsHint ? "Hint" : "") + "=" +
324 packagePath.string() + " -P " + scriptPath,
325 result,
326 suppressStdErr);
327 }
328 else if (!readCMakeCache(this->packageName, resultStr))
329 {
330 resultStr = ExecCommand("cd " + tmpDir + "; cmake -DPACKAGE=" + this->packageName +
331 " -P " + scriptPath,
332 result,
333 suppressStdErr);
334 updateCMakeCache(this->packageName, resultStr);
335 }
336 }
337 catch (...)
338 {
339 }
340
341
342 auto dura = (IceUtil::Time::now() - start);
343
344 if (dura.toMilliSeconds() > 10000)
345 {
346 ARMARX_INFO_S << "Cmakefinder took long for package " << packagePath
347 << " - Duration: " << dura.toMilliSeconds();
348 }
349
350 std::vector<std::string> resultList;
351 resultList = extractVariables(resultStr);
352
353 // ARMARX_INFO_S << resultList;
354 }
355
356 std::vector<std::string>
358 {
359 // _CreateSharedMutex();
360
361 std::string output = FindPackageIncludePaths(packageName);
362 std::vector<std::string> result;
363 boost::split_regex(result, output, boost::regex("-I"));
364
365 for (size_t i = 0; i < result.size(); i++)
366 {
367 simox::alg::trim(result[i]);
368
369 if (result[i].empty())
370 {
371 result.erase(result.begin() + i);
372 i--;
373 }
374 }
375
376 return result;
377 }
378
379 std::string
381 {
382 // boost::interprocess::scoped_lock<boost::interprocess::file_lock> e_lock(*CMakeFileLock());
383 auto lock = lockCMake();
384 auto start = IceUtil::Time::now();
385 try
386 {
387
388 std::stringstream str;
389 str << "cmake --find-package -DNAME=" << packageName
390 << " -DLANGUAGE=CXX -DCOMPILER_ID=GNU -DMODE=LINK";
391 int result;
392 std::string output = ExecCommand(str.str(), result);
393
394 if (result != 0)
395 {
396 return "";
397 }
398
399
400 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
401 if (dura > 10)
402 {
403 // ARMARX_INFO_S << packageName << " locked for " << dura;
404 }
405 return output;
406 }
407 catch (...)
408 {
409 return "";
410 }
411 }
412
413 std::string
415 {
416 auto start = IceUtil::Time::now();
417 // std::cout << start.toDateTime() << " Waiting for lock;" << std::endl;
418 auto lock = lockCMake();
419 // auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
420 // if(dura > 1000)
421 // ARMARX_IMPORTANT_S << " waited for " << dura;
422 start = IceUtil::Time::now();
423 try
424 {
425 std::string tmpDir = getArmarXCMakeTempDir();
426 std::stringstream str;
427 str << "cd " + tmpDir + ";cmake --find-package -DNAME=" << packageName
428 << " -DLANGUAGE=CXX -DCOMPILER_ID=GNU -DMODE=COMPILE";
429 int result;
430 std::string output = ExecCommand(str.str(), result);
431
432 if (result != 0)
433 {
434 ARMARX_IMPORTANT_S << packageName << " search failed - output: " << output;
435 return "";
436 }
437
438
439 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
440 if (dura > 10)
441 {
442 // ARMARX_INFO_S << packageName << " locked for " << dura;
443 }
444 return output;
445 }
446 catch (...)
447 {
448
449
450 return "";
451 }
452 }
453
454 std::string
455 CMakePackageFinder::ExecCommand(std::string command, int& result, bool suppressStdErr)
456 {
457 auto start = IceUtil::Time::now();
458 if (suppressStdErr)
459 {
460 command += " 2>/dev/null";
461 }
462 ARMARX_DEBUG << "Cmd: " << command;
463 FILE* fp = popen(command.c_str(), "r");
464 char line[50];
465 std::string output;
466
467 while (fgets(line, sizeof line, fp))
468 {
469 output += line;
470 }
471
472 result = pclose(fp) / 256;
473 auto dura = (IceUtil::Time::now() - start).toMilliSecondsDouble();
474 if (dura > 1000)
475 {
476 ARMARX_INFO_S << "ExecCommand took " << dura << " \n command: " << command;
477 }
478 return output;
479 }
480
481 std::vector<std::string>
483 {
484 std::vector<std::string> result;
485 using namespace std::filesystem;
486
487 char** env;
488
489 env = environ;
490
491 for (; *env; ++env)
492 {
493 const std::string envVar(*env);
494 ARMARX_DEBUG << "Retrieved environment variable " << envVar;
495
496 const std::vector<std::string> elements = simox::alg::split(envVar, "=");
497 if (not(elements.size() == 2))
498 {
499 continue;
500 }
501
502 const std::string& envVarName = elements.front();
503 const std::string& envVarValue = elements.back();
504
505 if (simox::alg::ends_with(envVarName, "_DIR"))
506 {
507 const std::string packageName = simox::alg::remove_suffix(envVarName, "_DIR");
508 auto pckFinder = CMakePackageFinder{packageName, envVarValue, true};
509
510 ARMARX_DEBUG << "Package name: " << packageName;
511 ARMARX_DEBUG << "Path: " << envVarValue;
512
513 // check if this is really a package
514 if (pckFinder.packageFound() && !pckFinder.getBuildDir().empty())
515 {
516 ARMARX_DEBUG << "Found package " << packageName;
517 result.push_back(packageName);
518 }
519 }
520 }
521
522 return result;
523 }
524
525 std::string
527 {
528 return packageName;
529 }
530
531 bool
533 {
534 return found;
535 }
536
537 const std::map<std::string, std::string>&
539 {
540 return vars;
541 }
542
543 std::string
544 CMakePackageFinder::getVar(const std::string& varName) const
545 {
546 std::map<std::string, std::string>::const_iterator it = vars.find(varName);
547
548 if (it != vars.end())
549 {
550 return it->second;
551 }
552
553 return "";
554 }
555
556 std::vector<std::string>
558 {
559 auto depListString = getVar("DEPENDENCIES");
560 std::vector<std::string> resultList = armarx::Split(depListString, ";", true, true);
561
562 return resultList;
563 }
564
565 std::map<std::string, std::string>
567 {
568 // is of type "package1:package1Path;package2:packagePath2"
569 auto depListString = getVar("PACKAGE_DEPENDENCY_PATHS");
570 std::vector<std::string> resultList = simox::alg::split(depListString, ";");
571 std::map<std::string, std::string> resultMap;
572
573 for (auto& depPairString : resultList)
574 {
575 std::vector<std::string> depPair = simox::alg::split(depPairString, ":");
576
577 if (depPair.size() < 2)
578 {
579 continue;
580 }
581
582 resultMap[depPair.at(0)] = depPair.at(1);
583 }
584
585 return resultMap;
586 }
587
588 bool
589 CMakePackageFinder::_ParseString(const std::string& input,
590 std::string& varName,
591 std::string& content)
592 {
593 // ARMARX_INFO_S << "input: " << input;
594 const boost::regex e("\\-\\- ([a-zA-Z0-9_]+):(.+)");
595 boost::match_results<std::string::const_iterator> what;
596
597 bool found = boost::regex_search(input, what, e);
598
599 for (size_t i = 1; i < what.size(); i++)
600 {
601
602 if (i == 1)
603 {
604 varName = what[i];
605 }
606 else if (i == 2)
607 {
608 content = what[i];
609 }
610
611 // ARMARX_INFO_S << VAROUT(varName);
612 }
613
614 simox::alg::trim(varName);
615 simox::alg::trim(content);
616 return found;
617 }
618
619 void
623
624 std::vector<std::string>
625 CMakePackageFinder::extractVariables(const std::string& cmakeVarString)
626 {
627 std::vector<std::string> resultList = simox::alg::split(cmakeVarString, "\n");
628
629 for (size_t i = 0; i < resultList.size(); i++)
630 {
631 simox::alg::trim(resultList[i]);
632
633 if (resultList[i].empty())
634 {
635 resultList.erase(resultList.begin() + i);
636 i--;
637 }
638 else
639 {
640 std::string var;
641 std::string content;
642
643 if (_ParseString(resultList[i], var, content))
644 {
645 found = true;
646 vars[var] = content;
647 }
648 }
649 }
650
651 return resultList;
652 }
653
654 std::vector<std::string>
656 {
657 auto depListString = getIncludePaths();
658 std::vector<std::string> resultList = simox::alg::split(depListString, ";");
659 return resultList;
660 }
661
662 std::string
664 {
665 const std::string tmpDir = "/tmp";
666 std::string result = tmpDir;
667 char* username = getenv("USER");
668 if (username)
669 {
670 std::string usernameString = std::string(username);
671 simox::alg::trim(usernameString);
672 result += "/armarxcmake-" + usernameString;
673 if (!std::filesystem::exists(result))
674 {
675 if (!std::filesystem::create_directories(result))
676 {
677 result = tmpDir;
678 }
679 }
680 }
681 return result;
682 }
683
684 bool
686 {
687 const boost::regex e("\\$C\\{([a-zA-Z0-9_\\-]+):([a-zA-Z0-9_\\-]+)\\}");
688 boost::match_results<std::string::const_iterator> what;
689 bool found = boost::regex_search(string, what, e);
690 std::map<std::string, CMakePackageFinder> finders;
691 if (found)
692 {
693 for (size_t i = 1; i < what.size(); i += 3)
694 {
695 std::string package = what[i];
696 auto it = finders.find(package);
697 if (it == finders.end())
698 {
699 it =
700 finders
701 .insert(std::make_pair(
702 package, CMakePackageFinderCache::GlobalCache.findPackage(package)))
703 .first;
704 // it = finders.find(package);
705 }
706 std::string var = what[i + 1];
707
708
709 auto envVar = it->second.getVar(var);
710 string = boost::regex_replace(string, e, std::string(envVar));
711 ARMARX_DEBUG << "Replacing '" << var << "' with '" << std::string(envVar) << "'";
712 }
713 }
714 return found;
715 }
716
717 std::string
719 {
720 return getVar("EXECUTABLE");
721 }
722
723 std::vector<std::string>
725 {
726 namespace fs = std::filesystem;
727
728 const fs::path componentReportFilename =
729 fs::path(getBuildDir()) / "component_executables_report.txt";
730 if (fs::exists(componentReportFilename))
731 {
732 std::ifstream componentReportFile(componentReportFilename);
733 if (componentReportFile.bad())
734 {
735 ARMARX_WARNING << "Could not load file: " << componentReportFilename;
736 return {};
737 }
738
739 const std::string content(std::istreambuf_iterator<char>{componentReportFile}, {});
740 return simox::alg::split(content, "\n");
741 }
742
743 // TODO legacy mode. Remove after full migration
744 if (vars.count("EXECUTABLE") > 0)
745 {
746 return simox::alg::split(getVar("EXECUTABLE"), " ");
747 }
748
749 ARMARX_WARNING << "No component executables available. Check if `" << packageName
750 << "/build/component_executables_report.txt` is generated properly and "
751 "EXECUTABLE variable (legacy).";
752 return {};
753 }
754} // namespace armarx
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:200
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_IMPORTANT_S
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:208
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
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
std::string getCMakeCacheDir()
Definition Impl.cpp:41
#define ARMARX_TRACE
Definition trace.h:75