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

时间:2013-05-06 12:38:25

标签: cmake

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

此外,两个项目都是使用CMake构建的,并且不需要安装项目A(通过'安装'目标),以便项目B使用它,因为这可能会对开发人员造成麻烦。

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

3 个答案:

答案 0 :(得分:123)

易。以下是我头脑中的例子:

顶级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

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

注意:我已成功在多个中型和大型项目中使用此结构。

答案 1 :(得分:8)

亚历山大·舒卡耶夫(Alexander Shukaev)有了一个不错的开端,但是还有很多事情可以做得更好:

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

    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
    )
    

    这将处理include目录,库等。如果您在B的标头中使用Boost,则使用PUBLIC而不是PRIVATE,并且这些依赖项将被可传递地添加到任何依赖于B的事物中。

  3. 请勿使用文件查看(除非您使用3.12)。直到最近,文件遍历仅在配置期间起作用,因此,如果添加文件并进行构建,则在您明确重新生成项目之前,它无法检测到更改。但是,如果直接列出文件并尝试进行构建,它应该会识别出配置已过时并在构建步骤中自动重新生成。

这里有个好话题(YouTube): C++Now 2017: Daniel Pfeifer “Effective CMake"

其中涵盖了一个程序包管理器构想,该构想允许您的根级别CMake与find_packagesubdirectory一起使用,但是我一直在尝试采用这种思想,并且在使用时遇到了很多问题find_package适用于所有内容,并具有类似于您的目录结构。

答案 2 :(得分:0)

这也可以使用CMake Cache机制来实现相同的目标(即共享特定于项目的变量):

  

设置(VAR“值”CACHE INTERNAL "")

请参阅Stack Overflow问题 How to share variables between different CMake files

相关问题