CMake:如何设置源,库和CMakeLists.txt依赖项?

Flo*_*ian 13 c++ cmake

我有几个项目(所有建筑都使用来自相同源树结构的CMake)都使用他们自己的混合数十个支持库.

所以我想到了如何在CMake中正确设置这个问题.到目前为止,我只发现CMake如何正确地创建目标之间的依赖关系,但我仍然在设置所有具有全局依赖关系(项目级别确实知道所有)或本地依赖关系(每个子级别目标仅处理其自己的依赖).

这里是我的目录结构的简化例子,我现在想出了使用CMake和本地依赖性(例子中只有一个可执行的项目,App1但也有更多的实际,App2,App3等):

Lib
+-- LibA
    +-- Inc
        +-- a.h
    +-- Src
        +-- a.cc
    +-- CMakeLists.txt
+-- LibB
    +-- Inc
        +-- b.h
    +-- Src
        +-- b.cc
    +-- CMakeLists.txt
+-- LibC
    +-- Inc
        +-- c.h
    +-- Src
        +-- c.cc
    +-- CMakeLists.txt
App1
+-- Src
    +-- main.cc
+-- CMakeLists.txt
Run Code Online (Sandbox Code Playgroud)

LIB /力霸/的CMakeLists.txt

include_directories(Inc ../LibC/Inc)
add_subdirectory(../LibC LibC)
add_library(LibA Src/a.cc Inc/a.h)
target_link_libraries(LibA LibC)
Run Code Online (Sandbox Code Playgroud)

LIB/LibB /的CMakeLists.txt

include_directories(Inc)
add_library(LibB Src/b.cc Inc/b.h)
Run Code Online (Sandbox Code Playgroud)

LIB /的LibC /的CMakeLists.txt

include_directories(Inc ../LibB/Inc)
add_subdirectory(../LibB LibB)
add_library(LibC Src/c.cc Inc/c.h)
target_link_libraries(LibC LibB)
Run Code Online (Sandbox Code Playgroud)

App1/CMakeLists.txt(为了便于再现它,我在这里生成源/头文件)

cmake_minimum_required(VERSION 2.8)

project(App1 CXX)

file(WRITE "Src/main.cc" "#include \"a.h\"\n#include \"b.h\"\nint main()\n{\na();\nb();\nreturn 0;\n}")
file(WRITE "../Lib/LibA/Inc/a.h" "void a();")
file(WRITE "../Lib/LibA/Src/a.cc" "#include \"c.h\"\nvoid a()\n{\nc();\n}")
file(WRITE "../Lib/LibB/Inc/b.h" "void b();")
file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}")
file(WRITE "../Lib/LibC/Inc/c.h" "void c();")
file(WRITE "../Lib/LibC/Src/c.cc" "#include \"b.h\"\nvoid c()\n{\nb();\n}")

include_directories(
    ../Lib/LibA/Inc
    ../Lib/LibB/Inc
)

add_subdirectory(../Lib/LibA LibA)
add_subdirectory(../Lib/LibB LibB)

add_executable(App1 Src/main.cc)

target_link_libraries(App1 LibA LibB)
Run Code Online (Sandbox Code Playgroud)

上例中的库依赖项看起来像这样:

App1 -> LibA -> LibC -> LibB
App1 -> LibB
Run Code Online (Sandbox Code Playgroud)

目前我更喜欢本地依赖变体,因为它更容易使用.我只是在源级别给出依赖关系include_directories(),在链接级别使用target_link_libraries()和在CMake级别add_subdirectory().

有了这个,你不需要知道支持库之间的依赖关系 - 并且 - 使用CMake级别"包含" - 你只会最终得到你真正使用的目标.当然,你可以让全局知道所有包含目录和目标,并让编译器/链接器整理其余部分.但这对我来说似乎是一种臃肿.

我还试图Lib/CMakeLists.txt处理Lib目录树中的所有依赖项,但我最终得到了很多if ("${PROJECT_NAME}" STREQUAL ...)检查,并且我无法创建中间库分组目标而不提供至少一个源文件的问题.

所以上面的例子"到目前为止一直很好",但它会抛出以下错误,因为你应该/不能添加CMakeLists.txt两次:

CMake Error at Lib/LibB/CMakeLists.txt:2 (add_library):
  add_library cannot create target "LibB" because another target with the
  same name already exists.  The existing target is a static library created
  in source directory "Lib/LibB".
  See documentation for policy CMP0002 for more details.
Run Code Online (Sandbox Code Playgroud)

目前我看到了两个解决方案,但我认为这样做太复杂了.

1.覆盖add_subdirectory()以防止重复

function(add_subdirectory _dir)
    get_filename_component(_fullpath ${_dir} REALPATH)
    if (EXISTS ${_fullpath} AND EXISTS ${_fullpath}/CMakeLists.txt)
        get_property(_included_dirs GLOBAL PROPERTY GlobalAddSubdirectoryOnceIncluded)
        list(FIND _included_dirs "${_fullpath}" _used_index)
        if (${_used_index} EQUAL -1)
            set_property(GLOBAL APPEND PROPERTY GlobalAddSubdirectoryOnceIncluded "${_fullpath}")
            _add_subdirectory(${_dir} ${ARGN})
        endif()
    else()
        message(WARNING "add_subdirectory: Can't find ${_fullpath}/CMakeLists.txt")
    endif()
endfunction(add_subdirectory _dir)
Run Code Online (Sandbox Code Playgroud)

2.为所有子级别添加"包含守卫" CMakeLists.txt,例如:

if (NOT TARGET LibA)
    ...
endif()
Run Code Online (Sandbox Code Playgroud)

我一直在测试tamas.kenezms建议的概念,并取得了一些有希望的结果.摘要可以在以下答案中找到:

tam*_*nez 11

多次添加相同的子目录是不可能的,这不是CMake的工作方式.有两种主要的替代方案可以干净利落地完成:

  1. 在与应用程序相同的项目中构建库.对于您正在积极处理的图书馆(当您正在使用该应用程序时),请选择此选项,以便经常对其进行编辑和重建.它们也将出现在同一个IDE项目中.

  2. 在外部项目中构建库(我不是指ExternalProject).对于您的应用程序刚刚使用但尚未使用它们的库,请选择此选项.大多数第三方库都是这种情况.它们也不会使IDE工作区混乱.

方法#1

  • 你的应用程序CMakeLists.txt添加了库的子目录(而你的库CMakeLists.txt不是)
  • 您的应用程序CMakeLists.txt负责添加所有即时和传递依赖项,并以正确的顺序添加它们
  • 它假定添加子目录libx将创建一些libx可以随时使用的目标(比如说)target_link_libraries

作为旁注:对于库来说,创建一个功能齐全的库目标是一个很好的做法,即包含使用库所需的所有信息的库目标:

add_library(LibB Src/b.cc Inc/b.h)
target_include_directories(LibB PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>)
Run Code Online (Sandbox Code Playgroud)

因此,库的包含目录的位置可以保持lib的内部事务.你只需要这样做;

target_link_libraries(LibC LibB)
Run Code Online (Sandbox Code Playgroud)

那么包括的dirs LibB也将被添加到汇编中LibC.PRIVATE如果LibB以下公共标头未使用修饰符,请使用LibC:

target_link_libraries(LibC PRIVATE LibB)
Run Code Online (Sandbox Code Playgroud)

方法#2

在单独的CMake项目中构建和安装库.您的库将安装一个所谓的config-module,它描述头文件和库文件的位置,并编译标志.您的应用程序CMakeList.txt假定已经构建并安装了库,并且find_package命令可以找到配置模块.这是另一个故事,所以我不会在这里详述.

几点说明:

  • 您可以混合使用#1和#2,因为在大多数情况下,您将拥有不变的第三方库和正在开发的自己的库.
  • #1和#2之间的折衷是使用ExternalProject模块,很多人都喜欢.这就像将您的库的外部项目(在他们自己的构建树中构建)包含到您的应用程序的项目中.在某种程度上,它结合了两种方法的缺点:您不能将库用作目标(因为它们位于不同的项目中)并且您无法调用find_package(因为在您的应用程序CMakeLists配置时没有安装库).
  • #2的变体是在外部项目中构建库,但不是安装工件,而是从源/构建位置使用它们.想了解更多看到的export()命令.

  • @Florian,对于方法2我创建了一个教程问题:http://stackoverflow.com/questions/31537602/how-to-use-cmake-to-find-and-link-to-a-library-using-install-export -并找到-pac 。它仅涵盖没有依赖项的单个库。我可能会在接下来的几天中添加一个新的教程来介绍依赖项。或者您也可以针对该问题提出一个新问题。 (2认同)