cmInstallTargetGenerator: optimize rpath adjustments

With builds that have many internal library directories or many
external libraries, rpaths can be quite large. The cost of calling
install_name_tool thousands of times can add up to minutes on a build,
especially if virus scanning software is there to help you out. With
this change, instead of deleting and then re-adding an rpath, we ignore
it.  Likewise we batch all the rpath adjustment calls into a single command.

Before, installing SCALE (some libraries have 70+ build-time RPATHs that
get deleted, plus up to a dozen external RPATHs from upstream
dependencies that should remain in the binary) would take almost 9
minutes on my laptop, and after this change the installation takes only
30 second.
This commit is contained in:
Seth R Johnson
2021-09-30 15:36:18 -04:00
parent 89d134c61d
commit 8f7e98ef09

View File

@@ -2,11 +2,13 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmInstallTargetGenerator.h"
#include <algorithm>
#include <cassert>
#include <map>
#include <set>
#include <sstream>
#include <utility>
#include <vector>
#include "cmComputeLinkInformation.h"
#include "cmGeneratorExpression.h"
@@ -680,34 +682,53 @@ void cmInstallTargetGenerator::AddChrpathPatchRule(
" this limitation.";
mf->IssueMessage(MessageType::WARNING, msg.str());
} else {
// Note: These paths are kept unique to avoid
// install_name_tool corruption.
std::set<std::string> runpaths;
for (std::string const& i : oldRuntimeDirs) {
std::string runpath =
mf->GetGlobalGenerator()->ExpandCFGIntDir(i, config);
if (runpaths.find(runpath) == runpaths.end()) {
runpaths.insert(runpath);
os << indent << "execute_process(COMMAND " << installNameTool
<< "\n";
os << indent << " -delete_rpath \"" << runpath << "\"\n";
os << indent << " \"" << toDestDirPath << "\")\n";
// To be consistent with older versions, runpath changes must be ordered,
// deleted first, then added, *and* the same path must only appear once.
std::map<std::string, std::string> runpath_change;
std::vector<std::string> ordered;
for (std::string const& dir : oldRuntimeDirs) {
// Normalize path and add to map of changes to make
auto iter_inserted = runpath_change.insert(
{ mf->GetGlobalGenerator()->ExpandCFGIntDir(dir, config),
"delete" });
if (iter_inserted.second) {
// Add path to ordered list of changes
ordered.push_back(iter_inserted.first->first);
}
}
runpaths.clear();
for (std::string const& i : newRuntimeDirs) {
std::string runpath =
mf->GetGlobalGenerator()->ExpandCFGIntDir(i, config);
if (runpaths.find(runpath) == runpaths.end()) {
os << indent << "execute_process(COMMAND " << installNameTool
<< "\n";
os << indent << " -add_rpath \"" << runpath << "\"\n";
os << indent << " \"" << toDestDirPath << "\")\n";
for (std::string const& dir : newRuntimeDirs) {
// Normalize path and add to map of changes to make
auto iter_inserted = runpath_change.insert(
{ mf->GetGlobalGenerator()->ExpandCFGIntDir(dir, config), "add" });
if (iter_inserted.second) {
// Add path to ordered list of changes
ordered.push_back(iter_inserted.first->first);
} else if (iter_inserted.first->second != "add") {
// Rpath was requested to be deleted and then later re-added. Drop it
// from the list by marking as an empty value.
iter_inserted.first->second.clear();
}
}
// Remove rpaths that are unchanged (value was set to empty)
ordered.erase(
std::remove_if(ordered.begin(), ordered.end(),
[&runpath_change](const std::string& runpath) {
return runpath_change.find(runpath)->second.empty();
}),
ordered.end());
if (!ordered.empty()) {
os << indent << "execute_process(COMMAND " << installNameTool << "\n";
for (std::string const& runpath : ordered) {
// Either 'add_rpath' or 'delete_rpath' since we've removed empty
// entries
os << indent << " -" << runpath_change.find(runpath)->second
<< "_rpath \"" << runpath << "\"\n";
}
os << indent << " \"" << toDestDirPath << "\")\n";
}
}
} else {
// Construct the original rpath string to be replaced.