如何在cmake中构建参数化的第三方库?
How do I build a parameterized third-party library in cmake?
我有一个项目,其中我将第三方库检出为 git 子模块。第三方库只是没有构建系统的源代码。此外,第三方库必须通过编译器定义在每个可执行文件的基础上进行配置,并有选择地只编译我需要的库部分。我在很多不同的存储库中使用这个库,所以我想制作一个可重用的组件来为任何特定的可执行文件生成库的实例化。
我目前尝试的方法是创建 generate_thirdparty.cmake
。该文件看起来像这样:
function(generate_thirdparty parameters)
# several calls to add_library for the different components of this third party library
# the generated libraries depend on the parameters argument
# the parameters configure compile definitions and which source files are compiled
endfunction()
然后在我的项目中 CMakeLists.txt
我有类似的东西:
cmake_minimum_required(VERSION 3.8)
project(test C)
include(generate_thirdparty.cmake)
set(parameters <some parameters here>)
generate_thirdparty(${parameters})
add_executable(my_exe main.c)
target_link_libraries(my_exe <library names from generate_thirdparty>)
我的方法似乎很有效,但我对您应该如何做到这一点感到困惑。我通读了其他帖子,看到有人建议使用 find_package
或 ExternalProject_add
。给定包含源代码但没有构建系统的第三方存储库,您无法控制它,您如何创建可重用的方式来构建该库,尤其是在必须对任何给定可执行文件进行参数化的情况下?
编辑:我希望能够灵活地在同一个项目中对库进行多个实例化。
总结一下:
- third-party 库不 提供自己的构建。
- 您需要在单个构建中对库进行多次实例化。
- 您在多个不同的存储库中使用这些实例化。
我认为您采取的方法几乎是正确的。为简洁起见,我们将 third-party 库称为 libFoo
。这是我认为你应该做的...
- 为
libFoo
创建一个 wrapper 存储库,其中包含一个 FindFoo.cmake
文件和旁边的实际 foo
存储库子模块。这是为了避免 FindFoo.cmake
的内容在您的各个项目中被独立版本化。
- 将 wrapper 作为子模块包含在依赖项目中,比如在目录
third_party/foo_wrapper
中
- 在那些依赖项目中,写:
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party/foo_wrapper")
find_package(Foo REQUIRED)
generate_foo(config1 ...)
generate_foo(config2 ...)
add_executable(app1 src/app1/main.cpp)
target_link_libraries(app1 PRIVATE foo::config1)
add_executable(app2 src/app2/main.cpp)
target_link_libraries(app2 PRIVATE foo::config2)
FindFoo.cmake
的内容就是:
cmake_minimum_required(VERSION 3.18)
function(generate_foo target)
# Use CMAKE_CURRENT_FUNCTION_LIST_DIR to reference foo's source files
# for example:
set(sources "src/src1.cpp" "src/src2.cpp" ...)
list(TRANSFORM sources PREPEND "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/foo/")
add_library(${target} ${sources})
add_library(foo::${target} ALIAS ${target})
# Do other things with ARGN
endfunction()
# Define a version for this dependency + script combo.
set(Foo_VERSION 0.1.0)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo VERSION_VAR Foo_VERSION
HANDLE_COMPONENTS)
CMAKE_CURRENT_FUNCTION_LIST_DIR
是包含被调用函数的文件的绝对路径。请记住,这是您的 wrapper 存储库的根目录,因此 libFoo
的实际源代码将位于相邻的 foo
目录中。 list(TRANSFORM)
允许我们在源列表中写入相对路径,为了 add_library
而将其转换为绝对路径(将相对路径传递给 add_library
将相对于 调用者的 源目录)。
我还创建了一个 ALIAS
目标,以便 generate_foo
的调用者可以 link 到别名。这很重要,因为 CMake 在需要库时将包含 ::
的名称视为目标。这非常有助于将拼写错误从无意的 linker 标志转变为 configure-time“找不到目标”错误。
然后我们像正常一样定义函数并调用 find_package_handle_standard_args
来处理 find_package
参数,例如 REQUIRED
、COMPONENTS
(甚至只是检查 none 被错误指定)和 VERSION
。请在此处查看文档:https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html
我有一个项目,其中我将第三方库检出为 git 子模块。第三方库只是没有构建系统的源代码。此外,第三方库必须通过编译器定义在每个可执行文件的基础上进行配置,并有选择地只编译我需要的库部分。我在很多不同的存储库中使用这个库,所以我想制作一个可重用的组件来为任何特定的可执行文件生成库的实例化。
我目前尝试的方法是创建 generate_thirdparty.cmake
。该文件看起来像这样:
function(generate_thirdparty parameters)
# several calls to add_library for the different components of this third party library
# the generated libraries depend on the parameters argument
# the parameters configure compile definitions and which source files are compiled
endfunction()
然后在我的项目中 CMakeLists.txt
我有类似的东西:
cmake_minimum_required(VERSION 3.8)
project(test C)
include(generate_thirdparty.cmake)
set(parameters <some parameters here>)
generate_thirdparty(${parameters})
add_executable(my_exe main.c)
target_link_libraries(my_exe <library names from generate_thirdparty>)
我的方法似乎很有效,但我对您应该如何做到这一点感到困惑。我通读了其他帖子,看到有人建议使用 find_package
或 ExternalProject_add
。给定包含源代码但没有构建系统的第三方存储库,您无法控制它,您如何创建可重用的方式来构建该库,尤其是在必须对任何给定可执行文件进行参数化的情况下?
编辑:我希望能够灵活地在同一个项目中对库进行多个实例化。
总结一下:
- third-party 库不 提供自己的构建。
- 您需要在单个构建中对库进行多次实例化。
- 您在多个不同的存储库中使用这些实例化。
我认为您采取的方法几乎是正确的。为简洁起见,我们将 third-party 库称为 libFoo
。这是我认为你应该做的...
- 为
libFoo
创建一个 wrapper 存储库,其中包含一个FindFoo.cmake
文件和旁边的实际foo
存储库子模块。这是为了避免FindFoo.cmake
的内容在您的各个项目中被独立版本化。 - 将 wrapper 作为子模块包含在依赖项目中,比如在目录
third_party/foo_wrapper
中
- 在那些依赖项目中,写:
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party/foo_wrapper")
find_package(Foo REQUIRED)
generate_foo(config1 ...)
generate_foo(config2 ...)
add_executable(app1 src/app1/main.cpp)
target_link_libraries(app1 PRIVATE foo::config1)
add_executable(app2 src/app2/main.cpp)
target_link_libraries(app2 PRIVATE foo::config2)
FindFoo.cmake
的内容就是:
cmake_minimum_required(VERSION 3.18)
function(generate_foo target)
# Use CMAKE_CURRENT_FUNCTION_LIST_DIR to reference foo's source files
# for example:
set(sources "src/src1.cpp" "src/src2.cpp" ...)
list(TRANSFORM sources PREPEND "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/foo/")
add_library(${target} ${sources})
add_library(foo::${target} ALIAS ${target})
# Do other things with ARGN
endfunction()
# Define a version for this dependency + script combo.
set(Foo_VERSION 0.1.0)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo VERSION_VAR Foo_VERSION
HANDLE_COMPONENTS)
CMAKE_CURRENT_FUNCTION_LIST_DIR
是包含被调用函数的文件的绝对路径。请记住,这是您的 wrapper 存储库的根目录,因此 libFoo
的实际源代码将位于相邻的 foo
目录中。 list(TRANSFORM)
允许我们在源列表中写入相对路径,为了 add_library
而将其转换为绝对路径(将相对路径传递给 add_library
将相对于 调用者的 源目录)。
我还创建了一个 ALIAS
目标,以便 generate_foo
的调用者可以 link 到别名。这很重要,因为 CMake 在需要库时将包含 ::
的名称视为目标。这非常有助于将拼写错误从无意的 linker 标志转变为 configure-time“找不到目标”错误。
然后我们像正常一样定义函数并调用 find_package_handle_standard_args
来处理 find_package
参数,例如 REQUIRED
、COMPONENTS
(甚至只是检查 none 被错误指定)和 VERSION
。请在此处查看文档:https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html