CMake:如何设置源,库和CMakeLists.txt依赖关系

CMake: How to set up source, library and CMakeLists.txt dependencies?

本文关键字:库和 CMakeLists txt 关系 依赖 何设置 设置 CMake      更新时间:2023-10-16

我有几个项目(都是用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

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

/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级别的"include"-你只会得到你真正使用的目标。当然,你可以让所有的include目录和目标都是全局已知的,让编译器/链接器来整理剩下的。但这对我来说似乎是一种肿胀。

我也试图有一个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.txt s添加"include保护",如:

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

我一直在测试tamas提出的概念。Kenez和m.s.取得了一些有希望的成果。这些总结可以在我的以下回答中找到:

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

多次添加相同的子目录是毫无疑问的,这不是CMake打算如何工作。有两种主要的替代方法可以以一种干净的方式完成:

  1. 在与应用程序相同的项目中构建库。对于你正在积极工作的库(当你在应用程序上工作时),更喜欢这个选项,这样它们可能会经常被编辑和重建。它们也会显示在同一个IDE项目中。

  2. 在外部项目中构建库(,我不是指ExternalProject)。对于那些只被你的应用程序使用,但你没有处理它们的库,你更喜欢这个选项。这是大多数第三方库的情况。

方法# 1

  • 你的应用程序的CMakeLists.txt添加了库的子目录(和你的lib ' CMakeLists.txt 's不)
  • 你的应用的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>)

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

target_link_libraries(LibC LibB)

LibB的include目录也会被添加到LibC的编译中。如果LibB没有被LibC的公共头使用,则使用PRIVATE修饰符:

target_link_libraries(LibC PRIVATE LibB)
<标题>方法# 2 h1> onfig-module,它描述了头文件和库文件的位置以及编译标志。您的应用程序的CMakeList.txt假定库已经构建并安装,并且可以通过find_package命令找到config-模块。这完全是另一个故事,所以我不会在这里详细说明。

注意事项:

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