因此,在较新的 CMake 中,所有这些运行时依赖项查找魔法都非常令人讨厌,而且这根本不是他们的错。问题是你、我以及大约 90% 的 CMake 用户世界都已经查找模块wrong #这段时间 https://discourse.cmake.org/t/windows-libraries-find-modules-and-target-runtime-dlls-re-re-revisited/4286,现在我们的鸡已经回家了,因为,正如你可能已经发现的那样,GET_RUNTIME_DEPENDENCIES
/ RUNTIME_DEPENDENCY_SET
, $<TARGET_RUNTIME_DLLS>
如果你尝试将它们与已经(我现在知道)损坏的目标一起使用,将会完全把床弄脏IMPORTED
查找未正确设置的模块创建的依赖项。因此,上个月我在 CMake Discourse 论坛上发布了这篇文章(我之前的链接):
Windows 库、查找模块和 TARGET_RUNTIME_DLLS
Windows DLL Question™ 之前已经以这样或那样的形式出现过多次,但它被赋予了新的视角:$<TARGET_RUNTIME_DLLS>
,所以这里有一个新的看法。
如果您像我一样(从我在公共项目的源代码树中观察到的情况来看,大约 90% 的 CMake 用户/开发人员都像我一样),那么您在 Windows 上编写 Find 模块的方法可能已经是这样的:
- 在所有三个桌面平台上使用相同的代码
- 让 CMake 发现
.lib
/ .dll.a
导入库而不是实际的 DLL,使用find_library()
.
- 最终将目标创建为 UNKNOWN IMPORTED,因为如果您尝试仅使用导入库创建共享导入库目标,它将无法工作,但 UNKNOWN IMPORTED 工作得很好,所以嗯。
- 将导入库设置为目标
IMPORTED_LOCATION
因为这似乎工作正常。
- 今天到此为止,因为嘿——一切都编译好了。
这已经为我们所有人服务了很多年(实际上是几十年),所以我们基本上已经接受它作为 CMake 在 Windows 上的工作方式。
但现在出现了$<TARGET_RUNTIME_DLLS>
。如果您尝试在 Windows 上实际使用它,您可能会发现,虽然所有 CONFIG 模式包依赖项的 DLL 都被很好地捕获,但生成器表达式将愉快地忽略从编写的 Find 模块创建的任何目标就像我上面描述的那样。 ……这可能是其中的大多数。 (在我自己的库构建中,它是所有的,甚至是我没有写的。)
For $<TARGET_RUNTIME_DLLS>
为了工作,导入的目标必须被正确定义为共享库目标,并且它需要有它的IMPORTED_
属性设置正确:导入lib路径IMPORTED_IMPLIB
,DLL路径在IMPORTED_LOCATION
.
所以,现在我有了这个使用的新模块DLLTOOL.EXE
而且它很方便-I
标志来获取导入库的 DLL 的名称,然后使用find_program()
。 (仅仅是因为find_library()
与 DLL 不匹配,我想查看 PATH。我本来可以用find_file()
但我很确定我必须明确地给它更多的搜索路径。)
该宏有一个参数,即name已经配置的变量<prefix>_IMPLIB
. (Or <prefix>_IMPLIBS
,它与复数无关,并且将遵循您在命名其输出变量时输入使用的任何形式。)
您传递给它的名称的变量应该已经包含导入库的有效路径。通常由以下设置find_library()
,尽管我们一直将它们视为运行时库 (DLL),但实际上它们并非如此。
武装find_library(<prefix>_IMPLIB ...)
输出,implib_to_dll(<prefix>_IMPLIB)
将尝试发现并自动填充相应的变量<prefix>_LIBRARY
以及导入库的关联运行时 DLL 的路径。
将所有正确的变量设置为正确的值后,现在可以在 Windows 上正确配置共享导入库目标。$<TARGET_RUNTIME_DLLS>
然后可用于发现和操作由这些目标定义的一组 DLL。
Find 有点痛苦,而且确实感觉像是 CMake 至少可以半自动完成的事情。但是,至少目前它是有效的。
现在我只需重写所有查找模块即可使用它。叹。
ImplibUtils.cmake
#[=======================================================================[.rst:
IMPLIB_UTILS
------------
Tools for CMake on WIN32 to associate IMPORTED_IMPLIB paths (as discovered
by the :command:`find_library` command) with their IMPORTED_LOCATION DLLs.
Writing Find modules that create ``SHARED IMPORTED`` targets with the
correct ``IMPORTED_IMPLIB`` and ``IMPORTED_LOCATION`` properties is a
requirement for ``$<TARGET_RUNTIME_DLLS>`` to work correctly. (Probably
``IMPORTED_RUNTIME_DEPENDENCIES`` as well.)
Macros Provided
^^^^^^^^^^^^^^^
Currently the only tool here is ``implib_to_dll``. It takes a single
argument, the __name__ (_not_ value!) of a prefixed ``<prefix>_IMPLIB``
variable (containing the path to a ``.lib`` or ``.dll.a`` import library).
``implib_to_dll`` will attempt to locate the corresponding ``.dll`` file
for that import library, and set the variable ``<prefix>_LIBRARY``
to its location.
``implib_to_dll`` relies on the ``dlltool.exe`` utility. The path can
be set by defining ``DLLTOOL_EXECUTABLE`` in the cache prior to
including this module, if it is not set implib_utils will attempt to locate
``dlltool.exe`` using ``find_program()``.
Revision history
^^^^^^^^^^^^^^^^
2021-11-18 - Updated docs to remove CACHE mentions, fixed formatting
2021-10-14 - Initial version
Author: FeRD (Frank Dana) <[email protected] /cdn-cgi/l/email-protection>
License: CC0-1.0 (Creative Commons Universal Public Domain Dedication)
#]=======================================================================]
include_guard(DIRECTORY)
if (NOT WIN32)
# Nothing to do here!
return()
endif()
if (NOT DEFINED DLLTOOL_EXECUTABLE)
find_program(DLLTOOL_EXECUTABLE
NAMES dlltool dlltool.exe
DOC "The path to the DLLTOOL utility"
)
if (DLLTOOL_EXECUTABLE STREQUAL "DLLTOOL_EXECUTABLE-NOTFOUND")
message(WARNING "DLLTOOL not available, cannot continue")
return()
endif()
message(DEBUG "Found dlltool at ${DLLTOOL_EXECUTABLE}")
endif()
#
### Macro: implib_to_dll
#
# (Win32 only)
# Uses dlltool.exe to find the name of the dll associated with the
# supplied import library.
macro(implib_to_dll _implib_var)
set(_implib ${${_implib_var}})
set(_library_var "${_implib_var}")
# Automatically update the name, assuming it's in the correct format
string(REGEX REPLACE
[[_IMPLIBS$]] [[_LIBRARIES]]
_library_var "${_library_var}")
string(REGEX REPLACE
[[_IMPLIB$]] [[_LIBRARY]]
_library_var "${_library_var}")
# We can't use the input variable name without blowing away the
# previously-discovered contents, so that's a non-starter
if ("${_implib_var}" STREQUAL "${_library_var}")
message(ERROR "Name collision! You probably didn't pass "
"implib_to_dll() a correctly-formatted variable name. "
"Only <prefix>_IMPLIB or <prefix>_IMPLIBS is supported.")
return()
endif()
if(EXISTS "${_implib}")
message(DEBUG "Looking up dll name for import library ${_implib}")
execute_process(COMMAND
"${DLLTOOL_EXECUTABLE}" -I "${_implib}"
OUTPUT_VARIABLE _dll_name
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(DEBUG "DLLTOOL returned ${_dll_name}, finding...")
# Check the directory where the import lib is found
get_filename_component(_implib_dir ".." REALPATH
BASE_DIR "${_implib}")
message(DEBUG "Checking import lib directory ${_implib_dir}")
# Add a check in ../../bin/, relative to the import library
get_filename_component(_bindir "../../bin" REALPATH
BASE_DIR "${_implib}")
message(DEBUG "Also checking ${_bindir}")
find_program(${_library_var}
NAMES ${_dll_name}
HINTS
${_bindir}
${_implib_dir}
PATHS
ENV PATH
)
set(${_library_var} "${${_library_var}}" PARENT_SCOPE)
message(DEBUG "Set ${_library_var} to ${${_library_var}}")
endif()
endmacro()