Мы проведем полную настройку этих инструментов, и я покажу вам, почему они стоят потраченного времени;) Я предоставлю файлы конфигурации, а также предложу некоторое понимание того, почему мы делаем эти настройки.

Вступление

Есть много статей, объясняющих некоторые комбинации инструментов, упомянутых в названии. Недавно я пролистал исходный код своих старых проектов и наткнулся на C ++ - проект, на который давно не смотрел. Он был настроен в классическом стиле C ++ - проекта:

  • Ридми, который был довольно старым
  • Некоторые файлы .h и .cpp
  • Файл CmakeLists, который содержал только код, необходимый для работы интеграции Boost, и загрязнял основную папку.
  • Повсюду разбросаны артефакты старых построек

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

Если у вас возникли проблемы с выполнением инструкций командной строки, перечисленных в этой статье, попробуйте установить пакеты, которым они принадлежат, позвонив

apt-get update
apt-get install git cmake build-essential tar curl g++ gcc-9 clang-10

Я запускаю Ubuntu 20.04 в своих системах. Если, например, gcc-9 вам недоступен, используйте старую версию или просто используйте версию, предоставленную по умолчанию (заменив gcc-9 на gcc) . Если вы используете версию компилятора ниже 9, имейте в виду, что Cxx20 не будет вам доступен, и вам следует заменить упоминания C ++ 20 на C ++ 14 или C ++ 17 в своих CMakeLists. txt.

CMake

Большинство проектов C ++ начинаются с хорошего файла CmakeLists. Поскольку C ++ не зависит от платформы, каждая платформа должна иметь инструменты сборки (также известные как компиляторы, компоновщики и т. Д.). Это привело к необходимости найти способы упростить этот процесс, если у языка будет будущее, и именно здесь на помощь приходит make. make - это инструмент автоматизации сборки, который в основном вызывает команды и устанавливает для них параметры. Некоторые люди до сих пор сами пишут конфигурационные файлы для make (также известные как Makefiles), но большинство разработчиков перешли на cmake, который, в свою очередь, автоматизирует настройку make-файлов. Лучшая особенность CMake заключается в том, что он может обнаруживать библиотеки и компиляторы в вашей системе и автоматически настраивать их для вас.

Например, в моем проекте я хочу включить библиотеку Boost, которая является одной из крупнейших библиотек C ++. Cmake предлагает возможность определить, установлен ли он, и автоматизировать процесс сообщения компилятору, где он находится. Еще одним приятным свойством является то, что Cmake использует только один файл конфигурации (или каскадные файлы, если вы хотите настраивать подпроекты самостоятельно). Поскольку он настолько компактен и является абсолютным стандартом в мире C ++, интеграция с IDE также великолепна.

В качестве первого шага я очистил структуру папок в проекте, создав следующие папки:

  • src: Эта папка будет содержать файлы .h и .cpp, которые содержат функции проекта. Здесь можно было бы ввести больше структуры, но я не хочу здесь перебарщивать. Многие проекты будут состоять только из 10 файлов или меньше, и тогда одной папки должно хватить. Если вы решите дополнительно разделить это, следующие шаги будут точно такими же.
  • main: В этой папке я помещаю точку входа проекта для автономного выполнения. На основе этого файла будет построен основной двоичный файл.
  • tests: Эта папка будет содержать файлы c ++ с юнит-тестами. Добавление gtest произойдет позже, но я создал эту папку сразу.
  • Third_party: эта папка будет содержать внешние зависимости. В моем случае это будет только googletest. Если бы Boost был меньше, его также можно было бы разместить здесь, но, поскольку он очень большой и не должен создаваться как часть моего проекта, я решил установить его на уровне операционной системы.

Теперь, когда у нас есть эти 4 папки, мы можем начать их заполнение. Main должен содержать main.cpp с кодом для основного двоичного файла. src должен содержать некоторые заголовки (.h) и исходные файлы (.cpp), которые содержат фактическую функциональность. тесты пока могут оставаться пустыми, и мы заполним third_party позже. На данный момент нам не нужен googletest и Boost, поэтому мы можем приступить к настройке сборки с помощью cmake. CMakeLists.txt, который я бы предложил, выглядит следующим образом:

cmake_minimum_required(VERSION 3.0)
project(MyProject)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -o3")
add_library(Core src/file1.cpp src/file2.cpp)
add_executable(Main main/main.cpp)
target_link_libraries(Main Core)

Строки первая и вторая - это основы cmake. Я рекомендую вам использовать cmake 3, поскольку он доступен для каждой платформы, а проект просто называет ваш проект. Затем мы выполняем set (VARIABLE VALUE) 5 раз, чтобы указать подробную информацию о сборке. Первый из них наиболее конкретен: CMAKE_EXPORT_COMPILE_COMMANDS заставляет cmake создавать файл с именем compile_commands.json. Эта строка требуется только в том случае, если вы используете Clang в качестве компилятора или утилиты в своей среде IDE. Лично я использую clang для лучшего самоанализа кода в VSCode, и если у вас нет причин не делать этого, я рекомендую вам сделать то же самое. Время от времени это немного медленно, но я думаю, что подождать стоит.

В следующей строке CMAKE_CXX_STANDARD установлено значение 20, что указывает на то, что я хочу использовать C ++ в версии 2020. В моем коде используется std :: set, который получил полезный метод contains () в C ++ 20, и я все вокруг просто хочу использовать последнюю версию, если это возможно. . На самом деле это не обязательно, но если в вашей системе есть gcc-9 или clang-10 (запустите gcc - version, чтобы узнать), почему бы не использовать лучший C ++ доступный?

Затем я добавил два флага C ++ для компилятора, а именно - fopenmp и - o3. Первый делает доступным OpenMP, второй заставляет компилятор выполнять некоторую базовую оптимизацию кода. Внедрить OpenMP здесь невозможно, здесь просто слишком много сказать, однако я могу помочь вам решить, стоит ли изучать: если у вас когда-либо был длительный цикл, выполняющий какую-то задачу и занимавший вечность, даже если отдельные запуски loop были независимыми, и вы хотели запустить его параллельно, потому что ваш компьютер имеет более одного ядра, тогда вам следует изучить OpenMP. Вы можете просто добавить #pragma omp parallel for перед циклом for, и это заставит его работать параллельно с таким количеством процессов, которое поддерживает ваша система. Эти две команды влияют только на производительность кода и могут быть удалены, если вам это не нужно.

Последние три строки - это те, в которых CMake выполняет некоторую фактическую работу: Add_library (Libname Libfiles…) объявляет библиотеку. Библиотека - это модуль, который вы можете использовать в другом коде, который содержит определенные функции. Мы компилируем все файлы в каталоге src, поэтому все наши функции в одну библиотеку под названием Core. После имени библиотеки вы просто указываете пути к своим файлам .cpp и любым отдельным файлам .h. Мой шаблон - всегда включать заголовки из исходных файлов. Итак, в a.cpp есть включаемый «a.h». Вдобавок у меня обычно есть файлы constants.h и types.h, которые содержат объявления типов и постоянные значения. Поскольку для них нет соответствующего файла .cpp, я также добавляю их сюда.

Сама библиотека только декларирует некоторые функции, но не выполняет их, поэтому нам нужен исполняемый файл. Для этого у нас есть main.cpp, который содержит статический метод void main (), объявляющий точку входа в наше приложение. Чтобы cmake построил из него исполняемый файл, мы объявляем его с помощью add_executable (BinaryName main / main.cpp). Это объявило, что должен быть скомпилирован двоичный файл, содержащий код из main.cpp.

Скорее всего, двоичный файл не будет работать сам по себе. В нем будет #include «../src/someHeader.h», ссылающийся на код из каталога src, и поэтому нам придется предоставить эту функциональность, которая теперь содержится в Core-библиотеке, чтобы Это. Для этого мы связываем их вместе в последней команде, что означает, что так называемый компоновщик сообщит исполняемому файлу, где найти функции, которые он включил из внешних исходных файлов. Эта функция содержится в Core-библиотеке, поэтому мы передаем ее компоновщику.

В качестве последнего замечания по этой теме я рекомендую сборки «вне исходного кода». Это означает, что вы помещаете все свои Make-файлы, двоичные файлы и библиотеки в отдельную папку. Этого можно добиться, запустив cmake в папке, отличной от той, которая содержит ваш CMakeLists.txt. Как упоминалось ранее, Cmake записывает только Makefile, поэтому после запуска cmake нам все равно нужно вызывать make для выполнения настроенных нами действий. Перейдите в командной строке в корневую папку вашего проекта и просто вызовите

mkdir build
cd build
cmake ..
make

Сначала мы создаем каталог сборки, вызывая mkdir. Затем вводим его и запускаем cmake. Две точки после cmake означают «запустить cmake в родительском каталоге». и make начнет все компилировать. После этих команд вы найдете CMake-мусор и все другие файлы сборки в папке сборки, которые вы можете игнорировать (.git).

Итак, теперь у нас настроена сборка, и, если вы измените только часть содержимого исходных файлов, вам нужно только вызвать make, чтобы перекомпилировать их. Приятным свойством этих сгенерированных Make-файлов является то, что они содержат всю информацию о шагах, которые могут выполняться параллельно. Таким образом, вы можете вызвать make -j4 для параллельного выполнения 4 процессов, что должно немного ускорить процесс. Вы также можете использовать больше или меньше процессов, в зависимости от того, что ваш компьютер может обрабатывать. Однако никогда не используйте больше доступного количества ядер.

GIT

Мы немного очистили проект или, если вы начали с чистого рабочего пространства, теперь у нас есть базовая структура, с которой мы можем начать контроль версий. В корневом каталоге нашего проекта мы вызываем git init. Чтобы убедиться, что мы не фиксируем двоичные файлы или другие файлы, которые не должны быть частью репозитория, мы создаем файл .gitignore, который сообщает git, какие файлы следует игнорировать. Предлагаю начать с этого файла:

*.com
*.class
*.dll
*.exe
*.a
*.o
*.so
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.cbp
*.log
*.sql
*.sqlite
 .DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db 
*.vtk
*.eps
*.gpl
*.1
*.cmake
Makefile
CMakeFiles/
CMakeCache.txt
.cproject
.settings/
.idea/
build/
.vscode/
*.pc
.clangd/

Это немного длинновато, но он содержит множество типов, которые вы не хотите иметь в своей VCS, например, двоичные выходы (.exe, .a и .o), а также конфигурации IDE. Теперь мы можем создать репозиторий GitHub. Он предоставит вам URL-адрес клона, который вы можете использовать для настройки локального репозитория для его использования.

git init
git add --all
git commit -m "Initiali commit."
git remote add origin YourURLHere
git push -u origin master

Это добавляет в исходную версию все файлы, которые явно не исключены в файле .gitignore. После push-команды вы также должны увидеть файлы онлайн в интерфейсе GitHub. Далее мы приступим к тестированию нашего кода!

Gtest

Фреймворк googletest - это мой фреймворк goto, когда я тестирую код C ++. Опять же, полное введение выйдет за рамки отдельной статьи, но давайте создадим файл unit_tests / SomeTest.cpp и добавим простой тестовый пример:

#include "gtest/gtest.h"
#include "../src/someHeader.h"
TEST(SomeTestSuite, SomeTest) {
  MyType obj = new MyType();
  obj->set_the_value(5);
  ASSERT_EQ(obj->get_the_value(), 5);
}

В первой строке мы ссылаемся на Googletest, чтобы мы могли использовать его функции. Затем мы загружаем некоторые части нашего собственного кода, которые хотим протестировать. Макрос TEST объявляет тестовый пример. Каждый тестовый пример принадлежит к набору (SomeTestSuite) и имеет собственное имя (SomeTest). Эти значения больше нигде не определены, вы только передаете их макросу TEST, и он будет их использовать. Это создает тестовый пример, поэтому теперь нам нужно только реализовать то, что на самом деле представляет собой этот тест, что мы и сделаем в следующих 3 строках. Мы создаем объект из какой-то части нашей библиотеки. Мы выполняем с ним какое-то действие, а затем используем другой макрос Googletest: макрос ASSERT_EQ (говорят assert equals) означает, что тест будет успешным, если первый и второй аргументы имеют одинаковое значение. Вы можете найти полный список утверждений здесь.

Как упоминалось выше, мы хотим поместить весь проект googletest в папку third_party. Первый способ сделать это - просто скопировать туда файлы. Таким образом, вы создадите для себя несколько проблем:

  • Размер вашего репо сильно вырастет, потому что googletest велик.
  • Вам придется вручную обновить код, если будут выпущены новые версии googletest.

В качестве альтернативы мы можем использовать функциональность подмодуля git. Таким образом, вы можете интегрировать другой репозиторий в качестве дочернего в ваш, и тогда git сможет получать для него обновления и исключать файлы из вашего репозитория. В веб-интерфейсах, таких как GitHub, он даже будет ссылаться на репозиторий включенного проекта, когда вы попытаетесь заглянуть в папку. Команды, которые вам нужно выполнить, просты:

cd third_party
git submodule add https://github.com/google/googletest.git
git submodule init
git submodule update

После входа в каталог third_party вы добавляете подмодуль git, указав его местоположение. По сути, это выполнит git-клон репозитория и пометит основную папку как подмодуль. Затем вы говорите git инициализировать его как подмодуль git и обновить его конфигурацию. Эти шаги нужно выполнить только один раз. Чтобы обновить версию googletest позже, просто выполните git submodule update - remote. Теперь вы увидите это в своей учетной записи GitHub (после следующей фиксации и нажатия):

Одно важное замечание: если вы когда-нибудь захотите клонировать свое репо, просто добавьте - recurse-submodules к команде clone. Это скажет git также клонировать подмодуль googletest. Затем мы обновим CMake, чтобы сделать две вещи:

  • Скомпилируйте наши тестовые примеры: мы написали тестовые примеры, в которых используются макросы googletest. Мы хотим скомпилировать эти тесты, чтобы иметь возможность их выполнять. Нам нужно будет связать тестовый исполняемый файл с googletest, чтобы его функциональность была доступна. Поскольку мы включили только исходный код проекта googletest, нам также необходимо
  • Скомпилируйте googletest. Мы включили googletest в наш проект напрямую, поместив весь проект в папку third_party / googletest. Недостаточно просто сказать, что он нужен нам как зависимость, мы также должны его построить. Также есть возможность установить googletest для всей системы и запустить автоматическое обнаружение cmakes. Этот подход работает в большинстве случаев, но требует, чтобы весь ваш проект использовал одну и ту же версию googletest, которая, в свою очередь, является версией в репозитории пакетов вашей ОС и может быть старой.

Мы добавляем следующие строки в файл, который у нас был раньше:

set (gtest_force_shared_crt ON CACHE BOOL "MSVC defaults to shared CRT" FORCE)
add_subdirectory(third_party/googletest)
target_compile_definitions(gtest
  PUBLIC
    GTEST_LANG_CXX20
    GTEST_HAS_TR1_TUPLE=0
)
add_executable(tests)
target_sources(tests
  PRIVATE
    unit_tests/SomeTests.cpp
)
set_target_properties(tests PROPERTIES COMPILE_FLAGS "${cxx_strict}")
target_link_libraries(tests gtest gtest_main Core)

Первую строку нужно просто скопировать. Это устраняет некоторые проблемы в системах Windows, связанные с несовместимостью параметров компиляции. Это заставляет поведение по умолчанию для googletest быть совместимым с настройками по умолчанию в вашем собственном проекте. Затем мы добавляем в наш проект подкаталог, содержащий googletest. Поскольку эта папка содержит файл CMakeLists.txt, он запустит CMake в этом проекте и тем самым настроит его для нас. CMakeLists.txt в googletest определяет цель с именем googletest, которую мы теперь настраиваем в соответствии с нашими потребностями: мы указываем, что он должен быть PUBLIC, что означает, что мы сможем использовать его и связать с ним наш тестовый двоичный файл. Мы также указываем, что googletest должен использовать последний стандарт C ++ (GTEST_LANG_CXX20 - C ++ 2020). GTEST_HAS_TR_TUPLE = 0 отключает поддержку некоторых классов кортежей STL, которые вызывают ошибки в некоторых средах Windows, поэтому просто скопируйте эту команду. Add_executable объявляет наш двоичный файл тестов. Выполнение этого двоичного файла запустит за нас тест. Пока это пустая оболочка без скомпилированных в нее исходных файлов. В следующей строке мы заявляем, что целевые тесты должны быть построены из файла unit_tests / SomeTests.cpp, который мы создали ранее.

Вторая строка снизу требуется только для установки некоторых необходимых свойств для совместимости с googletest, а последняя строка объявляет, что двоичный файл, содержащий наши тесты, должен быть связан с googletest, а также с нашей собственной библиотекой Core. Теперь, после вызова make в нашем проекте, мы можем запустить наш тест, выполнив ./tests, или более подробно: перейдите в папку сборки, вызвав cd build. Вызовите cmake .., а затем make. Теперь вы увидите в этой папке несколько файлов:

  • Main: это двоичный файл, который обычно выполняет ваш код, т.е. запускается в int main () в main.cpp.
  • tests: этот двоичный файл выполняет ваши тесты Google и записывает результат. В качестве примечания: этот двоичный файл содержит гораздо больше функций, чем просто написанные вами тестовые примеры. Например, вы можете вызвать ./tests - help, чтобы получить некоторую помощь о том, как его использовать, вы можете указать запускать только определенный тест, и вы можете передать аргумент - gtest_output = XML , чтобы указать googletest, что нужно написать тестовый выходной файл, который может прочитать система CI. Запуск ./tests должен дать примерно такой результат:
[==========] Running 4 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from PaperTests
[ RUN      ] PaperTests.OccuringWordsBase
[       OK ] PaperTests.OccuringWordsBase (0 ms)
[ RUN      ] PaperTests.IllegalChar
[       OK ] PaperTests.IllegalChar (0 ms)
[ RUN      ] PaperTests.OccuringWordsExtended
[       OK ] PaperTests.OccuringWordsExtended (0 ms)
[----------] 3 tests from PaperTests (1 ms total)
[----------] 1 test from HistogramTests
[ RUN      ] HistogramTests.BasicHistogramTests
[       OK ] HistogramTests.BasicHistogramTests (0 ms)
[----------] 1 test from HistogramTests (0 ms total)
[----------] Global test environment tear-down
[==========] 4 tests from 2 test suites ran. (1 ms total)
[  PASSED  ] 4 tests.

Что мы сделали до сих пор

Возьмите кофе, вы его действительно заслужили! :)

В качестве краткого обзора того, что мы сделали до сих пор, мы сначала создали базовый проект C ++. Затем мы добавили некоторую структуру, построив структуру папок и настроив CMake для этого проекта. Это позволило нам скомпилировать проект, не вызывая сам компилятор. Затем мы представили googletest, чтобы иметь возможность писать тесты для нашего кода. Мы включили googletest в проект, указали, как его построить, и определили тестовый двоичный файл, который выполняет тесты. У нас есть настройка, которая разделяет нашу основную сборку, которая создает рабочий двоичный файл, программу, которую мы хотим создать, и тесты, которые мы пишем, чтобы убедиться, что она работает.

Проект управляется git и содержит googletst в качестве подмодуля. У нас есть резервная копия на GitHub, и мы указали способы запуска как основной функции, так и наших тестовых примеров.

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

Если мы больше не запускаем тесты локально, нам нужно указать, где их запускать. Для этого мы используем докер. Docker предоставляет образы операционной системы в так называемых контейнерах. Мы создадим образ контейнера, который содержит все функции, необходимые для нашего проекта, и настроим CircleCi для использования этого контейнера для запуска наших тестов. Затем он получит результаты теста и сделает их доступными в веб-интерфейсе и в виде значка, показанного на нашей странице репозитория GitHub.

CircleCi предоставляет чертежи для создания проектов на разных языках - C ++, к сожалению, на данный момент среди них нет. Поскольку нам понадобится контейнер, чтобы позволить CircleCi запускать наш код, мы начнем с создания контейнера Docker, а затем настроим его в CircleCi.

Докер

Docker превратился в огромную экосистему со сложной функциональностью, ядром которой является управление образами операционных систем. Способ создания такого образа по умолчанию называется Dockerfile. Это тот, который мы будем использовать:

FROM ubuntu:focal
LABEL maintainer="Pascal Kraft" \
      description="Basic C++ stuff for CircleCi repo." \
      version="0.1.0"
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Berlin
RUN apt-get update -y && \
    apt-get install -y tzdata
RUN apt-get install -y --no-install-recommends\
                    git \
                    curl \
                    gcc-9 \
                    g++ \
                    clang-10 \
                    build-essential \
                    cmake \
                    unzip \
                    tar \
                    ca-certificates && \
    apt-get autoclean && \
    apt-get autoremove && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

Первая строка имеет решающее значение: этот Dockerfile использует другой как основу, а затем только настраивает систему. ubuntu: focal означает, что мы хотим начать с Ubuntu 20.04. Затем мы указываем некоторую метаинформацию об образе Docker, который мы создаем, например, его версию, для чего он нужен и кто его поддерживает. Строки ARG и ENV задают свойства в системе. Первый гласит, что все команды должны выполняться так, как если бы не было доступных устройств ввода, т.е. скрипт должен работать без нашего вмешательства, поэтому никакие программы не должны задавать никаких вопросов на консоли. Мы будем зависеть от пакета tzdata, который содержит информацию о часовых поясах. По умолчанию он спрашивает, где находится компьютер, во время установки. Чтобы обойти это, мы предоставляем переменную TZ как часть среды (EV), которую tzdata будет использовать вместо запроса.

После этой начальной настройки мы можем запускать простые команды командной строки с синтаксисом Docker RUN. В первом блоке RUN мы загружаем источники пакетов из источников пакетов, чтобы мы могли выполнить установку позже. Затем мы устанавливаем tzdata. Я выделил это в отдельный блок, потому что он часто давал мне сбой, поэтому я хотел, чтобы этот шаг был выполнен первым.

Далее мы устанавливаем в образ все основные зависимости нашего проекта. -y указывает apt ни о чем не спрашивать, а просто установить пакеты. - no-install-рекомендует сводит количество установленных пакетов к минимуму и сохраняет полученный образ немного меньше. Этот вариант можно убрать. Пакеты, которые мы устанавливаем, содержат git, очевидно, GCC-9 и Clang, CMake и некоторые другие полезные утилиты. Ca-сертификаты установлены, потому что они позволяют нам хранить артефакты, потому что в противном случае мы увидим ошибки о том, что не авторизованы для загрузки артефактов, что происходит из-за невозможности установить https-соединения. После этого мы удаляем все ненужные данные, выполняя apt-get autoremove, apt-get clean и apt-get autoclean, а также удаляя загруженные источники пакетов, вызвав rm -rf / var / lib / apt / lists / *. Эти шаги просто уменьшают размер нашего образа докера, что сокращает время, необходимое для загрузки образа.

Когда наш Dockerfile готов, мы создаем новый репозиторий GitHub, содержащий только этот единственный файл. Вы можете назвать это DockerfileRepositoryForCpp. Он должен содержать только Dockerfile, который должен называться Dockerfile (без окончания файла). Затем перейдите в DockerHub. Если у вас его еще нет, создайте аккаунт и подтвердите свой адрес электронной почты. Я бы рекомендовал зарегистрироваться с вашей учетной записью GitHub. После входа в систему выберите Репозитории вверху и нажмите Создать репозиторий в правом верхнем углу. Введите имя и описание, сделайте его общедоступным и прокрутите вниз. Вы должны увидеть раздел Настройки сборки и в нем значок Github. Щелкните его и подключите свою учетную запись GitHub к своей учетной записи Docker. После подключения учетных записей вы должны увидеть слово подключено зеленым цветом под значком Github на странице создания репозитория. Щелчок по значку GitHub должен открыть два поля ввода: одно для организации, где вы выбираете свою учетную запись Github, и одно для репозитория, где вы выбираете свой репозиторий DockerfileRepositoryForCpp. Затем нажмите Создать и построить.

Процесс займет некоторое время, потому что теперь Docker загрузит базовый образ, указанный в строке 1, выполнит конфигурации, которые мы отметили в файле Docker, а затем сделает доступным образ полученной операционной системы. Есть и другие способы настройки образа Docker, например, вы можете использовать инструменты Docker CLI на локальном компьютере и локально выполнить сборку Docker. После этого вы можете отправить готовое изображение в репозиторий. У этого есть обратная сторона, требующая полной загрузки образа с вашего компьютера в Docker, что, если ваше интернет-соединение асимметрично (что обычно бывает), может занять очень много времени. Преимущество описанного выше способа состоит в том, что серверы GitHub и Docker обмениваются только файлом Docker, а все остальное происходит на серверах Docker, поэтому загрузка не требуется. Он также вводит управление версиями вашего Dockerfile, которое может пригодиться в какой-то момент.

CircleCi

Теперь у нас есть готовая база кода, доступная в GitHub, у нас есть тесты, мы настроили CMake и у нас есть образ Docker для выполнения всех этих шагов. Все, что нам сейчас нужно, это настройка конвейера для CircleCi, чтобы он выполнял необходимые шаги.

В качестве первого шага мы авторизуемся в CircleCi, используя нашу учетную запись GitHub. Затем мы добавляем проект и выбираем проект, содержащий наш код и тесты на GitHub. В настоящее время CircleCi не имеет шаблона для проектов C ++, поэтому вы можете выбрать план HelloWorld, который CircleCi предложит вам. Щелкните Добавить конфигурацию. CircleCi создаст проект, а также добавит новую ветку в ваш репозиторий с именем circleci-project-setup. Он содержит один коммит с одним новым файлом .circleci / config.yml, который содержит настройку CI-pipeline. В будущем CircleCi будет получать информацию обо всех фиксациях в этом репозитории и всегда будет выполнять шаги, перечисленные в файле config.yml. Таким образом, изменив этот файл, вы можете изменить шаги, которые CircleCi выполняет за вас. Предлагаю использовать вот такой config.yml:

version: 2.1
executors:
  exectr:
    docker:
      - image: YourGitHubName/YourDockerRepoName:latest
jobs:
  build:
    executor: exectr
    steps:
      - checkout
      - run:
          name: Setup gtest child repo
          command: |
            cd third_party
            git submodule init
            git submodule update 
      - run:
          name: Setup cmake and build artifacts
          command: |
            mkdir build
            cd build
            cmake ..
            make
      - persist_to_workspace:
          root: .
          paths: build
  test:
    executor: exectr
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Execute Tests
          command: |
            cd build
            ./tests --gtest_output=XML
      - store_test_results:
          path: build
workflows:
  version: 2
  build-and-test:
    jobs:
      - build
      - test:
          requires:
            - build

Чтобы объяснить этот файл, мы начнем с самого низа: он объявляет рабочий процесс. Рабочий процесс называется сборкой и тестированием и включает два задания: сборку и тестирование, из которых проверка зависит от сборки. Теперь все, что нам нужно сделать, это объявить, что на самом деле представляют собой этапы сборки и тестирования.

В строке 8 мы начинаем определять нашу работу по сборке. Он содержит список шагов, которые необходимо выполнить. На этапе оформления заказа будет подготовлен наш код (поскольку CircleCi знает, какое репо предполагается использовать, нам не нужно указывать здесь репозиторий). Однако он не загружает подмодули, поэтому мы делаем это вручную сразу после оформления заказа. Синтаксис шага довольно интуитивно понятен: каждый шаг состоит из имени и команды, которая должна выполняться в оболочке. Вертикальная черта (|) в начале командного блока означает, что будет несколько команд, которые должны выполняться друг за другом. Входим в стороннюю папку и загружаем репозиторий googletest как описано выше.

Затем мы создаем каталог сборки, входим в него, настраиваем нашу кодовую базу с помощью cmake .. и начинаем компиляцию с помощью make. Мы могли бы запускать наши тесты сразу в этом или последующем командном блоке, но это имело бы один серьезный недостаток: в нашем проекте есть две точки отказа: кодовая база больше не может компилироваться или тест больше не может работать. Если мы разделим конвейер CircleCi на две задачи, у нас будет то преимущество, что мы сможем видеть на панели инструментов CircleCi, какие из них сработали, а какие нет, что значительно ускорит диагностику проблем.

Поэтому после завершения сборки мы сохраняем содержимое каталога сборки в нашей рабочей области, чтобы мы могли легко сделать их доступными в любом другом задании. В тестовом задании мы сначала загружаем это рабочее пространство и теперь снова находимся в том же состоянии, что и в конце задания сборки. Мы запускаем только один блок команд, который входит в каталог сборки и запускает ./tests - gtest_output = XML. В дополнение к результатам теста на консоли googletest теперь также будет генерировать XML-файл, содержащий все запущенные тесты и их результаты. CircleCi может очищать папки для таких результатов, если мы добавим шаг add_test_results с путем к папке, содержащей такие файлы XML.

В обоих наших заданиях мы указали исполнителя. Это сокращенное обозначение системы, на которой запускается конвейер. Мы определяем этого исполнителя в самом начале файла конфигурации. Мы определяем исполнителя с именем exectr, который использует образ Docker, который мы создали ранее. Чтобы узнать URL-адрес, который вы должны указать здесь, перейдите в список ваших репозиториев в DockerHub. Вы увидите что-то вроде

Здесь silverlinings89 / cpp_build_environment будет тем, что вы ищете. Чтобы указать, какое состояние этого репозитория следует использовать, добавьте к этому имени : latest. Поэтому в моем случае я бы написал - image: silverlinings89 / cpp_build_environment: latest

После адаптации этого файла к вашему проекту, зафиксируйте его в своем репозитории и отправьте фиксацию на GitHub. Конвейер CircleCi должен сразу же запустить ваш конвейер, построить ваш проект, выполнить тесты и показать вам состояние каждого задания. Вы должны увидеть что-то вроде этого:

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

В этом представлении перечислены отдельные шаги, и вы можете увидеть посередине вкладку TESTS [4], в которой перечислены проанализированные результаты тестов из файла, который мы загрузили. Если вы нажмете ТЕСТЫ, вы увидите

В этой настройке у меня есть 4 теста, и все они зеленые. Визуализация результатов тестирования является неоптимальной, и вы можете предпринять шаги, чтобы повысить удобочитаемость результатов тестирования, но на данный момент этой базовой настройки должно хватить для запуска вашего проекта. Этот конвейер будет запускаться всякий раз, когда вы нажимаете коммиты на GitHub, и покажет вам результаты конвейера для текущего состояния каждой ветки вашего репозитория на панели управления CircleCi. Есть еще одна приятная деталь, которую вы можете использовать для интеграции состояния репозитория (если сборка пройдена или нет) в GitHub и сделать его быстро доступным:

README.md

Всегда полезно добавить Readme.md в ваш репозиторий, потому что GitHub прекрасно его визуализирует и дает людям, работающим с кодом, отправную точку. Если вы не знакомы с синтаксисом, на GitHub есть хорошая шпаргалка. На этой странице вы найдете введение в создание значков для вашего Readme.md, которые визуализируют состояние вашего конвейера. Будет отображаться либо зеленый значок пройденного, либо красный значок отказа. Код для этого:
[! [‹ORG_NAME›] (https://circleci.com/ ‹VCS› / ‹ORG_NAME› / ‹PROJECT_NAME› .svg? Style = svg)] (‹LINK›)

В моем проекте это было

[![SilverLinings89](https://circleci.com/gh/SilverLinings89/HallmarksSearchTools.svg?style=svg)](https://app.circleci.com/pipelines/github/SilverLinings89)

Заключительное слово

Снова выпейте кофе, вы его заслужили!

Я надеюсь, что это было поучительно для вас и что я смогу помочь вам внести некоторую структуру и автоматизацию в ваш проект. Я попытался объяснить, почему эти шаги были необходимы по пути. Если вы нашли эту статью полезной, поделитесь ею с другими людьми, которые могут найти ее интересной, и оставьте мне комментарий, если возникнут какие-либо вопросы. Также спасибо Miguel Á. Padriñán в Pexels за изображение.