CMake:如何设置Source,Library和CMakeLists.txt的依赖关系?

我有几个项目(全部使用来自同一个源代码树结构的CMake构build)都使用自己的混合数十个支持库。

所以我提出了如何在CMake中正确设置这个问题。 到目前为止,我只find了CMake如何正确创build目标之间的依赖关系,但我仍然在设置所有与全局依赖关系(项目级别都知道这一切)或与本地依赖关系(每个子级目标只处理自己的依赖)。

这里是我的目录结构和我目前使用CMake和本地依赖关系的例子(这个例子只显示了一个可执行项目App1 ,但实际上有更多的App2App3 ,…):

 Lib +-- LibA +-- Inc +-- ah +-- Src +-- a.cc +-- CMakeLists.txt +-- LibB +-- Inc +-- bh +-- Src +-- b.cc +-- CMakeLists.txt +-- LibC +-- Inc +-- ch +-- Src +-- c.cc +-- CMakeLists.txt App1 +-- Src +-- main.cc +-- CMakeLists.txt 

LIB /力霸/的CMakeLists.txt

 include_directories(Inc ../LibC/Inc) add_subdirectory(../LibC LibC) add_library(LibA Src/a.cc Inc/ah) target_link_libraries(LibA LibC) 

LIB / LibB /的CMakeLists.txt

 include_directories(Inc) add_library(LibB Src/b.cc Inc/bh) 

LIB /的LibC /的CMakeLists.txt

 include_directories(Inc ../LibB/Inc) add_subdirectory(../LibB LibB) add_library(LibC Src/c.cc Inc/ch) target_link_libraries(LibC LibB) 

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

 cmake_minimum_required(VERSION 2.8) project(App1 CXX) file(WRITE "Src/main.cc" "#include \"ah\"\n#include \"bh\"\nint main()\n{\na();\nb();\nreturn 0;\n}") file(WRITE "../Lib/LibA/Inc/ah" "void a();") file(WRITE "../Lib/LibA/Src/a.cc" "#include \"ch\"\nvoid a()\n{\nc();\n}") file(WRITE "../Lib/LibB/Inc/bh" "void b();") file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}") file(WRITE "../Lib/LibC/Inc/ch" "void c();") file(WRITE "../Lib/LibC/Src/c.cc" "#include \"bh\"\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) 

上例中的库依赖关系如下所示:

 App1 -> LibA -> LibC -> LibB App1 -> LibB 

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

有了这个,你不需要知道支持库之间的依赖关系,并且在CMake级别为“includes”的情况下,最终只能得到你真正使用的目标。 当然,你可以把所有包含的目录和目标全局都知道,让编译器/链接器把其余部分整理出来。 但是这对我来说似乎是一种膨胀。

我也尝试过使用Lib/CMakeLists.txt来处理Lib目录树中的所有依赖项,但是最后我得到了很多if ("${PROJECT_NAME}" STREQUAL ...)检查以及可以解决的问题不创build至less一个源文件而创build中间库分组目标。

所以上面的例子是“迄今为止好”,但它会抛出以下错误,因为你应该/不能添加一个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. 

目前我看到了两个解决scheme,但我认为我有这样的复杂。

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) 

2.为所有的子级CMakeLists.txt添加一个“包含后卫”,例如:

 if (NOT TARGET LibA) ... endif() 

提前感谢您的帮助整理出来。

编辑:我一直在testingtamas.kenez和MS提出的一些promissing结果的概念。 摘要可以在我的以下答案中find:

  • 首选的cmake项目结构
  • CMake共享库与多个可执行文件
  • 使cmake库可以被其他cmake软件包自动访问

多次添加相同的子目录是毫无疑问的,这不是CMake如何工作的。 有两个主要的替代scheme可以以一种干净的方式来实现:

  1. 在与您的应用程序相同的项目中构build您的库。 对于正在使用的库(当您在应用程序中工作时),select此选项可能会频繁地进行编辑和重build。 他们也将出现在同一个IDE项目中。

  2. 在一个外部项目中构build你的库( 我不是指ExternalProject )。 对于刚刚由您的应用使用的库而言,首选此选项,但是您不在其中工作。 大多数第三方库就是这种情况。 它们也不会混乱IDE工作区。

方法#1

  • 你的应用程序的CMakeLists.txt添加库的子目录(而你的libs的CMakeLists.txt则不要)
  • 您的应用程序的CMakeLists.txt负责添加所有即时和传递依赖项,并按照正确的顺序添加它们
  • 它假定为libx添加子目录将创build一些目标(比如说libx ),可以很方便地使用target_link_libraries

作为一个旁注:对于图书馆来说,创build一个全function的图书馆目标是一个很好的做法,那就是包含使用图书馆所需的全部信息:

 add_library(LibB Src/b.cc Inc/bh) target_include_directories(LibB PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>) 

所以库的include目录的位置可以保留在lib的内部事务中。 你只需要这样做;

 target_link_libraries(LibC LibB) 

那么LibB的包括LibB也将被添加到LibC的编译中。 如果LibB未被LibC的公共头使用,则使用PRIVATE修饰符:

 target_link_libraries(LibC PRIVATE LibB) 

方法#2

在独立的CMake项目中构build和安装库。 你的库将安装一个所谓的configuration模块 ,它描述了头文件和库文件的位置以及编译标志。 您的应用程序的CMakeList.txt假定库已经被构build和安装,并且可以通过find_package命令findconfiguration模块。 这是另外一个故事,所以我不会在这里详述。

一些注意事项:

  • 您可以混合#1和#2,因为在大多数情况下,您将拥有不变的第三方库和您自己的库,正在开发中。
  • #1和#2之间的折衷是使用ExternalProject模块 ,这是许多人所喜欢的。 这就好像将您的库的外部项目(构build在自己的构build树中)包含到应用程序的项目中。 在方法上,它结合了这两种方法的缺点:你不能使用你的库作为目标(因为它们在不同的项目中),你不能调用find_package (因为在你的应用程序的CMakeListsconfiguration的时候没有安装库)。
  • #2的一个变体是在外部项目中构build库,而不是安装工件从源/构build位置使用它们。 有关更多信息,请参阅export()命令 。