我的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项目中使用需要先安装的库?
答案 0 :(得分:5)
您可以将项目分为三组:
在配置超级项目之前,您需要在组#1和#2中配置,构建和安装项目:
CMakeLists.txt
之前执行此操作,例如,从shell脚本CMakeLists.txt
在超级项目的execute_process(${CMAKE_COMMAND} ...)
内。您可以使用适当的find_package(... QUIET)
命令的结果有条件地执行此操作。您需要确定组#3中的项目(如engine
)是否仅用于将它们用作子目录的项目中,或者您打算将它们用作独立库,这些库是在自己的构建树中构建的。
另外,您提到过:“我的库包含外部库的目录”。让我们介绍engine
可以依赖的所有可能的库:
LIB1
和LIB2
是engine
的私有和公共外部依赖关系及其配置模块导出旧学校LIB1_*
和LIB2_*
变量< / LI>
LIB3
和LIB4
是engine
的私有和公共外部依赖项,其配置模块导出LIB3
和LIB4
导入 libraries 公共和私有依赖关系是指在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)
引擎及其公共依赖项的include dirs将正确转发到game
。
如果engine
也将在自己的构建树中构建(在另一个项目中),则需要将导出代码添加到engine/CMakeLists.txt
,并且可能是调用find_package
的自定义配置模块(或find_dependency
)的依赖关系。有关详细信息,请参阅How to use CMake to find and link to a library using install-export and find_package?。该答案中未讨论的一个问题是在库的配置模块中找到库的依赖关系:
引用的SO答案只是安装<lib>-targets.cmake
命令生成的install(EXPORT ...)
脚本作为配置模块:
install(EXPORT engine-targets
FILE engine-config.cmake
DESTINATION lib/cmake/engine)
当engine
没有进一步的依赖关系时,此解决方案很好。如果是,则需要在配置模块的开头找到它们,这应该手动编写。
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选项不会转发到相关性)。
答案 1 :(得分:4)
从engine
项目导出库时,您需要指定其包含目录。以下代码是http://www.cmake.org/cmake/help/v3.0/manual/cmake-packages.7.html#creating-packages提供的示例的简化。对路径进行调整以使用安装前缀install/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}
)
发动机/ 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>)
游戏/的CMakeLists.txt:
find_package(Engine)
add_executable(game ...)
target_link_libraries(game engine)
答案 2 :(得分:0)
感谢@Tsyvarev和@tamas.kenez您的两个好答案。我最终使用了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
的人。