file(GET_RUNTIME_DEPENDENCIES): Preserve casing for Windows PE binaries

For Windows PE files the `file(GET_RUNTIME_DEPENDENCIES)` command
converts the name of all  DLLs found during binary scanning to
lowercase in order to simplify the syntax requirements of its regex
filters; however, this has the side-effect of causing all DLL paths
returned via RESOLVED_DEPENDENCIES_VAR to be in lowercase, regardless
of their actual casing.

Instead, respect the original casing as closely as possible when
returning resolved dependencies after all filters have been
passed:

When evaluating a Windows PE format binary on a non-Windows host
the casing of dependencies recorded within the binary are
used. When the host is running Windows, the actual casing of the
dependencies on-disk are used instead.

Fixes: #23091
This commit is contained in:
Christian Heimlich
2023-03-06 13:48:24 -05:00
parent 14cfd6a1eb
commit fa45594407
6 changed files with 78 additions and 11 deletions

View File

@@ -3,7 +3,10 @@
#include "cmBinUtilsWindowsPELinker.h"
#include <algorithm>
#include <iterator>
#include <sstream>
#include <utility>
#include <vector>
#include <cm/memory>
@@ -16,6 +19,27 @@
#ifdef _WIN32
# include <windows.h>
# include "cmsys/Encoding.hxx"
#endif
#ifdef _WIN32
namespace {
void ReplaceWithActualNameCasing(std::string& path)
{
WIN32_FIND_DATAW findData;
HANDLE hFind = ::FindFirstFileW(
cmsys::Encoding::ToWindowsExtendedPath(path).c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
auto onDiskName = cmsys::Encoding::ToNarrow(findData.cFileName);
::FindClose(hFind);
path.replace(path.end() - onDiskName.size(), path.end(), onDiskName);
}
}
}
#endif
cmBinUtilsWindowsPELinker::cmBinUtilsWindowsPELinker(
@@ -60,29 +84,47 @@ bool cmBinUtilsWindowsPELinker::ScanDependencies(
if (!this->Tool->GetFileInfo(file, needed)) {
return false;
}
for (auto& n : needed) {
n = cmSystemTools::LowerCase(n);
}
struct WinPEDependency
{
WinPEDependency(std::string o)
: Original(std::move(o))
, LowerCase(cmSystemTools::LowerCase(Original))
{
}
std::string const Original;
std::string const LowerCase;
};
std::vector<WinPEDependency> depends;
depends.reserve(needed.size());
std::move(needed.begin(), needed.end(), std::back_inserter(depends));
std::string origin = cmSystemTools::GetFilenamePath(file);
for (auto const& lib : needed) {
if (!this->Archive->IsPreExcluded(lib)) {
for (auto const& lib : depends) {
if (!this->Archive->IsPreExcluded(lib.LowerCase)) {
std::string path;
bool resolved = false;
if (!this->ResolveDependency(lib, origin, path, resolved)) {
if (!this->ResolveDependency(lib.LowerCase, origin, path, resolved)) {
return false;
}
if (resolved) {
if (!this->Archive->IsPostExcluded(path)) {
#ifdef _WIN32
ReplaceWithActualNameCasing(path);
#else
path.replace(path.end() - lib.Original.size(), path.end(),
lib.Original);
#endif
bool unique;
this->Archive->AddResolvedPath(lib, path, unique);
this->Archive->AddResolvedPath(lib.Original, path, unique);
if (unique &&
!this->ScanDependencies(path, cmStateEnums::SHARED_LIBRARY)) {
return false;
}
}
} else {
this->Archive->AddUnresolvedPath(lib);
this->Archive->AddUnresolvedPath(lib.Original);
}
}
}

View File

@@ -784,6 +784,7 @@ if(DEFINED CMake_COMPILER_FORCES_NEW_DTAGS)
endif()
add_RunCMake_test(file-GET_RUNTIME_DEPENDENCIES
-DCMake_INSTALL_NAME_TOOL_BUG=${CMake_INSTALL_NAME_TOOL_BUG}
-DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
)
add_RunCMake_test(CPackCommandLine)

View File

@@ -9,6 +9,10 @@ function(run_install_test case)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --config Debug)
# Check "all" components.
set(CMAKE_INSTALL_PREFIX ${RunCMake_TEST_BINARY_DIR}/root-all)
set(maybe_stderr "${case}-all-stderr-${CMAKE_C_COMPILER_ID}.txt")
if(EXISTS "${RunCMake_SOURCE_DIR}/${maybe_stderr}")
set(RunCMake-stderr-file "${maybe_stderr}")
endif()
run_cmake_command(${case}-all ${CMAKE_COMMAND} --install . --prefix ${CMAKE_INSTALL_PREFIX} --config Debug)
endfunction()

View File

@@ -1,20 +1,29 @@
if(CMAKE_C_COMPILER_ID STREQUAL "Borland")
# Borland upper-cases dll names referenced in import libraries.
set(conflict_dll [[CONFLICT\.DLL]])
set(unresolved_dll [[UNRESOLVED\.DLL]])
else()
set(conflict_dll [[conflict\.dll]])
set(unresolved_dll [[unresolved\.dll]])
endif()
set(_check
[=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\.conflict/\.\./(lib)?libdir\.dll]=]
[=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\.search/(lib)?search\.dll]=]
[=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?mixedcase\.dll]=]
[=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?MixedCase\.dll]=]
[=[[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?testlib\.dll]=]
)
check_contents(deps/deps1.txt "^${_check}$")
check_contents(deps/deps2.txt "^${_check}$")
check_contents(deps/deps3.txt "^${_check}$")
set(_check
[=[(lib)?unresolved\.dll]=]
"(lib)?${unresolved_dll}"
)
check_contents(deps/udeps1.txt "^${_check}$")
check_contents(deps/udeps2.txt "^${_check}$")
check_contents(deps/udeps3.txt "^${_check}$")
set(_check
"^(lib)?conflict\\.dll:[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\\.conflict/(lib)?conflict\\.dll;[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?conflict\\.dll\n$"
"^(lib)?${conflict_dll}:[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/\\.conflict/(lib)?conflict\\.dll;[^;]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-build/root-all/bin/(lib)?conflict\\.dll\n$"
)
check_contents(deps/cdeps1.txt "${_check}")
check_contents(deps/cdeps2.txt "${_check}")

View File

@@ -0,0 +1,7 @@
^CMake Error at cmake_install\.cmake:[0-9]+ \(file\):
file Multiple conflicting paths found for PATH\.DLL:
[^
]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-build/root-all/lib/test1/path\.dll
[^
]*/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/windows-conflict-build/root-all/lib/test2/path\.dll$

View File

@@ -0,0 +1,4 @@
^CMake Error at cmake_install\.cmake:[0-9]+ \(file\):
file Could not resolve runtime dependencies:
UNRESOLVED\.DLL$