Ответы пользователя по тегу C++
  • Как правильно построить структуру проекта используя cmake?

    @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. а прямо так передаются в зависимые проекты. На одну операцию упаковки/распаковки меньше ).
    Ответ написан
    2 комментария