CMake并找到其他项目及其依赖项

blo*_*dev 72 cmake

想象一下以下场景:项目A是一个共享库,它有几个依赖项(LibA,LibB,LibC).项目B是一个可执行文件,它依赖于项目A,因此也需要所有项目A的依赖项才能构建.

此外,这两个项目都是使用CMake的建成,项目A不应该需要安装(通过"安装"的目标),以便项目B使用它,因为这可能成为滋扰给开发者.

所以问题是,使用CMake解决这些依赖关系的最佳方法是什么?理想的解决方案尽可能简单(尽管不简单),并且只需要最少的维护.

Ale*_*aev 133

简单.以下是我头脑中的例子:

顶级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)
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txtcomponents/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)
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txtcomponents/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)
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txtcomponents/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)
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txtcomponents/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)
Run Code Online (Sandbox Code Playgroud)

为清楚起见,这里是相应的源树结构:

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
Run Code Online (Sandbox Code Playgroud)

有很多方面可以调整/定制或更改以满足某些需求,但这至少应该让你开始.

注意:我已经成功地在几个中型和大型项目中使用了这种结构.

  • 你是一个F **** STAR !你真的让我摆脱了持续近一天的剧烈头痛。非常感谢 1 (14认同)
  • 我很好奇,如果我要从“可执行文件”目录中调用Cmake,它会编译吗?还是应该始终从项目的根目录进行编译? (2认同)
  • 我认为它确实有一个小缺点,即您在每个项目中定义两次包含目录(一次用于“set(...INCLUDE_DIRS”,一次用于“include_directories()”),我发现很难维护(总是记得添加*两个*地方的新包含依赖项)。您可以使用“get_property(...PROPERTY INCLUDE_DIRECTORIES)”查询它们。 (2认同)
  • 这真的很棒,但是当 A、B 和 C 是完全独立的项目时,如何实现同样的事情呢?即我想构建 A 并将其导出以拥有自己的 ProjectConfig.cmake 文件,然后在 B 中使用 find_package 在系统上查找 A,以某种方式获取 A 依赖的所有库的列表,以便它们可以在何时链接B楼 (2认同)
  • @Ben Farmer,这确实是一个更复杂的话题,值得一篇大文章正确解释.我从来没有足够的耐心在这里概述它.事实上,这就是我管理我的项目所做的事情,因为这实际上是CMake的最终(专业)用法和意图.为了管理所有这些,我拥有自己的CMake框架,可以在幕后处理很多事情.举个例子,您可以尝试构建我的任何一个[C++ Hacks](https://bitbucket.org/Alexander-Shukaev/c-hacks)或[C++ Firewall](https://bitbucket.org/Alexander-Shukaev/ c-firewall)玩具项目. (2认同)

joh*_*003 9

Alexander Shukaev有一个很好的开始,但有很多事情可以做得更好:

  1. 不要使用include_directories.至少,使用target_include_directories.但是,如果使用导入的目标,则可能甚至不需要这样做.
  2. 使用导入的目标.提升示例:

    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
    )
    
    Run Code Online (Sandbox Code Playgroud)

    这会处理include目录,库等.如果你在B中的头文件中使用了Boost,那么使用PUBLIC而不是PRIVATE,这些依赖关系将被传递到依赖于B的任何东西.

  3. 不要使用文件全局(除非你使用3.12).直到最近,文件通配仅在配置时有效,因此如果添加文件和构建,则在显式重新生成项目之前,它无法检测到更改.但是,如果直接列出文件并尝试构建,则应识别配置已过期并在构建步骤中自动重新生成.

这里有很好的谈话(YouTube):2017年的C++:Daniel Pfeifer"有效的CMake"

这涵盖了一个包管理器的想法,允许您的根级别CMake使用find_packageOR subdirectory,但是,我一直在尝试采用这种意识形态,并且在使用find_package所有内容和拥有像您这样的目录结构时遇到了很大的问题.