CMake:如何设置源,库和CMakeLists.txt依赖项? [英] CMake: How to set up source, library and CMakeLists.txt dependencies?

查看:139
本文介绍了CMake:如何设置源,库和CMakeLists.txt依赖项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有好几个项目(所有项目都是从同一个源树结构使用CMake构建的),它们都是使用自己的混合方法从数十个支持库中提取出来的。



所以我想到了这个问题如何在CMake中正确设置。到目前为止,我只发现了 CMake如何正确地创建目标 ,但我仍然在设置具有全局依赖项(项目级别确实了解全部)或具有本地依赖项(每个子级别目标仅处理其自身依赖项)之间进行挣扎。 / p>

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

  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

库/库/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(版本2.8)

项目(App1 CXX)

文件(WRITE Src / main.cc #include \ ah\ \n#包括\ bh\ \nint main()\n {\na(); \nb(); \nreturn 0; \n})
file(WRITE。 ./Lib/LibA/Inc/ah void a();)
文件(写 ../Lib/LibA/Src/a.cc #include \ ch\ \ voidnvoid a()\n {\nc(); \n})
文件(WRITE ../Lib/LibB/Inc/bh void b();)
文件(写 ../Lib/LibB/Src/b.cc void b(){})
文件(写 ../Lib/LibC/Inc/ch void c ();)
文件(写 ../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级别的包含,您最终只会使用真正使用的目标。可以肯定的是,您可以使所有包含目录和目标都为全局所知,然后让编译器/链接器对其余的进行分类。



我也尝试过使用 Lib / CMakeLists.txt 来处理 Lib 目录树中的所有依赖关系,但是我最终遇到了很多 if( $ {PROJECT_NAME} STREQUAL ... )检查以及无法创建目标目标文件库的问题,而没有给出至少一个源文件。



所以上面的示例是到目前为止一切都很好,但是它会引发以下错误,因为您应该/不能两次添加 CMakeLists.txt



Lib / LibB / CMakeLists.txt:2(add_library)上的

  CMake错误:add_library:
add_library无法创建目标 LibB,因为另一个目标与
相同名称已经存在。现有目标是在源目录 Lib / LibB中创建的
静态库。
有关更多详细信息,请参阅策略CMP0002的文档。

目前,我看到了两种解决方案,但是我觉得这种方法太复杂了。 / p>

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

 函数(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()
消息(警告 add_subdirectory:找不到$ {_ fullpath} /CMakeLists.txt)
endif()
endfunction(add_subdirectory _dir)

2。 向所有子级别 CMakeLists.txt s添加包含卫士,例如:

 如果(非目标LibA)
...
endif()






我一直在测试 tamas.kenez ms ,结果有些可喜。可以在我的以下答案中找到这些摘要:




解决方案

多次添加同一子目录是没有问题的,这不是CMake的工作方式。干净的方法有两种主要选择:


  1. 在与应用程序相同的项目中构建库。对于正在积极使用(在使用该应用程序时)正在使用的库,请首选此选项,以便它们可能经常被编辑和重建。它们还将显示在同一个IDE项目中。


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




方法#1




  • 应用程序的 CMakeLists.txt 添加库的子目录(以及您的libs CMakeLists.txt 不要)

  • 您应用的 CMakeLists.txt 负责添加所有直接和可传递的内容依赖关系并按照正确的顺序添加它们

  • 它假定为 libx 添加子目录将创建一些目标(例如 libx )可以很容易地与 target_link_libraries


$ b一起使用$ b

作为旁注:对于库,创建一个全功能库目标是一个好习惯,即,其中包含使用该库所需的所有信息:

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

因此,库的包含目录的位置可以保留在lib的内部。您只需这样做;

  target_link_libraries(LibC LibB)

,那么 LibB 的包含目录也将添加到 LibC的汇编中。如果 LibC LibB ,请使用 PRIVATE 修饰符c $ c>:

  target_link_libraries(LibC PRIVATE LibB)



方法#2



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



一些注意事项:




  • 您可以混合使用#1和#2,因为在大多数情况下,您将同时拥有不变的第三方库和正在开发的自己的库。

  • #1和#2使用的是许多人都喜欢的 ExternalProject模块。这就像将库的外部项目(在其自己的构建树中构建)包含到应用程序的项目中一样。在某种程度上,它结合了两种方法的缺点:您不能将库用作目标(因为它们位于不同的项目中),并且不能调用 find_package (因为在安装应用的 CMakeLists 时未安装库)。

  • #2的一种变体是在其中构建库外部项目,但无需安装工件,而是从源/构建位置使用它们。有关此的更多信息,请参见 export()命令


I have several projects (all building with CMake from the same source tree structure) all using their own mix out of dozens of supporting libraries.

So I came about the question how to set up this correctly in CMake. So far I have only found CMake how to correctly create dependencies between targets, but I'm still struggling between setting up everything with global dependencies (the project level does know it all) or with local dependencies (each sub-level target only handles its own dependencies).

Here is a reduced example of my directory structure and what I currently came up with using CMake and local dependencies (the example shows only one executable project, App1, but there are actually more, App2, App3, etc.):

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 (for the ease of reproducing it I generate the source/header files here)

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)

The library dependencies in the above example do look like this:

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

At the moment I prefer the local dependencies variant, because it's easier to use. I just give the dependencies at the source level with include_directories(), at the link level with target_link_libraries() and at the CMake level with add_subdirectory().

With this you don't need to know the dependencies between the supporting libraries and - with the CMake level "includes" - you will only end-up with the targets you really use. Sure enough you could just make all include directories and targets be known globally and let the compiler/linker sort out the rest. But this seems like a kind of bloating to me.

I also tried to have a Lib/CMakeLists.txt to handle all the dependencies in the Lib directory tree, but I ended up having a lot of if ("${PROJECT_NAME}" STREQUAL ...) checks and the problem that I can't create intermediate libraries grouping targets without giving at least one source file.

So the above example is "so far so good", but it throws the following error because you should/can not add a CMakeLists.txt twice:

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.

At the moment I see two solutions for this, but I think I got this way too complicated.

1. Overwriting add_subdirectory() to prevent duplicates

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. Adding an "include guard" to all sub-level CMakeLists.txts, like:

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


I've been testing the concepts suggested by tamas.kenez and m.s. with some promising results. The summaries can be found in my following answers:

解决方案

Adding the same subdirectory multiple times is out of question, it's not how CMake is intended to work. There are two main alternatives to do it in a clean way:

  1. Build your libraries in the same project as your app. Prefer this option for libraries you're actively working on (while you're working on the app) so they are likely to be frequently edited and rebuilt. They will also show up in the same IDE project.

  2. Build your libraries in an external project (and I don't mean ExternalProject). Prefer this option for libraries that are just used by your app but you're not working on them. This is the case for most third-party libraries. They will not clutter your IDE workspace, either.

Method #1

  • your app's CMakeLists.txt adds the subdirectories of the libraries (and your libs' CMakeLists.txt's don't)
  • your app's CMakeLists.txt is responsible to add all immediate and transitive dependencies and to add them in the proper order
  • it assumes that adding the subdirectory for libx will create some target (say libx) that can be readily used with target_link_libraries

As a sidenote: for the libraries it's a good practice to create a full-featured library target, that is, one that contains all the information needed to use the library:

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

So the location of include directories of the library can remain an internal affair of the lib. You will only have to do this;

target_link_libraries(LibC LibB)

then the include dirs of LibB will also be added to the compilation of LibC. Use the PRIVATE modifier if LibB is not used by the public headers of LibC:

target_link_libraries(LibC PRIVATE LibB)

Method #2

Build and install your libraries in seperate CMake projects. Your libraries will install a so-called config-module which describes the locations of the headers and library files and also compile flags. Your app's CMakeList.txt assumes the libraries has already been built and installed and the config-modules can be found by the find_package command. This is a whole another story so I won't go into details here.

A few notes:

  • You can mix #1 and #2 as in most cases you will have both unchanging, third-party libs and your own libraries under development.
  • A compromise between #1 and #2 is using the ExternalProject module, preferred by many. It's like including the external projects of your libraries (built in their own build tree) into your app's project. In way it combines the disadvantages of both approaches: you can't use your libraries as targets (because they're in a different project) and you can't call find_package (because the libs are not installed the time your app's CMakeLists is configuring).
  • A variant of #2 is to build the library in an external project but instead of installing the artifacts use them from their source/build locations. For more about this see the export() command.

这篇关于CMake:如何设置源,库和CMakeLists.txt依赖项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆