Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров2.4K

Всем привет! На написание данной статьи меня толкнула суровая реальность и лень разбираться с английскими текстами о том, что и куда жмать, дабы собрать адекватный проект на 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

Теги:
Хабы:
+8
Комментарии5

Публикации

Работа

Программист С
39 вакансий
Программист C++
98 вакансий
QT разработчик
7 вакансий

Ближайшие события