syxme
@syxme

Как правильно построить структуру проекта используя cmake?

Добрый день, есть проект на c++ CMake.

Состоит из 2х частей:
1. Библиотека моего движка где находится вся вычислительная часть.
2. Программный продукт который использует и наследует классы библиотеки движка.

Цель:
Сделать удобной разработку продукта разрабатывая продукт и движок паралельно (2 репозитория)
Например хочется отладку движка в продукте используя статическую библиотеку движка.
Чтоб при сборки продукта собирался движок.

Первая идея была использовать add_subdirectory но я не знаю, правильно ли это.
  • Вопрос задан
  • 102 просмотра
Решения вопроса 1
@klirichek
либа - это обычно файл (.a, .lib, .so, .dll, .dylib - зависит от статики/динамики) + хедеры, которые должно включить приложение, использующее либу.

При разработке одновременно либы и приложения add_subdirectory достаточно очевидный вариант, покуда объединяет их оба в один проект. Желательно позаботиться о модульности (раз уж вы поделили проект на две части - значит, это зачем-то было нужно!).

В папке с либой должен быть свой собственный CMakeLists.txt, собирающий либу; он должен быть самодостаточным для сборки. Т.е. если ей нужны какие-то зависимости - чтобы они искались именно для проекта либы, а не "где-то выше". Т.е какие-то исходники, какие-то find(), потом add_library(name). И нужно определить, что из либы должно увидеть использующее её приложение. Это делается через задание свойств target_XXX(). Например, чтобы показать консумеру путь к нужным хедерам - target_include_directories, чтобы добавить ключи компиляции - target_compile_definitions(). В современном cmake это можно настроить как в настоящем ООП - свойства, нужные для сборки самой либы (например, зависимости, с которыми она статически линкуется. Ей нужны, но как слинковались - консумеру они уже невидимы и ненужны); свойства, нужные консумеру, но не нужные для сборки либы (например, хедеры, специфичные для включения либы, но не нужные для её сборки), и общие свойства, нужные как для либы, так и для её пользователей. Смысл в том, что все эти "настройки для внешних пользователей" не кладутся в некие глобальные переменные cmake (так делали многие годы, но это далеко не best practices), а определяются как свойства самой библиотеки. И вишенкой на торте, в конце проекта либы можно дописать для неё экспорт и алиас (зачем это нужно - чуть ниже).

export(TARGETS mylib FILE "mylib-targets.cmake" NAMESPACE mylib::)
add_library(mylib::mylib ALIAS mylib)

Приложение, использующее либу, должно, во-первых, найти её. И во-вторых, слинковать. Про add_subdirectory - понятно. Добавляете папку и либа доступна. Другой вариант - если либа уже устоялась и НЕ хочется каждый раз её пересобирать. Тогда вместо add_subdirectory включаете (через include) файлик 'mylib-targets.cmake', который будет в сборочной папке либы. На машине разработчика это вполне рабочий вариант! Ну и крайний вариант - это делать полноценный экспорт, тогда пользователи смогут находить либу через штатный find_package().
После нахождения либа добавляется единственной строчкой:

target_link_libraries(myprog PUBLIC mylib::mylib)

Благодаря прописанным свойствам либы эта строчка автоматически добавляет всё необходимое - пути к хедерам, либу для линковки, дополнительные параметры компиляции и т.д. (раньше во время глобальных переменных подразумевалось, что после нахождения либы с помощью find_package у вас появлялось несколько переменных, вида mylib_FOUND - что оно нашлось, mylib_INCLUDE_DIRECTORIES - путь к хедерам, mylib_LIBRARIES - что линковать. А если либа появлялась через add_subdirectory, там вообще фантазия на тему "как сообщить о себе" ничем не ограничивалась, что, в свою очередь, приводило к лапшевидному коду, который использовал библиотеку. Со свойствами целей это всё уже не нужно!). Ну а имя mylib::mylib - оно работает как для импортированной либы (через включение mylib-targets.cmake), так и для add_subdirectory(), именно для этого оно было добавлено там как алиас.

Итого, в основном проекте у вас будет что-то вроде

add_subdirectory(mylib)
target_link_libraries(myprog PUBLIC mylib::mylib)

или

include (mylib/build/mylib-targets.cmake)
target_link_libraries(myprog PUBLIC mylib::mylib)

или

find_package(mylib REQUIRED)
target_link_libraries(myprog PUBLIC mylib::mylib)

Первый вариант - для одновременной разработки. В IDE будут исходники и основного проекта, и либы сразу. Тёмная сторона - когда сделаете clean all, либа тоже сотрётся :) (что очевидно)
Второй - для приложения. Либа будет всегда под рукой, но уже как бинарь. Линковаться можно сколько угодно, при очистке ничего не сотрётся, путь привязан к папке (двинуть в другую папку нельзя, потому что в файле прописаны абсолютные пути).
Третий - для широкой разработки. Когда либа уже отдельно, и ставится отдельно, и кто-то с ней что-то разрабатывает. Это самый цивилизованный вариант, папку можно двигать куда угодно, главное сообщить cmake, где её искать. Но настраивать это чуть сложнее и не всегда оправдано.

И да - если ещё поизучать cmake, то либа - это не обязательно библиотека для линковки "в общепринятом смысле". Но это просто набор свойств (include-папки, опции компилятора, опции линковки и т.д.), инкапсулированные в нечто под названием "библиотека". Например, можно определить либу для задания варнингов компилятора -wall, где не будет ни исходников, ни библиотек, а только один параметр компилятора. И это вполне нормальный и рабочий способ! Помимо статических либ для чуть-чуть ускорения сборки можно использовать либу типа object. Это просто набор скомпилированных объектников, которые не пакуются в отдельный файл .a или .lib. а прямо так передаются в зависимые проекты. На одну операцию упаковки/распаковки меньше ).
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@res2001
Developer, ex-admin
Отлаживать библиотеку логичнее специально написанными для этого тестами. В этом случае библиотека будет представлять самостоятельный продукт. Ее можно будет устанавливать клиентам отдельно. А в основном продукте использовать find_package для поиска библиотеки.

Если вам этого не надо, то вполне нормально включать каталог с исходниками библиотеки как подкаталог продукта и использовать add_subdirectory.
Ответ написан
Ваш ответ на вопрос

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

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