Введение в ООП

СMake

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

Базовый вариант

Мы написали простую программу в файле main.cpp:

// main.cpp
#include <iostream>

unsigned long long factorial(unsigned long long n){
    return n == 0 ? 1ull : n * factorial(n - 1);
}

int main() {
    std::cout << factorial(5) << std::endl;
    return 0;
}

Теперь нам нужно её скомпилировать. Мы могли бы вручную вызвать компилятор и прописать ему все необходимые флаги. Получилось бы подобная команда (для release сборки):

g++ main.cpp -o example.exe -O3 -DNDEBUG -std=c++20

Но каждый раз прописывать все флаги немного утомительно. А также, если у нас в проекте есть несколько .cpp файлов, то команда станет ещё намного страшнее. Поэтому попробуем использовать CMake, где мы пропишем всё необходимое 1 раз.

Для работы с CMake необходимо создать конфигурационный файл CMakeLists.txt в корне проекта. Для нешей задачи его можно заполнить следующим образом:

# Проверка версии CMake.
cmake_minimum_required(VERSION 3.2)
# Если версия установленой программы
# старее указаной, произайдёт аварийный выход.

project(ExampleProg LANGUAGES CXX)
# Название проекта
# и указание используемых языков
# какой компилятор использовать к какому файлу
# будет определяться по расширению можно добавить
# "C" -- си, "FORTRAN" -- фортран, "CUDA" -- cuda

set(CMAKE_CXX_STANDARD 20) # Установка стандарта c++

set(SOURCE_EXE main.cpp)
# Установка переменной со списком исходников
# (может быть несколько файлов)

add_executable(Example ${SOURCE_EXE})
# Создает исполняемый файл с именем Example
# из всех исходников в ${SOURCE_EXE}

Сборка

После этого нам остаётся собрать проект:

mkdir build # Создать папку build
cd build # Зайти в папку build
cmake -G "MinGW Makefiles" .. # Подготовить файлы для сборки
cmake --build . # Запустить сборку

Флаг -G нужно писать, когда у вас несколько компиляторов (например, от Visual Studio и MinGW).

Можно делегировать создание папки самому CMake с помощью флагов -S и -B, которые указывают на директорию с CMakeLists.txt и директорию для сборки. Также можно прописать тип сборки указав значение CMAKE_BUILD_TYPE. Это можно было прописать в конфигруационный файл, но тогда мы бы не имели возможность выбора. В итоге для сборки нужно прописать

  • в случае Release сборки:
cmake -S . -B build/ -D CMAKE_BUILD_TYPE=Release -G "MinGW Makefiles"
cmake --build build/
  • в случае Debug сборки:
cmake -S . -B build/ -D CMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles"
cmake --build build/

Вариант с библиотекой

Теперь попробуем вынести factorial в библиотеку. Теперь у нас структура проекта:
-- main.cpp
-- CMakeLists.txt
-- factorial
. . . .| -- CMakeLists.txt
. . . .| -- factorial.h
. . . .| -- factorial.cpp

// main.cpp
#include <iostream>
#include <factorial.h>

int main() {
    std::cout << factorial(5) << std::endl;
    return 0;
}

Обратите внимание на строку #include <factorial.h>. В обычной ситуации мы бы использовали #include "factorial/factorial.h", но CMake позволит нам не писать полные пути до файлов.

// factorial.h
unsigned long long factorial(unsigned long long);
// factorial.cpp
#include "factorial.h"

unsigned long long factorial(unsigned long long n){
    return n == 0 ? 1ull : n * factorial(n - 1);
}

Дополним исходный CMakeLists.txt:

cmake_minimum_required(VERSION 3.2)

project(ExampleProg LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20) 
set(SOURCE_EXE main.cpp)

include_directories(./factorial)
# Расположение заголовочных файлов
# (будет доступно всем target'ам проекта)
# Альтернатива: target_include_directories

add_executable(Example ${SOURCE_EXE})

add_subdirectory(./factorial)
# Добавление подпроекта, указывается имя дирректории
# в этой директории должен быть свой небольшой CMakeLists.txt

# Линковка программы с библиотеками
target_link_libraries(Example Factorial)
# после названия цели (Example) идёт перечисление библиотек

И добавим CMakeLists.txt в директорию factorial:

cmake_minimum_required(VERSION 3.2)
project(factorial)
set(SOURCE_LIB factorial.cpp)
# Создание статической библиотеки
add_library(Factorial STATIC ${SOURCE_LIB})
# если написать SHARED, то будет динамическая

Подпроекты получат глобальные параметры (используемые языки, доп.библиотеки, настройки компиляторов и т.п.) из основного CMakeLists.txt.

Проект готов к сборке.