Начало изучения системы сборки программ CMake
Предыдущий пост в этой серии постов: «Как начать изучать тестирование программ на C++».
Напомню, в основном мы работаем в операционной системе Windows 10, но некоторые студенты используют на своих компьютерах операционные системы из семейства Linux. Вообще, до сих пор для получения исполняемого файла из исходного кода на языке C++ мы пользовались либо каким-нибудь компилятором из командной строки, либо, при работе в среде Visual Studio Community, пользовались системой сборки MSBuild, которую использует эта среда по умолчанию при сборке исполняемого файла из исходного кода на языке C++.
Многие студенты даже не подозревают о существовании MSBuild в частности и систем сборки вообще, так как просто нажимают на кнопочки в графическом интерфейсе среды Visual Studio, когда им нужно получить исполняемый файл из исходного кода, не задумываясь о том, что происходит при нажатии на эти кнопочки.
Что такое «сборка» (build) и «система сборки»
«Сборкой» называют как сам процесс, так и его результат. Если понимать «сборку» как процесс, то речь идет о процессе получения из множества исходных файлов (например, из файлов main.cpp, lib.cpp, lib.h) одного исполняемого файла (например, main.exe). Нередко также «сборкой» называют результат сборки-процесса, например, файл main.exe. В литературе по языку C++ я почти не встречаю случаев, когда исполняемый файл называют «сборкой», а вот в литературе и статьях по языку C# такое часто встречается. Чтобы было поменьше путаницы, я «сборкой» далее буду называть процесс, а не его результат.
В принципе, я знал о существовании средств (программ, систем) сборки, но никогда не понимал, зачем они нужны: сборку же можно выполнить с помощью компилятора/компоновщика. Как я понял, во-первых, система сборки становится нужна в сложных случаях, с которыми компилятор/компоновщик не может справиться (например, если нужно при компиляции еще автоматически выполнять модульное тестирование).
Во-вторых, как я понимаю, система сборки облегчает кроссплатформенную разработку. Ведь что такое «система сборки»? Это программа (или ряд программ), выполняющих сборку, работой которых мы управляем с помощью специального файла (или ряда файлов). В этом файле мы пишем скрипт сборки на специальном языке. Если в скрипте оставить только вещи, общие для разных операционных систем, то получится кроссплатформенный скрипт управления сборкой.
Почему я выбрал для изучения систему CMake
CMake — одна из самых популярных на сегодня систем сборки. Популярность — это преимущество. Но вообще сначала я выбирал тестовый фреймворк для модульного тестирования программ на языке C++. Поскольку не хотелось возиться с установкой тестового фреймворка, то я решил выбирать фреймворк из четырех, встроенных в среду Visual Studio Community, которая у нас уже установлена (эти четыре фреймворка доступны для программ на языке C++ при установленной рабочей нагрузке «Desktop Development with C++»).
Выше я уже упоминал, что мы в основном работаем на Windows 10 (иногда на Windows 11), но некоторые студенты на своих собственных компьютерах используют системы из семейства Linux. Поэтому «родной» (native) тестовый фреймворк среды Visual Studio для программ на языке C++ нам не подошел (он доступен только в Visual Studio, то есть только на Windows, так как Visual Studio недоступна на Linux). Из оставшихся вариантов самым популярным был тестовый фреймворк Google Test (доступен на всех трех самых популярных платформах), поэтому выбрали для изучения его.
В документации тестового фреймворка Google Test описана его интеграция с двумя системами сборки: Bazel и CMake. Про систему сборки Bazel (https://bazel.build) я ранее не слышал, поэтому выбрал CMake.
Установка системы сборки CMake на компьютер
В пользу CMake также оказалось то обстоятельство, что эта система сборки тоже (как и тестовый фреймворк Google Test) встроена в Visual Studio Community и доступна в составе рабочей нагрузки «Desktop Development with C++» на Windows 10. Таким образом, на компьютерах в аудитории не потребовалось устанавливать ни тестовый фреймворк Google Test, ни систему сборки CMake.
Тем студентам, которые используют на своих собственных компьютерах системы из семейства Linux, придется самостоятельно установить на свой компьютер как тестовый фреймворк Google Test, так и систему сборки CMake. Обе эти системы доступны для установки с помощью соответствующих пакетных менеджеров (в разных системах из семейства Linux используются разные пакетные менеджеры). Сам я этого не делал (я работаю на Windows 10, но могу в крайнем случае использовать WSL), но ребята пообещали установить и рассказать о результате.
Как связаны компилятор и система сборки
Многие студенты путают компиляторы и системы сборки. Насколько я понимаю, сама система сборки не транслирует (не переводит) исходный код с языка, понятного человеку, на машинный язык. Эту трансляцию производит компилятор. По идее, любая система сборки должна давать возможность использовать любой из компиляторов для конкретного языка (в нашем случае — C++). Насколько я понимаю, выбор компилятора производится на этапе установки системы сборки на компьютер. Система сборки CMake в составе среды Visual Studio Community связана с компилятором MSVC (cl.exe).
Поскольку я сам не устанавливал систему сборки CMake на компьютер, то могу только предполагать, как это происходит. В интернетах пишут, что компилятор можно привязать к CMake с помощью одного из трех способов: переменные окружения; через параметр при запуске CMake из командной строки; в файле со скриптом, управляющем сборкой.
Проверка доступности CMake на компьютере
В нашем случае, в операционной системе Windows 10 при использовании среды Visual Studio Community, имеет смысл сначала проверить, что для Visual Studio установлена рабочая нагрузка «Desktop Development with C++». После этого стоит проверить, что в рамках этой рабочей нагрузки установлена система сборки CMake. Это можно проверить из программы Visual Studio Installer (она доступна в списке установленных приложений кнопки «Пуск», если вы устанавливали Visual Studio к себе на компьютер). Вот как это выглядит у меня в окне программы Visual Studio Installer:

После этого проверим доступность программы CMake из командной строки разработчика среды Visual Studio. Я собираюсь работать с системой сборки CMake из этой командной строки. Для этого можно использовать следующую команду:
cmake --version
Вот как такая проверка выглядит у меня:

Простой пример использования системы CMake
В качестве первого практического примера мы написали проект из трех файлов: main.cpp, lib.cpp и lib.h. Вот содержимое этих файлов:
// main.cpp (в кодировке UTF-8)
#include <iostream>
#include "lib.h"
int main()
{
std::cout << "Факториал числа 8 равен " << factorial(8) << '\n';
return 0;
}
// lib.h
long long factorial(int n);
// lib.cpp
long long factorial(int n)
{
if (n < 0) return -1;
long long fact{ 1 };
for (int i{ 1 }; i <= n; i++)
{
fact *= i;
}
return fact;
}
Для описания порядка сборки исполняемого файла из исходных файлов с помощью системы сборки CMake используют скрипт на специальном языке, сохраняемый в файлах с названием CMakeLists.txt. В каждой папке внутри папки проекта может находиться такой файл. В таких простых случаях, как наш, достаточно одного такого файла в корневой папке проекта, где у нас хранятся и вышеописанные исходные файлы. Вот код нашего файла CMakeLists.txt:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(CMakeFirstSample)
add_executable(MyProgram)
target_sources(MyProgram
PRIVATE
main.cpp
lib.cpp
)
Вышеприведенный файл мы составили на основе глав (шагов) 0 и 1 руководства по работе с системой сборки CMake, опубликованного на официальном сайте этой системы. В этой статье я не буду подробно останавливаться на синтаксисе команд в файле CMakeLists.txt. Кому интересно — обратитесь к упомянутому руководству, оно хорошее.
Нужно понимать, что при использовании системы сборки CMake сначала следует создать так называемый «генератор». При создании этого генератора учитываются все ранее сконфигурированные настройки, в том числе выбранный при установке CMake компилятор. По идее, для одного и того же проекта можно создать разные генераторы, которые будут генерировать исполняемый файл с использованием разных компиляторов (можно создать разные генераторы для одного и того же компилятора, но с разными конфигурациями вроде Debug и Release) и/или для разных операционных систем.
Генератор создается один раз и в дальнейшем используется для получения исполняемого файла из исходных файлов. Для этого можно использовать следующую команду из командной строки:
cmake -B build
-B (подробнее) — это название параметра программы cmake.exe. После него через пробел вы можете указать название подпапки вашего проекта, в которой будут храниться файлы генератора. Как видно выше, мы выбрали название build для подпапки генератора (это традиционное название для этой подпапки). Вот как у меня на компьютере для моего проекта выглядит запуск этой команды:

Для получения исполняемого файла теперь и в будущем достаточно запустить полученный генератор. Это можно сделать с помощью следующей команды из командной строки:
cmake --build build
--build (подробнее) — это название параметра программы cmake.exe. После него через пробел нужно указать название подпапки проекта с файлами нужного генератора (у нас это подпапка build). Вот как у меня на компьютере выглядит для моего проекта генерация исполняемого файла из заданных исходных файлов:

На иллюстрации выше можно заметить, что в результате сборки получился исполняемый файл MyProgram.exe (его название можно настроить в вышеупомянутом файле CMakeLists.txt). Запустим его, чтобы проверить, как он работает:

Как видно на этой иллюстрации, исполняемый файл был записан в подпапку .\build\Debug\ относительно текущей папки проекта. Как я понимаю, по умолчанию при компиляции была использована конфигурация Debug, которая используется при отладке продукта. Исполняемый файл называется MyProgram.exe, но в командной строке cmd.exe расширение .exe можно не указывать. Полученная программа отработала без ошибок и выдала ожидаемый результат.
Переключение кодировки командной строки с помощью команды chcp я использовал, чтобы командная строка корректно отобразила русские буквы при выполнении нашей программы (в исходном файле они сохранены в кодировке UTF-8, в системе Windows этой кодировке соответствует кодовая страница 65001).
Если у меня будет достаточно времени, я опишу использование модульного тестирования с системой сборки CMake в отдельной статье.