| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmOrderDirectories.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <functional> | |
| #include <sstream> | |
| #include <vector> | |
| #include <cm/memory> | |
| #include <cmext/algorithm> | |
| #include "cmGeneratorTarget.h" | |
| #include "cmGlobalGenerator.h" | |
| #include "cmMessageType.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmake.h" | |
| /* | |
| Directory ordering computation. | |
| - Useful to compute a safe runtime library path order | |
| - Need runtime path for supporting INSTALL_RPATH_USE_LINK_PATH | |
| - Need runtime path at link time to pickup transitive link dependencies | |
| for shared libraries. | |
| */ | |
| class cmOrderDirectoriesConstraint | |
| { | |
| public: | |
| cmOrderDirectoriesConstraint(cmOrderDirectories* od, std::string const& file) | |
| : OD(od) | |
| , GlobalGenerator(od->GlobalGenerator) | |
| { | |
| this->FullPath = file; | |
| if (file.rfind(".framework") != std::string::npos) { | |
| static cmsys::RegularExpression splitFramework( | |
| "^(.*)/(.*).framework/(.*)$"); | |
| if (splitFramework.find(file) && | |
| (std::string::npos != | |
| splitFramework.match(3).find(splitFramework.match(2)))) { | |
| this->Directory = splitFramework.match(1); | |
| this->FileName = | |
| std::string(file.begin() + this->Directory.size() + 1, file.end()); | |
| } | |
| } | |
| if (this->FileName.empty()) { | |
| this->Directory = cmSystemTools::GetFilenamePath(file); | |
| this->FileName = cmSystemTools::GetFilenameName(file); | |
| } | |
| } | |
| virtual ~cmOrderDirectoriesConstraint() = default; | |
| void AddDirectory() | |
| { | |
| this->DirectoryIndex = this->OD->AddOriginalDirectory(this->Directory); | |
| } | |
| virtual void Report(std::ostream& e) = 0; | |
| void FindConflicts(unsigned int index) | |
| { | |
| for (unsigned int i = 0; i < this->OD->OriginalDirectories.size(); ++i) { | |
| // Check if this directory conflicts with the entry. | |
| std::string const& dir = this->OD->OriginalDirectories[i]; | |
| if (!this->OD->IsSameDirectory(dir, this->Directory) && | |
| this->FindConflict(dir)) { | |
| // The library will be found in this directory but this is not | |
| // the directory named for it. Add an entry to make sure the | |
| // desired directory comes before this one. | |
| cmOrderDirectories::ConflictPair p(this->DirectoryIndex, index); | |
| this->OD->ConflictGraph[i].push_back(p); | |
| } | |
| } | |
| } | |
| void FindImplicitConflicts(std::ostringstream& w) | |
| { | |
| bool first = true; | |
| for (std::string const& dir : this->OD->OriginalDirectories) { | |
| // Check if this directory conflicts with the entry. | |
| if (dir != this->Directory && | |
| cmSystemTools::GetRealPath(dir) != | |
| cmSystemTools::GetRealPath(this->Directory) && | |
| this->FindConflict(dir)) { | |
| // The library will be found in this directory but it is | |
| // supposed to be found in an implicit search directory. | |
| if (first) { | |
| first = false; | |
| w << " "; | |
| this->Report(w); | |
| w << " in " << this->Directory << " may be hidden by files in:\n"; | |
| } | |
| w << " " << dir << "\n"; | |
| } | |
| } | |
| } | |
| protected: | |
| virtual bool FindConflict(std::string const& dir) = 0; | |
| bool FileMayConflict(std::string const& dir, std::string const& name); | |
| cmOrderDirectories* OD; | |
| cmGlobalGenerator* GlobalGenerator; | |
| // The location in which the item is supposed to be found. | |
| std::string FullPath; | |
| std::string Directory; | |
| std::string FileName; | |
| // The index assigned to the directory. | |
| int DirectoryIndex; | |
| }; | |
| bool cmOrderDirectoriesConstraint::FileMayConflict(std::string const& dir, | |
| std::string const& name) | |
| { | |
| // Check if the file exists on disk. | |
| std::string file = cmStrCat(dir, '/', name); | |
| if (cmSystemTools::FileExists(file, true)) { | |
| // The file conflicts only if it is not the same as the original | |
| // file due to a symlink or hardlink. | |
| return !cmSystemTools::SameFile(this->FullPath, file); | |
| } | |
| // Check if the file will be built by cmake. | |
| std::set<std::string> const& files = | |
| (this->GlobalGenerator->GetDirectoryContent(dir, false)); | |
| auto fi = files.find(name); | |
| return fi != files.end(); | |
| } | |
| class cmOrderDirectoriesConstraintSOName : public cmOrderDirectoriesConstraint | |
| { | |
| public: | |
| cmOrderDirectoriesConstraintSOName(cmOrderDirectories* od, | |
| std::string const& file, | |
| char const* soname) | |
| : cmOrderDirectoriesConstraint(od, file) | |
| , SOName(soname ? soname : "") | |
| { | |
| if (this->SOName.empty()) { | |
| // Try to guess the soname. | |
| std::string soguess; | |
| if (cmSystemTools::GuessLibrarySOName(file, soguess)) { | |
| this->SOName = soguess; | |
| } | |
| } | |
| } | |
| void Report(std::ostream& e) override | |
| { | |
| e << "runtime library ["; | |
| if (this->SOName.empty()) { | |
| e << this->FileName; | |
| } else { | |
| e << this->SOName; | |
| } | |
| e << "]"; | |
| } | |
| bool FindConflict(std::string const& dir) override; | |
| private: | |
| // The soname of the shared library if it is known. | |
| std::string SOName; | |
| }; | |
| bool cmOrderDirectoriesConstraintSOName::FindConflict(std::string const& dir) | |
| { | |
| // Determine which type of check to do. | |
| if (!this->SOName.empty()) { | |
| // We have the library soname. Check if it will be found. | |
| if (this->FileMayConflict(dir, this->SOName)) { | |
| return true; | |
| } | |
| } else { | |
| // We do not have the soname. Look for files in the directory | |
| // that may conflict. | |
| std::set<std::string> const& files = | |
| (this->GlobalGenerator->GetDirectoryContent(dir, true)); | |
| // Get the set of files that might conflict. Since we do not | |
| // know the soname just look at all files that start with the | |
| // file name. Usually the soname starts with the library name. | |
| std::string base = this->FileName; | |
| auto first = files.lower_bound(base); | |
| ++base.back(); | |
| auto last = files.upper_bound(base); | |
| if (first != last) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| class cmOrderDirectoriesConstraintLibrary : public cmOrderDirectoriesConstraint | |
| { | |
| public: | |
| cmOrderDirectoriesConstraintLibrary(cmOrderDirectories* od, | |
| std::string const& file) | |
| : cmOrderDirectoriesConstraint(od, file) | |
| { | |
| } | |
| void Report(std::ostream& e) override | |
| { | |
| e << "link library [" << this->FileName << "]"; | |
| } | |
| bool FindConflict(std::string const& dir) override; | |
| }; | |
| bool cmOrderDirectoriesConstraintLibrary::FindConflict(std::string const& dir) | |
| { | |
| // We have the library file name. Check if it will be found. | |
| if (this->FileMayConflict(dir, this->FileName)) { | |
| return true; | |
| } | |
| // Now check if the file exists with other extensions the linker | |
| // might consider. | |
| if (!this->OD->LinkExtensions.empty() && | |
| this->OD->RemoveLibraryExtension.find(this->FileName)) { | |
| std::string lib = this->OD->RemoveLibraryExtension.match(1); | |
| std::string ext = this->OD->RemoveLibraryExtension.match(2); | |
| for (std::string const& LinkExtension : this->OD->LinkExtensions) { | |
| if (LinkExtension != ext) { | |
| std::string fname = cmStrCat(lib, LinkExtension); | |
| if (this->FileMayConflict(dir, fname)) { | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| cmOrderDirectories::cmOrderDirectories(cmGlobalGenerator* gg, | |
| cmGeneratorTarget const* target, | |
| char const* purpose) | |
| { | |
| this->GlobalGenerator = gg; | |
| this->Target = target; | |
| this->Purpose = purpose; | |
| this->Computed = false; | |
| } | |
| cmOrderDirectories::~cmOrderDirectories() = default; | |
| std::vector<std::string> const& cmOrderDirectories::GetOrderedDirectories() | |
| { | |
| if (!this->Computed) { | |
| this->Computed = true; | |
| this->CollectOriginalDirectories(); | |
| this->FindConflicts(); | |
| this->OrderDirectories(); | |
| } | |
| return this->OrderedDirectories; | |
| } | |
| void cmOrderDirectories::AddRuntimeLibrary(std::string const& fullPath, | |
| char const* soname) | |
| { | |
| // Add the runtime library at most once. | |
| if (this->EmittedConstraintSOName.insert(fullPath).second) { | |
| // Implicit link directories need special handling. | |
| if (!this->ImplicitDirectories.empty()) { | |
| std::string dir = cmSystemTools::GetFilenamePath(fullPath); | |
| if (fullPath.rfind(".framework") != std::string::npos) { | |
| static cmsys::RegularExpression splitFramework( | |
| "^(.*)/(.*).framework/(.*)$"); | |
| if (splitFramework.find(fullPath) && | |
| (std::string::npos != | |
| splitFramework.match(3).find(splitFramework.match(2)))) { | |
| dir = splitFramework.match(1); | |
| } | |
| } | |
| if (this->IsImplicitDirectory(dir)) { | |
| this->ImplicitDirEntries.push_back( | |
| cm::make_unique<cmOrderDirectoriesConstraintSOName>(this, fullPath, | |
| soname)); | |
| return; | |
| } | |
| } | |
| // Construct the runtime information entry for this library. | |
| this->ConstraintEntries.push_back( | |
| cm::make_unique<cmOrderDirectoriesConstraintSOName>(this, fullPath, | |
| soname)); | |
| } else { | |
| // This can happen if the same library is linked multiple times. | |
| // In that case the runtime information check need be done only | |
| // once anyway. For shared libs we could add a check in AddItem | |
| // to not repeat them. | |
| } | |
| } | |
| void cmOrderDirectories::AddLinkLibrary(std::string const& fullPath) | |
| { | |
| // Link extension info is required for library constraints. | |
| assert(!this->LinkExtensions.empty()); | |
| // Add the link library at most once. | |
| if (this->EmittedConstraintLibrary.insert(fullPath).second) { | |
| // Implicit link directories need special handling. | |
| if (!this->ImplicitDirectories.empty()) { | |
| std::string dir = cmSystemTools::GetFilenamePath(fullPath); | |
| if (this->IsImplicitDirectory(dir)) { | |
| this->ImplicitDirEntries.push_back( | |
| cm::make_unique<cmOrderDirectoriesConstraintLibrary>(this, | |
| fullPath)); | |
| return; | |
| } | |
| } | |
| // Construct the link library entry. | |
| this->ConstraintEntries.push_back( | |
| cm::make_unique<cmOrderDirectoriesConstraintLibrary>(this, fullPath)); | |
| } | |
| } | |
| void cmOrderDirectories::AddUserDirectories( | |
| std::vector<std::string> const& extra) | |
| { | |
| cm::append(this->UserDirectories, extra); | |
| } | |
| void cmOrderDirectories::AddLanguageDirectories( | |
| std::vector<std::string> const& dirs) | |
| { | |
| cm::append(this->LanguageDirectories, dirs); | |
| } | |
| void cmOrderDirectories::SetImplicitDirectories( | |
| std::set<std::string> const& implicitDirs) | |
| { | |
| this->ImplicitDirectories.clear(); | |
| for (std::string const& implicitDir : implicitDirs) { | |
| this->ImplicitDirectories.insert(this->GetRealPath(implicitDir)); | |
| } | |
| } | |
| bool cmOrderDirectories::IsImplicitDirectory(std::string const& dir) | |
| { | |
| std::string const& real = this->GetRealPath(dir); | |
| return this->ImplicitDirectories.find(real) != | |
| this->ImplicitDirectories.end(); | |
| } | |
| void cmOrderDirectories::SetLinkExtensionInfo( | |
| std::vector<std::string> const& linkExtensions, | |
| std::string const& removeExtRegex) | |
| { | |
| this->LinkExtensions = linkExtensions; | |
| this->RemoveLibraryExtension.compile(removeExtRegex); | |
| } | |
| void cmOrderDirectories::CollectOriginalDirectories() | |
| { | |
| // Add user directories specified for inclusion. These should be | |
| // indexed first so their original order is preserved as much as | |
| // possible subject to the constraints. | |
| this->AddOriginalDirectories(this->UserDirectories); | |
| // Add directories containing constraints. | |
| for (auto const& entry : this->ConstraintEntries) { | |
| entry->AddDirectory(); | |
| } | |
| // Add language runtime directories last. | |
| this->AddOriginalDirectories(this->LanguageDirectories); | |
| } | |
| int cmOrderDirectories::AddOriginalDirectory(std::string const& dir) | |
| { | |
| // Add the runtime directory with a unique index. | |
| auto i = this->DirectoryIndex.find(dir); | |
| if (i == this->DirectoryIndex.end()) { | |
| std::map<std::string, int>::value_type entry( | |
| dir, static_cast<int>(this->OriginalDirectories.size())); | |
| i = this->DirectoryIndex.insert(entry).first; | |
| this->OriginalDirectories.push_back(dir); | |
| } | |
| return i->second; | |
| } | |
| void cmOrderDirectories::AddOriginalDirectories( | |
| std::vector<std::string> const& dirs) | |
| { | |
| for (std::string const& dir : dirs) { | |
| // We never explicitly specify implicit link directories. | |
| if (this->IsImplicitDirectory(dir)) { | |
| continue; | |
| } | |
| // Skip the empty string. | |
| if (dir.empty()) { | |
| continue; | |
| } | |
| // Add this directory. | |
| this->AddOriginalDirectory(dir); | |
| } | |
| } | |
| struct cmOrderDirectoriesCompare | |
| { | |
| using ConflictPair = std::pair<int, int>; | |
| // The conflict pair is unique based on just the directory | |
| // (first). The second element is only used for displaying | |
| // information about why the entry is present. | |
| bool operator()(ConflictPair l, ConflictPair r) | |
| { | |
| return l.first == r.first; | |
| } | |
| }; | |
| void cmOrderDirectories::FindConflicts() | |
| { | |
| // Allocate the conflict graph. | |
| this->ConflictGraph.resize(this->OriginalDirectories.size()); | |
| this->DirectoryVisited.resize(this->OriginalDirectories.size(), 0); | |
| // Find directories conflicting with each entry. | |
| for (unsigned int i = 0; i < this->ConstraintEntries.size(); ++i) { | |
| this->ConstraintEntries[i]->FindConflicts(i); | |
| } | |
| // Clean up the conflict graph representation. | |
| for (ConflictList& cl : this->ConflictGraph) { | |
| // Sort the outgoing edges for each graph node so that the | |
| // original order will be preserved as much as possible. | |
| std::sort(cl.begin(), cl.end()); | |
| // Make the edge list unique so cycle detection will be reliable. | |
| auto last = std::unique(cl.begin(), cl.end(), cmOrderDirectoriesCompare()); | |
| cl.erase(last, cl.end()); | |
| } | |
| // Check items in implicit link directories. | |
| this->FindImplicitConflicts(); | |
| } | |
| void cmOrderDirectories::FindImplicitConflicts() | |
| { | |
| // Check for items in implicit link directories that have conflicts | |
| // in the explicit directories. | |
| std::ostringstream conflicts; | |
| for (auto const& entry : this->ImplicitDirEntries) { | |
| entry->FindImplicitConflicts(conflicts); | |
| } | |
| // Skip warning if there were no conflicts. | |
| std::string const text = conflicts.str(); | |
| if (text.empty()) { | |
| return; | |
| } | |
| // Warn about the conflicts. | |
| this->GlobalGenerator->GetCMakeInstance()->IssueMessage( | |
| MessageType::WARNING, | |
| cmStrCat("Cannot generate a safe ", this->Purpose, " for target ", | |
| this->Target->GetName(), | |
| " because files in some directories may " | |
| "conflict with libraries in implicit directories:\n", | |
| text, "Some of these libraries may not be found correctly."), | |
| this->Target->GetBacktrace()); | |
| } | |
| void cmOrderDirectories::OrderDirectories() | |
| { | |
| // Allow a cycle to be diagnosed once. | |
| this->CycleDiagnosed = false; | |
| this->WalkId = 0; | |
| // Iterate through the directories in the original order. | |
| for (unsigned int i = 0; i < this->OriginalDirectories.size(); ++i) { | |
| // Start a new DFS from this node. | |
| ++this->WalkId; | |
| this->VisitDirectory(i); | |
| } | |
| } | |
| void cmOrderDirectories::VisitDirectory(unsigned int i) | |
| { | |
| // Skip nodes already visited. | |
| if (this->DirectoryVisited[i]) { | |
| if (this->DirectoryVisited[i] == this->WalkId) { | |
| // We have reached a node previously visited on this DFS. | |
| // There is a cycle. | |
| this->DiagnoseCycle(); | |
| } | |
| return; | |
| } | |
| // We are now visiting this node so mark it. | |
| this->DirectoryVisited[i] = this->WalkId; | |
| // Visit the neighbors of the node first. | |
| ConflictList const& clist = this->ConflictGraph[i]; | |
| for (ConflictPair const& j : clist) { | |
| this->VisitDirectory(j.first); | |
| } | |
| // Now that all directories required to come before this one have | |
| // been emitted, emit this directory. | |
| this->OrderedDirectories.push_back(this->OriginalDirectories[i]); | |
| } | |
| void cmOrderDirectories::DiagnoseCycle() | |
| { | |
| // Report the cycle at most once. | |
| if (this->CycleDiagnosed) { | |
| return; | |
| } | |
| this->CycleDiagnosed = true; | |
| // Construct the message. | |
| std::ostringstream e; | |
| e << "Cannot generate a safe " << this->Purpose << " for target " | |
| << this->Target->GetName() | |
| << " because there is a cycle in the constraint graph:\n"; | |
| // Display the conflict graph. | |
| for (unsigned int i = 0; i < this->ConflictGraph.size(); ++i) { | |
| ConflictList const& clist = this->ConflictGraph[i]; | |
| e << " dir " << i << " is [" << this->OriginalDirectories[i] << "]\n"; | |
| for (ConflictPair const& j : clist) { | |
| e << " dir " << j.first << " must precede it due to "; | |
| this->ConstraintEntries[j.second]->Report(e); | |
| e << "\n"; | |
| } | |
| } | |
| e << "Some of these libraries may not be found correctly."; | |
| this->GlobalGenerator->GetCMakeInstance()->IssueMessage( | |
| MessageType::WARNING, e.str(), this->Target->GetBacktrace()); | |
| } | |
| bool cmOrderDirectories::IsSameDirectory(std::string const& l, | |
| std::string const& r) | |
| { | |
| return this->GetRealPath(l) == this->GetRealPath(r); | |
| } | |
| std::string const& cmOrderDirectories::GetRealPath(std::string const& dir) | |
| { | |
| auto i = this->RealPaths.lower_bound(dir); | |
| if (i == this->RealPaths.end() || | |
| this->RealPaths.key_comp()(dir, i->first)) { | |
| using value_type = std::map<std::string, std::string>::value_type; | |
| i = this->RealPaths.insert( | |
| i, value_type(dir, cmSystemTools::GetRealPath(dir))); | |
| } | |
| return i->second; | |
| } |