1. Создание проекта CMake

Цель

Создать проект, включающий следующие компоненты:

  1. Библиотеку (статическую или динамическую).

  2. Unit-тесты.

  3. Консольное приложение.

  4. Сторонние библиотеки.

  5. Конфиг clang-tidy.

  6. Конфиг clang-format.

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

Целевые версии ПО в рамках курса:

  1. gcc >= 9.3.0

  2. llvm >= 12.0.1 (clang, clang-tidy, clang-format)

  3. cmake >= 3.21

Материалы

  1. CMake Tutorial

  2. An Introduction to Modern CMake

  3. Clang-Tidy

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

Для C++ нет единого общепринятого способа структурировать проекты. Существует несколько рекомендаций.

  1. Canonical Project Structure

  2. The Pitchfork Layout (PFL) — предлагает различные варианты расположения заголовочных файлов и тестов:

    1. Separate header placement

    2. Merged header placement

    3. Separate test placement

    4. Merged test placement

Clang Tidy

Clang Tidy — статический анализатор кода. Его использование поможет избежать ряда ошибок в коде и иногда компенсирует незнание некоторых особенностей языка.

На данный момент реализовано 300+ различных проверок. Диагностики объединены в группы: специфичные для конкретных проектов (abseil-*, llvm-* и др.), кодстайлов (google-*, hicpp-*) и прочие (readability-*, bugprone-*).

По-хорошему нужно сгенерировать когфиг (clang-tidy --dump-config), затем вручную вписать все нужные для вашего проекта опции. Тогда вы получите конфиг, устойчивый к обновлению clang-tidy (при переходе на новую версию не появится новых предупреждений, пока вы не добавите соответствующие диагностики в конфиг). Однако такой процесс слишком трудоемок, поэтому в рамках курса предлагаем другой подход.

  1. Сгенерируйте конфиг со всеми включенными проверками:

    clang-tidy --checks='*' --dump-config > .clang-tidy
    
  2. По мере разработки проекта отключайте ненужные проверки, если они срабатывают.

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

Примеры некоторых диагностик, которые в рамках курса можно отключить сразу:

  1. fuchsia-* — проверки, специфичные для кодстайла проекта fuchsia.

  2. llvmlibc-*

  3. llvm-header-guard — в нашем курсе мы используем #pragma once

  4. modernize-use-nodiscard

  5. modernize-use-trailing-return-type — такой стиль мы будем использовать только по необходимости или для заметного сокращения кода.

  6. cppcoreguidelines-owning-memory — проверка требует использования библиотеки GSL

  7. cppcoreguidelines-pro-bounds-pointer-arithmetic

  8. cppcoreguidelines-avoid-magic-numbers — проверка слишком шумит на тестах. В коде руководствуйтесь здравым смыслом.

Таким образом, начало конфига будет выглядеть так:

---
Checks: >
  clang-diagnostic-*,
  clang-analyzer-*,
  *,
  -altera-*,
  -fuchsia-*,
  -llvmlibc-*,
  -llvm-header-guard,
  -modernize-use-nodiscard,
  -modernize-use-trailing-return-type,
  -cppcoreguidelines-owning-memory,
  -google-runtime-references,
  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
  -cppcoreguidelines-avoid-magic-number,
  -readability-magic-numbers

Использование сторонних библиотек

В рамках курса мы будем использовать один из двух способов подключения сторонних библиотек. С точки зрения структуры проекта они выглядят почти одинаково. Сторонние библиотеки располагаются в каталоге <project>/external:

<project>
|
`-- external
    |-- CMakeLists.txt
    |-- cxxopts
    |-- fmtlib
    `-- googletest

Файл external/CMakeLists.txt содержит только подключение дочерних каталогов.

FetchContent

https://cmake.org/cmake/help/latest/module/FetchContent.html

FetchContent — cmake-модуль для скачивания внешних проектов и автоматического добавления их как подпроектов.

В этом случае каталог external/<libname> содержит файл CMakeLists.txt с вызовом функций FetchContent_Declare и FetchContent_MakeAvailable.

Git Submodules

https://git-scm.com/book/en/v2/Git-Tools-Submodules

В этом случае вы добавляете репозиторий как подмодуль в каталог external/<libname>.

Установка инструментов LLVM

В Ubuntu 22.04 версия 14 доступна из репозиториев:

sudo apt install clang-tidy clang-format

Репозитории LLVM и инструкции по установке доступны здесь: https://apt.llvm.org/

Пример для Ubuntu 20.04:

wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main"
sudo apt install clang-tidy-14 clang-format-14

Есть известная проблема с конфликтом зависимостей LLVM и Steam: https://steamcommunity.com/app/221410/discussions/0/2288338908683011755/, на других версиях не проверялось. Может приводить к сносу половины системы вместе с графическим интерфейсом. Читайте внимательно, на установку/удаление каких пакетов соглашаетесь.

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

Руководство

Сборку приложения следует выполнять в режиме out-of-source. См. CMake FAQ.

  1. Создайте репозиторий для проекта: https://classroom.github.com/a/9CFYd8ny

  2. В репозитории создайте ветку lab-1. Как обычно, все работы выполняются в отдельных ветках.

  3. Создайте структуру каталогов в соответствии со своим вариантом.

  4. Создайте конфиги .clang-format и .clang-tidy.

  5. Создайте библиотеку fts, содержащую функцию сложения двух чисел с плавающей точкой. Для настройки общих параметров библиотеки и всех прочих таргетов можете воспользоваться функцией (сохраните ее в cmake/CompileOptions.cmake):

    function(set_compile_options target_name)
      if(MSVC)
        target_compile_options(${target_name} PRIVATE /W4 /WX /EHsc)
      else()
        target_compile_options(${target_name} PRIVATE -Wall -Wextra -Werror -pedantic)
      endif()
    
      set_target_properties(
        ${target_name}
        PROPERTIES
          CXX_STANDARD 17
          CXX_STANDARD_REQUIRED ON
          CXX_EXTENSIONS OFF
      )
    
      if(CLANG_TIDY_EXE)
        set_target_properties(
          ${target_name}
          PROPERTIES
            CXX_CLANG_TIDY ${CLANG_TIDY_EXE}
        )
      endif()
    endfunction()
    
  6. Разработайте приложение, использующее библиотеку. Пусть приложение принимает два числа через аргументы командной строки:

    $ ./sum --first 40 --second 2
    42
    

    Для обработки аргументов командной строки используйте стороннюю библиотеку в соответствии со своим вариантом.

  7. Напишите один юнит-тест на функцию библиотеки. Для написания тестов используйте библиотеку GoogleTest

  8. В CMake настройте запуск clang-tidy при компиляции. Убедитесь, что проверки срабатывают. Для определения переменной CXX_CLANG_TIDY воспользуйтесь функцией find_program

  9. Напишите CMakePresets.json. Предусмотрите конфигурации debug и release.

  10. По готовности передайте выполненное задание на ревью преподавателю практики.

Для этого:

  1. Если история вашей ветки читаемая и поэтапная, можете вмержить ее в main с опцией --no-ff. Если в ветке треш и угар, и пользы от разделения на коммиты нет, то находясь на ветке main выполните merge --squash, а после создайте коммит с коротким заголовком, хранить все заголовки в этом случае не нужно.

  2. Откройте в браузере страницу репозитория.

  3. На вкладке Pull requests перейдите в Feedback. Этот Pull request создан автоматически. Не закрывайте и не вливайте его. В лабораторных и курсовых он должен оставаться открытым.

  4. В поле Reviewers выберите своего преподавателя.

../_images/review-box.png
  1. Если ревью было пройдено со статусом Changes requested, исправьте замечения в отдельной ветке и после мержа запросите повторное ревью.

../_images/review-request.png

Примеры

Разобранные на лекции исходники доступны здесь: csc-cpp/cpp-examples/01-cmake.

В примерах нет запуска тестов, их настройка остается частью самостоятельной работы.

Варианты

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

  1. Canonical Project Structure (CPS)

  2. Pitchfork Separate Headers + Separate Test (PFL SH ST)

  3. Pitchfork Separate Headers + Merged Test (PFL SH MT)

    .
    |-- cmake
    |   `-- CompileOptions.cmake
    |-- external
    |   |-- cli11
    |   |   `-- CMakeLists.txt
    |   |-- fmtlib
    |   |   `-- CMakeLists.txt
    |   |-- googletest
    |   |   `-- CMakeLists.txt
    |   `-- CMakeLists.txt
    |-- include
    |   `-- libmath
    |       `-- sum.h
    |-- src
    |   |-- libmath
    |   |   |-- CMakeLists.txt
    |   |   |-- sum.cpp
    |   |   `-- sum.test.cpp
    |   |-- sum-cli
    |   |   |-- CMakeLists.txt
    |   |   `-- main.cpp
    |   `-- CMakeLists.txt
    |-- .clang-format
    |-- .clang-tidy
    |-- .gitignore
    |-- CMakeLists.txt
    `-- CMakePresets.json
    
  4. Pitchfork Merged Headers + Separate Test (PFL MH ST)

  5. Pitchfork Merged Headers + Merged Test (PFL MH MT)

Использование сторонних библиотек:

  1. FetchContent (FC)

  2. Git Submodules (SM)

Библиотека для парсинга аргументов командной строки:

  1. cli11

  2. cxxopts

#

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

Использование библиотек

Argparser

1

CPS

FC

cxxopts

2

PFL SH ST

FC

cxxopts

3

PFL SH MT

FC

cxxopts

4

PFL MH ST

FC

cxxopts

5

PFL MH MT

FC

cxxopts

6

CPS

FC

cli11

7

PFL SH ST

FC

cli11

8

PFL SH MT

FC

cli11

9

PFL MH ST

FC

cli11

10

PFL MH MT

FC

cli11

11

CPS

SM

cxxopts

12

PFL SH ST

SM

cxxopts

13

PFL SH MT

SM

cxxopts

14

PFL MH ST

SM

cxxopts

15

PFL MH MT

SM

cxxopts

16

CPS

SM

cli11

17

PFL SH ST

SM

cli11

18

PFL SH MT

SM

cli11

19

PFL MH ST

SM

cli11

20

PFL MH MT

SM

cli11

Контрольные вопросы

  1. Опишите общую схему работы cmake.

  2. Что такое in-source build и out-of-source build?

  3. Расскажите о преимуществах и недостатках вашего способа использования сторонних библиотек. Предложите свой вариант решения этой задачи.