Всем привет! На написание данной статьи меня толкнула суровая реальность и лень разбираться с английскими текстами о том, что и куда жмать, дабы собрать адекватный проект на 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)
Итак, у нас готов скрипт для сборки нашего первого проекта, рассмотрим же подходы к сборке:
собрать все в корневой директории вместе с исходниками {фатальная ошибка};
собрать все в отдельно созданной директории для сборки проекта.
Минусы первого подхода достаточно хорошо объясняют, почему так лучше не делать:
трудности с сохранением разных сборок проекта
захламление корневой директории
Последний шаг: Сборка
Ну а теперь нам нужно залезть в любимый терминал или командную строку. Переходим в корень проекта.
Если просто написать:
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