想象以下场景:项目 A 是一个共享库,它具有多个依赖项(LibA、LibB 和 LibC)。项目 B 是一个依赖于项目 A 的可执行文件,因此还需要项目 A 的所有依赖项才能构建。
此外,这两个项目都是使用 CMake 构建的,并且项目 A 不需要安装(通过“安装”目标)以便项目 B 使用它,因为这可能会给开发人员带来麻烦。
使用 CMake 解决这些依赖关系的最佳方法是什么?理想的解决方案将尽可能简单(尽管并不简单)并且需要最少的维护。
想象以下场景:项目 A 是一个共享库,它具有多个依赖项(LibA、LibB 和 LibC)。项目 B 是一个依赖于项目 A 的可执行文件,因此还需要项目 A 的所有依赖项才能构建。
此外,这两个项目都是使用 CMake 构建的,并且项目 A 不需要安装(通过“安装”目标)以便项目 B 使用它,因为这可能会给开发人员带来麻烦。
使用 CMake 解决这些依赖关系的最佳方法是什么?理想的解决方案将尽可能简单(尽管并不简单)并且需要最少的维护。
简单的。这是我脑海中的例子:
CMakeLists.txt
:cmake_minimum_required(VERSION 2.8.10)
# You can tweak some common (for all subprojects) stuff here. For example:
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(SEND_ERROR "In-source builds are not allowed.")
endif ()
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_MAKEFILE ON)
# Remove 'lib' prefix for shared libraries on Windows
if (WIN32)
set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()
# When done tweaking common stuff, configure the components (subprojects).
# NOTE: The order matters! The most independent ones should go first.
add_subdirectory(components/B) # B is a static library (depends on Boost)
add_subdirectory(components/C) # C is a shared library (depends on B and external XXX)
add_subdirectory(components/A) # A is a shared library (depends on C and B)
add_subdirectory(components/Executable) # Executable (depends on A and C)
CMakeLists.txt
在components/B
:cmake_minimum_required(VERSION 2.8.10)
project(B C CXX)
find_package(Boost
1.50.0
REQUIRED)
file(GLOB CPP_FILES source/*.cpp)
include_directories(${Boost_INCLUDE_DIRS})
add_library(${PROJECT_NAME} STATIC ${CPP_FILES})
# Required on Unix OS family to be able to be linked into shared libraries.
set_target_properties(${PROJECT_NAME}
PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(${PROJECT_NAME})
# Expose B's public includes (including Boost transitively) to other
# subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
在components/C
:cmake_minimum_required(VERSION 2.8.10)
project(C C CXX)
find_package(XXX REQUIRED)
file(GLOB CPP_FILES source/*.cpp)
add_definitions(${XXX_DEFINITIONS})
# NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS.
include_directories(${B_INCLUDE_DIRS}
${XXX_INCLUDE_DIRS})
add_library(${PROJECT_NAME} SHARED ${CPP_FILES})
target_link_libraries(${PROJECT_NAME} B
${XXX_LIBRARIES})
# Expose C's definitions (in this case only the ones of XXX transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS}
CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)
# Expose C's public includes (including the ones of C's dependencies transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${B_INCLUDE_DIRS}
${XXX_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
在components/A
:cmake_minimum_required(VERSION 2.8.10)
project(A C CXX)
file(GLOB CPP_FILES source/*.cpp)
# XXX's definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})
# NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS.
include_directories(${C_INCLUDE_DIRS})
add_library(${PROJECT_NAME} SHARED ${CPP_FILES})
# You could need `${XXX_LIBRARIES}` here too, in case if the dependency
# of A on C is not purely transitive in terms of XXX, but A explicitly requires
# some additional symbols from XXX. However, in this example, I assumed that
# this is not the case, therefore A is only linked against B and C.
target_link_libraries(${PROJECT_NAME} B
C)
# Expose A's definitions (in this case only the ones of C transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS}
CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)
# Expose A's public includes (including the ones of A's dependencies
# transitively) to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
${C_INCLUDE_DIRS}
CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)
CMakeLists.txt
在components/Executable
:cmake_minimum_required(VERSION 2.8.10)
project(Executable C CXX)
file(GLOB CPP_FILES source/*.cpp)
add_definitions(${A_DEFINITIONS})
include_directories(${A_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} ${CPP_FILES})
target_link_libraries(${PROJECT_NAME} A C)
为了清楚起见,这里是对应的源码树结构:
Root of the project
├───components
│ ├───Executable
│ │ ├───resource
│ │ │ └───icons
│ │ ├───source
| | └───CMakeLists.txt
│ ├───A
│ │ ├───include
│ │ │ └───A
│ │ ├───source
| | └───CMakeLists.txt
│ ├───B
│ │ ├───include
│ │ │ └───B
│ │ ├───source
| | └───CMakeLists.txt
│ └───C
│ ├───include
│ │ └───C
│ ├───source
| └───CMakeLists.txt
└───CMakeLists.txt
有很多地方可以调整/定制或更改以满足某些需求,但这至少应该让你开始。
注意:我已经在几个大中型项目中成功地使用了这种结构。
Alexander Shukaev 的开端很好,但还有很多事情可以做得更好:
target_include_directories
. 但是,如果您使用导入的目标,您可能甚至不需要这样做。使用导入的目标。升压示例:
find_package(Boost 1.56 REQUIRED COMPONENTS
date_time filesystem iostreams)
add_executable(foo foo.cc)
target_link_libraries(foo
PRIVATE
Boost::date_time
Boost::filesystem
Boost::iostreams
)
这会处理包含目录、库等。如果您在 B 的标头中使用了 Boost,那么请使用 PUBLIC 而不是 PRIVATE,这些依赖项将被传递添加到依赖于 B 的任何内容中。
不要使用文件通配符(除非您使用 3.12)。直到最近,文件 globbing 仅在配置期间有效,因此如果您添加文件并构建,它无法检测到更改,直到您明确重新生成项目。但是,如果您直接列出文件并尝试构建,它应该会识别出配置已过期并在构建步骤中自动重新生成。
这里有很好的讨论(YouTube):C++Now 2017:Daniel Pfeifer “Effective CMake”
其中涵盖了一个包管理器的想法,它允许您的根级别 CMake 与find_package
OR一起使用subdirectory
,但是,我一直在尝试采用这种思想,并且在使用find_package
所有内容和拥有像您这样的目录结构时遇到了很大的问题。
这也可以使用 CMakeCache
机制来实现(即共享项目特定变量):
设置(VAR“值”
CACHE INTERNAL "")
请参阅 Stack Overflow 问题How to share variables between different CMake files。