summaryrefslogtreecommitdiffstats
path: root/Source/cmOrderDirectories.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/cmOrderDirectories.cxx')
-rw-r--r--Source/cmOrderDirectories.cxx573
1 files changed, 573 insertions, 0 deletions
diff --git a/Source/cmOrderDirectories.cxx b/Source/cmOrderDirectories.cxx
new file mode 100644
index 0000000..20f2246
--- /dev/null
+++ b/Source/cmOrderDirectories.cxx
@@ -0,0 +1,573 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+#include "cmOrderDirectories.h"
+
+#include "cmAlgorithms.h"
+#include "cmGlobalGenerator.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+
+#include <assert.h>
+
+#include <algorithm>
+
+/*
+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() {}
+
+ 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 (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 (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 = dir;
+ file += "/";
+ file += name;
+ if (cmSystemTools::FileExists(file.c_str(), 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));
+ std::set<std::string>::const_iterator fi = files.find(name);
+ return fi != files.end();
+}
+
+class cmOrderDirectoriesConstraintSOName : public cmOrderDirectoriesConstraint
+{
+public:
+ cmOrderDirectoriesConstraintSOName(cmOrderDirectories* od,
+ std::string const& file,
+ const char* 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) CM_OVERRIDE
+ {
+ e << "runtime library [";
+ if (this->SOName.empty()) {
+ e << this->FileName;
+ } else {
+ e << this->SOName;
+ }
+ e << "]";
+ }
+
+ bool FindConflict(std::string const& dir) CM_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;
+ std::set<std::string>::const_iterator first = files.lower_bound(base);
+ ++base[base.size() - 1];
+ std::set<std::string>::const_iterator 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) CM_OVERRIDE
+ {
+ e << "link library [" << this->FileName << "]";
+ }
+
+ bool FindConflict(std::string const& dir) CM_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::vector<std::string>::iterator i =
+ this->OD->LinkExtensions.begin();
+ i != this->OD->LinkExtensions.end(); ++i) {
+ if (*i != ext) {
+ std::string fname = lib;
+ fname += *i;
+ if (this->FileMayConflict(dir, fname)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+cmOrderDirectories::cmOrderDirectories(cmGlobalGenerator* gg,
+ const cmGeneratorTarget* target,
+ const char* purpose)
+{
+ this->GlobalGenerator = gg;
+ this->Target = target;
+ this->Purpose = purpose;
+ this->Computed = false;
+}
+
+cmOrderDirectories::~cmOrderDirectories()
+{
+ cmDeleteAll(this->ConstraintEntries);
+ cmDeleteAll(this->ImplicitDirEntries);
+}
+
+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,
+ const char* soname)
+{
+ // Add the runtime library at most once.
+ if (this->EmmittedConstraintSOName.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->ImplicitDirectories.find(dir) !=
+ this->ImplicitDirectories.end()) {
+ this->ImplicitDirEntries.push_back(
+ new cmOrderDirectoriesConstraintSOName(this, fullPath, soname));
+ return;
+ }
+ }
+
+ // Construct the runtime information entry for this library.
+ this->ConstraintEntries.push_back(
+ new 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->EmmittedConstraintLibrary.insert(fullPath).second) {
+ // Implicit link directories need special handling.
+ if (!this->ImplicitDirectories.empty()) {
+ std::string dir = cmSystemTools::GetFilenamePath(fullPath);
+ if (this->ImplicitDirectories.find(dir) !=
+ this->ImplicitDirectories.end()) {
+ this->ImplicitDirEntries.push_back(
+ new cmOrderDirectoriesConstraintLibrary(this, fullPath));
+ return;
+ }
+ }
+
+ // Construct the link library entry.
+ this->ConstraintEntries.push_back(
+ new cmOrderDirectoriesConstraintLibrary(this, fullPath));
+ }
+}
+
+void cmOrderDirectories::AddUserDirectories(
+ std::vector<std::string> const& extra)
+{
+ this->UserDirectories.insert(this->UserDirectories.end(), extra.begin(),
+ extra.end());
+}
+
+void cmOrderDirectories::AddLanguageDirectories(
+ std::vector<std::string> const& dirs)
+{
+ this->LanguageDirectories.insert(this->LanguageDirectories.end(),
+ dirs.begin(), dirs.end());
+}
+
+void cmOrderDirectories::SetImplicitDirectories(
+ std::set<std::string> const& implicitDirs)
+{
+ this->ImplicitDirectories = implicitDirs;
+}
+
+void cmOrderDirectories::SetLinkExtensionInfo(
+ std::vector<std::string> const& linkExtensions,
+ std::string const& removeExtRegex)
+{
+ this->LinkExtensions = linkExtensions;
+ this->RemoveLibraryExtension.compile(removeExtRegex.c_str());
+}
+
+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 (unsigned int i = 0; i < this->ConstraintEntries.size(); ++i) {
+ this->ConstraintEntries[i]->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.
+ std::map<std::string, int>::iterator 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::vector<std::string>::const_iterator di = dirs.begin();
+ di != dirs.end(); ++di) {
+ // We never explicitly specify implicit link directories.
+ if (this->ImplicitDirectories.find(*di) !=
+ this->ImplicitDirectories.end()) {
+ continue;
+ }
+
+ // Skip the empty string.
+ if (di->empty()) {
+ continue;
+ }
+
+ // Add this directory.
+ this->AddOriginalDirectory(*di);
+ }
+}
+
+struct cmOrderDirectoriesCompare
+{
+ typedef std::pair<int, int> ConflictPair;
+
+ // 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 const& l, ConflictPair const& 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 (std::vector<ConflictList>::iterator i = this->ConflictGraph.begin();
+ i != this->ConflictGraph.end(); ++i) {
+ // Sort the outgoing edges for each graph node so that the
+ // original order will be preserved as much as possible.
+ std::sort(i->begin(), i->end());
+
+ // Make the edge list unique so cycle detection will be reliable.
+ ConflictList::iterator last =
+ std::unique(i->begin(), i->end(), cmOrderDirectoriesCompare());
+ i->erase(last, i->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 (unsigned int i = 0; i < this->ImplicitDirEntries.size(); ++i) {
+ this->ImplicitDirEntries[i]->FindImplicitConflicts(conflicts);
+ }
+
+ // Skip warning if there were no conflicts.
+ std::string text = conflicts.str();
+ if (text.empty()) {
+ return;
+ }
+
+ // Warn about the conflicts.
+ std::ostringstream w;
+ w << "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->GlobalGenerator->GetCMakeInstance()->IssueMessage(
+ cmake::WARNING, w.str(), 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 (ConflictList::const_iterator j = clist.begin(); j != clist.end(); ++j) {
+ this->VisitDirectory(j->first);
+ }
+
+ // Now that all directories required to come before this one have
+ // been emmitted, 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 (ConflictList::const_iterator j = clist.begin(); j != clist.end();
+ ++j) {
+ 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(
+ cmake::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)
+{
+ std::map<std::string, std::string>::iterator i =
+ this->RealPaths.lower_bound(dir);
+ if (i == this->RealPaths.end() ||
+ this->RealPaths.key_comp()(dir, i->first)) {
+ typedef std::map<std::string, std::string>::value_type value_type;
+ i = this->RealPaths.insert(
+ i, value_type(dir, cmSystemTools::GetRealPath(dir)));
+ }
+ return i->second;
+}