CMake:如何设置Source,Library和CMakeLists.txt依赖项? [英] CMake: How to setup Source, Library and CMakeLists.txt dependencies?
问题描述
我有几个项目(所有建立与CMake从相同的源代码树结构)都使用自己的混合出几十个支持库。
所以我想到了如何在CMake中正确设置这个问题。到目前为止,我只找到 CMake如何正确创建目标之间的依赖关系但是我仍然在设置一切与全局依赖(项目级别知道这一切)或与本地依赖(每个子级目标只处理自己的依赖)之间奋斗。
这里是我的目录结构和我目前想出了使用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
Lib / LibA / CMakeLists.txt
include_directories(Inc ../LibC/Inc)
add_subdirectory(../ LibC LibC)
add_library(LibA Src /a.cc Inc / ah)
target_link_libraries(LibA LibC)
/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 Src / c.cc Inc / ch)
target_link_libraries(LibC LibB)
App1 / CMakeLists.txt (为了便于复制,我在这里生成源文件/头文件)
cmake_minimum_required (VERSION 2.8)
项目(App1 CXX)
文件(WRITESrc / main.cc#include \ah\\\\
#include \bh \\ nint main()\\\
{\\\
a(); \\\
b(); \\\
return 0; \\\
})
文件/ lib / LibA / Inc / ahvoid a();)
文件(WRITE../Lib/LibA/Src/a.cc#include \ch \\ nvoid a()\\\
{\\\
c(); \\\
})
文件(WRITE../Lib/LibB/Inc/bhvoid b();)
文件(WRITE../Lib/LibB/Src/b.ccvoid b(){})
文件(WRITE../Lib/LibC/Inc/chvoid c );)
文件(WRITE../Lib/LibC/Src/c.cc#include \bh \\\\
void c()\\\
{\\\
b ; \\\
})
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
目前我更喜欢使用本地依赖项,因为它更容易使用。我只是在链接级别使用 target_link_libraries()
和CMake级别在链接级别给出源级别的依赖性 include_directories()
与 add_subdirectory()
。
有了这一点,你不需要知道支持库之间的依赖关系,并且 - 使用CMake级别包含,你只能结束目标真的用。果然,你可以让所有包括目录和目标已知全局,让编译器/链接器整理其余。
我也尝试过一个 Lib / CMakeLists.txt
以处理 Lib
目录树中的所有依赖项,但我最终有很多 if($ {PROJECT_NAME}STREQUAL ... )
检查和我无法创建中间库分组目标而不给出至少一个源文件的问题。
所以上面的例子到目前为止这么好,但它引发以下错误,因为你应该/不能添加 CMakeLists.txt
两次:
在Lib / LibB / CMakeLists.txt中的CMake错误:2(add_library):
add_library无法创建目标LibB,因为另一个目标与
相同名称已存在。现有目标是在源目录Lib / LibB中创建的
的静态库。
有关详细信息,请参阅policy 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
中添加包含警卫 :
if(NOT TARGET LibA)
...
endif()
感谢您提供排序的帮助。
strong> EDIT:我一直在测试 tamas.kenez 和 ms 有一些promissing的结果。总结可以在我的以下答案中找到:
多次添加相同的子目录是没有问题的,这不是CMake如何工作。有两个主要的替代方法,以干净的方式:
-
在与您的应用程序相同的项目中构建库。对于您正在处理的库(当您在应用程序上工作时),优先选择此选项,因此它们可能会被频繁编辑和重建。
-
在外部项目中构建库(,我不是指ExternalProject >)。对于刚刚由您的应用程序使用的库,但不是对它们进行处理,优选此选项。这是大多数第三方库的情况。他们不会混淆您的IDE工作区。
方法#1
- 您的应用程式的
CMakeLists.txt
新增了图书馆的子目录(以及您的库的CMakeLists.txt 不会)
- 您的应用程式的
CMakeLists.txt
负责将所有即时和并且以正确的顺序添加它们 - 它假定为
libx
添加子目录将创建一些目标(例如libx
),可以随时使用target_link_libraries
作为旁注:对于库,创建一个全功能库目标,也就是包含使用库所需的所有信息是一个很好的做法:
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)
pre>
那么
LibB
的include dirs也将被添加到LibC
。如果LibC $的公共头未使用
LibB
,请使用PRIVATE
c $ c>:target_link_libraries(LibC PRIVATE LibB)
方法2
在独立的CMake项目中构建并安装您的库。您的库将安装一个所谓的 config-module ,它描述了标题和库文件的位置以及编译标志。你的应用程序的
CMakeList.txt
假设库已经被构建和安装,config-modules可以通过find_package
命令。
一些注意事项:
- 您可以混合#1和#2,因为在大多数情况下,您将拥有不变的,第三方库和正在开发的自己的库。
- #1和#2正在使用许多人首选的 ExternalProject模块。这就像将你的库的外部项目(构建在自己的构建树中)包含到你的应用程序的项目中。在方法上,它结合了两种方法的缺点:你不能使用你的库作为目标(因为他们在一个不同的项目),你不能调用
find_package
因为libs没有安装你的应用程序的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 setup 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 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
, ...):
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 source level with include_directories()
, at link level with target_link_libraries()
and at 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 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 to 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.txt
like:
if (NOT TARGET LibA)
...
endif()
Thanks in advance for your help sorting this out.
EDIT: I've been testing the concepts suggested by tamas.kenez and m.s. with some promissing results. The summaries can be found in my following answers:
- preferred cmake project structure
- CMake share library with multiple executables
- Making cmake library accessible by other cmake packages automatically
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:
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.
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 (saylibx
) that can be readily used withtarget_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'sCMakeLists
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:如何设置Source,Library和CMakeLists.txt依赖项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!