CMake: Как установить зависимости Source, Library и CMakeLists.txt?
У меня есть несколько проектов (все здания с 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 с некоторыми результатами. Резюме можно найти в моих следующих ответах:
Ответы
Ответ 1
Добавление одного и того же подкаталога несколько раз не может быть и речи, это не то, как должен работать CMake. Есть два основных варианта, которые можно сделать чистым способом:
-
Создайте свои библиотеки в том же проекте, что и ваше приложение. Предпочитайте этот вариант для библиотек, в которых вы активно работаете (пока работаете над приложением), поэтому они, вероятно, будут часто редактироваться и перестраиваться. Они также будут отображаться в том же проекте IDE.
-
Постройте свои библиотеки во внешнем проекте (и я не имею в виду ExternalProject). Предпочитайте этот вариант для библиотек, которые просто используются вашим приложением, но вы не работаете над ними. Это относится к большинству сторонних библиотек. Они также не будут загромождать рабочую область IDE.
Метод # 1
- ваше приложение
CMakeLists.txt
добавляет поддиректории библиотек (и ваши libs 'CMakeLists.txt
do not)
- ваше приложение
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)
то включающие в него dirs LibB
также будут добавлены к компиляции LibC
. Используйте модификатор PRIVATE
, если LibB
не используется публичными заголовками LibC
:
target_link_libraries(LibC PRIVATE LibB)
Метод # 2
Создавайте и устанавливайте свои библиотеки в отдельных проектах CMake. В ваших библиотеках будет установлен так называемый конфигурационный модуль, который описывает расположение файлов заголовков и библиотек, а также компилирует флаги. Ваше приложение CMakeList.txt
предполагает, что библиотеки уже созданы и установлены, а config-modules можно найти с помощью команды find_package
. Это совершенно другая история, поэтому я не буду вдаваться в подробности здесь.
Несколько примечаний:
- Вы можете смешивать # 1 и # 2, так как в большинстве случаев вы будете иметь как неизменные, так и сторонние библиотеки и ваши собственные библиотеки, находящиеся в разработке.
- Компромисс между # 1 и # 2 использует модуль ExternalProject, который предпочитают многие. Это похоже на внешние проекты ваших библиотек (встроенные в их собственное дерево построения) в ваш проект приложения. Таким образом, он сочетает в себе недостатки обоих подходов: вы не можете использовать свои библиотеки в качестве целей (потому что они находятся в другом проекте), и вы не можете вызывать
find_package
(потому что libs не установлены во время вашего приложения CMakeLists
настраивается).
- Вариант # 2 состоит в том, чтобы построить библиотеку во внешнем проекте, но вместо того, чтобы устанавливать артефакты, используйте их в своих источниках/строках. Для получения дополнительной информации см. Команду
export()
.