У меня есть несколько проектов (все здания с 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/Lība/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.txt
следующим образом:
if (NOT TARGET LibA)
...
endif()
Заранее благодарим за помощь в сортировке.
EDIT: Я тестировал концепции, предложенные tamas.kenez и ms с некоторыми результатами. Резюме можно найти в моих следующих ответах: