CMake и C++ — братья навек

  • Tutorial

Дружба навек


В процессе разработки я люблю менять компиляторы, режимы сборки, версии зависимостей, производить статический анализ, замерять производительность, собирать покрытие, генерировать документацию и т.д. И очень люблю CMake, потому что он позволяет мне делать всё то, что я хочу.


Многие ругают CMake, и часто заслуженно, но если разобраться, то не всё так плохо, а в последнее время очень даже неплохо, и направление развития вполне позитивное.


В данной заметке я хочу рассказать, как достаточно просто организовать заголовочную библиотеку на языке C++ в системе CMake, чтобы получить следующую функциональность:


  1. Сборку;
  2. Автозапуск тестов;
  3. Замер покрытия кода;
  4. Установку;
  5. Автодокументирование;
  6. Генерацию онлайн-песочницы;
  7. Статический анализ.

Кто и так разбирается в плюсах и си-мейке может просто скачать шаблон проекта и начать им пользоваться.


Содержание


  1. Проект изнутри
    1. Структура проекта
    2. Главный CMake-файл (./CMakeLists.txt)
      1. Информация о проекте
      2. Опции проекта
      3. Опции компиляции
      4. Основная цель
      5. Установка
      6. Тесты
      7. Документация
      8. Онлайн-песочница
    3. Скрипт для тестов (test/CMakeLists.txt)
      1. Тестирование
      2. Покрытие
    4. Скрипт для документации (doc/CMakeLists.txt)
    5. Скрипт для онлайн-песочницы (online/CMakeLists.txt)
  2. Проект снаружи
    1. Сборка
      1. Генерация
      2. Сборка
    2. Опции
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. Сборочные цели
      1. По умолчанию
      2. mylib-unit-tests
      3. check
      4. coverage
      5. doc
      6. wandbox
    4. Примеры
  3. Инструменты
  4. Статический анализ
  5. Послесловие


Проект изнутри



Структура проекта


.
├── CMakeLists.txt
├── README.en.md
├── README.md
├── doc
│   ├── CMakeLists.txt
│   └── Doxyfile.in
├── include
│   └── mylib
│       └── myfeature.hpp
├── online
│   ├── CMakeLists.txt
│   ├── mylib-example.cpp
│   └── wandbox.py
└── test
    ├── CMakeLists.txt
    ├── mylib
    │   └── myfeature.cpp
    └── test_main.cpp

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



Главный CMake-файл (./CMakeLists.txt)



Информация о проекте


В первую очередь нужно затребовать нужную версию системы CMake. CMake развивается, меняются сигнатуры команд, поведение в разных условиях. Чтобы CMake сразу понимал, чего мы от него хотим, нужно сразу зафиксировать наши к нему требования.


cmake_minimum_required(VERSION 3.13)

Затем обозначим наш проект, его название, версию, используемые языки и прочее (см. команду project).


В данном случае указываем язык CXX (а это значит C++), чтобы CMake не напрягался и не искал компилятор языка C (по умолчанию в CMake включены два языка: C и C++).


project(Mylib VERSION 1.0 LANGUAGES CXX)

Здесь же можно сразу проверить, включён ли наш проект в другой проект в качестве подпроекта. Это сильно поможет в дальнейшем.


get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)


Опции проекта


Предусмотрим две опции.


Первая опция — MYLIB_TESTING — для выключения модульных тестов. Это может понадобиться, если мы уверены, что с тестами всё в порядке, а мы хотим, например, только установить или запакетировать наш проект. Или наш проект включён в качестве подпроекта — в этом случае пользователю нашего проекта не интересно запускать наши тесты. Вы же не тестируете зависимости, которыми пользуетесь?


option(MYLIB_TESTING "Включить модульное тестирование" ON)

Кроме того, мы сделаем отдельную опцию MYLIB_COVERAGE для замеров покрытия кода тестами, но она потребует дополнительных инструментов, поэтому включать её нужно будет явно.


option(MYLIB_COVERAGE "Включить измерение покрытия кода тестами" OFF)


Опции компиляции


Разумеется, мы крутые программисты-плюсовики, поэтому хотим от компилятора максимального уровня диагностик времени компиляции. Ни одна мышь не проскочит.


add_compile_options(
    -Werror

    -Wall
    -Wextra
    -Wpedantic

    -Wcast-align
    -Wcast-qual
    -Wconversion
    -Wctor-dtor-privacy
    -Wenum-compare
    -Wfloat-equal
    -Wnon-virtual-dtor
    -Wold-style-cast
    -Woverloaded-virtual
    -Wredundant-decls
    -Wsign-conversion
    -Wsign-promo
)

Расширения тоже отключим, чтобы полностью соответствовать стандарту языка C++. По умолчанию в CMake они включены.


if(NOT CMAKE_CXX_EXTENSIONS)
    set(CMAKE_CXX_EXTENSIONS OFF)
endif()


Основная цель


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


Для этой цели создаём интерфейсную библиотеку.


add_library(mylib INTERFACE)

Привязываем заголовки к нашей интерфейсной библиотеке.


Современное, модное, молодёжное использование CMake подразумевает, что заголовки, свойства и т.п. передаются через одну единственную цель. Таким образом, достаточно сказать target_link_libraries(target PRIVATE dependency), и все заголовки, которые ассоциированы с целью dependency, будут доступны для исходников, принадлежащих цели target. И не требуется никаких [target_]include_directories. Это будет продемонстрировано ниже при разборе CMake-скрипта для модульных тестов.


Также стоит обратить внимание на т.н. выражения-генераторы: $<...>.


Данная команда ассоциирует нужные нам заголовки с нашей интерфейсной библиотекой, причём, в случае, если наша библиотека будет подключена к какой-либо цели в рамках одной иерархии CMake, то с ней будут ассоциированы заголовки из директории ${CMAKE_CURRENT_SOURCE_DIR}/include, а если наша библиотека установлена в систему и подключена в другой проект с помощью команды find_package, то с ней будут ассоциированы заголовки из директории include относительно директории установки.


target_include_directories(mylib INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

Установим стандарт языка. Разумеется, самый последний. При этом не просто включаем стандарт, но и распространяем его на тех, кто будет использовать нашу библиотеку. Это достигается за счёт того, что установленное свойство имеет категорию INTERFACE (см. команду target_compile_features).


target_compile_features(mylib INTERFACE cxx_std_17)

Заводим псевдоним для нашей библиотеки. Причём для красоты он будет в специальном "пространстве имён". Это будет полезно, когда в нашей библиотеке появятся разные модули, и мы заходим подключать их независимо друг от друга. Как в Бусте, например.


add_library(Mylib::mylib ALIAS mylib)


Установка


Установка наших заголовков в систему. Тут всё просто. Говорим, что папка со всеми заголовками должна попасть в директорию include относительно места установки.


install(DIRECTORY include/mylib DESTINATION include)

Далее сообщаем системе сборки о том, что мы хотим иметь возможность в сторонних проектах звать команду find_package(Mylib) и получать цель Mylib::mylib.


install(TARGETS mylib EXPORT MylibConfig)
install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake)

Следующее заклинание нужно понимать так. Когда в стороннем проекте мы вызовем команду find_package(Mylib 1.2.3 REQUIRED), и при этом реальная версия установленной библиотеки окажется несовместимой с версией 1.2.3, CMake автоматически сгенерирует ошибку. То есть не нужно будет следить за версиями вручную.


include(CMakePackageConfigHelpers)
write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake"
    VERSION
        ${PROJECT_VERSION}
    COMPATIBILITY
        AnyNewerVersion
)
install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake)


Тесты


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


if(NOT MYLIB_TESTING)
    message(STATUS "Тестирование проекта Mylib выключено")
elseif(IS_SUBPROJECT)
    message(STATUS "Mylib не тестируется в режиме подмодуля")
else()
    add_subdirectory(test)
endif()


Документация


Документация также не будет генерироваться в случае подпроекта.


if(NOT IS_SUBPROJECT)
    add_subdirectory(doc)
endif()


Онлайн-песочница


Аналогично, онлайн-песочницы у подпроекта тоже не будет.


if(NOT IS_SUBPROJECT)
    add_subdirectory(online)
endif()


Скрипт для тестов (test/CMakeLists.txt)



Тестирование


Первым делом находим пакет с нужным тестовым фреймворком (замените на свой любимый).


find_package(doctest 2.3.3 REQUIRED)

Создаём наш исполняемый файл с тестами. Обычно непосредственно в исполняемый бинарник я добавляю только файл, в котором будет функция main.


add_executable(mylib-unit-tests test_main.cpp)

А файлы, в которых описаны сами тесты, добавляю позже. Но так делать не обязательно.


target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp)

Подключаем зависимости. Обратите внимание, что к нашему бинарнику мы привязали только нужные нам CMake-цели, и не вызывали команду target_include_directories. Заголовки из тестового фреймворка и из нашей Mylib::mylib, а также параметры сборки (в нашем случае это стандарт языка C++) пролезли вместе с этими целями.


target_link_libraries(mylib-unit-tests
    PRIVATE
        Mylib::mylib
        doctest::doctest
)

Наконец, создаём фиктивную цель, "сборка" которой эквивалентна запуску тестов, и добавляем эту цель в сборку по умолчанию (за это отвечает атрибут ALL). Это значит, что сборка по умолчанию инициирует запуск тестов, то есть мы никогда не забудем их запустить.


add_custom_target(check ALL COMMAND mylib-unit-tests)


Покрытие


Далее включаем замер покрытия кода, если задана соответствующая опция. В детали вдаваться не буду, потому что они относятся больше к инструменту для замеров покрытия, чем к CMake. Важно только отметить, что по результатам будет создана цель coverage, с помощью которой удобно запускать замер покрытия.


find_program(GCOVR_EXECUTABLE gcovr)
if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE)
    message(STATUS "Измерение покрытия кода тестами включено")

    target_compile_options(mylib-unit-tests PRIVATE --coverage)
    target_link_libraries(mylib-unit-tests PRIVATE gcov)

    add_custom_target(coverage
        COMMAND
            ${GCOVR_EXECUTABLE}
                --root=${PROJECT_SOURCE_DIR}/include/
                --object-directory=${CMAKE_CURRENT_BINARY_DIR}
        DEPENDS
            check
    )
elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE)
    set(MYLIB_COVERAGE OFF)
    message(WARNING "Для замеров покрытия кода тестами требуется программа gcovr")
endif()


Скрипт для документации (doc/CMakeLists.txt)


Нашли Doxygen.


find_package(Doxygen)

Дальше проверяем, установлена ли пользователем переменная с языком. Если да, то не трогаем, если нет, то берём русский. Затем конфигурируем файлы системы Doxygen. Все нужные переменные, в том числе и язык попадают туда в процессе конфигурации (см. команду configure_file).


После чего создаём цель doc, которая будет запускать генерирование документации. Поскольку генерирование документации — не самая большая необходимость в процессе разработки, то по умолчанию цель включена не будет, её придётся запускать явно.


if (Doxygen_FOUND)
    if (NOT MYLIB_DOXYGEN_LANGUAGE)
        set(MYLIB_DOXYGEN_LANGUAGE Russian)
    endif()
    message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}")
    configure_file(Doxyfile.in Doxyfile)
    add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
endif ()


Скрипт для онлайн-песочницы (online/CMakeLists.txt)


Тут находим третий Питон и создаём цель wandbox, которая генерирует запрос, соответствующий API сервиса Wandbox, и отсылает его. В ответ приходит ссылка на готовую песочницу.


find_program(PYTHON3_EXECUTABLE python3)
if(PYTHON3_EXECUTABLE)
    set(WANDBOX_URL "https://wandbox.org/api/compile.json")

    add_custom_target(wandbox
        COMMAND
            ${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include |
            curl -H "Content-type: application/json" -d @- ${WANDBOX_URL}
        WORKING_DIRECTORY
            ${CMAKE_CURRENT_SOURCE_DIR}
        DEPENDS
            mylib-unit-tests
    )
else()
    message(WARNING "Для создания онлайн-песочницы требуется интерпретатор ЯП python 3-й версии")
endif()


Проект снаружи


Теперь рассмотрим, как этим всем пользоваться.



Сборка


Сборка данного проекта, как и любого другого проекта на системе сборки CMake, состоит из двух этапов:



Генерация


cmake -S путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Если команда выше не сработала из-за старой версии CMake, попробуйте опустить -S:
cmake путь/к/исходникам -B путь/к/сборочной/директории [опции ...]

Подробнее про опции.



Сборка проекта


cmake --build путь/к/сборочной/директории [--target target]

Подробнее про сборочные цели.



Опции



MYLIB_COVERAGE


cmake -S ... -B ... -DMYLIB_COVERAGE=ON [прочие опции ...]

Включает цель coverage, с помощью которой можно запустить замер покрытия кода тестами.



MYLIB_TESTING


cmake -S ... -B ... -DMYLIB_TESTING=OFF [прочие опции ...]

Предоставляет возможность выключить сборку модульных тестов и цель check. Как следствие, выключается замер покрытия кода тестами (см. MYLIB_COVERAGE).


Также тестирование автоматически отключается в случае, если проект подключается в другой проект качестве подпроекта с помощью команды add_subdirectory.



MYLIB_DOXYGEN_LANGUAGE


cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [прочие опции ...]

Переключает язык документации, которую генерирует цель doc на заданный. Список доступных языков см. на сайте системы Doxygen.


По умолчанию включён русский.



Сборочные цели



По умолчанию


cmake --build path/to/build/directory
cmake --build path/to/build/directory --target all

Если цель не указана (что эквивалентно цели all), собирает всё, что можно, а также вызывает цель check.



mylib-unit-tests


cmake --build path/to/build/directory --target mylib-unit-tests

Компилирует модульные тесты. Включено по умолчанию.



check


cmake --build путь/к/сборочной/директории --target check

Запускает собранные (собирает, если ещё не) модульные тесты. Включено по умолчанию.


См. также mylib-unit-tests.



coverage


cmake --build путь/к/сборочной/директории --target coverage

Анализирует запущенные (запускает, если ещё не) модульные тесты на предмет покрытия кода тестами при помощи программы gcovr.


Выхлоп покрытия будет выглядеть примерно так:


------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: /path/to/cmakecpptemplate/include/
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
mylib/myfeature.hpp                            2       2   100%   
------------------------------------------------------------------------------
TOTAL                                          2       2   100%
------------------------------------------------------------------------------

Цель доступна только при включённой опции MYLIB_COVERAGE.


См. также check.



doc


cmake --build путь/к/сборочной/директории --target doc

Запускает генерацию документации к коду при помощи системы Doxygen.



wandbox


cmake --build путь/к/сборочной/директории --target wandbox

Ответ от сервиса выглядит примерно так:


{
    "permlink" :    "QElvxuMzHgL9fqci",
    "status" :  "0",
    "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci"
}

Для этого используется сервис Wandbox. Не знаю, насколько у них резиновые сервера, но думаю, что злоупотреблять данной возможностью не стоит.



Примеры


Сборка проекта в отладочном режиме с замером покрытия


cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON
cmake --build путь/к/сборочной/директории --target coverage --parallel 16

Установка проекта без предварительной сборки и тестирования


cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=путь/к/установойной/директории
cmake --build путь/к/сборочной/директории --target install

Сборка в выпускном режиме заданным компилятором


cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=путь/к/директории/куда/установлены/зависимости
cmake --build путь/к/сборочной/директории --parallel 4

Генерирование документации на английском


cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English
cmake --build путь/к/сборочной/директории --target doc


Инструменты


  1. CMake 3.13


    На самом деле версия CMake 3.13 требуется только для запуска некоторых консольных команд, описанных в данной справке. С точки зрения синтаксиса CMake-скриптов достаточно версии 3.8, если генерацию вызывать другими способами.


  2. Библиотека тестирования doctest


    Тестирование можно отключать (см. опцию MYLIB_TESTING).


  3. Doxygen


    Для переключения языка, на котором будет сгенерирована документация, предусмотрена опция MYLIB_DOXYGEN_LANGUAGE.


  4. Интерпретатор ЯП Python 3


    Для автоматической генерации онлайн-песочницы.




Статический анализ


С помощью CMake и пары хороших инструментов можно обеспечить статический анализ с минимальными телодвижениями.


Cppcheck


В CMake встроена поддержка инструмента для статического анализа Cppcheck.


Для этого нужно воспользоваться опцией CMAKE_CXX_CPPCHECK:


cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-Iпуть/к/исходникам/include"

После этого статический анализ будет автоматически запускаться каждый раз во время компиляции и перекомпиляции исходников. Ничего дополнительного делать не нужно.


Clang


При помощи чудесного инструмента scan-build тоже можно запускать статический анализ в два счёта:


scan-build cmake -S путь/к/исходникам -B путь/к/сборочной/директории -DCMAKE_BUILD_TYPE=Debug
scan-build cmake --build путь/к/сборочной/директории

Здесь, в отличие от случая с Cppcheck, требуется каждый раз запускать сборку через scan-build.



Послесловие


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




Скачать шаблон проекта

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 51

    +2
    «CMake и C++ — братья навек» — звучит как угроза.
      +3

      Мне и то, и другое нравится, поэтому никаких негативных ассоциаций не возникает.

        +2

        Надо поменять местами, чтобы в рифму было: «C++ и CMake — братья навек».

          0

          Отличная мысль, чёрт возьми.

      +6
      я хочу рассказать, как достаточно просто организовать заголовочную библиотеку на языке C++ в системе CMake

      Это и без cmake просто делается.


      А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?
      С вишенкой на торте в виде бинарника программы.
      И при этом состоящих не из полутора файлов, а из десятков структурированных по папкам.


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

        +1
        Это и без cmake просто делается.

        Можно вообще всё руками делать. Но лучше иметь удобный инструмент.


        А вот как просто организовать не одну, а несколько библиотек, при этом не все из которых заголовочные, с зависимостями друг между другом и от внешних библиотек?

        На самом деле, не сильно сложнее. Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.

          +3

          Присоединяюсь к запросу на материал более высокого уровня и про кроссплатформенную сборку тоже расскажите. Большие проекты не только GCC собираются.

            +2
            Но лучше иметь удобный инструмент.

            Безусловно, но после тулинга в языках, авторы которых уже наелись C++, нырять во все эти ninja, cmake, autotools и прочее довольно напрягает. CMake при всех его достоинствах не самый удобный инструмент всё же.


            Если это не стёб, а реальный вопрос, то могу попробовать подготовить соответствующий материал.

            Не стёб, к сожалению. Адекватного материала на тему того, как удобно организовывать проекты сложнее hello world с использованием cmake и при этом без "ну, давайте тут вставим костыль, который используется в нашей компании для легаси-кода с cmake 2.8" днём с огнём не сыщешь.

              +2
              Я ни одного проекта не видел без костылей. Зайдите на github, возьмите любой проект… Там такой дурдом везде, что можно плакать сразу. Это же происходит и в других коммерческих проектах. Чтобы был cmake нужно держать отдельного специалиста по cmake =)
            0

            Есть опыт в проекте, не большой, но и не hello world. Мы разбиваем проект на модули, которые собираются как библиотеки (статические, динамические, объектные — не важно). У этих целей настроены PUBLIC target_link_libraries, target_include_directories, соответственно, когда мы линкуем модуль к другому, все эти настройки подтягиваются. Так получается весьма модулльная структура, особо не зависящая от того, что где лежит и от этих include_subdirectory и прочих танцев.

              0
              А не поделитесь ли опытом, как эти модули потом находить друг относительно друга? Например есть три модуля A, B и С, при том A и B зависят от C: им нужны его инклюды и его нужно слинковать с ними. Вопрос: как организовать структуру проекта без уродливых "../" или модуля D который в правильном порядке выставляет add_subdirectory? При этом A, B и C отдельными проектами не являются (от них зависят другие модули которые потом в сумме могут использоваться в нескольких полноценных проектах), но было бы здорово собирать их в статические библиотеки для тестирования например.
                +1

                Вот, я сделал пример. Надеюсь, это вам поможет.


                Из недостатков (на мой взгляд), то, что модули инклюдятся как


                #include "a.h"

                а не как


                #include "module_a/a.h"

                Наверное, это весьма и весьма существенный недостаток, когда публичных хидеров в модуле много. Ах да, забыл. В папку include модулей кладутся их публичные хидера, которые должны быть видны другим. Если требуются какие-то приватные хидера, то мы кладем их прямо в src модуля.

                  0
                  Спасибо! Хотя это не совсем то, что я ожидал. Скорее всего я хочу слишком много.

                  Говоря «модули» я имел в виду отдельные проекты в терминах CMake. Например проблема в том, что я не могу собрать отдельно проект A, не собирая B и не используя CMakeLists.txt из корня.
                    0

                    Пишет, что пример не найден...

                    +1

                    Продолжение истории: https://habr.com/ru/post/463295/

                  +1

                  Продолжение истории: https://habr.com/ru/post/463295/

                    0

                    Есть большой опыт работы с CMake в крупных проектах по ~150-200 модулей. Используемые языки программирования C C++ Fortran.
                    Начинал с версии 2.8. С этого момента много чего поменялось, и меняется все к лучшему. Так как для себя явных неудобств не находил.
                    На самом деле порядка недели уходит для полной настройки каркаса нового проекта, связка с серверами и т.д. А после модули уже добавляются по аналогии с существующими на раз и два, проблем не наблюдаем. Отдельно выделенного человека под эти цели у нас нет :)

                    0

                    А как происходит подключение проекта в другой проект, чтобы get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY) работало?
                    Через add_subdirectory, ExternalProject, include() или find_package(… PATHS ...)?

                      0

                      Через add_subdirectory.


                      find_package работает с уже установленным проектом, а там исходных CMake-скриптов уже нет.

                      0

                      Мне вот недавно пришлось собирать gRPC под достаточно большой зоопарк компиляторов. Если бы я не перечитал содержимое папки test, я бы очень долго возился с тем, чтобы получить gRPC, которы находятся через find_package.
                      Очень много надо делать велосипедов, чтобы нормально поддержать debug/release. Не говоря уже об автоматическом создании сборок cmake package.

                        0

                        Да, к сожалению, цмейком многие пользуются неправильно или не до конца правильно. Поэтому иногда приходится делать работу за таких горе-разработчиков библиотек. Однако, если следовать лучшим практикам, то всё легко и просто.

                          0
                          Ещё, к сожалению, стандартные скрипты тоже далеки от совершенства. Простой пример — собираем zlib с помощью CMake. И вот без танцев с бубном линковаться с ним статически не получается. Хотя это очень популярная библиотека.
                          По факту, нужно очень много велосипедить, чтобы получить красивую сборку. А если разрабатываешь не конечное приложение, а какой-либо SDK, то вообще мрак. Но за неимением альтернатив приходится пользоваться. Нормальных руководств я так и не нашёл, собираю везде знания по крупицам. Дошло до того, что пишу сейчас свой велосипед на CMake, который собирает нужные мне thirdparty. Может, выложу его в open source.
                            0

                            Если я правильно понимаю, так называемые "стандартные" скрипты тоже пишут сами разработчики библиотек.

                              0

                              На самом деле, всё зло, как обычно, от наследия царского режима. А именно, от версии 2.x. Многие скрипты и учебники либо оттуда, либо тащат за собой весь этот хлам.

                                +1

                                За последнее время было несколько докладов про современный cmake.
                                И даже статья на хабре есть: https://habr.com/ru/post/330902/

                                  0

                                  И это прекрасно.

                          +7


                          Так, CMake значит говорите. Говорите, что с помощью CMake можно обеспечить статический анализ с минимальными телодвижениями? Понятненько… Ok, мы идём к этому проекту.

                          И, видимо, статический анализ обошел сам CMake. Ждите скоро статью. Коллега как раз пишет и пока раздумывает, поликорректно ли использовать в названии слово «говнокод» :).
                            +1

                            Даже если и так, то мне не известно ни одного случая, когда CMake упал бы или сделал что-то не то, что его просили.
                            Что, впрочем, не отменяет культуру разработки.

                              0
                              В embedded сталкивался. Когда cmake не верно передавал g++ параметры linker-а. В 3.10.1 кажется. В 3.14 уже поправили.
                                0

                                Пожалуй, я наврал. Сейчас подумал и тоже вспомнил случай, когда от порядка перечисления компонент в команде target_link_libraries зависел конечный результат.

                                  +3

                                  Дык самому линкеру важен порядок аргументов.

                                0

                                Ну, как сказать… с одной стороны, язык декларативный, то есть порядок выполнения он выбирает сам. С другой стороны, в некоторых случаях порядок описания сборки важен. И перечисление либ для линковщика это ещё самое очевидное.

                                0
                                Хорошая статья! Я бы ещё упомянул модуль GNUInstallDirs — мне кажется, что это более правильный способ указания target install directories. Плюс, для полноты картины можно подробнее рассказать о тестировании (add_test, etc.).
                                  0

                                  CTest не люблю, потому что он заставляет каждый тест оформлять в виде отдельного исполняемого файла, а это, мягко говоря, не очень удобно.
                                  В проекте-шаблоне я использую сторонний тестовый фреймворк, так что и в статье про CTest ни слова.

                                    0

                                    Ну, вообще-то необязательно.
                                    Если тест-фреймворк такой, что всё пихает в единственный файл и отдельные тесты там нельзя выбрать — это проблема фреймворка. А если можно выбрать — то единственный файл не помеха запуску отдельных тестов. И даже (если фреймворк популярен) скорее всего уже и модуль для cmake соответствующий есть.


                                    Самый очевидный, навскидку, пример — gtests. Да, единственный бинарь. Но модуль cmake умеет его исследовать и создаёт множество соответствующих тестов для ctest. В каждом запускается бинарь с нужными ключами, чтобы выполнился именно конкретный тест, а не вся сюита.


                                    Ну и плюшки в виде экспорта результата (тестов, а заодно вывода конфигурации, компиляции, coverage и санитайзеров) в xml и загрузки всего этого добра в cdash (который отлично живёт в докере). Или несложного преобразования в формат junit и закачки туда, где его понимают (например, gitlab CI)

                                      0

                                      Создавать бинарник со всеми тестами, а потом запускать его N раз по разу на каждый тест, когда можно запустить один раз и получить тот же результат — неэффективно, долго и бессмысленно.


                                      И, главное, для чего? Просто чтобы использовать CTest?


                                      … плюшки ...

                                      Все перечисленные вещи есть в Catch2 или doctest. При этом, в отличие от gtest, их не нужно компилировать и линковать.

                                  +1

                                  Кто интересуется системами сборки, посмотрите также https://buck.build — это космос.

                                    –2
                                    Опции компиляции

                                    Вот и недоработки CMake на лицо. На msvc это работать не будет, придётся другие опции указывать. А способов задавать компиляторонезависимые опции в CMake пока ещё не завезли.
                                      0
                                      Вы еще скажите, что кросс-компиляции с cmake не бывает.
                                        +1
                                        Зато есть способы указывать компиляторозависимые опции, и указать компилятор, что есть тоже самое.
                                          +1

                                          В какой-то степени завезли. Хотя бы версию C++ или всякие там "хочу в целом собираться с отладкой" можно указать.

                                            0

                                            Базовые опции в зависимости от вида сборки (debug/release и т.д.) уже есть встроенные и достаточно кросс-компиляторные (тем, кто по-привычке вручную конфигурирует -O0 -g для debug, -O2 для release и т.д. рекомендую попробовать их убрать и посмотреть, как будет без них (make VERBOSE=1 покажет)). А специфические (включить какой-нибудь санитайзер или thread_safety_analysis для clang) на то и специфические, что в других компиляторах просто отсутствуют. Да, там другие опции.
                                            Разные пробрасываемые определения, которые в gcc задаются через -D а в msvc через /D cmake и так умеет. И в процессе развития научился различать определения, опции компилятора, опции линкера, инклюды и т.д. (раньше почти всё приходилось через add_definitions добавлять, теперь есть разные варианты)

                                            0

                                            Дмитрий, а как вы относитесь к Ninja?

                                              0

                                              Честно говоря, никак не отношусь.
                                              Генерировал тем же си-мейком, но не увидел никакой разницы в положительную сторону по сравнению с make.
                                              Отдельно от CMake не пробовал.

                                                0
                                                У ниндзи единственное преимущество по сравнению с make — это автоматическое распараллеливание билда при вызове без дополнительных опций. Для make придется указывать опцию -j, чтобы задать сборку в несколько потоков.
                                              0

                                              А как правильно добавить python bindings для pybind11, boost python, swig. И так что бы можно было потом установить отдельно библиотеку, отдельно bindings, пакетом и через pypi/conda

                                                0

                                                Не совсем понятно что именно вы хотите сделать, но посмотрите вот на этот проект: https://github.com/scikit-build/scikit-build


                                                Чтобы что-то ставить через conda, нужно писать рецепт для conda, и я вообще не советовал бы пользоваться кондой. Как высказался про конду один компетентный товарищ:


                                                Ужасная кодовая база, беспорядок в рецептах, устаревшие версии пакетов, отсутствие нативной поддержки pypi и чего бы то ни было ещё, без рецептов.

                                                А ещё conda очень медленная.

                                                  0
                                                  Не стану спорить, что у конды есть недостатки, но иногда она позволяет легко создать python-окружение там, где иначе на это требуется слишком много усилий. За линк спасибо, но раскрыть этот вопрос в туториале, как это сделано для документации и тестов не помешало бы.
                                              • UFO just landed and posted this here

                                              Only users with full accounts can post comments. Log in, please.