Урок 15. CMake
Привет! На этом уроке мы добавим еще немного профессиональных деталей в наш проект. Освежим в памяти команды для сборки проекта из Урока 14. Сборка библиотеки:
g++ -c src/libmath.cpp -o libmath.o -I./include
ar rvs libmath.a libmath.o
sudo cp libmath.a /usr/local/lib/
sudo cp include/libmath.h /usr/local/include/
Сборка проекта:
g++ src/app.cpp src/main.cpp -lmath -o calculator
Команд достаточно много. Но основная проблема в другом: каждая из команда состоит из набора файлов, из которых состоит библиотека и проект. Т.е. при каждом добавлении нового файла в библиотеку или проект, мы должны будем обновлять команды для сборки. А если в нашем проекте будут десятки файлов? Команда на сборку разрастется до немыслимых размеров!
Помочь справиться с проблемами масштабирования проекта нам поможет система сборки проекта. Система сборки предоставляет разработчики высокоуровневый интерфейс над низкоуровневыми командами для сборки и установки проектов, тем самым значительно упрощают работу с большими проектами. На современном рынке С++ в качестве системы сборки доминирует CMake - на изучение его я и остановлюсь в рамках моего курса.
Прежде чем мы перейдем к изучению синтаксиса CMake, я спешу тебя предостеречь от попытки “глубокого и основательного изучения CMake”. CMake - это прикладной инструмент, ты должен изучать его по мере твоей профессиональной необходимости, изучать CMake просто так - занятие бессмысленное. Тебе необходимо изучить основные возможности CMake, чтобы ты имел представление о его возможностях, со всем остальным тебе может разобраться любой ИИ помощник:)
Структура репозитория нашей математический библиотеки, которая теперь собирается с помощью CMake:
libmath/
├── src/ # Исходные файлы проекта
│ └── libmath.cpp # Реализация математических функций
├── include/
│ └── libmath.h # Заголовочный файл математических функций
├── .gitignore # Список файлов, игнорируемых Git
├── CMakeLists.txt # Конфигурационный файл CMake для сборки проекта
└── README.md # Описание проекта и инструкции по сборке
Содержимое минимально достаточного файла CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(libmath)
add_library(math STATIC
src/libmath.cpp
)
target_include_directories(math
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
install(TARGETS math
ARCHIVE DESTINATION /usr/local/lib
)
install(FILES include/libmath.h
DESTINATION /usr/local/include
)
Давай сначала соберем и установим наш проект, а потом разберемся с синтаксисом конфигурационного файла.
Конфигурирование проекта Для начала нужно выполнить команду на подготовку конфигурации для сборки нашего проекта:
cmake -B build
После выполнения этой команды CMake автоматически выполнит всякие подготовительные операции, например, найдет компилятор С++, поддерживаемый стандарт языка, скачает необходимые библиотеки, проверит пути до файлов нашего проекта и многое другое. После завершения этапа конфигурирования CMake сгенерит директорию build, в которой будут хранится все артефакты сборки нашего проекта. Обрати внимание, что именно имя директории build мы указали после параметра -B. Директорию build не должна попасть под версионный контроль, поэтому обязательно добавь строчку с ней в файл .gitignore:
/build
Аналог команды cmake -B build: представь, что перед сборкой проекты ты проверил наличие компилятора, какую версию стандарта он поддерживает, а так же проверил пути до исходных файлов.
Сборка проекта Далее выполним команду на сборку проекта:
cmake --build build
После параметра --build указан путь до директории с артефактами сборки build.
На этом этап CMake выполняет сборку нашего проекта, т.е. это полный аналог команд
g++ -c src/libmath.cpp -o libmath.o -I./include
ar rvs libmath.a libmath.o
Да, теперь сборка библиотеки выполняется одной командой. Кроме того, тебе больше не нужно запоминать из каких файлов состоит библиотека, где лежат заголовочные файлы, тебе даже не нужно помнить, как называется библиотека! Вся эта информация “зашита” в файле CMakeLists.txt.
После окончания этого этапа мы получим файл библиотеки libmath.a, который лежит где-то в недрах директории build.
Установка проекта И наконец выполним команду на установку проекта:
sudo cmake --build build --target install
Эта команда полный аналог командам:
sudo cp libmath.a /usr/local/lib/
sudo cp include/libmath.h /usr/local/include/
Преимущества такие же, как и для команды для сборки проекта - тебе больше не нужно задумываться о директориях, в которых будет располагаться библиотека!
Обрати внимание на параметр --target после него выполняется конкретная цель для установки проекта install. В файле CMakeLists.txt могут быть и другие цели, с которыми ты познакомишься в следующем уроке.
Итак, чтобы собраться и установить библиотеку мы должны выполнить следующие команды:
cmake -B build
cmake --build build
sudo cmake --build build --target install
Забегая вперед, я удивлю тебя: эти команды используются для сборки и установки любого проекта на CMake! Ты один раз запоминаешь команды, а потом используешь их в любом проекте. Вот в этом и заключается вся мощь систем сборок!
Изучим CMakeLists.txt
Изучим построчно содержимое файла CMakeLists.txt.
Указываем минимальную версию CMake:
cmake_minimum_required(VERSION 3.10)
Функционал, а иногда и синтаксис CMake значительно отличается от версии к версии. Если у пользователя нашего проекта версия CMake будет старее, чем версия с который был разработан проект, при сборке у него могут возникнуть проблемы. Проверка версии предупредит подобные уже на этапе конфигурирования проекта.
Создаем новый проект:
project(libmath)
При выполнении этой команды CMake инициализирует внутренние переменные, которые он далее будет использовать при сборке проекта.
Создаем правило для сборки библиотеки:
add_library(math STATIC
src/libmath.cpp
)
Этой командой мы задаем имя библиотеки - math, далее указываем тип библиотеки, после чего перечисляем файлы, которые будут добавлены в библиотеку. Обрати внимание, что при добавлении в библиотеку новых файлов, ты должен обязательно добавить их в команду add_library. В этом уроке я продемонстрирую сборку статической библиотеки с использованием CMake. В качестве самостоятельно работы я рекомендую тебе собрать с помощью CMake динамическую библиотеку.
Указываем директорию, в которой компилятор будет искать заголовочные файлы:
target_include_directories(math
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
CMAKE_CURRENT_SOURCE_DIR - это одна из глобальных констант CMake. CMAKE_CURRENT_SOURCE_DIR хранит в себе полный путь до директории с CMake проектом.
Следующие две инструкции отвечают за установку библиотеки:
install(TARGETS math
ARCHIVE DESTINATION /usr/local/lib
)
install(FILES include/libmath.h
DESTINATION /usr/local/include
)
Эти инструкции выполняются при установке проекта с помощью команды:
sudo cmake --build build --target install
А теперь давай перейдем к сборке нашего проекта консольного калькулятора.
Сборка проекта с использованием библиотеки математических функций
Структура директории проекта калькулятора теперь выглядит следующим образом:
calculator/
├── src/ # Исходные файлы проекта
│ ├── app.cpp # Реализация бизнес-логики
│ ├── app.h # Заголовочный файл бизнес-логики
│ └── main.cpp # Точка входа в программу
├── .gitignore # Список файлов, игнорируемых Git
├── CMakeLists.txt # Конфигурационный файл CMake для сборки проекта
└── README.md # Описание проекта и инструкции по сборке
Давай сразу посмотрим содержимое минимально достаточного файла CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(calculator)
include(FetchContent)
FetchContent_Declare(
libmath
GIT_REPOSITORY https://github.com/your-repo/libmath.git
GIT_TAG main
)
FetchContent_MakeAvailable(libmath)
add_executable(calculator
src/app.cpp
src/main.cpp
)
target_link_libraries(calculator
PRIVATE
math
)
install(TARGETS calculator
RUNTIME DESTINATION /usr/local/bin
)
Давай разбираться, что здесь написано:) Но для начала я напомню последовательность сборки нашего проекта:
- Сначала мы собираем и устанавливаем библиотеку
math - И только потом мы собираем и устанавливаем
А теперь подумай, что произойдет, если пункт 1 не будет выполнен? Верно, проект калькулятора просто не соберется!
Да, можно написать инструкцию по сборке нашего проекта, но пользователи всегда будут забывать её читать:( Поэтом добавим немного автоматизации в процесс сборки нашего проекта. Знакомься: менеджер зависимостей FetchContent - помогает выкачивать, собирать и подключать зависимости к нашему проекту. Один нюанс: зависимости должны собираться с помощью CMake.
Разбираемся с новыми строчками в файле CMakeLists.txt:
Подключаем модуль FetchContent:
include(FetchContent)
Даём модулю инструкции, какую библиотеку и откуда нужно скачивать:
FetchContent_Declare(
libmath # название библиотеки
GIT_REPOSITORY https://github.com/your-repo/libmath.git # ssh-путь до репозитория
GIT_TAG main # ветка, которая будет скачена
)
Собираем скаченную библиотеку:
FetchContent_MakeAvailable(libmath)
Создаем цель для сборки исполняемого модуля с именем calculator:
add_executable(calculator
src/app.cpp
src/main.cpp
)
Подключаем к цели библиотеку math:
target_link_libraries(calculator
PRIVATE
math
)
Две последние инструкции являются аналогом команды для сборки и установки проекта:
g++ src/app.cpp src/main.cpp -lmath -o calculator
В конце файла - инструкция для установки исполняемого модуля в системные пути:
install(TARGETS calculator
RUNTIME DESTINATION /usr/local/bin
)
Эта инструкция является прямым аналогом ручной команды:
sudo cp calculator /usr/local/bin/
Для сборки и установки консольного калькулятора используются команды, и для сборки библиотеки математических функций:
cmake -B build
cmake --build build
sudo cmake --build build --target install
Удобно, не правда ли?
На этом, пожалуй, всё, что тебе нужно знать о CMake для успешного старта в профессии C++ разработчика. До встречи на следующем уроке!