Сборка на CMake для новичка

от автора

Всем привет! На написание данной статьи меня толкнула суровая реальность и лень разбираться с английскими текстами о том, что и куда жмать, дабы собрать адекватный проект на CMake.

Установка CMake тут не рассматривается, вот ссылки:

офф сайт

гайд по установке

Все будет происходить из-под Windows, структурно мало чем будет отличаться от Linux, кроме команд для сборки.

Для начала предлагаю разобраться со структурой проекта в целом:

|Проект №1 | |CMakeLists.txt #этот лист находится в корне и описывает все группы проектов и папок  | |--подпроект №1 |----CMakeLists.txt #листы внутри проекта описывают,  |----*.cpp         #то как этот проект должен собраться |----*.h   | |--подроект №2 |----CMakeLists.txt  |----*.cpp |----*.h   | |... (еще кучка проектов) | |--include  #эта папка содержит те хеддеры библиотек,  |----*.h#которые мы в дальнейшем будем использовать для проектов | |--build  #как устроена эта папка, может решаться по разному             #хранит скомпилированные фалы (exe lib dll)             #также эта папка хранит кучу сгенерированных файлов CMake

Надеюсь, прочитав данную статью, вы сможете собрать проекты такого формата, но для начала возьмем что-то попроще (старый добрый «Hello World!»).

//hello.cpp #include <iostream>  int main() { std::cout << "Hello world from cmake!"<< std::endl; return 0; }

Структура проекта:

|CMakeLists.txt | |--hello_world_dir |----hello.cpp | |--build

CMakeLists.txt — это файлы (скрипты) конфигурации проекта, благодаря им мы можем собирать наших проекты. Сейчас мы разберем его простейшую структуру и разберем подходы к сборке проекта.

cmake_minimum_required(VERSION %тут могла быть ваша цифра%) #показывает какой минимальной версией можно собрать проект  project(hello_world) #дает имя проекту  #дает понять что будет собираться исполняеый файл(exe) #первый аргумент - это название собираемоего проекта, остальные исходные файлы *.h *.cpp #все аргументы записываются через пробел add_executable(hello_world hello_world/hello.cpp) 

Итак, у нас готов скрипт для сборки нашего первого проекта, рассмотрим же подходы к сборке:

  1. собрать все в корневой директории вместе с исходниками {фатальная ошибка};

  2. собрать все в отдельно созданной директории для сборки проекта.

Минусы первого подхода достаточно хорошо объясняют, почему так лучше не делать:

  • трудности с сохранением разных сборок проекта

  • захламление корневой директории

Последний шаг: Сборка

Ну а теперь нам нужно залезть в любимый терминал или командную строку. Переходим в корень проекта.

Если просто написать:

cmake

То нам скажут как инициализировать проект или пересобрать его, ну и про --help упомянут, для получения большей информации

Usage    cmake [options] <path-to-source>   cmake [options] <path-to-existing-build>   cmake [options] -S <path-to-source> -B <path-to-build>  Specify a source directory to (re-)generate a build system for it in the current working directory.  Specify an existing build directory to re-generate its build system.  Run 'cmake --help' for more information.

Я предлагаю использовать вариант cmake [options] -S <path-to-source> -B <path-to-build. Ключ -S устанавливает директорию с исходниками, обязательно содержащую файл CMakeLists.txt
Ключ -B устанавливает директорию куда будет собираться наш проект. В этой директории будут служебные файлы CMakeCashe и файлы *.sln для с проектов в VisualStudio, а также директории подпроектов (в Linux данная директория содержит MakeFile)

cmake -S . -B build

Теперь, можно уже и скомпилировать, пишем:

cmake --build <папка содержащая CMakeCache.txt>

В нашем случае данной папкой является build.

cmake --build build

Поздравляю, Вы собрали свой первый проект!

Для такого простого проекта кажется, что все это сплошное нагромождение, поэтому переходим к более сложному проекту.

Проект будет состоять из нашей самописной библиотеки и проекта, который использует эту библиотеку. (весь код будет на github)

Структура проекта:

| |CMakeLists.txt #основной скрипт сборки, в котором объявляются стандарты языка,  |               #места хранения скомпилированных файлов, подключение общих зависимостей |               #подключаются каталоги подпроектов,  | #подкпроект библиотеки |--our_lib  |----CMakeLists.txt #скрипт сборки подпроекта  |----*.cpp   |----*.h | #подпроект программы использующей библиотеку |--samples |----CMakeLists.txt  |----*.cpp |----*.h   | #директория с хедарами нашей библиотеки |--include    |----*.h | |--build 

Рассмотрим CMakeLists в корне

cmake_minimum_required(VERSION %Ваша цифра%)  set(PROJECT_NAME <имя проекта>) project(${PROJECT_NAME})  #настраиваем стандарты языка set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)  #настраиваем места хранения скомпилированных файлов set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE})  #даем названия подпроектам  set(PROG_LIBRARY "${PROJECT_NAME}")   #сораняем путь до директории include нашими хедарами  set(PROG_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/include")  #добавляет путь до нашей директории в область видимости всех проектов include_directories(${PROG_INCLUDE})  #добавляем директории наших подпроектов в область видимости CMake  add_subdirectory(src) add_subdirectory(samples)

Команды cmake_minimum_required и project нам уже знакомы. Многочисленная set(<имя>, <значение>) — создает переменные и объявляет их значения. Благодаря переменным, получается более модифицируемые и универсальные скрипты сборки, также, изменяя значения служебных переменных самого CMake, проект обретает определенные свойства, например: используемый стандарт языка, места хранения скомпилированных файлов и т.д. Перечислять смысл каждой не вижу, так как большинство из них имеют говорящие названия.

${имя_переменной} — такая конструкция позволяет использовать значение переменных.

include_directories(список путей) — добавляет директории в область поиска хедеров в проекте.

add_subdirectory(путь до директории) — добавляет в проект поддиректорию в которой обязательно должен быть файл CMakeLists.txt. В нашем случае это папки с проектом библиотеки и проектом, ее использующим.

Стоит заметить, что все переменные, объявленные в скрипте вызывающем команду, доступны для поддиректорий.

Скрипты сборки в подпроектах нам в целом уже знакомы, для начала рассмотрим библиотечный (/src/CMakeLists.txt).

Пользуясь опытом Hello World, можно написать этот скрипт так:

add_library(${PROG_LIBRARY} STATIC <список всех исходников>)

add_library(<имя> <тип библиотеки> <список исходников>) — команда аналогичнаяadd_executable, только для библиотек. (STATIC — статические библиотеки, SHARED — динамические).

Да, проект соберется, но постоянно прописывать весь список исходников руками то еще удовольствие, поэтому немного модифицируем скрипт:

set(target ${PROG_LIBRARY})  file(GLOB hdrs "*.h")      #команда для работы с файлами file(GLOB srcs "*.cpp")  add_library(${target} STATIC ${srcs} ${hdrs} )

file() — команда для работы с файлами, у нее есть множество возможных параметров позволяющих: читать, редактировать, находить,создавать, удалять, загружать …
file(GLOB <имя переменной> <регулярное выражение>) — ищет файлы в текущей директории подходящие регулярному выражению и сохраняет результат в переменную.

Таким образом, скрипт сам ищет исходные файлы (хедеры и cpp) и в add_library мы просто используем значения сохраненные в переменных.

В Cmake подпроекты называются target (таргетами — целями), поэтому я добавил одноименную переменную для удобства.

Скрипт проекта, использующего библиотеку отличается одной строчкой.

set(target sample)  file(GLOB hdrs "*.h") file(GLOB srcs "*.cpp")  add_library(${target} STATIC ${srcs} ${hdrs} ) target_link_libraries(${target} ${PROG_LIIBRARY})

target_link_libraries(<имя таргета> <список подключаемых библиотек>) — подключает библиотеки к выбранному таргету.

Ну и теперь стандартная процедура инициализации и сборки. (не забудьте переместиться в корень или указать правильные пути)

cmake -S . -B build
cmake --build build

Ура, более сложный проект создан и собран!Но, а что если возникли какие-то проблемы во время компиляции отдельного подпроекта(таргета), можно как-то обойтись без сборки всего проекта?

МОЖНО!

cmake --build -t <имя таргета>

Надеюсь, данная статья дала хорошее начало в изучении сборки на CMake.

Как закрепление материала предлагаю подключить вам юнит тесты для нашей самописной библиотеки (гугл тесты). Данную библиотеку можно скомпилировать самому, как отдельную часть проекта, а можно воспользоваться официальным гайдом, чтобы загрузить код непосредственно репозитория (более сложный вариант, дополнительные ссылки будут прикреплены ниже)

Ссылки:
cmake
https://cmake.org/cmake/help/latest/command/add_executable.html
https://cmake.org/cmake/help/latest/command/add_library.html
https://cmake.org/cmake/help/latest/command/target_link_libraries.html
https://cmake.org/cmake/help/latest/command/set.html
https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html
https://cmake.org/cmake/help/latest/command/add_subdirectory.html
https://cmake.org/cmake/help/latest/command/include_directories.html
https://cmake.org/cmake/help/latest/command/file.html

google tests
https://github.com/google/googletest
https://google.github.io/googletest/quickstart-cmake.html
https://cmake.org/cmake/help/latest/command/include.html
https://cmake.org/cmake/help/latest/module/FetchContent.html


ссылка на оригинал статьи https://habr.com/ru/articles/904992/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *