CMake та пошук інших проектів та їх залежностей


77

Уявіть такий сценарій: Проект A - це спільна бібліотека, яка має кілька залежностей (LibA, LibB та LibC). Проект B є виконуваним файлом, який має залежність від проекту A, а тому вимагає всіх залежностей проекту A також для побудови.

Крім того, обидва проекти побудовані за допомогою CMake, і проект A не повинен встановлюватись (через ціль 'install'), щоб Project B використовував його, оскільки це може стати неприємністю для розробників.

Який найкращий спосіб вирішити ці залежності за допомогою CMake? Ідеальним рішенням було б якомога простіше (хоча і не простіше) і вимагати мінімального обслуговування.


2
Для майбутнього процвітання: cmake.org/Wiki/CMake/Tutorials / ...
blockchaindev

1
Здається, цей підручник не пояснює, як обробляти експорт зовнішніх бібліотечних залежностей. Єдина бібліотека, з якою пов'язано, - це бібліотека, створена проектом. Мені потрібно знати, як сказати проекту B, що для проекту A потрібні різні зовнішні бібліотеки, і тому їх потрібно додати до етапу зв’язування проекту B.
Бен Фермер,

Насправді вам слід спробувати Linux або підсистему Linux, якщо ви фанат ПК. Найкраще з цією платформою - це те, що Linux встановить для вас усі залежності. Або ще краще, він пропонує, яких залежностей вам не вистачає, та надає Sudo apt-get install mydependencies, як встановити. Дійсно просто.
Juniar

@Juniar, це дуже спрощує та оптимізує речі, я згоден. Але розгортання програмного забезпечення робить кошмаром. Я вважаю за краще мати все в одному пакеті для мого програмного забезпечення і розгорнути все разом (навіть частково продублюючи деякі бібліотеки). Не кажучи вже про проблеми з технічним обслуговуванням. Кожна коробка матиме унікальний набір підвесок (до певної міри).
OpalApps,

@OpalApps, Залежності можуть бути встановлені на різні шляхи та каталоги, однак ви все одно можете додати ці залежності під час компіляції або налаштувати / включити зовнішні шляхи. Вони не будуть встановлені за одним шляхом True, однак "sudo apt-get install" встановлюється в певні каталоги, просто перемикайте їх між собою.
Juniar

Відповіді:


147

Легко. Ось приклад з верхньої частини моєї голови:

Верхній рівень CMakeLists.txt:

cmake_minimum_required(VERSION 2.8.10)

# You can tweak some common (for all subprojects) stuff here. For example:

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES  ON)

if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
  message(SEND_ERROR "In-source builds are not allowed.")
endif ()

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_MAKEFILE   ON)

# Remove 'lib' prefix for shared libraries on Windows
if (WIN32)
  set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()

# When done tweaking common stuff, configure the components (subprojects).
# NOTE: The order matters! The most independent ones should go first.
add_subdirectory(components/B) # B is a static library (depends on Boost)
add_subdirectory(components/C) # C is a shared library (depends on B and external XXX)
add_subdirectory(components/A) # A is a shared library (depends on C and B)

add_subdirectory(components/Executable) # Executable (depends on A and C)

CMakeLists.txtу components/B:

cmake_minimum_required(VERSION 2.8.10)

project(B C CXX)

find_package(Boost
             1.50.0
             REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

include_directories(${Boost_INCLUDE_DIRS})

add_library(${PROJECT_NAME} STATIC ${CPP_FILES})

# Required on Unix OS family to be able to be linked into shared libraries.
set_target_properties(${PROJECT_NAME}
                      PROPERTIES POSITION_INDEPENDENT_CODE ON)

target_link_libraries(${PROJECT_NAME})

# Expose B's public includes (including Boost transitively) to other
# subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${Boost_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txtу components/C:

cmake_minimum_required(VERSION 2.8.10)

project(C C CXX)

find_package(XXX REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${XXX_DEFINITIONS})

# NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS.
include_directories(${B_INCLUDE_DIRS}
                    ${XXX_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} B
                                      ${XXX_LIBRARIES})

# Expose C's definitions (in this case only the ones of XXX transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose C's public includes (including the ones of C's dependencies transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${B_INCLUDE_DIRS}
                                 ${XXX_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txtу components/A:

cmake_minimum_required(VERSION 2.8.10)

project(A C CXX)

file(GLOB CPP_FILES source/*.cpp)

# XXX's definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})

# NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS.
include_directories(${C_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

# You could need `${XXX_LIBRARIES}` here too, in case if the dependency 
# of A on C is not purely transitive in terms of XXX, but A explicitly requires
# some additional symbols from XXX. However, in this example, I assumed that 
# this is not the case, therefore A is only linked against B and C.
target_link_libraries(${PROJECT_NAME} B
                                      C)

# Expose A's definitions (in this case only the ones of C transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose A's public includes (including the ones of A's dependencies
# transitively) to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${C_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txtу components/Executable:

cmake_minimum_required(VERSION 2.8.10)

project(Executable C CXX)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${A_DEFINITIONS})

include_directories(${A_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} A C)

Щоб це було зрозуміло, ось відповідна деревна структура джерела:

Root of the project
├───components
│   ├───Executable
│   │   ├───resource
│   │   │   └───icons
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───A
│   │   ├───include
│   │   │   └───A
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───B
│   │   ├───include
│   │   │   └───B
│   │   ├───source
|   |   └───CMakeLists.txt
│   └───C
│       ├───include
│       │   └───C
│       ├───source
|       └───CMakeLists.txt
└───CMakeLists.txt

Є багато моментів, де це можна змінити / налаштувати або змінити, щоб задовольнити певні потреби, але це, принаймні, повинно розпочати.

ПРИМІТКА: Я успішно використовував цю структуру в декількох середніх та великих проектах.


15
Ви F **** ЗІРКА! ти справді визволив мене з масивного головного болю, який тривав майже добу. Велике спасибі 1
rsacchettini

2
Мені цікаво, чи компілюється він, якби я мав викликати Cmake з каталогу "Виконання"; чи я повинен постійно компілювати з кореня проекту?
Ахмет Іпкін

1
Я думаю, що він має невеликий недолік, який ви визначаєте у кожному проекті двічі (один раз set(...INCLUDE_DIRSі один раз include_directories()) каталоги включення , які мені важко підтримувати (завжди пам’ятаючи про додавання нової залежності включення в двох місцях). Ви можете добувати їх за допомогою get_property(...PROPERTY INCLUDE_DIRECTORIES).
pseyfert

2
Це справді чудово, але як можна досягти того самого, коли A, B і C є повністю окремими проектами? Тобто я хочу побудувати A та експортувати його, щоб мати власний файл ProjectConfig.cmake, а потім у B використати find_package, щоб знайти A у системі, якимось чином отримати список усіх бібліотек, від яких залежить A, щоб їх можна було зв’язати, коли будинок Б.
Бен Фермер

2
@Ben Farmer, це справді більш складна тема, яка заслуговує величезної статті для правильного пояснення. У мене ніколи не вистачало терпіння, щоб окреслити це тут. Насправді, це те, що я роблю для управління своїми проектами, оскільки насправді це кінцеве (професійне) використання та намір CMake. Для управління всім цим я маю свій власний фреймворк CMake, який багато чого робить за кадром. Як приклад, ви можете спробувати створити будь-який з моїх іграшкових проектів C ++ або брандмауер C ++ .
Олександр Шукаєв

15

Олександр Шукаєв чудово розпочав роботу, але є ряд речей, які можна зробити краще:

  1. Не використовуйте include_directories. Принаймні, використовуйте target_include_directories. Однак вам, мабуть, навіть не потрібно цього робити, якщо ви використовуєте імпортовані цілі.
  2. Використовуйте імпортовані цілі. Приклад для Boost:

    find_package(Boost 1.56 REQUIRED COMPONENTS
                 date_time filesystem iostreams)
    add_executable(foo foo.cc)
    target_link_libraries(foo
      PRIVATE
        Boost::date_time
        Boost::filesystem
        Boost::iostreams
    )
    

    Це дбає про каталоги включення, бібліотеки і т. Д. Якщо ви використовували Boost у заголовках на B, то замість PRIVATE використовуйте PUBLIC, і ці залежності будуть транзитивно додані до будь-чого, що залежить від B.

  3. Не використовуйте глобінг файлів (якщо ви не використовуєте 3.12). До недавнього часу глобалізація файлів працює лише під час налаштування, тому, якщо ви додаєте файли та збираєте, він не може виявити зміни, поки ви явно не регенеруєте проект. Однак якщо ви перераховуєте файли безпосередньо та намагаєтесь побудувати, він повинен визнати, що конфігурація застаріла, і автоматично відновити її на етапі побудови.

Тут є хороша розмова (YouTube): C ++ Зараз 2017: Даніель Пфайфер “Ефективний CMake”

Що охоплює ідею менеджера пакетів, яка дозволяє вашому корінному рівню CMake працювати з find_packageАБО subdirectory, однак, я намагався прийняти ідеологію цього і маю великі проблеми з використанням find_packageдля всього і маючи структуру каталогів, як ваша.


0

Це також можна зробити за допомогою Cacheмеханізму CMake, щоб досягти того ж (тобто спільного використання змінних, характерних для проекту):

встановлений (VAR "значення" CACHE INTERNAL "")

Зверніться до запитання про переповнення стека Як надати спільний доступ до змінних між різними файлами CMake .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.