1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmOrderDirectories.h"
12 #include <cmext/algorithm>
14 #include "cmGeneratorTarget.h"
15 #include "cmGlobalGenerator.h"
16 #include "cmMessageType.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
22 Directory ordering computation.
23 - Useful to compute a safe runtime library path order
24 - Need runtime path for supporting INSTALL_RPATH_USE_LINK_PATH
25 - Need runtime path at link time to pickup transitive link dependencies
29 class cmOrderDirectoriesConstraint
32 cmOrderDirectoriesConstraint(cmOrderDirectories* od, std::string const& file)
34 , GlobalGenerator(od->GlobalGenerator)
36 this->FullPath = file;
38 if (file.rfind(".framework") != std::string::npos) {
39 static cmsys::RegularExpression splitFramework(
40 "^(.*)/(.*).framework/(.*)$");
41 if (splitFramework.find(file) &&
43 splitFramework.match(3).find(splitFramework.match(2)))) {
44 this->Directory = splitFramework.match(1);
46 std::string(file.begin() + this->Directory.size() + 1, file.end());
50 if (this->FileName.empty()) {
51 this->Directory = cmSystemTools::GetFilenamePath(file);
52 this->FileName = cmSystemTools::GetFilenameName(file);
55 virtual ~cmOrderDirectoriesConstraint() = default;
59 this->DirectoryIndex = this->OD->AddOriginalDirectory(this->Directory);
62 virtual void Report(std::ostream& e) = 0;
64 void FindConflicts(unsigned int index)
66 for (unsigned int i = 0; i < this->OD->OriginalDirectories.size(); ++i) {
67 // Check if this directory conflicts with the entry.
68 std::string const& dir = this->OD->OriginalDirectories[i];
69 if (!this->OD->IsSameDirectory(dir, this->Directory) &&
70 this->FindConflict(dir)) {
71 // The library will be found in this directory but this is not
72 // the directory named for it. Add an entry to make sure the
73 // desired directory comes before this one.
74 cmOrderDirectories::ConflictPair p(this->DirectoryIndex, index);
75 this->OD->ConflictGraph[i].push_back(p);
80 void FindImplicitConflicts(std::ostringstream& w)
83 for (std::string const& dir : this->OD->OriginalDirectories) {
84 // Check if this directory conflicts with the entry.
85 if (dir != this->Directory &&
86 cmSystemTools::GetRealPath(dir) !=
87 cmSystemTools::GetRealPath(this->Directory) &&
88 this->FindConflict(dir)) {
89 // The library will be found in this directory but it is
90 // supposed to be found in an implicit search directory.
95 w << " in " << this->Directory << " may be hidden by files in:\n";
97 w << " " << dir << "\n";
103 virtual bool FindConflict(std::string const& dir) = 0;
105 bool FileMayConflict(std::string const& dir, std::string const& name);
107 cmOrderDirectories* OD;
108 cmGlobalGenerator* GlobalGenerator;
110 // The location in which the item is supposed to be found.
111 std::string FullPath;
112 std::string Directory;
113 std::string FileName;
115 // The index assigned to the directory.
119 bool cmOrderDirectoriesConstraint::FileMayConflict(std::string const& dir,
120 std::string const& name)
122 // Check if the file exists on disk.
123 std::string file = cmStrCat(dir, '/', name);
124 if (cmSystemTools::FileExists(file, true)) {
125 // The file conflicts only if it is not the same as the original
126 // file due to a symlink or hardlink.
127 return !cmSystemTools::SameFile(this->FullPath, file);
130 // Check if the file will be built by cmake.
131 std::set<std::string> const& files =
132 (this->GlobalGenerator->GetDirectoryContent(dir, false));
133 auto fi = files.find(name);
134 return fi != files.end();
137 class cmOrderDirectoriesConstraintSOName : public cmOrderDirectoriesConstraint
140 cmOrderDirectoriesConstraintSOName(cmOrderDirectories* od,
141 std::string const& file,
143 : cmOrderDirectoriesConstraint(od, file)
144 , SOName(soname ? soname : "")
146 if (this->SOName.empty()) {
147 // Try to guess the soname.
149 if (cmSystemTools::GuessLibrarySOName(file, soguess)) {
150 this->SOName = soguess;
155 void Report(std::ostream& e) override
157 e << "runtime library [";
158 if (this->SOName.empty()) {
166 bool FindConflict(std::string const& dir) override;
169 // The soname of the shared library if it is known.
173 bool cmOrderDirectoriesConstraintSOName::FindConflict(std::string const& dir)
175 // Determine which type of check to do.
176 if (!this->SOName.empty()) {
177 // We have the library soname. Check if it will be found.
178 if (this->FileMayConflict(dir, this->SOName)) {
182 // We do not have the soname. Look for files in the directory
183 // that may conflict.
184 std::set<std::string> const& files =
185 (this->GlobalGenerator->GetDirectoryContent(dir, true));
187 // Get the set of files that might conflict. Since we do not
188 // know the soname just look at all files that start with the
189 // file name. Usually the soname starts with the library name.
190 std::string base = this->FileName;
191 auto first = files.lower_bound(base);
193 auto last = files.upper_bound(base);
201 class cmOrderDirectoriesConstraintLibrary : public cmOrderDirectoriesConstraint
204 cmOrderDirectoriesConstraintLibrary(cmOrderDirectories* od,
205 std::string const& file)
206 : cmOrderDirectoriesConstraint(od, file)
210 void Report(std::ostream& e) override
212 e << "link library [" << this->FileName << "]";
215 bool FindConflict(std::string const& dir) override;
218 bool cmOrderDirectoriesConstraintLibrary::FindConflict(std::string const& dir)
220 // We have the library file name. Check if it will be found.
221 if (this->FileMayConflict(dir, this->FileName)) {
225 // Now check if the file exists with other extensions the linker
227 if (!this->OD->LinkExtensions.empty() &&
228 this->OD->RemoveLibraryExtension.find(this->FileName)) {
229 std::string lib = this->OD->RemoveLibraryExtension.match(1);
230 std::string ext = this->OD->RemoveLibraryExtension.match(2);
231 for (std::string const& LinkExtension : this->OD->LinkExtensions) {
232 if (LinkExtension != ext) {
233 std::string fname = cmStrCat(lib, LinkExtension);
234 if (this->FileMayConflict(dir, fname)) {
243 cmOrderDirectories::cmOrderDirectories(cmGlobalGenerator* gg,
244 const cmGeneratorTarget* target,
247 this->GlobalGenerator = gg;
248 this->Target = target;
249 this->Purpose = purpose;
250 this->Computed = false;
253 cmOrderDirectories::~cmOrderDirectories() = default;
255 std::vector<std::string> const& cmOrderDirectories::GetOrderedDirectories()
257 if (!this->Computed) {
258 this->Computed = true;
259 this->CollectOriginalDirectories();
260 this->FindConflicts();
261 this->OrderDirectories();
263 return this->OrderedDirectories;
266 void cmOrderDirectories::AddRuntimeLibrary(std::string const& fullPath,
269 // Add the runtime library at most once.
270 if (this->EmmittedConstraintSOName.insert(fullPath).second) {
271 // Implicit link directories need special handling.
272 if (!this->ImplicitDirectories.empty()) {
273 std::string dir = cmSystemTools::GetFilenamePath(fullPath);
275 if (fullPath.rfind(".framework") != std::string::npos) {
276 static cmsys::RegularExpression splitFramework(
277 "^(.*)/(.*).framework/(.*)$");
278 if (splitFramework.find(fullPath) &&
279 (std::string::npos !=
280 splitFramework.match(3).find(splitFramework.match(2)))) {
281 dir = splitFramework.match(1);
285 if (this->IsImplicitDirectory(dir)) {
286 this->ImplicitDirEntries.push_back(
287 cm::make_unique<cmOrderDirectoriesConstraintSOName>(this, fullPath,
293 // Construct the runtime information entry for this library.
294 this->ConstraintEntries.push_back(
295 cm::make_unique<cmOrderDirectoriesConstraintSOName>(this, fullPath,
298 // This can happen if the same library is linked multiple times.
299 // In that case the runtime information check need be done only
300 // once anyway. For shared libs we could add a check in AddItem
301 // to not repeat them.
305 void cmOrderDirectories::AddLinkLibrary(std::string const& fullPath)
307 // Link extension info is required for library constraints.
308 assert(!this->LinkExtensions.empty());
310 // Add the link library at most once.
311 if (this->EmmittedConstraintLibrary.insert(fullPath).second) {
312 // Implicit link directories need special handling.
313 if (!this->ImplicitDirectories.empty()) {
314 std::string dir = cmSystemTools::GetFilenamePath(fullPath);
315 if (this->IsImplicitDirectory(dir)) {
316 this->ImplicitDirEntries.push_back(
317 cm::make_unique<cmOrderDirectoriesConstraintLibrary>(this,
323 // Construct the link library entry.
324 this->ConstraintEntries.push_back(
325 cm::make_unique<cmOrderDirectoriesConstraintLibrary>(this, fullPath));
329 void cmOrderDirectories::AddUserDirectories(
330 std::vector<std::string> const& extra)
332 cm::append(this->UserDirectories, extra);
335 void cmOrderDirectories::AddLanguageDirectories(
336 std::vector<std::string> const& dirs)
338 cm::append(this->LanguageDirectories, dirs);
341 void cmOrderDirectories::SetImplicitDirectories(
342 std::set<std::string> const& implicitDirs)
344 this->ImplicitDirectories.clear();
345 for (std::string const& implicitDir : implicitDirs) {
346 this->ImplicitDirectories.insert(this->GetRealPath(implicitDir));
350 bool cmOrderDirectories::IsImplicitDirectory(std::string const& dir)
352 std::string const& real = this->GetRealPath(dir);
353 return this->ImplicitDirectories.find(real) !=
354 this->ImplicitDirectories.end();
357 void cmOrderDirectories::SetLinkExtensionInfo(
358 std::vector<std::string> const& linkExtensions,
359 std::string const& removeExtRegex)
361 this->LinkExtensions = linkExtensions;
362 this->RemoveLibraryExtension.compile(removeExtRegex);
365 void cmOrderDirectories::CollectOriginalDirectories()
367 // Add user directories specified for inclusion. These should be
368 // indexed first so their original order is preserved as much as
369 // possible subject to the constraints.
370 this->AddOriginalDirectories(this->UserDirectories);
372 // Add directories containing constraints.
373 for (const auto& entry : this->ConstraintEntries) {
374 entry->AddDirectory();
377 // Add language runtime directories last.
378 this->AddOriginalDirectories(this->LanguageDirectories);
381 int cmOrderDirectories::AddOriginalDirectory(std::string const& dir)
383 // Add the runtime directory with a unique index.
384 auto i = this->DirectoryIndex.find(dir);
385 if (i == this->DirectoryIndex.end()) {
386 std::map<std::string, int>::value_type entry(
387 dir, static_cast<int>(this->OriginalDirectories.size()));
388 i = this->DirectoryIndex.insert(entry).first;
389 this->OriginalDirectories.push_back(dir);
395 void cmOrderDirectories::AddOriginalDirectories(
396 std::vector<std::string> const& dirs)
398 for (std::string const& dir : dirs) {
399 // We never explicitly specify implicit link directories.
400 if (this->IsImplicitDirectory(dir)) {
404 // Skip the empty string.
409 // Add this directory.
410 this->AddOriginalDirectory(dir);
414 struct cmOrderDirectoriesCompare
416 using ConflictPair = std::pair<int, int>;
418 // The conflict pair is unique based on just the directory
419 // (first). The second element is only used for displaying
420 // information about why the entry is present.
421 bool operator()(ConflictPair l, ConflictPair r)
423 return l.first == r.first;
427 void cmOrderDirectories::FindConflicts()
429 // Allocate the conflict graph.
430 this->ConflictGraph.resize(this->OriginalDirectories.size());
431 this->DirectoryVisited.resize(this->OriginalDirectories.size(), 0);
433 // Find directories conflicting with each entry.
434 for (unsigned int i = 0; i < this->ConstraintEntries.size(); ++i) {
435 this->ConstraintEntries[i]->FindConflicts(i);
438 // Clean up the conflict graph representation.
439 for (ConflictList& cl : this->ConflictGraph) {
440 // Sort the outgoing edges for each graph node so that the
441 // original order will be preserved as much as possible.
442 std::sort(cl.begin(), cl.end());
444 // Make the edge list unique so cycle detection will be reliable.
445 auto last = std::unique(cl.begin(), cl.end(), cmOrderDirectoriesCompare());
446 cl.erase(last, cl.end());
449 // Check items in implicit link directories.
450 this->FindImplicitConflicts();
453 void cmOrderDirectories::FindImplicitConflicts()
455 // Check for items in implicit link directories that have conflicts
456 // in the explicit directories.
457 std::ostringstream conflicts;
458 for (const auto& entry : this->ImplicitDirEntries) {
459 entry->FindImplicitConflicts(conflicts);
462 // Skip warning if there were no conflicts.
463 std::string const text = conflicts.str();
468 // Warn about the conflicts.
469 this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
470 MessageType::WARNING,
471 cmStrCat("Cannot generate a safe ", this->Purpose, " for target ",
472 this->Target->GetName(),
473 " because files in some directories may "
474 "conflict with libraries in implicit directories:\n",
475 text, "Some of these libraries may not be found correctly."),
476 this->Target->GetBacktrace());
479 void cmOrderDirectories::OrderDirectories()
481 // Allow a cycle to be diagnosed once.
482 this->CycleDiagnosed = false;
485 // Iterate through the directories in the original order.
486 for (unsigned int i = 0; i < this->OriginalDirectories.size(); ++i) {
487 // Start a new DFS from this node.
489 this->VisitDirectory(i);
493 void cmOrderDirectories::VisitDirectory(unsigned int i)
495 // Skip nodes already visited.
496 if (this->DirectoryVisited[i]) {
497 if (this->DirectoryVisited[i] == this->WalkId) {
498 // We have reached a node previously visited on this DFS.
500 this->DiagnoseCycle();
505 // We are now visiting this node so mark it.
506 this->DirectoryVisited[i] = this->WalkId;
508 // Visit the neighbors of the node first.
509 ConflictList const& clist = this->ConflictGraph[i];
510 for (ConflictPair const& j : clist) {
511 this->VisitDirectory(j.first);
514 // Now that all directories required to come before this one have
515 // been emitted, emit this directory.
516 this->OrderedDirectories.push_back(this->OriginalDirectories[i]);
519 void cmOrderDirectories::DiagnoseCycle()
521 // Report the cycle at most once.
522 if (this->CycleDiagnosed) {
525 this->CycleDiagnosed = true;
527 // Construct the message.
528 std::ostringstream e;
529 e << "Cannot generate a safe " << this->Purpose << " for target "
530 << this->Target->GetName()
531 << " because there is a cycle in the constraint graph:\n";
533 // Display the conflict graph.
534 for (unsigned int i = 0; i < this->ConflictGraph.size(); ++i) {
535 ConflictList const& clist = this->ConflictGraph[i];
536 e << " dir " << i << " is [" << this->OriginalDirectories[i] << "]\n";
537 for (ConflictPair const& j : clist) {
538 e << " dir " << j.first << " must precede it due to ";
539 this->ConstraintEntries[j.second]->Report(e);
543 e << "Some of these libraries may not be found correctly.";
544 this->GlobalGenerator->GetCMakeInstance()->IssueMessage(
545 MessageType::WARNING, e.str(), this->Target->GetBacktrace());
548 bool cmOrderDirectories::IsSameDirectory(std::string const& l,
549 std::string const& r)
551 return this->GetRealPath(l) == this->GetRealPath(r);
554 std::string const& cmOrderDirectories::GetRealPath(std::string const& dir)
556 auto i = this->RealPaths.lower_bound(dir);
557 if (i == this->RealPaths.end() ||
558 this->RealPaths.key_comp()(dir, i->first)) {
559 using value_type = std::map<std::string, std::string>::value_type;
560 i = this->RealPaths.insert(
561 i, value_type(dir, cmSystemTools::GetRealPath(dir)));