636 lines
18 KiB
C++
636 lines
18 KiB
C++
/*============================================================================
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void Report(std::ostream& e)
|
|
{
|
|
e << "runtime library [";
|
|
if(this->SOName.empty())
|
|
{
|
|
e << this->FileName;
|
|
}
|
|
else
|
|
{
|
|
e << this->SOName;
|
|
}
|
|
e << "]";
|
|
}
|
|
|
|
virtual bool FindConflict(std::string const& dir);
|
|
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)
|
|
{
|
|
}
|
|
|
|
virtual void Report(std::ostream& e)
|
|
{
|
|
e << "link library [" << this->FileName << "]";
|
|
}
|
|
|
|
virtual bool FindConflict(std::string const& dir);
|
|
};
|
|
|
|
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;
|
|
}
|