Commit 1f9ef251 authored by Kyle Edwards's avatar Kyle Edwards

file: Add GET_RUNTIME_DEPENDENCIES mode

Co-Authored-by: Bryon Bean's avatarBryon Bean <bryon.bean@kitware.com>
parent f3671911
This diff is collapsed.
get-runtime-dependencies
------------------------
* The :command:`file` command learned a new sub-command,
``GET_RUNTIME_DEPENDENCIES``, which allows you to recursively get the list of
libraries linked by an executable or library. This sub-command is intended as
a replacement for :module:`GetPrerequisites`.
......@@ -146,6 +146,28 @@ set(SRCS
cmArgumentParser.cxx
cmArgumentParser.h
cmBase32.cxx
cmBinUtilsLinker.cxx
cmBinUtilsLinker.h
cmBinUtilsLinuxELFGetRuntimeDependenciesTool.cxx
cmBinUtilsLinuxELFGetRuntimeDependenciesTool.h
cmBinUtilsLinuxELFLinker.cxx
cmBinUtilsLinuxELFLinker.h
cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.cxx
cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.h
cmBinUtilsMacOSMachOGetRuntimeDependenciesTool.cxx
cmBinUtilsMacOSMachOGetRuntimeDependenciesTool.h
cmBinUtilsMacOSMachOLinker.cxx
cmBinUtilsMacOSMachOLinker.h
cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool.cxx
cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool.h
cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool.cxx
cmBinUtilsWindowsPEDumpbinGetRuntimeDependenciesTool.h
cmBinUtilsWindowsPEGetRuntimeDependenciesTool.cxx
cmBinUtilsWindowsPEGetRuntimeDependenciesTool.h
cmBinUtilsWindowsPELinker.cxx
cmBinUtilsWindowsPELinker.h
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.cxx
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h
cmCacheManager.cxx
cmCacheManager.h
cmCLocaleEnvironmentScope.h
......@@ -295,6 +317,10 @@ set(SRCS
cmInstallTargetGenerator.cxx
cmInstallDirectoryGenerator.h
cmInstallDirectoryGenerator.cxx
cmLDConfigLDConfigTool.cxx
cmLDConfigLDConfigTool.h
cmLDConfigTool.cxx
cmLDConfigTool.h
cmLinkedTree.h
cmLinkItem.cxx
cmLinkItem.h
......@@ -360,6 +386,8 @@ set(SRCS
cmQtAutoRcc.h
cmRST.cxx
cmRST.h
cmRuntimeDependencyArchive.cxx
cmRuntimeDependencyArchive.h
cmScriptGenerator.h
cmScriptGenerator.cxx
cmSourceFile.cxx
......
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsLinker.h"
#include "cmRuntimeDependencyArchive.h"
cmBinUtilsLinker::cmBinUtilsLinker(cmRuntimeDependencyArchive* archive)
: Archive(archive)
{
}
void cmBinUtilsLinker::SetError(const std::string& e)
{
this->Archive->SetError(e);
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmBinUtilsLinker_h
#define cmBinUtilsLinker_h
#include "cmStateTypes.h"
#include <string>
class cmRuntimeDependencyArchive;
class cmBinUtilsLinker
{
public:
cmBinUtilsLinker(cmRuntimeDependencyArchive* archive);
virtual ~cmBinUtilsLinker() = default;
virtual bool Prepare() { return true; }
virtual bool ScanDependencies(std::string const& file,
cmStateEnums::TargetType type) = 0;
protected:
cmRuntimeDependencyArchive* Archive;
void SetError(const std::string& e);
};
#endif // cmBinUtilsLinker_h
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsLinuxELFGetRuntimeDependenciesTool.h"
#include "cmRuntimeDependencyArchive.h"
cmBinUtilsLinuxELFGetRuntimeDependenciesTool::
cmBinUtilsLinuxELFGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive)
: Archive(archive)
{
}
void cmBinUtilsLinuxELFGetRuntimeDependenciesTool::SetError(
const std::string& error)
{
this->Archive->SetError(error);
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmBinUtilsLinuxELFGetRuntimeDependenciesTool_h
#define cmBinUtilsLinuxELFGetRuntimeDependenciesTool_h
#include <string>
#include <vector>
class cmRuntimeDependencyArchive;
class cmBinUtilsLinuxELFGetRuntimeDependenciesTool
{
public:
cmBinUtilsLinuxELFGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive);
virtual ~cmBinUtilsLinuxELFGetRuntimeDependenciesTool() = default;
virtual bool GetFileInfo(std::string const& file,
std::vector<std::string>& needed,
std::vector<std::string>& rpaths,
std::vector<std::string>& runpaths) = 0;
protected:
cmRuntimeDependencyArchive* Archive;
void SetError(const std::string& e);
};
#endif // cmBinUtilsLinuxELFGetRuntimeDependenciesTool_h
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsLinuxELFLinker.h"
#include "cmAlgorithms.h"
#include "cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.h"
#include "cmLDConfigLDConfigTool.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmRuntimeDependencyArchive.h"
#include "cmSystemTools.h"
#include <cmsys/RegularExpression.hxx>
#include <memory>
#include <sstream>
static std::string ReplaceOrigin(const std::string& rpath,
const std::string& origin)
{
static const cmsys::RegularExpression originRegex(
"(\\$ORIGIN)([^a-zA-Z0-9_]|$)");
static const cmsys::RegularExpression originCurlyRegex("\\${ORIGIN}");
cmsys::RegularExpressionMatch match;
if (originRegex.find(rpath.c_str(), match)) {
std::string begin = rpath.substr(0, match.start(1));
std::string end = rpath.substr(match.end(1));
return begin + origin + end;
}
if (originCurlyRegex.find(rpath.c_str(), match)) {
std::string begin = rpath.substr(0, match.start());
std::string end = rpath.substr(match.end());
return begin + origin + end;
}
return rpath;
}
cmBinUtilsLinuxELFLinker::cmBinUtilsLinuxELFLinker(
cmRuntimeDependencyArchive* archive)
: cmBinUtilsLinker(archive)
{
}
bool cmBinUtilsLinuxELFLinker::Prepare()
{
std::string tool = this->Archive->GetGetRuntimeDependenciesTool();
if (tool.empty()) {
tool = "objdump";
}
if (tool == "objdump") {
this->Tool =
cm::make_unique<cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool>(
this->Archive);
} else {
std::ostringstream e;
e << "Invalid value for CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL: " << tool;
this->SetError(e.str());
return false;
}
std::string ldConfigTool =
this->Archive->GetMakefile()->GetSafeDefinition("CMAKE_LDCONFIG_TOOL");
if (ldConfigTool.empty()) {
ldConfigTool = "ldconfig";
}
if (ldConfigTool == "ldconfig") {
this->LDConfigTool =
cm::make_unique<cmLDConfigLDConfigTool>(this->Archive);
} else {
std::ostringstream e;
e << "Invalid value for CMAKE_LDCONFIG_TOOL: " << ldConfigTool;
this->SetError(e.str());
return false;
}
return true;
}
bool cmBinUtilsLinuxELFLinker::ScanDependencies(
std::string const& file, cmStateEnums::TargetType /* unused */)
{
std::vector<std::string> parentRpaths;
return this->ScanDependencies(file, parentRpaths);
}
bool cmBinUtilsLinuxELFLinker::ScanDependencies(
std::string const& file, std::vector<std::string> const& parentRpaths)
{
std::string origin = cmSystemTools::GetFilenamePath(file);
std::vector<std::string> needed;
std::vector<std::string> rpaths;
std::vector<std::string> runpaths;
if (!this->Tool->GetFileInfo(file, needed, rpaths, runpaths)) {
return false;
}
for (auto& runpath : runpaths) {
runpath = ReplaceOrigin(runpath, origin);
}
for (auto& rpath : rpaths) {
rpath = ReplaceOrigin(rpath, origin);
}
std::vector<std::string> searchPaths;
if (!runpaths.empty()) {
searchPaths = runpaths;
} else {
searchPaths = rpaths;
searchPaths.insert(searchPaths.end(), parentRpaths.begin(),
parentRpaths.end());
}
std::vector<std::string> ldConfigPaths;
if (!this->LDConfigTool->GetLDConfigPaths(ldConfigPaths)) {
return false;
}
searchPaths.insert(searchPaths.end(), ldConfigPaths.begin(),
ldConfigPaths.end());
for (auto const& dep : needed) {
if (!this->Archive->IsPreExcluded(dep)) {
std::string path;
bool resolved = false;
if (dep.find('/') != std::string::npos) {
this->SetError("Paths to dependencies are not supported");
return false;
}
if (!this->ResolveDependency(dep, searchPaths, path, resolved)) {
return false;
}
if (resolved) {
if (!this->Archive->IsPostExcluded(path)) {
bool unique;
this->Archive->AddResolvedPath(dep, path, unique);
if (unique && !this->ScanDependencies(path, rpaths)) {
return false;
}
}
} else {
this->Archive->AddUnresolvedPath(dep);
}
}
}
return true;
}
bool cmBinUtilsLinuxELFLinker::ResolveDependency(
std::string const& name, std::vector<std::string> const& searchPaths,
std::string& path, bool& resolved)
{
for (auto const& searchPath : searchPaths) {
path = searchPath + '/' + name;
if (cmSystemTools::PathExists(path)) {
resolved = true;
return true;
}
}
for (auto const& searchPath : this->Archive->GetSearchDirectories()) {
path = searchPath + '/' + name;
if (cmSystemTools::PathExists(path)) {
std::ostringstream warning;
warning << "Dependency " << name << " found in search directory:\n "
<< searchPath
<< "\nSee file(GET_RUNTIME_DEPENDENCIES) documentation for "
<< "more information.";
this->Archive->GetMakefile()->IssueMessage(MessageType::WARNING,
warning.str());
resolved = true;
return true;
}
}
resolved = false;
return true;
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmBinUtilsLinuxELFLinker_h
#define cmBinUtilsLinuxELFLinker_h
#include "cmBinUtilsLinker.h"
#include "cmBinUtilsLinuxELFGetRuntimeDependenciesTool.h"
#include "cmLDConfigTool.h"
#include "cmStateTypes.h"
#include <memory> // IWYU pragma: keep
#include <string>
#include <vector>
class cmRuntimeDependencyArchive;
class cmBinUtilsLinuxELFLinker : public cmBinUtilsLinker
{
public:
cmBinUtilsLinuxELFLinker(cmRuntimeDependencyArchive* archive);
bool Prepare() override;
bool ScanDependencies(std::string const& file,
cmStateEnums::TargetType type) override;
private:
std::unique_ptr<cmBinUtilsLinuxELFGetRuntimeDependenciesTool> Tool;
std::unique_ptr<cmLDConfigTool> LDConfigTool;
bool HaveLDConfigPaths = false;
std::vector<std::string> LDConfigPaths;
bool ScanDependencies(std::string const& file,
std::vector<std::string> const& parentRpaths);
bool ResolveDependency(std::string const& name,
std::vector<std::string> const& searchPaths,
std::string& path, bool& resolved);
bool GetLDConfigPaths();
};
#endif // cmBinUtilsLinuxELFLinker_h
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.h"
#include "cmRuntimeDependencyArchive.h"
#include "cmSystemTools.h"
#include "cmUVProcessChain.h"
#include <cmsys/RegularExpression.hxx>
#include <sstream>
cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool::
cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive)
: cmBinUtilsLinuxELFGetRuntimeDependenciesTool(archive)
{
}
bool cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool::GetFileInfo(
std::string const& file, std::vector<std::string>& needed,
std::vector<std::string>& rpaths, std::vector<std::string>& runpaths)
{
cmUVProcessChainBuilder builder;
builder.SetBuiltinStream(cmUVProcessChainBuilder::Stream_OUTPUT);
std::vector<std::string> command;
if (!this->Archive->GetGetRuntimeDependenciesCommand("objdump", command)) {
this->SetError("Could not find objdump");
return false;
}
command.emplace_back("-p");
command.push_back(file);
builder.AddCommand(command);
auto process = builder.Start();
if (!process.Valid()) {
std::ostringstream e;
e << "Failed to start objdump process for:\n " << file;
this->SetError(e.str());
return false;
}
std::string line;
static const cmsys::RegularExpression neededRegex("^ *NEEDED *([^\n]*)$");
static const cmsys::RegularExpression rpathRegex("^ *RPATH *([^\n]*)$");
static const cmsys::RegularExpression runpathRegex("^ *RUNPATH *([^\n]*)$");
while (std::getline(*process.OutputStream(), line)) {
cmsys::RegularExpressionMatch match;
if (neededRegex.find(line.c_str(), match)) {
needed.push_back(match.match(1));
} else if (rpathRegex.find(line.c_str(), match)) {
std::vector<std::string> rpathSplit =
cmSystemTools::SplitString(match.match(1), ':');
rpaths.reserve(rpaths.size() + rpathSplit.size());
for (auto const& rpath : rpathSplit) {
rpaths.push_back(rpath);
}
} else if (runpathRegex.find(line.c_str(), match)) {
std::vector<std::string> runpathSplit =
cmSystemTools::SplitString(match.match(1), ':');
runpaths.reserve(runpaths.size() + runpathSplit.size());
for (auto const& runpath : runpathSplit) {
runpaths.push_back(runpath);
}
}
}
if (!process.Wait()) {
std::ostringstream e;
e << "Failed to wait on objdump process for:\n " << file;
this->SetError(e.str());
return false;
}
auto status = process.GetStatus();
if (!status[0] || status[0]->ExitStatus != 0) {
std::ostringstream e;
e << "Failed to run objdump on:\n " << file;
this->SetError(e.str());
return false;
}
return true;
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmBinUtilsLinuxELFGetRuntimeCollectDependenciesTool_h
#define cmBinUtilsLinuxELFGetRuntimeCollectDependenciesTool_h
#include "cmBinUtilsLinuxELFGetRuntimeDependenciesTool.h"
#include <string>
#include <vector>
class cmRuntimeDependencyArchive;
class cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool
: public cmBinUtilsLinuxELFGetRuntimeDependenciesTool
{
public:
cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive);
bool GetFileInfo(std::string const& file, std::vector<std::string>& needed,
std::vector<std::string>& rpaths,
std::vector<std::string>& runpaths) override;
};
#endif // cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool_h
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsMacOSMachOGetRuntimeDependenciesTool.h"
#include "cmRuntimeDependencyArchive.h"
cmBinUtilsMacOSMachOGetRuntimeDependenciesTool::
cmBinUtilsMacOSMachOGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive)
: Archive(archive)
{
}
void cmBinUtilsMacOSMachOGetRuntimeDependenciesTool::SetError(
const std::string& error)
{
this->Archive->SetError(error);
}
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#ifndef cmBinUtilsMacOSMachOGetRuntimeDependenciesTool_h
#define cmBinUtilsMacOSMachOGetRuntimeDependenciesTool_h
#include <string>
#include <vector>
class cmRuntimeDependencyArchive;
class cmBinUtilsMacOSMachOGetRuntimeDependenciesTool
{
public:
cmBinUtilsMacOSMachOGetRuntimeDependenciesTool(
cmRuntimeDependencyArchive* archive);
virtual ~cmBinUtilsMacOSMachOGetRuntimeDependenciesTool() = default;
virtual bool GetFileInfo(std::string const& file,
std::vector<std::string>& libs,
std::vector<std::string>& rpaths) = 0;
protected:
cmRuntimeDependencyArchive* Archive;
void SetError(const std::string& error);
};
#endif // cmBinUtilsMacOSMachOGetRuntimeDependenciesTool_h
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmBinUtilsMacOSMachOLinker.h"
#include "cmAlgorithms.h"
#include "cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool.h"
#include "cmRuntimeDependencyArchive.h"
#include "cmSystemTools.h"
#include <sstream>
#include <string>
#include <vector>
cmBinUtilsMacOSMachOLinker::cmBinUtilsMacOSMachOLinker(
cmRuntimeDependencyArchive* archive)
: cmBinUtilsLinker(archive)
{
}
bool cmBinUtilsMacOSMachOLinker::Prepare()
{
std::string tool = this->Archive->GetGetRuntimeDependenciesTool();
if (tool.empty()) {
tool = "otool";
}
if (tool == "otool") {
this->Tool =
cm::make_unique<cmBinUtilsMacOSMachOOToolGetRuntimeDependenciesTool>(
this->Archive);
} else {
std::ostringstream e;
e << "Invalid value for CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL: " << tool;
this->SetError(e.str());
return false;
}
return true;
}
bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
std::string const& file, cmStateEnums::TargetType type)
{
std::string executableFile;
if (type == cmStateEnums::EXECUTABLE) {
executableFile = file;
} else {
executableFile = this->Archive->GetBundleExecutable();
}
std::string executablePath;
if (!executableFile.empty()) {
executablePath = cmSystemTools::GetFilenamePath(executableFile);
}
return this->ScanDependencies(file, executablePath);
}
bool cmBinUtilsMacOSMachOLinker::ScanDependencies(
std::string const& file, std::string const& executablePath)
{
std::vector<std::string> libs, rpaths;
if (!this->Tool->GetFileInfo(file, libs, rpaths)) {
return false;
}
std::string loaderPath = cmSystemTools::GetFilenamePath(file);
return this->GetFileDependencies(libs, executablePath, loaderPath, rpaths);
}
bool cmBinUtilsMacOSMachOLinker::GetFileDependencies(
std::vector<std::string> const& names, std::string const& executablePath,
std::string const& loaderPath, std::vector<std::string> const& rpaths)
{
for (std::string const& name : names) {
if (!this->Archive->IsPreExcluded(name)) {
std::string path;
bool resolved;
if (!this->ResolveDependency(name, executablePath, loaderPath, rpaths,
path, resolved)) {
return false;
}
if (resolved) {
if (!this->Archive->IsPostExcluded(path)) {
auto filename = cmSystemTools::GetFilenameName(path);
bool unique;
this->Archive->AddResolvedPath(filename, path, unique);
if (unique && !this->ScanDependencies(path, executablePath)) {
return false;
}
}
} else {
this->Archive->AddUnresolvedPath(name);
}
}
}
return true;
}
bool cmBinUtilsMacOSMachOLinker::ResolveDependency(
std::string const& name, std::string const& executablePath,
std::string const& loaderPath, std::vector<std::string> const& rpaths,
std::string& path, bool& resolved)
{
resolved = false;
if (cmHasLiteralPrefix(name, "@rpath/")) {
if (!this->ResolveRPathDependency(name, executablePath, loaderPath, rpaths,
path, resolved)) {
return false;
}
} else if (cmHasLiteralPrefix(name, "@loader_path/")) {
if (!this->ResolveLoaderPathDependency(name, loaderPath, path, resolved)) {
return false;
}
} else if (cmHasLiteralPrefix(name, "@executable_path/")) {
if (!this->ResolveExecutablePathDependency(name, executablePath, path,
resolved)) {
return false;
}
} else {
resolved = true;
path = name;
}
if (resolved && !cmSystemTools::FileIsFullPath(path)) {
this->SetError("Resolved path is not absolute");
return false;
}
return true;
}
bool cmBinUtilsMacOSMachOLinker::ResolveExecutablePathDependency(
std::string const& name, std::string const& executablePath,
std::string& path, bool& resolved)
{