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().