【问题标题】:CMake: How to set up source, library and CMakeLists.txt dependencies?CMake:如何设置源、库和 CMakeLists.txt 依赖项?
【发布时间】:2015-10-09 08:08:01
【问题描述】:

我有几个项目(全部使用 CMake 从相同的源树结构构建)都使用自己的数十个支持库的组合。

所以我想到了如何在 CMake 中正确设置这个问题。到目前为止,我只找到了 CMake how to correctly create dependencies between targets,但我仍然在使用全局依赖项(项目级别确实知道一切)或本地依赖项(每个子级别目标仅处理自己的依赖项)。

这是我的目录结构的简化示例以及我目前使用 CMake 和本地依赖项提出的内容(该示例仅显示了一个可执行项目,App1,但实际上还有更多,App2App3等):

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

Lib/LibA/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)

Lib/LibB/CMakeLists.txt

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

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)

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)

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

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

目前我更喜欢本地依赖变体,因为它更容易使用。我只是在源代码级别使用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.

目前我看到了两种解决方案,但我认为这种方式太复杂了。

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.txts 添加“包含防护”,例如:

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

我一直在测试tamas.kenezm.s. 提出的概念,并取得了一些有希望的结果。总结可以在我的以下答案中找到:

【问题讨论】:

  • 我在 App1/CMakeLists.txt 中看不到 add_subdirectory(../Lib/LibA LibA) 的原因。你为什么需要这个?通常你有一个根 CMakeLists.txt 来添加子目录。
  • @m.s.在我的真实世界项目中,应用程序比我的App1 更多(我在问题中添加了一些关于此的细节)。到目前为止,我没有添加根级别 CMakeLists.txt 以防止在工作 App1App2 等的不同项目团队之间产生依赖关系(我想不出一个站点上的一个足够通用的对于所有项目和在另一个站点上足够具体,最后它只生成一个可执行目标)。目前他们只是去他们的子目录,创建一个二进制子目录,用cmake ..生成他们的make环境并调用make
  • App1工作时,通过ExternalProject_Add添加LibALibB等怎么样?
  • @m.s.在我的App1 示例中,如果我通过外部项目同时添加LibALibB,我会不会得到LibB 中所有内容的重复符号?
  • 在这里查看文章系列:coderwall.com/p/qk2eog/…

标签: c++ cmake


【解决方案1】:

多次添加同一个子目录是没有问题的,这不是 CMake 的工作方式。有两种主要的替代方法可以以干净的方式进行:

  1. 在与您的应用相同的项目中构建您的库。对于您正在积极使用的库(当您在使用应用程序时),请优先使用此选项,这样它们可能会经常被编辑和重建。它们也会出现在同一个 IDE 项目中。

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

方法#1

  • 您的应用的 CMakeLists.txt 添加库的子目录(而您的库的 CMakeLists.txt 不添加)
  • 您应用的 CMakeLists.txt 负责添加所有直接和传递依赖项并按正确顺序添加它们
  • 它假设为libx 添加子目录将创建一些可以很容易地与target_link_libraries 一起使用的目标(比如libx

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

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

所以库的包含目录的位置可以保留为库的内部事务。你只需要这样做;

target_link_libraries(LibC LibB)

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

target_link_libraries(LibC PRIVATE LibB)

方法#2

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

几点说明:

  • 您可以混合使用 #1 和 #2,因为在大多数情况下,您将拥有不变的第三方库和正在开发的您自己的库。
  • #1 和#2 之间的折衷方案是使用ExternalProject module,这是许多人的首选。这就像将您的库的外部项目(内置在他们自己的构建树中)包含到您的应用程序的项目中。它结合了两种方法的缺点:您不能将库用作目标(因为它们在不同的项目中)并且您不能调用find_package(因为在您的应用程序@ 987654341@正在配置中)。
  • #2 的变体是在外部项目中构建库,但不是安装工件,而是从其源/构建位置使用它们。有关此内容的更多信息,请参阅export() command

【讨论】:

  • 方法 1:我喜欢你使用 target_include_directories() 命令(CMake >= 2.8.12),它绝对让事情变得更容易。但我正在寻找一种解决方案,其中库的用户不需要知道内部依赖项。 方法2:我将研究使用二进制传递和find_package() 命令。你能推荐一些有用的链接吗?
  • 关于ExternalProject 模块:如果我们谈论更大的单体库,我认为命令ExternalProject_Add() 是可能的。但是对于我的 50 多个较小的库,我认为这不是一个选择,并且在某种程度上,它不应该以这种方式使用的论点在这里也成立。开销太大了:例如我担心它——如果我使用SOURCE_DIR 选项——会开始对每个库进行编译器检测,我必须传递所有构建环境特定的选项,包括。我的工具链文件。
  • @Florian,对于方法 2,我创建了一个教程问题:stackoverflow.com/questions/31537602/…。它仅涵盖一个没有依赖关系的库。我可能会在接下来的几天里添加一个新的教程来介绍依赖关系。或者您也可以提出一个关于该问题的新问题。
  • @Florian,ExternalProject:关于开销,您是对的。由于我提到的其他原因,我也不使用它。
  • @Florian,管理 50 多个小型库:要有效地做到这一点,您将需要一些额外的基础设施,例如一组自定义或通用 shell 脚本。许多人围绕这个创建了完整的框架(biicode、cmakepp、CPM、fips、hunter)。我为自己编写了一个基于 CMake 的包/存储库管理器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 2011-09-24
  • 2011-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多