使用设置编译依赖项

时间:2018-03-25 00:35:14

标签: c++ cmake

我想知道如何为我的项目编译依赖项,同时为这些依赖项启用特定设置,例如将依赖项编译为静态或动态库或使用x64或x86设置,或者当项目将变量定义为确定如何构建项目(如使用Wayland或X.Org支持)。

我目前的设置如下:

文件夹结构

root_project
  |─── CMakeLists.txt
  |─── Project 1
  |      |─── .h and .cpp files
  |      └─── CMakeLists.txt
  |─── Dependency 1 (GLFW)
  |      |─── include directory
  |      |─── source directory
  |      |─── ...
  |      └─── CMakeLists.txt
  └─── Dependency 2 (GLEW)
         |─── build
         |      └─── cmake
         |            └─── CMakeLists.txt
         |─── source directory
         |─── include directory
         └─── ...

CMake文件

我的root cmake文件:

cmake_minimum_required (VERSION 3.8)
project ("EbsiStaller")
add_subdirectory ("EbsiStaller")

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory ("glfw")
include_directories("glfw/include")

add_subdirectory ("glew/build/cmake")
include_directories("glew/include")

我的项目cmake文件:

cmake_minimum_required (VERSION 3.8)

add_executable (EbsiStaller 
    "....cpp" 
    "....h"
)

SET(CMAKE_CXX_STANDARD 17)
SET(CMAKE_CXX_STANDARD_REQUIRED  ON)

# Links the CMake build output against glfw.
target_link_libraries(EbsiStaller glfw ${GLFW_LIBRARIES} glew ${GLEW_LIBRARIES})

附加说明:

我在Windows下使用Visual Studio 2017进行此项目,而项目应该与平台无关。由于我对CMake没有太多经验,所以我总是对我的CMake文件的任何建议更改开放。

为依赖项定义特定于编译的设置时,我不想编辑它们的CMake文件。

2 个答案:

答案 0 :(得分:1)

在CMake中这样做有很多困难,但我会尽我所能来回答它。

通常,您通过add_subdirectory添加的任何项目都将继承当前范围内当前定义的所有设置。更改单个依赖项设置的最简单方法(IMO)是将ExternalProject_Addmacros一起使用:

include(ExternalProject)

#
#   Add external project.
#
#   \param name             Name of external project
#   \param path             Path to source directory
#   \param external         Name of the external target
#
macro(add_external_project name path)
    # Create external project
    set(${name}_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${path})
    set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${path})
    ExternalProject_Add(${name}
        SOURCE_DIR "${${name}_SOURCE_DIR}"
        BINARY_DIR "${${name}_BINARY_DIR}"
        CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}"
                   "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
                   "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
                   "-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}"
                   # These are only useful if you're cross-compiling.
                   # They, however, will not hurt regardless.
                   "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}"
                   "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}"
                   "-DCMAKE_AR=${CMAKE_AR}"
                   "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
                   "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
                   "-DCMAKE_RC_COMPILER=${CMAKE_RC_COMPILER}"
                   "-DCMAKE_COMPILER_PREFIX=${CMAKE_COMPILER_PREFIX}"
                   "-DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}"
       INSTALL_COMMAND ""
    )

endmacro(add_external_project)

#
#   Add external target to external project.
#
#   \param name             Name of external project
#   \param includedir       Path to include directory
#   \param libdir           Path to library directory
#   \param build_type       Build type {STATIC, SHARED}
#   \param external         Name of the external target
#
macro(add_external_target name includedir libdir build_type external)
    # Configurations
    set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${libdir})

    # Create external library
    add_library(${name} ${build_type} IMPORTED)
    set(${name}_LIBRARY "${${name}_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_${build_type}_LIBRARY_PREFIX}${name}${CMAKE_${build_type}_LIBRARY_SUFFIX}")

    # Find paths and set dependencies
    add_dependencies(${name} ${external})
    set(${name}_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${includedir}")

    # Set interface properties
    set_target_properties(${name} PROPERTIES IMPORTED_LOCATION ${${name}_LIBRARY})
    set_target_properties(${name} PROPERTIES INCLUDE_DIRECTORIES ${${name}_INCLUDE_DIR})
endmacro(add_external_target)

宏说明

宏基本上配置了一个具有非常相似的CMake变量定义的CMake的新实例。

第一个宏ExternalProject_Add通知CMake它需要使用那些自定义CMake参数,源目录和输出二进制目录构建一次所需的外部项目。特别是,像"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"这样的选项告诉CMake使用与当前构建类型相同的构建类型(Debug,Release等),而"-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}"指示CMake在构建共享库时使用相同的首选项(默认情况下,如果BUILD_SHARED_LIBS设置为OFF,则项目应构建静态依赖项。)

然后第二个宏创建一个导入的目标,CMake可能会链接到与本机CMake库类似的属性。

使用这些宏

要默认使用这些宏,您可以执行以下操作:

add_external_project(googletest_external googletest)
add_external_target(gtest googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external)
add_external_target(gtest_main googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external)

在此示例中,我配置了外部项目googletest,然后创建了目标gtestgtest_main,它们应该是静态库(由于Googletest强制静态链接的方式),可以像任何普通的CMake库一样链接。

劫持这些用于自定义构建的宏

现在您已经粗略地了解了这些宏的功能,修改它们以允许每个依赖项的自定义配置非常容易。比如说,无论我的实际项目设置如何,我都想要一个静态版本的glew。我们还假设我希望将GLEW_OSMESA设置为ON

#
#   Add external project.
#
macro(add_release_osmesa_glew)
    # Create external project
    set(${name}_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/glew)
    set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/glew)
    ExternalProject_Add(glew_external
        SOURCE_DIR "${${name}_SOURCE_DIR}"
        BINARY_DIR "${${name}_BINARY_DIR}"
        CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}"
                   "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
                   "-DCMAKE_BUILD_TYPE=Release"
                   "-DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}"
                   # These are only useful if you're cross-compiling.
                   # They, however, will not hurt regardless.
                   "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}"
                   "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}"
                   "-DCMAKE_AR=${CMAKE_AR}"
                   "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
                   "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
                   "-DCMAKE_RC_COMPILER=${CMAKE_RC_COMPILER}"
                   "-DCMAKE_COMPILER_PREFIX=${CMAKE_COMPILER_PREFIX}"
                   "-DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}"
                   "-DGLEW_OSMESA=ON"
       INSTALL_COMMAND ""
    )

然后,要使用使用这些配置选项构建的glew,我可以执行以下操作:

add_release_osmesa_glew()
add_external_target(
    glew
    glew/include 
    glew 
    SHARED 
    glew_external
)

add_external_target(
    glew_s 
    glew/include 
    glew 
    STATIC 
    glew_external
)

最后,我可以使用以下选项链接它:

target_link_libraries(my_target
    glew_s
    ...
)

赞成

  • 不需要更改项目的CMakeLists。
  • 支持依赖项目支持的所有可能配置。
  • 仅构建一次依赖库,并可根据需要使用继承设置或自定义设置。
  • 应该是目标独立的(意味着它应该与Visual C ++项目,Makefile等一起使用)开箱即用。

缺点

  • 大量样板
  • 依赖于依赖项目中的CMakeLists的配置

答案 1 :(得分:1)

正确的方法是直接在目标上运作。例如(猜测目标名称,原谅我):

add_subdirectory ("glfw")
set_target_properties(glfw PROPERTIES
    COMPILE_FLAGS "-m32 -O2" # Adjust as needed
)
target_link_libraries(glew INTERFACE
    ${GLFW_LIBRARIES}
)

add_subdirectory ("glew/build/cmake")
target_include_directories(glew PUBLIC
    "glfw/include"
)
target_link_libraries(glew INTERFACE
    ${GLEW_LIBRARIES}
)

这使您可以按目标而不是全局调整内容(这是现代CMake使用的基础)。您可以使用这些函数及其朋友调整您喜欢的任何目标,包括调整编译器标志甚至添加新文件。

您正在使用的方法有效,但您正在影响之后声明的每个目标,包括稍后添加的子目录。

您的主要项目CMakeLists.txt可能如下所示:

 add_executable (EbsiStaller 
    "....cpp" 
    "....h"
)
target_compile_features(EbsiStaller PUBLIC
    cxx_std_17 # might actually be a cmake 3.9 thing, but you get the idea
)

# Links the CMake build output against glfw.
target_link_libraries(EbsiStaller
    glfw
    glew
)

这里有太多的内容,但这一切都归结为你的CMake的现代化。在线文档太棒了。