Приступим к практической работе с CMake. Как было сказано в предыдущей статье, CMake - это средство автоматизации сборки ПО. Оно не может ничего построить самостоятельно. Поэтому перед установкой CMake необходимо, как минимум, установить компилятор.
Для операционных систем семейства Linux и языка С++ наиболее популярными являются компиляторы g++ и clang++. Для проверки примеров статей из данной серии я использую операционную систему Ubuntu 20.04 и компилятор g++ . Вначале проверим наличие g++ в системе командой g++ --version. Если g++ установлен, то будет выведено сообщение с информацией о версии. На моем ПК оно выглядит следующим образом:
Если g++ не установлен, то будет выведено сообщение о том что команда g++ не найдена. Синтаксис команды установки зависят от того, какой дистрибутив Linux Вы используете. Я работаю на Ubuntu, поэтому для установки вначале необходимо обновить список пакетов командой sudo apt-get update , а после завершить установку, введя команду sudo apt get-install g++.
Далее необходимо установить CMake на Вашу систему. Для Ubuntu необходимо ввести следующую команду: sudo apt-get install cmake . Проверить правильность установки можно, введя следующую команду: cmake --version по аналогии с установкой g++.
После установки CMake перейдем к созданию проекта. Начнем разрабатывать простейший калькулятор, поддерживающий стандартный набор арифметических операций: +, -, *, / , а также побитовые операции and, or, xor, not (и, или, исключающее или, не).
Вначале необходимо создать директорию проекта в дереве каталогов. Крайне рекомендую создавать отдельную папку для каждого проекта. Назовем ее calculator. После создания папки перейдите в нее и создайте файл CMakeLists.txt, в котором разместим программу сборки проекта.
Кроме того, создадим в текущей папке еще одну с названием src. В ней будет находиться дерево исходников проекта. Здесь необходимо создать файлы arithmetic.h, arithmetic.cpp хранящие исходный код для арифметических операций и logic.h, logic.cpp для побитовых логических операций. В этой же папке создайте файл main.cpp, в котором будет код проверки работоспособности функций калькулятора.
Необходимо помнить, что пути для исходников, используемых в проекте, должны быть относительными, а не абсолютными. В противном случае, при переносе проекта на другой компьютер или в другой каталог, возникнут проблемы со сборкой. В результате описанных выше действий содержимое дерева файлов и каталогов должно иметь следующий вид:
Далее необходимо добавить код в проект.
arithmetic.h
#pragma once
double sum(double op1, double op2);
double sub(double op1, double op2);
double mult(double op1, double op2);
double divide(double op1, double op2);
arithmetic.cpp
#include "arithmetic.h"
double sum(double op1, double op2)
{ return op1 + op2; }
double sub(double op1, double op2)
{ return op1 - op2; }
double mult(double op1, double op2)
{ return op1 * op2; }
double divide(double op1, double op2)
{ return op1/op2; }
logic.h
#pragma once
int bitwise_and(int op1, int op2);
int bitwise_or(int op1, int op2);
int bitwise_xor(int op1, int op2);
int bitwise_not(int op);
logic.cpp
#include "logic.h"
int bitwise_and(int op1, int op2)
{ return op1 & op2; }
int bitwise_or(int op1, int op2)
{ return op1 | op2; }
int bitwise_xor(int op1, int op2)
{ return op1 ^ op2; }
int bitwise_not(int op)
{ return ~op; }
main.cpp
#include <iostream>
#include "arithmetic.h"
#include "logic.h"
int main() {
int a = 1;
int b = 2;
std::cout << "a and b:" << bitwise_and(a, b) << std::endl;
std::cout << "a or b:" << bitwise_or(a, b) << std::endl;
std::cout << "a xor b:" << bitwise_xor(a, b) << std::endl;
std::cout << "not a:" << bitwise_not(a) << std::endl;
a = 10;
b = 20;
std::cout << "a + b:" << sum(a, b) << std::endl;
std::cout << "a - b:" << sub(a, b) << std::endl;
std::cout << "a * b:" << mult(a, b) << std::endl;
std::cout << "a / b:" << divide(a, b) << std::endl;
return 0;
}
Далее добавим в CMakeLists.txt следующую программу сборки проекта калькулятора:
cmake_minimum_required(VERSION 3.15)
set(ProjectName Calculator)
project(${ProjectName} CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
if (CMAKE_BUILD_TYPE STREQUAL Debug)
message("build debug version of project")
else()
message("build release version of project")
endif()
message(${CMAKE_BINARY_DIR})
add_executable(${ProjectName}
src/main.cpp
src/arithmetic.cpp
src/logic.cpp)
Рассмотрим программу сборки проекта из CMakeLists.txt. Она должна содержать как минимум две команды:
• cmake_minimum_required(VERSION <x.xx>): устанавливает ожидаемую версию CMake. По сути это версия внутреннего языка построения проекта.
• project(<name> <OPTIONS>): присваивает имя проекту и указывает параметры для его настройки (в нашем случае - это язык на котором написаны исходники проекта. CXX соответствует С++).
По мере роста вашего проекта может потребоваться разбить его на более мелкие части, которые будут размещаться в отдельных директориях и содержать дополнительные файлы CMakeLists. Эта тема будет рассмотрена в следующей статье.
CMake включает в себя язык построения проекта, в котором можно создавать переменные и задавать им значения. Переменные можно разделить на системные и пользовательские. Имена системных являются общими для всех CMakeLists файлов, в то время как пользовательские задают настройки только для конкретного проекта.
Задать значение переменным можно при помощи команды set. В нашем CMakeLists.txt задается переменная ProjectName (при помощи команды set(Название_переменной Значение)), определяющая как имя проекта, так и название исполняемого файла программы. Получить значение переменной можно заключив ее название в фигурные скобки и разместив знак доллара перед открывающей скобкой, например так ${ProjectName}.
Как вы наверное уже догадались, ProjectName - пользовательская переменная. Далее задаются системные переменные CMAKE_CXX_STANDARD, CMAKE_CXX_STANDARD_REQUIRED, CMAKE_RUNTIME_OUTPUT_DIRECTORY. CMAKE_CXX_STANDARD определяет для какой версии стандарта C++ будет собираться проект. Число 20 обозначает стандарт С++ 20. CMAKE_CXX_STANDARD_REQUIRED - булево значение, описывающее, является ли значение CXX_STANDARD требованием.
Если это свойство установлено в ON, то значение целевого свойства CXX_STANDARD рассматривается как требование. Если это свойство установлено в OFF или не установлено вовсе, то целевое свойство CXX_STANDARD рассматривается как необязательное. CMAKE_RUNTIME_OUTPUT_DIRECTORY - задает в какую директорию записывается результат построения (в нашем случае - файл с программой).
Этот путь имеет вид ${CMAKE_BINARY_DIR}/bin . CMAKE_BINARY_DIR задает каталог верхнего уровня для дерева построения проектов. Эта переменная может быть задана при запуске CMake. Суффикс /bin говорит о том что файл с программой будет создан в отдельном подкаталоге bin директории, задаваемой при помощи переменной CMAKE_BINARY_DIR.
CMakeLists задает правила генерации как для отладочной, так и для релизной версии сборки. Необходимая версия задается при помощи переменной CMAKE_BUILD_TYPE. Обычно это делается при помощи указания ключа командной строки при запуске CMake. В процессе выполнения скрипта выводится информация о том какая из них собирается при помощи команды message. Решение о том какое сообщение выводить выполняется в операторе if. Его действие аналогично тому что используется в языках программирования.
Так как переменные CMake хранятся в строковом виде, то для выяснения типа сборки используется сравнение при помощи STREQUAL. Далее осуществляется вывод пути по которому строится дерево сборки проекта командой message(${CMAKE_BINARY_DIR}). Последняя команда add_executable задает исполняемый файл, который нужно построить. Для построения используется список исходных файлов с указанием путей, которые задаются относительно директории, в которой находится CMakeLists.txt (в данном случае - это корневая директория проекта).
Перейдем к рассмотрению процесса сборки проекта. Первый этап - это генерация системы сборки(buildsystem) при помощи запуска команды cmake. Общий вид команды: cmake [опции] -S <путь к папке исходников> -B <путь к папке с артефактами работы cmake>. Прямоугольные скобки вокруг слова «опции» означают что данный аргумент команды необязателен. Пути, стоящие после ключей команды -S и -B, можно также не указывать или указывать относительно текущего каталога.
Например, если написать cmake -S .. , то cmake будет искать исходники для компиляции в родительской директории, при этом артефакты работы cmake(дерево построения) будут создаваться в текущей директории. Если указать cmake -B build , то cmake будет искать исходники в текущей директории, а дерево построения будет создано в папке build, которая также будет создана в текущем каталоге. В качестве опций можно указывать переменные CMake, используемые в CMakeLists.
Каждая задается в формате -D Название_переменной=Значение. Находясь в каталоге calculator введем команду cmake -D CMAKE_BUILD_TYPE=Debug -B ./Debug (указываем что сборка будет отладочной и дерево построения проекта должно быть создано в поддиректории ./Debug текущей директории). В результате будет создана папка Debug . На моем ПК будет выведено следующее:
Сообщения, полученные в процессе работы CMake подтверждают что идет отладочная сборка и дерево построения будет создано в директории Debug. Эта директория будет хранить систему сборки и все, что создается во время сборки. Здесь будет создаваться ваша конфигурация сборки и ее артефакты, такие как двоичные файлы, исполняемые файлы и библиотеки, а также объектные файлы и архивы, используемые для компоновки проекта.
В документации CMake рекомендуется размещать этот каталог вне каталога исходного дерева (практика, известная как сборка вне исходного кода). Таким образом, мы можем предотвратить загрязнение дерева исходников проекта посторонними файлами.
Как Вы наверное уже догадались, при помощи ключа -B для команды cmake задается значение переменной CMAKE_BINARY_DIR. Для построения исполняемого файла необходимо перейти в каталог Debug и ввести команду cmake --build .
Если на предыдущих шагах все было сделано правильно, на экране будет отображен прогресс сборки с указанием названий имен обрабатываемых исходников. В конце появится сообщение о том что цель Calculator построена.
В данном случае цель - это исполняемый файл, но это гораздо более широкое понятие о котором будет рассказано в других статьях.
Чтобы убедиться в том что получена отладочная сборка, введем команду file ./bin/Calculator, находясь в директории Debug. В результате будет выведено сообщение, в котором, среди прочего, говорится что файл содержит отладочную информацию (with debug_info).
После этого проверим работу программы, введя команду ./bin/Calculator
Программа отработала как ожидалось. В следующий раз поговорим о том как можно улучшить структуру проекта и немного автоматизировать процесс для различных видов сборки.