如何在我的 CMake 项目中使用需要先安装的库?
How to use libraries within my CMake project that need to be installed first?
我的 CMake 构建系统有问题。有 CMakeLists.txt
文件定义 运行 次或库或使用 ExternalProjects_Add()
下载和构建外部代码。由于存在依赖关系,这些项目必须相互寻找。现在我想在顶层有一个 CMakeLists.txt
来一次构建所有这些。为了找到一个项目,必须安装。但是查找项目已经在配置时在 CMake 中完成。
repository
├─project
│ ├─game (Depends on engine, uses EngineConfig.cmake once installed)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ ├─src
│ │ └─textures
│ ├─engine (Depends on boost, uses built-in FindBoost.cmake)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ └─src
│ ├─boost (Not the source code, just an ExternalProject_Add call)
│ : └─CMakeLists.txt
│
├─build
│ ├─game
│ ├─engine
│ ├─boost (Source will be downloaded and built here)
│ : ├─download
│ ├─source
│ :
│
├─install
│ ├─game
│ │ ├─bin
│ │ └─textures
│ ├─engine
│ │ ├─include
│ │ │ └─engine
│ │ │ ├─EngineConfig.cmake (Needed to find the library)
│ │ │ :
│ │ │
│ │ └─lib
│ ├─boost (Layout is up to the external library)
│ : └─ ...
│
└─CMakeLists.txt (Calls add_subdirectory for all inside the project folder)
运行 每个项目的 CMake 进程: 使用 execute_process(${CMAKE_COMMAND} ...)
,我可以在配置时配置和构建每个项目。但是,这意味着我在编辑代码后总是必须 运行 CMake,并且无法从 IDE 我生成的项目文件中进行编译。
链接到 CMake 目标: 运行为所有外部库设置一个 CMake 进程是可以的,因为我不处理它们。可以通过使用目标名称调用 target_link_libraries()
来使用我自己的库。但是,链接是不够的。我的库包括外部库的目录。这些也必须提供给使用项目。
如何在我的 CMake 项目中使用需要先安装的库?
从 engine
项目导出库时,您需要指定其包含目录。下面的代码是对 http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages 中提供的示例的简化。调整路径以使用安装前缀 install/engine
构建和安装 engine
组件。
engine/CMakeLists.txt:
...
install(TARGETS engine EXPORT engineTargets
DESTINATION lib
INCLUDES DESTINATION include
)
set(ConfigPackageLocation lib/cmake/engine)
install(EXPORT engineTargets
FILE EngineTargets.cmake
DESTINATION ${ConfigPackageLocation}
)
install(FILES cmake/EngineConfig.cmake
DESTINATION ${ConfigPackageLocation}
)
engine/cmake/EngineConfig.cmake:
include("${CMAKE_CURRENT_LIST_DIR}/EngineTargets.cmake")
这提供了导出目标的接口。所以当它被可执行文件链接时,可执行文件得到正确的 INCLUDE_DIRECTORIES
属性:
CMakeLists.txt:
# Need for `find_package` to find `EngineConfig.cmake`.
set(CMAKE_PREFIX_PATH <path-pointed-to-install/engine>)
game/CMakeLists.txt:
find_package(Engine)
add_executable(game ...)
target_link_libraries(game engine)
您可以将您的项目分为三组:
- 您未在此超级项目中处理的外部依赖项
- 您正在处理的项目太复杂而无法将它们添加为子目录,例如目标太多或其他原因。 (您的示例中似乎没有这样的项目。)
- 您正在处理的项目:这些将作为超级项目的子目录添加。
在配置超级项目之前,您需要配置、构建和安装组#1 和#2 中的项目:
- 您可以在 运行 超级项目的
CMakeLists.txt
之前完成,例如,从 shell-script
- 或者,正如您提到的,在超级项目的
CMakeLists.txt
中,使用 execute_process(${CMAKE_COMMAND} ...)
。您可以使用适当的 find_package(... QUIET)
命令的结果有条件地执行此操作。
您需要决定第 3 组中的项目,如 engine
将仅用于将它们用作子目录的项目,或者您打算将它们用作独立的库,构建在自己的构建树中。
此外,您提到:"My libraries include directories of external libraries"。让我们涵盖 engine
可以依赖的所有此类可能的库:
- 说,
LIB1
和 LIB2
是私有的,public 外部依赖 engine
及其配置模块导出老式 LIB1_*
和 LIB2_*
变量
LIB3
和 LIB4
是私有的,public 外部依赖 engine
及其配置模块导出 LIB3
和 LIB4
导入 个库
通过public和私有依赖项我的意思是在engine
.
的接口上是否使用了特定的库。
现在,如果 engine
仅用作子目录,则 engine/CMakeLists.txt
的相关部分是:
add_library(engine ...)
target_include_directories(engine
PRIVATE
${LIB1_INCLUDE_DIRS}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
${LIB2_INCLUDE_DIRS})
target_compiled_definitions(engine
PRIVATE ${LIB1_DEFINITIONS}
PUBLIC ${LIB2_DEFINITIONS})
target_link_libraries(engine
PRIVATE LIB3
PUBLIC LIB4)
在repository/CMakeLists.txt
中:
add_subdirectory(engine)
add_subdirectory(game)
在game/CMakeLists.txt
中:
add_executable(game ...)
target_link_libraries(game engine)
引擎及其 public 依赖项的包含目录将正确转发到 game
。
如果 engine
也将构建在它自己的构建树中(在另一个项目中),您需要将导出代码添加到 engine/CMakeLists.txt
并且可能需要一个调用 [=40 的自定义配置模块=](或find_dependency
)的依赖项。有关详细信息,请参阅 How to use CMake to find and link to a library using install-export and find_package?。该答案中未讨论的一个问题是在库的配置模块中查找库的依赖项:
引用的 SO 答案只是安装由 install(EXPORT ...)
命令生成的 <lib>-targets.cmake
脚本作为配置模块:
install(EXPORT engine-targets
FILE engine-config.cmake
DESTINATION lib/cmake/engine)
当 engine
没有更多依赖项时,此解决方案很好。如果有,则需要在config模块的开头找到它们,这应该是手动编写的。
engine/engine-config.cmake:
include(CMakeFindDependencyMacro)
find_dependency(some-dep-of-engine)
include(${CMAKE_CURRENT_LIST_DIR}/engine-targets.cmake)
在engine/CMakeLists.txt
中:
install(EXPORT engine-targets
FILE engine-targets.cmake
DESTINATION lib/cmake/engine)
install(FILES engine-config.cmake
DESTINATION lib/cmake/engine)
注:CMake 3.0 引入了CMakeFindDependencyMacro
。对于较旧的 CMake,您可以使用 find_package
而不是 find_dependency
(QUIET 和 REQUIRED 选项的处理将不会转发到依赖项)。
谢谢@Tsyvarev and @tamas.kenez you for the two good answers. I ended up using the super-build pattern。顶级项目在配置时不会做太多事情。在构建时,它运行外部 CMake 进程来配置、构建和安装项目。
通常,这是使用 ExternalProject_Add()
而不是 add_subdirectory()
来添加项目。我发现 add_custom_command()
工作得更好,因为它不会在后台执行其他任务,例如创建图章文件等。
# add_project(<project> [DEPENDS project...])
function(add_project PROJECT)
cmake_parse_arguments(PARAM "" "" "DEPENDS" ${ARGN})
add_custom_target(${PROJECT} ALL DEPENDS ${PARAM_DEPENDS})
# Paths for this project
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT})
set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT})
set(INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/${PROJECT})
# Configure
escape_list(CMAKE_MODULE_PATH)
escape_list(CMAKE_PREFIX_PATH)
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--no-warn-unused-cli
"-DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH_ESCAPED}"
"-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH_ESCAPED}"
-DCMAKE_BINARY_DIR=${BUILD_DIR}
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
${SOURCE_DIR}
WORKING_DIRECTORY ${BUILD_DIR})
# Build
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--build .
--target install
WORKING_DIRECTORY ${BUILD_DIR})
# Help later find_package() calls
append_global(CMAKE_PREFIX_PATH ${INSTALL_DIR})
endfunction()
这是两个辅助函数。我花了很长时间才弄清楚将列表参数传递给其他 CMake 进程的正确方法,而不会将它们解释为多个参数。
# escape_list(<list-name>)
function(escape_list LIST_NAME)
string(REPLACE ";" "\;" ${LIST_NAME}_ESCAPED "${${LIST_NAME}}")
set(${LIST_NAME}_ESCAPED "${${LIST_NAME}_ESCAPED}" PARENT_SCOPE)
endfunction()
# append_global(<name> value...)
function(append_global NAME)
set(COMBINED "${${NAME}}" "${ARGN}")
list(REMOVE_DUPLICATES COMBINED)
set(${NAME} "${COMBINED}" CACHE INTERNAL "" FORCE)
endfunction()
唯一的缺点是每个项目都需要有一个安装目标。因此,您需要向没有安装命令的项目添加像 install(CODE "")
这样的虚拟安装命令,例如那些刚刚打电话的人ExternalProject_Add
。
我的 CMake 构建系统有问题。有 CMakeLists.txt
文件定义 运行 次或库或使用 ExternalProjects_Add()
下载和构建外部代码。由于存在依赖关系,这些项目必须相互寻找。现在我想在顶层有一个 CMakeLists.txt
来一次构建所有这些。为了找到一个项目,必须安装。但是查找项目已经在配置时在 CMake 中完成。
repository
├─project
│ ├─game (Depends on engine, uses EngineConfig.cmake once installed)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ ├─src
│ │ └─textures
│ ├─engine (Depends on boost, uses built-in FindBoost.cmake)
│ │ ├─CMakeLists.txt
│ │ ├─include
│ │ └─src
│ ├─boost (Not the source code, just an ExternalProject_Add call)
│ : └─CMakeLists.txt
│
├─build
│ ├─game
│ ├─engine
│ ├─boost (Source will be downloaded and built here)
│ : ├─download
│ ├─source
│ :
│
├─install
│ ├─game
│ │ ├─bin
│ │ └─textures
│ ├─engine
│ │ ├─include
│ │ │ └─engine
│ │ │ ├─EngineConfig.cmake (Needed to find the library)
│ │ │ :
│ │ │
│ │ └─lib
│ ├─boost (Layout is up to the external library)
│ : └─ ...
│
└─CMakeLists.txt (Calls add_subdirectory for all inside the project folder)
运行 每个项目的 CMake 进程: 使用 execute_process(${CMAKE_COMMAND} ...)
,我可以在配置时配置和构建每个项目。但是,这意味着我在编辑代码后总是必须 运行 CMake,并且无法从 IDE 我生成的项目文件中进行编译。
链接到 CMake 目标: 运行为所有外部库设置一个 CMake 进程是可以的,因为我不处理它们。可以通过使用目标名称调用 target_link_libraries()
来使用我自己的库。但是,链接是不够的。我的库包括外部库的目录。这些也必须提供给使用项目。
如何在我的 CMake 项目中使用需要先安装的库?
从 engine
项目导出库时,您需要指定其包含目录。下面的代码是对 http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages 中提供的示例的简化。调整路径以使用安装前缀 install/engine
构建和安装 engine
组件。
engine/CMakeLists.txt:
...
install(TARGETS engine EXPORT engineTargets
DESTINATION lib
INCLUDES DESTINATION include
)
set(ConfigPackageLocation lib/cmake/engine)
install(EXPORT engineTargets
FILE EngineTargets.cmake
DESTINATION ${ConfigPackageLocation}
)
install(FILES cmake/EngineConfig.cmake
DESTINATION ${ConfigPackageLocation}
)
engine/cmake/EngineConfig.cmake:
include("${CMAKE_CURRENT_LIST_DIR}/EngineTargets.cmake")
这提供了导出目标的接口。所以当它被可执行文件链接时,可执行文件得到正确的 INCLUDE_DIRECTORIES
属性:
CMakeLists.txt:
# Need for `find_package` to find `EngineConfig.cmake`.
set(CMAKE_PREFIX_PATH <path-pointed-to-install/engine>)
game/CMakeLists.txt:
find_package(Engine)
add_executable(game ...)
target_link_libraries(game engine)
您可以将您的项目分为三组:
- 您未在此超级项目中处理的外部依赖项
- 您正在处理的项目太复杂而无法将它们添加为子目录,例如目标太多或其他原因。 (您的示例中似乎没有这样的项目。)
- 您正在处理的项目:这些将作为超级项目的子目录添加。
在配置超级项目之前,您需要配置、构建和安装组#1 和#2 中的项目:
- 您可以在 运行 超级项目的
CMakeLists.txt
之前完成,例如,从 shell-script - 或者,正如您提到的,在超级项目的
CMakeLists.txt
中,使用execute_process(${CMAKE_COMMAND} ...)
。您可以使用适当的find_package(... QUIET)
命令的结果有条件地执行此操作。
您需要决定第 3 组中的项目,如 engine
将仅用于将它们用作子目录的项目,或者您打算将它们用作独立的库,构建在自己的构建树中。
此外,您提到:"My libraries include directories of external libraries"。让我们涵盖 engine
可以依赖的所有此类可能的库:
- 说,
LIB1
和LIB2
是私有的,public 外部依赖engine
及其配置模块导出老式LIB1_*
和LIB2_*
变量 LIB3
和LIB4
是私有的,public 外部依赖engine
及其配置模块导出LIB3
和LIB4
导入 个库
通过public和私有依赖项我的意思是在engine
.
现在,如果 engine
仅用作子目录,则 engine/CMakeLists.txt
的相关部分是:
add_library(engine ...)
target_include_directories(engine
PRIVATE
${LIB1_INCLUDE_DIRS}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
${LIB2_INCLUDE_DIRS})
target_compiled_definitions(engine
PRIVATE ${LIB1_DEFINITIONS}
PUBLIC ${LIB2_DEFINITIONS})
target_link_libraries(engine
PRIVATE LIB3
PUBLIC LIB4)
在repository/CMakeLists.txt
中:
add_subdirectory(engine)
add_subdirectory(game)
在game/CMakeLists.txt
中:
add_executable(game ...)
target_link_libraries(game engine)
引擎及其 public 依赖项的包含目录将正确转发到 game
。
如果 engine
也将构建在它自己的构建树中(在另一个项目中),您需要将导出代码添加到 engine/CMakeLists.txt
并且可能需要一个调用 [=40 的自定义配置模块=](或find_dependency
)的依赖项。有关详细信息,请参阅 How to use CMake to find and link to a library using install-export and find_package?。该答案中未讨论的一个问题是在库的配置模块中查找库的依赖项:
引用的 SO 答案只是安装由 install(EXPORT ...)
命令生成的 <lib>-targets.cmake
脚本作为配置模块:
install(EXPORT engine-targets
FILE engine-config.cmake
DESTINATION lib/cmake/engine)
当 engine
没有更多依赖项时,此解决方案很好。如果有,则需要在config模块的开头找到它们,这应该是手动编写的。
engine/engine-config.cmake:
include(CMakeFindDependencyMacro)
find_dependency(some-dep-of-engine)
include(${CMAKE_CURRENT_LIST_DIR}/engine-targets.cmake)
在engine/CMakeLists.txt
中:
install(EXPORT engine-targets
FILE engine-targets.cmake
DESTINATION lib/cmake/engine)
install(FILES engine-config.cmake
DESTINATION lib/cmake/engine)
注:CMake 3.0 引入了CMakeFindDependencyMacro
。对于较旧的 CMake,您可以使用 find_package
而不是 find_dependency
(QUIET 和 REQUIRED 选项的处理将不会转发到依赖项)。
谢谢@Tsyvarev and @tamas.kenez you for the two good answers. I ended up using the super-build pattern。顶级项目在配置时不会做太多事情。在构建时,它运行外部 CMake 进程来配置、构建和安装项目。
通常,这是使用 ExternalProject_Add()
而不是 add_subdirectory()
来添加项目。我发现 add_custom_command()
工作得更好,因为它不会在后台执行其他任务,例如创建图章文件等。
# add_project(<project> [DEPENDS project...])
function(add_project PROJECT)
cmake_parse_arguments(PARAM "" "" "DEPENDS" ${ARGN})
add_custom_target(${PROJECT} ALL DEPENDS ${PARAM_DEPENDS})
# Paths for this project
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT})
set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT})
set(INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/${PROJECT})
# Configure
escape_list(CMAKE_MODULE_PATH)
escape_list(CMAKE_PREFIX_PATH)
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--no-warn-unused-cli
"-DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH_ESCAPED}"
"-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH_ESCAPED}"
-DCMAKE_BINARY_DIR=${BUILD_DIR}
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
${SOURCE_DIR}
WORKING_DIRECTORY ${BUILD_DIR})
# Build
add_custom_command(TARGET ${TARGET}
COMMAND ${CMAKE_COMMAND}
--build .
--target install
WORKING_DIRECTORY ${BUILD_DIR})
# Help later find_package() calls
append_global(CMAKE_PREFIX_PATH ${INSTALL_DIR})
endfunction()
这是两个辅助函数。我花了很长时间才弄清楚将列表参数传递给其他 CMake 进程的正确方法,而不会将它们解释为多个参数。
# escape_list(<list-name>)
function(escape_list LIST_NAME)
string(REPLACE ";" "\;" ${LIST_NAME}_ESCAPED "${${LIST_NAME}}")
set(${LIST_NAME}_ESCAPED "${${LIST_NAME}_ESCAPED}" PARENT_SCOPE)
endfunction()
# append_global(<name> value...)
function(append_global NAME)
set(COMBINED "${${NAME}}" "${ARGN}")
list(REMOVE_DUPLICATES COMBINED)
set(${NAME} "${COMBINED}" CACHE INTERNAL "" FORCE)
endfunction()
唯一的缺点是每个项目都需要有一个安装目标。因此,您需要向没有安装命令的项目添加像 install(CODE "")
这样的虚拟安装命令,例如那些刚刚打电话的人ExternalProject_Add
。