- PVSM.RU - https://www.pvsm.ru -

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

Дружба навек

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

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

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

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

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

Содержание

  1. Проект изнутри [2]
    1. Структура проекта [3]
    2. Главный CMake-файл (./CMakeLists.txt) [4]
      1. Информация о проекте [5]
      2. Опции проекта [6]
      3. Опции компиляции [7]
      4. Основная цель [8]
      5. Установка [9]
      6. Тесты [10]
      7. Документация [11]
      8. Онлайн-песочница [12]
    3. Скрипт для тестов (test/CMakeLists.txt) [13]
      1. Тестирование [14]
      2. Покрытие [15]
    4. Скрипт для документации (doc/CMakeLists.txt) [16]
    5. Скрипт для онлайн-песочницы (online/CMakeLists.txt) [17]
  2. Проект снаружи [18]
    1. Сборка [19]
      1. Генерация [20]
      2. Сборка [21]
    2. Опции [22]
      1. MYLIB_COVERAGE [23]
      2. MYLIB_TESTING [24]
      3. MYLIB_DOXYGEN_LANGUAGE [25]
    3. Сборочные цели [26]
      1. По умолчанию [27]
      2. mylib-unit-tests [28]
      3. check [29]
      4. coverage [30]
      5. doc [31]
      6. wandbox [32]
    4. Примеры [33]
  3. Инструменты [34]
  4. Статический анализ [35]
  5. Послесловие [36]

Проект изнутри [37]

Структура проекта [37]

.
├── 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-скрипты, поэтому они будут разобраны подробно. Остальные файлы каждый желающий может посмотреть непосредственно на странице проекта-шаблона [1].

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

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

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

cmake_minimum_required(VERSION 3.13)

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

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

project(Mylib VERSION 1.0 LANGUAGES CXX)

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

get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)

Опции проекта [37]

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

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

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

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

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

Опции компиляции [37]

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

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()

Основная цель [37]

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

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

add_library(mylib INTERFACE)

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

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

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

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

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

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

target_compile_features(mylib INTERFACE cxx_std_17)

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

add_library(Mylib::mylib ALIAS mylib)

Установка [37]

Установка наших заголовков в систему. Тут всё просто. Говорим, что папка со всеми заголовками должна попасть в директорию 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)

Тесты [37]

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

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

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

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

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

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

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

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

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

Тестирование [37]

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

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)

Покрытие [37]

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

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) [37]

Нашли Doxygen [45].

find_package(Doxygen)

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

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

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) [37]

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

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()

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

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

Сборка [37]

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

Генерация [37]

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

Если команда выше не сработала из-за старой версии CMake, попробуйте опустить -S:

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

Подробнее про опции [22].

Сборка проекта [37]

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

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

Опции [37]

MYLIB_COVERAGE [37]

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

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

MYLIB_TESTING [37]

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

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

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

MYLIB_DOXYGEN_LANGUAGE [37]

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

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

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

Сборочные цели [37]

По умолчанию [37]

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

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

mylib-unit-tests [37]

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

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

check [37]

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

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

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

coverage [37]

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

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

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

------------------------------------------------------------------------------
                           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 [23].

См. также check [29].

doc [37]

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

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

wandbox [37]

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

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

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

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

Примеры [37]

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

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

Инструменты [37]

  1. CMake [51] 3.13

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

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

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

  3. Doxygen [50]

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

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

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

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

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

Cppcheck

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

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

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

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

Clang

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

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

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

Послесловие [37]

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


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

Автор: izvolov

Источник [57]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/open-source/325486

Ссылки в тексте:

[1] скачать шаблон проекта: https://github.com/izvolov/mylib

[2] Проект изнутри: #project-from-the-inside

[3] Структура проекта: #project-structure

[4] Главный CMake-файл (./CMakeLists.txt): #cmakeliststxt

[5] Информация о проекте: #project-information

[6] Опции проекта: #project-options

[7] Опции компиляции: #compile-options

[8] Основная цель: #main-target

[9] Установка: #installation

[10] Тесты: #tests

[11] Документация: #documentation

[12] Онлайн-песочница: #online-sandbox

[13] Скрипт для тестов (test/CMakeLists.txt): #testcmakeliststxt

[14] Тестирование: #testing

[15] Покрытие: #code-coverage

[16] Скрипт для документации (doc/CMakeLists.txt): #doccmakeliststxt

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

[18] Проект снаружи: #project-from-the-outside

[19] Сборка: #build

[20] Генерация: #generate

[21] Сборка: #project-build

[22] Опции: #options

[23] MYLIB_COVERAGE: #MYLIB_COVERAGE

[24] MYLIB_TESTING: #MYLIB_TESTING

[25] MYLIB_DOXYGEN_LANGUAGE: #MYLIB_DOXYGEN_LANGUAGE

[26] Сборочные цели: #build-targets

[27] По умолчанию: #default-target

[28] mylib-unit-tests: #mylib-unit-tests

[29] check: #check

[30] coverage: #coverage

[31] doc: #doc

[32] wandbox: #wandbox

[33] Примеры: #examples

[34] Инструменты: #tools

[35] Статический анализ: #static-analysis

[36] Послесловие: #afterword

[37] Проект изнутри: #contents

[38] команду project: https://cmake.org/cmake/help/v3.8/command/project.html

[39] target_link_libraries(target PRIVATE dependency): https://cmake.org/cmake/help/v3.8/command/target_link_libraries.html#libraries-for-a-target-and-or-its-dependents

[40] выражения-генераторы: $<...>: https://cmake.org/cmake/help/v3.8/manual/cmake-generator-expressions.7.html

[41] find_package: https://cmake.org/cmake/help/v3.8/command/find_package.html

[42] команду target_compile_features: https://cmake.org/cmake/help/v3.8/command/target_compile_features.html

[43] Как в Бусте, например: https://cmake.org/cmake/help/v3.8/module/FindBoost.html

[44] add_subdirectory: https://cmake.org/cmake/help/v3.8/command/add_subdirectory.html

[45] Нашли Doxygen: https://cmake.org/cmake/help/v3.8/module/FindDoxygen.html

[46] команду configure_file: https://cmake.org/cmake/help/v3.8/command/configure_file.html

[47] Wandbox: https://wandbox.org

[48] сайте системы Doxygen: http://www.doxygen.nl/manual/config.html#cfg_output_language

[49] gcovr: https://gcovr.com

[50] Doxygen: http://doxygen.nl

[51] CMake: https://cmake.org

[52] doctest: https://github.com/onqtam/doctest

[53] Python 3: https://www.python.org

[54] Cppcheck: http://cppcheck.sourceforge.net

[55] CMAKE_CXX_CPPCHECK: https://cmake.org/cmake/help/v3.10/variable/CMAKE_LANG_CPPCHECK.html#variable:CMAKE_<LANG>_CPPCHECK

[56] scan-build: https://clang-analyzer.llvm.org/scan-build

[57] Источник: https://habr.com/ru/post/461817/?utm_source=habrahabr&utm_medium=rss&utm_campaign=461817