CMake/Source/cmOrderDirectories.cxx
2019-12-18 17:27:16 +01:00

565 lines
17 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt 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,
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) 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,
const cmGeneratorTarget* target,
const char* 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,
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->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->EmmittedConstraintLibrary.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.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 (const auto& 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 (const auto& 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 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 (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;
}