【发布时间】:2015-10-09 08:08:01
【问题描述】:
我有几个项目(全部使用 CMake 从相同的源树结构构建)都使用自己的数十个支持库的组合。
所以我想到了如何在 CMake 中正确设置这个问题。到目前为止,我只找到了 CMake how to correctly create dependencies between targets,但我仍然在使用全局依赖项(项目级别确实知道一切)或本地依赖项(每个子级别目标仅处理自己的依赖项)。
这是我的目录结构的简化示例以及我目前使用 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/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.kenez 和m.s. 提出的概念,并取得了一些有希望的结果。总结可以在我的以下答案中找到:
【问题讨论】:
-
我在 App1/CMakeLists.txt 中看不到
add_subdirectory(../Lib/LibA LibA)的原因。你为什么需要这个?通常你有一个根 CMakeLists.txt 来添加子目录。 -
@m.s.在我的真实世界项目中,应用程序比我的
App1更多(我在问题中添加了一些关于此的细节)。到目前为止,我没有添加根级别CMakeLists.txt以防止在工作App1、App2等的不同项目团队之间产生依赖关系(我想不出一个站点上的一个足够通用的对于所有项目和在另一个站点上足够具体,最后它只生成一个可执行目标)。目前他们只是去他们的子目录,创建一个二进制子目录,用cmake ..生成他们的make环境并调用make。 -
在
App1工作时,通过ExternalProject_Add添加LibA、LibB等怎么样? -
@m.s.在我的
App1示例中,如果我通过外部项目同时添加LibA和LibB,我会不会得到LibB中所有内容的重复符号? -
在这里查看文章系列:coderwall.com/p/qk2eog/…