collect dependencies & rpaths in a single otool call

This commit is contained in:
SCG82 2020-01-06 06:45:18 -08:00
parent 7f41661132
commit e5cacbb3e6
8 changed files with 220 additions and 149 deletions

View File

@ -15,35 +15,33 @@
Dependency::Dependency(std::string path, const std::string& dependent_file) : is_framework(false)
{
char original_file_buffer[PATH_MAX];
char buffer[PATH_MAX];
rtrim_in_place(path);
std::string original_file;
std::string warning_msg;
rtrim_in_place(path);
if (Settings::verboseOutput()) {
std::cout<< "** Dependency ctor **" << std::endl;
if (path != dependent_file)
std::cout << " dependent file: " << dependent_file << std::endl;
std::cout << " dependency path: " << path << std::endl;
}
if (isRpath(path)) {
original_file = searchFilenameInRpaths(path, dependent_file);
}
else if (realpath(path.c_str(), original_file_buffer)) {
original_file = original_file_buffer;
if (Settings::verboseOutput())
std::cout << " original_file: " << original_file << std::endl;
else if (realpath(path.c_str(), buffer)) {
original_file = buffer;
}
else {
warning_msg = "\n/!\\ WARNING: Cannot resolve path '" + path + "'\n";
original_file = path;
}
if (Settings::verboseOutput()) {
std::cout<< "** Dependency ctor **" << std::endl;
if (path != dependent_file)
std::cout << " dependent file: " << dependent_file << std::endl;
std::cout << " dependency path: " << path << std::endl;
std::cout << " original_file: " << original_file << std::endl;
}
// check if given path is a symlink
if (original_file != path)
addSymlink(path);
AddSymlink(path);
prefix = filePrefix(original_file);
filename = stripPrefix(original_file);
@ -72,10 +70,8 @@ Dependency::Dependency(std::string path, const std::string& dependent_file) : is
// check if the lib is in a known location
if (prefix.empty() || !fileExists(prefix+filename)) {
std::vector<std::string> search_paths = Settings::searchPaths();
// the paths contains at least /usr/lib so if it is empty we have not initialized it
if (search_paths.empty())
initSearchPaths();
// check if file is contained in one of the paths
for (const auto& search_path : search_paths) {
if (fileExists(search_path+filename)) {
@ -103,45 +99,37 @@ Dependency::Dependency(std::string path, const std::string& dependent_file) : is
new_name = filename;
}
std::string Dependency::getInstallPath() const
{
return Settings::destFolder() + new_name;
}
std::string Dependency::getInnerPath() const
std::string Dependency::InnerPath() const
{
return Settings::insideLibPath() + new_name;
}
void Dependency::print() const
std::string Dependency::InstallPath() const
{
std::cout << "\n* " << filename << " from " << prefix << std::endl;
for (const auto& symlink : symlinks) {
std::cout << " symlink --> " << symlink << std::endl;
}
return Settings::destFolder() + new_name;
}
void Dependency::addSymlink(const std::string& s)
void Dependency::AddSymlink(const std::string& path)
{
if (std::find(symlinks.begin(), symlinks.end(), s) == symlinks.end())
symlinks.push_back(s);
if (std::find(symlinks.begin(), symlinks.end(), path) == symlinks.end())
symlinks.push_back(path);
}
bool Dependency::mergeIfSameAs(Dependency& dep2)
bool Dependency::MergeIfIdentical(Dependency& dependency)
{
if (dep2.getOriginalFileName() == filename) {
if (dependency.OriginalFilename() == filename) {
for (const auto& symlink : symlinks) {
dep2.addSymlink(symlink);
dependency.AddSymlink(symlink);
}
return true;
}
return false;
}
void Dependency::copyToAppBundle() const
void Dependency::CopyToBundle() const
{
std::string original_path = getOriginalPath();
std::string dest_path = getInstallPath();
std::string original_path = OriginalPath();
std::string dest_path = InstallPath();
if (is_framework) {
original_path = getFrameworkRoot(original_path);
@ -149,11 +137,11 @@ void Dependency::copyToAppBundle() const
}
if (Settings::verboseOutput()) {
std::string inner_path = getInnerPath();
std::string inner_path = InnerPath();
std::cout << " - original path: " << original_path << std::endl;
std::cout << " - inner path: " << inner_path << std::endl;
std::cout << " - dest_path: " << dest_path << std::endl;
std::cout << " - install path: " << getInstallPath() << std::endl;
std::cout << " - install path: " << InstallPath() << std::endl;
}
copyFile(original_path, dest_path);
@ -167,20 +155,28 @@ void Dependency::copyToAppBundle() const
deleteFile(dest_path + "/*.prl");
}
changeId(getInstallPath(), "@rpath/"+new_name);
changeId(InstallPath(), "@rpath/" + new_name);
}
void Dependency::fixDependentFiles(const std::string& file) const
void Dependency::FixDependentFile(const std::string& dependent_file) const
{
changeInstallName(file, getOriginalPath(), getInnerPath());
changeInstallName(dependent_file, OriginalPath(), InnerPath());
for (const auto& symlink : symlinks) {
changeInstallName(file, symlink, getInnerPath());
changeInstallName(dependent_file, symlink, InnerPath());
}
if (Settings::missingPrefixes()) {
changeInstallName(file, filename, getInnerPath());
changeInstallName(dependent_file, filename, InnerPath());
for (const auto& symlink : symlinks) {
changeInstallName(file, symlink, getInnerPath());
changeInstallName(dependent_file, symlink, InnerPath());
}
}
}
void Dependency::Print() const
{
std::cout << "\n* " << filename << " from " << prefix << std::endl;
for (const auto& symlink : symlinks) {
std::cout << " symlink --> " << symlink << std::endl;
}
}

View File

@ -1,7 +1,7 @@
#pragma once
#ifndef _depend_h_
#define _depend_h_
#ifndef DYLIBBUNDLER_DEPENDENCY_H
#define DYLIBBUNDLER_DEPENDENCY_H
#include <string>
#include <vector>
@ -10,25 +10,25 @@ class Dependency {
public:
Dependency(std::string path, const std::string& dependent_file);
[[nodiscard]] bool isFramework() const { return is_framework; }
[[nodiscard]] bool IsFramework() const { return is_framework; }
[[nodiscard]] std::string getPrefix() const { return prefix; }
[[nodiscard]] std::string getOriginalFileName() const { return filename; }
[[nodiscard]] std::string getOriginalPath() const { return prefix + filename; }
[[nodiscard]] std::string Prefix() const { return prefix; }
[[nodiscard]] std::string OriginalFilename() const { return filename; }
[[nodiscard]] std::string OriginalPath() const { return prefix + filename; }
[[nodiscard]] std::string getInstallPath() const;
[[nodiscard]] std::string getInnerPath() const;
[[nodiscard]] std::string InnerPath() const;
[[nodiscard]] std::string InstallPath() const;
void print() const;
void addSymlink(const std::string& s);
void AddSymlink(const std::string& path);
// Compare the given dependency with this one. If both refer to the same file,
// merge both entries into one and return true.
bool mergeIfSameAs(Dependency& dep2);
bool MergeIfIdentical(Dependency& dependency);
void copyToAppBundle() const;
void fixDependentFiles(const std::string& file) const;
void CopyToBundle() const;
void FixDependentFile(const std::string& dependent_file) const;
void Print() const;
private:
bool is_framework;

View File

@ -23,6 +23,7 @@ std::map<std::string, std::vector<Dependency>> deps_per_file;
std::map<std::string, bool> deps_collected;
std::set<std::string> frameworks;
std::set<std::string> rpaths;
std::map<std::string, bool> rpaths_collected;
bool qt_plugins_called = false;
void addDependency(const std::string& path, const std::string& dependent_file)
@ -32,71 +33,98 @@ void addDependency(const std::string& path, const std::string& dependent_file)
// check if this library was already added to |deps| to avoid duplicates
bool in_deps = false;
for (auto& dep : deps) {
if (dependency.mergeIfSameAs(dep))
if (dependency.MergeIfIdentical(dep))
in_deps = true;
}
// check if this library was already added to |deps_per_file[dependent_file]| to avoid duplicates
bool in_deps_per_file = false;
for (auto& dep : deps_per_file[dependent_file]) {
if (dependency.mergeIfSameAs(dep))
if (dependency.MergeIfIdentical(dep))
in_deps_per_file = true;
}
// check if this library is in /usr/lib, /System/Library, or in ignored list
if (!Settings::isPrefixBundled(dependency.getPrefix()))
if (!Settings::isPrefixBundled(dependency.Prefix()))
return;
if (!in_deps && dependency.isFramework())
frameworks.insert(dependency.getOriginalPath());
if (!in_deps && dependency.IsFramework())
frameworks.insert(dependency.OriginalPath());
if (!in_deps)
deps.push_back(dependency);
if (!in_deps_per_file)
deps_per_file[dependent_file].push_back(dependency);
}
void collectDependencies(const std::string& dependent_file, std::vector<std::string>& lines)
void collectDependencies(const std::string& dependent_file)
{
parseLoadCommands(dependent_file, std::string("LC_LOAD_DYLIB"), std::string("name"), lines);
}
if (deps_collected.find(dependent_file) != deps_collected.end() && Settings::fileHasRpath(dependent_file))
return;
void collectDependenciesForFile(const std::string& file, std::vector<std::string>& lines)
{
if (deps_collected.find(file) == deps_collected.end())
collectDependencies(file, lines);
}
std::map<std::string,std::string> cmds_values;
std::string dylib = "LC_LOAD_DYLIB";
std::string rpath = "LC_RPATH";
cmds_values[dylib] = "name";
cmds_values[rpath] = "path";
std::map<std::string,std::vector<std::string>> cmds_results;
void collectDependenciesForFile(const std::string& dependent_file)
{
std::vector<std::string> lines;
collectDependenciesForFile(dependent_file, lines);
collectRpathsForFilename(dependent_file);
parseLoadCommands(dependent_file, cmds_values, cmds_results);
for (const auto& line : lines) {
if (!Settings::isPrefixBundled(line))
continue; // skip system/ignored prefixes
addDependency(line, dependent_file);
if (rpaths_collected.find(dependent_file) == rpaths_collected.end()) {
auto rpath_results = cmds_results[rpath];
for (const auto& rpath_result : rpath_results) {
rpaths.insert(rpath_result);
Settings::addRpathForFile(dependent_file, rpath_result);
if (Settings::verboseOutput())
std::cout << " rpath: " << rpath_result << std::endl;
}
rpaths_collected[dependent_file] = true;
}
deps_collected[dependent_file] = true;
}
void collectRpaths(const std::string& filename)
{
std::vector<std::string> lines;
parseLoadCommands(filename, std::string("LC_RPATH"), std::string("path"), lines);
for (const auto& line : lines) {
rpaths.insert(line);
Settings::addRpathForFile(filename, line);
if (Settings::verboseOutput())
std::cout << " rpath: " << line << std::endl;
if (deps_collected.find(dependent_file) == deps_collected.end()) {
auto dylib_results = cmds_results[dylib];
for (const auto& dylib_result : dylib_results) {
// skip system/ignored prefixes
if (Settings::isPrefixBundled(dylib_result))
addDependency(dylib_result, dependent_file);
}
deps_collected[dependent_file] = true;
}
}
void collectRpathsForFilename(const std::string& filename)
{
if (!Settings::fileHasRpath(filename))
collectRpaths(filename);
}
//void collectDependencies(const std::string& dependent_file, std::vector<std::string>& lines)
//{
// if (deps_collected.find(dependent_file) == deps_collected.end())
// parseLoadCommands(dependent_file, std::string("LC_LOAD_DYLIB"), std::string("name"), lines);
//}
//void collectDependencies(const std::string& dependent_file)
//{
// std::vector<std::string> lines;
// collectDependencies(dependent_file, lines);
// collectRpaths(dependent_file);
//
// for (const auto& line : lines) {
// if (!Settings::isPrefixBundled(line))
// continue; // skip system/ignored prefixes
// addDependency(line, dependent_file);
// }
// deps_collected[dependent_file] = true;
//}
//void collectRpaths(const std::string& filename)
//{
// if (!Settings::fileHasRpath(filename)) {
// std::vector<std::string> lines;
// parseLoadCommands(filename, std::string("LC_RPATH"), std::string("path"), lines);
// for (const auto &line : lines) {
// rpaths.insert(line);
// Settings::addRpathForFile(filename, line);
// if (Settings::verboseOutput())
// std::cout << " rpath: " << line << std::endl;
// }
// }
//}
void collectSubDependencies()
{
@ -110,21 +138,23 @@ void collectSubDependencies()
while (true) {
deps_size = deps.size();
for (size_t n=0; n<deps_size; ++n) {
std::string original_path = deps[n].getOriginalPath();
std::string original_path = deps[n].OriginalPath();
if (Settings::verboseOutput())
std::cout << " (collect sub deps) original path: " << original_path << std::endl;
if (isRpath(original_path))
original_path = searchFilenameInRpaths(original_path);
std::vector<std::string> lines;
collectDependenciesForFile(original_path, lines);
collectRpathsForFilename(original_path);
// std::vector<std::string> lines;
// collectDependencies(original_path, lines);
// collectRpaths(original_path);
//
// for (const auto& line : lines) {
// if (!Settings::isPrefixBundled(line))
// continue; // skip system/ignored prefixes
// addDependency(line, original_path);
// }
for (const auto& line : lines) {
if (!Settings::isPrefixBundled(line))
continue; // skip system/ignored prefixes
addDependency(line, original_path);
}
collectDependencies(original_path);
}
// if no more dependencies were added on this iteration, stop searching
if (deps.size() == deps_size)
@ -143,28 +173,30 @@ void collectSubDependencies()
void changeLibPathsOnFile(const std::string& file_to_fix)
{
if (deps_collected.find(file_to_fix) == deps_collected.end())
collectDependenciesForFile(file_to_fix);
if (deps_collected.find(file_to_fix) == deps_collected.end() || rpaths_collected.find(file_to_fix) == rpaths_collected.end())
collectDependencies(file_to_fix);
std::cout << "* Fixing dependencies on " << file_to_fix << "\n";
const size_t dep_amount = deps_per_file[file_to_fix].size();
for (size_t n=0; n<dep_amount; ++n) {
deps_per_file[file_to_fix][n].fixDependentFiles(file_to_fix);
std::vector<Dependency> dependencies = deps_per_file[file_to_fix];
for (auto& dependency : dependencies) {
dependency.FixDependentFile(file_to_fix);
}
}
void fixRpathsOnFile(const std::string& original_file, const std::string& file_to_fix)
{
std::vector<std::string> rpaths_to_fix;
if (Settings::fileHasRpath(original_file))
rpaths_to_fix = Settings::getRpathsForFile(original_file);
if (!Settings::fileHasRpath(original_file))
return;
for (const auto& i : rpaths_to_fix) {
rpaths_to_fix = Settings::getRpathsForFile(original_file);
for (const auto& rpath_to_fix : rpaths_to_fix) {
std::string command = std::string("install_name_tool -rpath ");
command += i + " " + Settings::insideLibPath() + " " + file_to_fix;
command.append(rpath_to_fix).append(" ").append(Settings::insideLibPath());
command.append(" ").append(file_to_fix);
if (systemp(command) != 0) {
std::cerr << "\n\n/!\\ ERROR: An error occured while trying to fix dependencies of " << file_to_fix << "\n";
std::cerr << "\n\n/!\\ ERROR: An error occured while trying to fix rpath " << rpath_to_fix << " of " << file_to_fix << std::endl;
exit(1);
}
}
@ -173,21 +205,22 @@ void fixRpathsOnFile(const std::string& original_file, const std::string& file_t
void bundleDependencies()
{
for (const auto& dep : deps) {
dep.print();
dep.Print();
}
std::cout << "\n";
if (Settings::verboseOutput()) {
std::cout << "rpaths:" << std::endl;
for (const auto& rpath : rpaths) {
std::cout << "rpaths: " << rpath << std::endl;
std::cout << "* " << rpath << std::endl;
}
}
// copy & fix up dependencies
if (Settings::bundleLibs()) {
createDestDir();
for (const auto& dep : deps) {
dep.copyToAppBundle();
changeLibPathsOnFile(dep.getInstallPath());
fixRpathsOnFile(dep.getOriginalPath(), dep.getInstallPath());
dep.CopyToBundle();
changeLibPathsOnFile(dep.InstallPath());
fixRpathsOnFile(dep.OriginalPath(), dep.InstallPath());
}
}
// fix up selected files
@ -261,7 +294,7 @@ void bundleQtPlugins()
std::vector<std::string> files = lsDir(dest + plugin+"/");
for (const auto& file : files) {
Settings::addFileToFix(dest + plugin+"/"+file);
collectDependenciesForFile(dest + plugin+"/"+file);
collectDependencies(dest + plugin + "/" + file);
changeId(dest + plugin+"/"+file, "@rpath/" + plugin+"/"+file);
}
}
@ -276,7 +309,7 @@ void bundleQtPlugins()
mkdir(dest + "platforms");
copyFile(qt_plugins_prefix + "platforms/libqcocoa.dylib", dest + "platforms");
Settings::addFileToFix(dest + "platforms/libqcocoa.dylib");
collectDependenciesForFile(dest + "platforms/libqcocoa.dylib");
collectDependencies(dest + "platforms/libqcocoa.dylib");
fixupPlugin("printsupport");
fixupPlugin("styles");

View File

@ -1,23 +1,18 @@
#pragma once
#ifndef _DYLIB_BUNDLER_H_
#define _DYLIB_BUNDLER_H_
#ifndef DYLIBBUNDLER_DYLIBBUNDLER_H
#define DYLIBBUNDLER_DYLIBBUNDLER_H
#include <string>
#include <vector>
void addDependency(const std::string& path, const std::string& dependent_file);
// std::string searchFilenameInRpaths(const std::string& rpath_file, const std::string& dependent_file);
// std::string searchFilenameInRpaths(const std::string& rpath_file);
// fill |lines| with dependencies of |dependent_file|
void collectDependencies(const std::string& dependent_file, std::vector<std::string>& lines);
void collectDependenciesForFile(const std::string& dependent_file, std::vector<std::string>& lines);
void collectDependenciesForFile(const std::string& dependent_file);
//void collectDependencies(const std::string& dependent_file, std::vector<std::string>& lines);
void collectDependencies(const std::string& dependent_file);
void collectRpaths(const std::string& filename);
void collectRpathsForFilename(const std::string& filename);
//void collectRpaths(const std::string& filename);
// recursively collect each dependency's dependencies
void collectSubDependencies();

View File

@ -1,7 +1,7 @@
#pragma once
#ifndef _settings_
#define _settings_
#ifndef DYLIBBUNDLER_SETTINGS_H
#define DYLIBBUNDLER_SETTINGS_H
#include <string>
#include <vector>

View File

@ -299,27 +299,32 @@ std::string getUserInputDirForFile(const std::string& filename, const std::strin
}
}
void parseLoadCommands(const std::string& file, const std::string& cmd, const std::string& value, std::vector<std::string>& lines)
void otool(const std::string& flags, const std::string& file, std::vector<std::string>& lines)
{
std::string command = "otool -l " + file;
std::string command = "otool " + flags + " " + file;
std::string output = systemOutput(command);
if (output.find("can't open file") != std::string::npos
|| output.find("No such file") != std::string::npos
|| output.find("at least one file must be specified") != std::string::npos
|| output.empty()) {
|| output.find("No such file") != std::string::npos
|| output.find("at least one file must be specified") != std::string::npos
|| output.empty()) {
std::cerr << "\n\n/!\\ ERROR: Cannot find file " << file << " to read its load commands\n";
exit(1);
}
tokenize(output, "\n", &lines);
}
void parseLoadCommands(const std::string& file, const std::string& cmd, const std::string& value, std::vector<std::string>& lines)
{
std::vector<std::string> raw_lines;
tokenize(output, "\n", &raw_lines);
otool("-l", file, raw_lines);
bool searching = false;
std::string cmd_line = std::string("cmd ") + cmd;
std::string value_line = std::string(value + std::string(" "));
for (const auto& line : raw_lines) {
if (line.find(cmd_line) != std::string::npos) {
for (const auto& raw_line : raw_lines) {
if (raw_line.find(cmd_line) != std::string::npos) {
if (searching) {
std::cerr << "\n\n/!\\ ERROR: Failed to find " << value << " before next cmd\n";
exit(1);
@ -327,23 +332,62 @@ void parseLoadCommands(const std::string& file, const std::string& cmd, const st
searching = true;
}
else if (searching) {
size_t start_pos = line.find(value_line);
size_t start_pos = raw_line.find(value_line);
if (start_pos == std::string::npos)
continue;
size_t start = start_pos + value.size() + 1; // exclude data label "|value| "
size_t end = std::string::npos;
if (value == "name" || value == "path") {
size_t end_pos = line.find(" (");
size_t end_pos = raw_line.find(" (");
if (end_pos == std::string::npos)
continue;
end = end_pos - start;
}
lines.push_back(line.substr(start, end));
lines.push_back(raw_line.substr(start, end));
searching = false;
}
}
}
void parseLoadCommands(const std::string& file, const std::map<std::string,std::string>& cmds_values, std::map<std::string,std::vector<std::string>>& cmds_results)
{
std::vector<std::string> raw_lines;
otool("-l", file, raw_lines);
for (const auto& cmd_value : cmds_values) {
std::vector<std::string> lines;
std::string cmd = cmd_value.first;
std::string value = cmd_value.second;
std::string cmd_line = std::string("cmd ") + cmd;
std::string value_line = std::string(value) + std::string(" ");
bool searching = false;
for (const auto& raw_line : raw_lines) {
if (raw_line.find(cmd_line) != std::string::npos) {
if (searching) {
std::cerr << "\n\n/!\\ ERROR: Failed to find " << value << " before next cmd\n";
exit(1);
}
searching = true;
} else if (searching) {
size_t start_pos = raw_line.find(value_line);
if (start_pos == std::string::npos)
continue;
size_t start = start_pos + value.size() + 1; // exclude data label "|value| "
size_t end = std::string::npos;
if (value == "name" || value == "path") {
size_t end_pos = raw_line.find(" (");
if (end_pos == std::string::npos)
continue;
end = end_pos - start;
}
lines.push_back(raw_line.substr(start, end));
searching = false;
}
}
cmds_results[cmd] = lines;
}
}
std::string searchFilenameInRpaths(const std::string& rpath_file, const std::string& dependent_file)
{
if (Settings::verboseOutput()) {

View File

@ -1,8 +1,9 @@
#pragma once
#ifndef _utils_h_
#define _utils_h_
#ifndef DYLIBBUNDLER_UTILS_H
#define DYLIBBUNDLER_UTILS_H
#include <map>
#include <string>
#include <vector>
@ -44,7 +45,9 @@ void createDestDir();
std::string getUserInputDirForFile(const std::string& filename, const std::string& dependent_file);
void otool(const std::string& flags, const std::string& file, std::vector<std::string>& lines);
void parseLoadCommands(const std::string& file, const std::string& cmd, const std::string& value, std::vector<std::string>& lines);
void parseLoadCommands(const std::string& file, const std::map<std::string,std::string>& cmds_values, std::map<std::string,std::vector<std::string>>& cmds_results);
std::string searchFilenameInRpaths(const std::string& rpath_file, const std::string& dependent_file);
std::string searchFilenameInRpaths(const std::string& rpath_file);

View File

@ -126,7 +126,7 @@ int main(int argc, const char* argv[])
const std::vector<std::string> files_to_fix = Settings::filesToFix();
for (const auto& file_to_fix : files_to_fix) {
collectDependenciesForFile(file_to_fix);
collectDependencies(file_to_fix);
}
collectSubDependencies();
bundleDependencies();