@semenyakinVS
Писатель кода и не только

Как правильно организовать модульный проект с использованием CMake?

Пишу кроссплатформенный проект на С++. Так как CMake дефакто является стандартом для кроссплатформенных решений, проект использует CMake. Раньше добавлял зависимости - сторонние библиотеки - предварительно собирая их вручную в отдельной папке и потом копируя в структуру проекта либо перенося сторонние исходники в код напрямую. Однако при очередной сборке очередной версии сторонней библиотеки надоело - решил организовать всё по уму, через механизм пакетов (FIND_PACKAGE и иже с ним).

Набросал желаемую структуру проекта в первой итерации:
Структура проекта
packages_test
|-- .git
|-- build // Результат сборки
| |-- details // Папка для сборки, тут всякий временный хлам (make-файлы, .o-файлы, и проч.)
| |-- res.exe // Исполняемый файл
| |-- . . . // Ресурсы (необходимые .dll/.so, контент, и т.д.)
|-- CMakeLists.txt // Конфигурация сборки проекта
|-- main.cpp // Точка входа, main()
|-- . . . // Прочие файлы проекта
|--dependencies // Зависимости (подключаются через механизм submodule)
. |--vendor_package_0 // У каждой зависимости своя внутренняя организация
. |--vendor_package_1
. |-- . . .


Начал писать CMake-файл для проекта, но столкнулся с трудностями. Вот текущий CMake-файл:
CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(playrix_entrance_test)

# ------------------------ Настройка зависимостей -----------------------------
# Переменная для хранения пути к папке, в которой лежат зависимости
set(DEPENDENCIES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies")

# - - - - - - - - - - - - - Настройка библиотеки assimp - - - - - - - - - - - - - - -
# Тут может быть любая сторонняя библиотека. Указываю реально
# подключаемую чтобы подчеркнуть, что детали подключения каждой
# зависимости могут отличаться - минимум потому, что нет стандарта
# структуры проектов, использующих CMake

# Добавление папки с find-модулями CMake для assimp
# Это нужно чтобы find_package(...) смог извлечь информацию об assimp
list(APPEND CMAKE_MODULE_PATH "${DEPENDENCIES_DIR}/assimp/cmake-modules")

# Импорт переменных, описывающих пакет assimp. Данная команда
# создаёт всякие переменные с расположением заголовочных файлов пакета,
# расположением самого файла библиотеки ({.dll/.so} + .lib/.a), информацию
# о результате поиска пакета, и проч., однако не выполняет сборку пакета
find_package(assimp REQUIRED)

# Неизвестный код для сборки assimp
# ??? >>> add_subdirectory() <<< ???
# ??? >>> install() <<< ???

# - - - - - - - - - - - - - - - - -
# . . . - тут может быть код для подключения и сборки других сторонних библиотек

# ------------------------ Сборка проекта -----------------------------
add_executable(TestProject
"main.cpp")

# - - - - - - - - - - - - Подключение зависимостей - - - - - - - - - - - - -
# assimp
include_directories("${assimp_INCLUDE_DIR}")
target_link_libraries(TestProject assimp)


Вопрос, собственно, состоит в том, как корректно сконфигурировать сборку стороннего пакета в рамках конфигурации сборки своего проекта - если сторонний пакет не собран заранее. Судя по переменным, которые предоставляет FIND_PACKAGE(...), считается что пакет должен быть уж собран, а переменные могут быть использованы чтобы передать пути для линковки и подключения заголовочных файлов.

Также, возможно, я вообще что-то делаю не так - я пока слабо понимаю CMake. Был бы благодарен тогда если поделитесь как принято организовывать подобные штуки.

P.S.: Вообще, идеальной видится "плоская" структура проекта. Какая-то такая:
CMakeLists.txt
packages_test // Папка, содержащая все создаваемые и используемые пакеты
|-- project_package // Пакет проекта
| |-- .git
| |-- CMakeLists.txt // Конфигурация сборки пакета
| |-- main.cpp // Точка входа, main()
| |-- . . . // Прочие файлы проекта
| |-- product // Результат сборки пакета
| | |-- res.exe // Исходник
| | |-- . . . // Прочие необходимые ресурсы (необходимые .dll/.so, контент, и т.д.)
|--vendor_package_0 // Папка-пакет стороннего проекта
| |-- .git
| |-- product // Результат сборки, готовый к использованию ({.dll/.so} + .a/.lib + headers)
| |-- . . .
|--vendor_package_1
| |-- .git
| |-- product
| |-- . . .
|-- . . .


Но я понимаю что для реализации системы сборки, поддерживающей подобную структуру понадобится потратить слишком много времени - минимум на написание обёрток, подгоняющих конфигурацию сборки сторонних проектов под описанную.

P.P.S.: На всякий случай, платформа, на которой выполняю тестовую сборку - Windows, тулчейн - MinGW, IDE - QtCreator.
  • Вопрос задан
  • 1276 просмотров
Решения вопроса 1
@4rtzel
Поскольку CMake позволяет получить один и тот же результат кучей разный способов, то я постараюсь описать подход, который использовал бы я сам.
Структура:
packages_test
├── .git
├── cmake // Папка с доп. CMake скриптами если в этом есть необходимость
├── build // Результат сборки
│   └─ res.exe // Исполняемый файл
├── CMakeLists.txt // Конфигурация сборки проекта
├── src
│   ├─ main.cpp // Точка входа, main()
│   └─ ...// Прочие файлы проекта
├── dependencies // Зависимости (подключаются через механизм submodule)
│   ├─ vendor_package_0 // У каждой зависимости своя внутренняя организация
│   └─ vendor_package_1
└── test // Тесты
    ├── CMakeLists.txt
    └── src
        └── test_main.cpp

Теперь, как мы будем добавлять зависимости? Это зависит от того где расположены библиотеки и как они собираются. В вашем случае, как я понимаю, все vendor_package_x представляют из себя CMake проекты с использованием add_library и получаемые из git submodule'ей. Это значит, что мы можем использовать add_subdirectory для импорта их target'ов.

| Note: find_package чаще всего используется для поиска установленных в системе библиотек с помощью FindXXX.cmake скриптов

Главный CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(playrix_project VERSION 1.0 LANGUAGES CXX)
add_executable(playrix src/main.cpp)

# Импортируем наши зависимости. Это не приводит к сборке, но просто позволяет нам использовать target'ы этих проектов.
add_subdirectory(dependencies/vendor_package_0)
add_subdirectory(dependencies/vendor_package_1)

# Забудьте про include_directories и link_directories! В современном CMake следует использовать targets и properties.
# Опредеяем зависимости нашего проекта
target_link_libraries(playrix # Имя нашего executable'а
    PRIVATE # Определяет область видимости зависимостей для внешних проектов
        vendor_package_0_target # Настоящее имя target'а надо смотреть в vendor_package_0 CMakeLists.txt (add_library)
        vendor_package_1_target
)

target_link_libraries позаботится о том, чтобы собрать все зависимости, слинковать (если надо) их с нашим проектом и инклюднуть необходимые директории. Теперь можно собирать проект!

Дополнительные ссылки:
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы