Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров4.8K
Всем привет! На написание данной статьи меня толкнула суровая реальность и лень разбираться с английскими текстами о том, что и куда жмать, дабы собрать адекватный проект на 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: 10 советов по улучшению скриптов сборки.
Терминология
- Файл
CMakeLists.txt
служит скриптом (рецептом, сценарием) сборки проекта. Обычно один такой файл собирает все исходники в своём каталоге и в подкаталогах, при этом подкаталоги могут содержать, а могут не содержать дочерние файлыCMakeLists.txt
. С точки зрения IDE, таких как CLion или Visual Studio, файлCMakeLists.txt
также служит проектом, с которым работает программист внутри IDE. - В cmake есть “цель” (“target”) — компонент, который следует собрать. Компонент может быть исполняемым файлом, так и статической либо динамической библиотекой.
- В cmake есть “проект” (“project”) — это набор компонентов, по смыслу похожий на Solution в Visual Studio.
- В cmake есть “флаги” (flags) — это аргументы командной строки для компилятора, компоновщика и других утилит, вызываемых при сборке.
- В cmake есть переменные, и в процессе интерпретации файла
CMakeLists.txt
система сборки cmake вычисляет ряд встроенных переменных для каждой цели, тем самым получая флаги. Затем cmake создаёт вторичный скрипт сборки, который будет напрямую вызывать компилятор, компоновщик и другие утилиты с вычисленными флагами.
Сборка проекта из командной строки (Linux)
На первом шаге проект нужно сконфигурировать, то есть создать финальный скрипт сборки, запустив cmake <параметры> <путь-к-каталогу>
в будущем каталоге сборки.
# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Создадим каталог `myapp-release` и перейдём в него.
mkdir --parents ../myapp-release
cd ../myapp-release
# Сконфигурируем проект для сборки в Release.
# Флаг установит опцию CMAKE_BUILD_TYPE в значение "Release",
# интерпретатор cmake считает это переключением на Release конфигурацию.
cmake -DCMAKE_BUILD_TYPE=Release ../myapp
На втором шаге нужно запустить финальный скрипт. Не вызывайте make
! Утилита cmake сделает это сама:
# Просим CMake запустить сборку в каталоге `myapp-release`
# Можно добавить флаг `--target mylibrary` для сборки только mylibrary
# Можно добавить флаг `--clean-first`, чтобы в начале новой сборки
# стирать остатки предыдущей.
cmake --build .
# Аналогичный способ для GNU/Linux. Его по привычке советуют в сети, хотя
# make доступен не всегда, а cmake --build работает на всех платформах.
make
Структура CMakeLists.txt
В начале главного файла CMakeLists.txt
ставят метаинформацию о минимальной версии CMake и названии проекта:
# Указывайте последнюю доступную вам версию CMake.
cmake_minimum_required(VERSION 3.8)
# Синтаксис: project(<имя> VERSION <версия> LANGUAGES CXX),
# теги VERSION и LANGUAGES необязательные.
project(libmodel3d)
Затем следует список инструкций, служащих для вычисления различных переменных, создания целей сборки,
подключения проектов из подкаталогов и так далее. Например, подключить дополнительный CMakeLists.txt
из подкаталога можно так:
# Простая версия: подключает скрипт по пути <подкаталог>/CMakeLists.txt
add_subdirectory(<подкаталог>)
# Расширенная версия: дополнительно установит подкаталог сборки подпроекта
add_subdirectory(<подкаталог> <подкаталог сборки>)
Целью может стать исполняемый файл, собираемый из исходного кода
# Синтаксис: add_executable(<имя> <список исходников...>)
# Добавлять `.h` необязательно, но лучше для работы из IDE:
# - IDE определит заголовок как часть проекта
# - cmake будет отслеживать изменения в заголовке и пересобирать
# проект при изменениях.
add_executable(pngconverter main.cpp PngReader.h PngReader.cpp)
Целью также может быть библиотека, статическая или динамическая.
# Синтаксис: add_library(<имя> [STATIC|SHARED|INTERFACE] <список исходников...>)
# Тип библиотеки (staic или shared) зависит от параметров сборки
add_library(libpngutils PngUtils.h PngUtils.cpp)
# Тип библиотеки: static
add_library(libpngtools STATIC PngTools.h PngTools.cpp)
Автогенерация проекта для Visual Studio (Windows)
Если используется Visual C++, то путь немного другой: на шаге конфигурирования создаётся проект для Visual Studio, который затем можно собрать из IDE либо так же из командной строки.
Созданный проект Visual Studio нельзя изменять и использовать постоянно, потому что при генерации проекта используются абсолютные пути и другие неприемлемые для постоянной работы вещи.
# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Сгенерируем проект Visual Studio для сборки.
mkdir --parents ../myapp-build
cd ../myapp-build
# Конфигурируем для сборки с Visual Studio 2017, версия тулчейна v140
cmake -G "Visual Studio 2017"
Если проект был сконфигурирован успешно, то в каталоге ../myapp-build
появятся автоматически сгенерированный BUILD_ALL.sln и проекты для Visual Studio. Их можно открыть к IDE, либо собрать из командной строки с помощью cmake. Названия опций говорят сами за себя:
cmake --build . \
--target myapp \
--config Release \
--clean-first
Зависимости между библиотеками и приложениями
Не используйте директивы include_directories
, add_definitions
, add_compile_options
!
Они меняют глобальные настройки для всех целей, это создаёт проблемы при масштабировании.
- Используйте target_link_libraries для добавления статических и динамических библиотек, от которых зависит цель
- Используйте target_include_directories вместо include_directories для добавления путей поиска заголовков, от которых зависит цель
- Используйте target_compile_definitions вместо add_definitions для добавления макросов, с которыми собирается цель
- Используйте target_compile_options для добавления специфичных флагов компилятора, с которыми собирается цель
Пример:
# Добавляем цель - статическую библиотеку
add_library(mylibrary STATIC \
ColorDialog.h ColorDialog.cpp \
ColorPanel.h ColorPanel.cpp)
# ! Осторожно - непереносимый код !
# Добавляем к цели путь поиска заголовков /usr/include/wx-3.0
# Лучше использовать find_package для получения пути к заголовкам.
target_include_directories(mylibrary /usr/include/wx-3.0)
Вы можете выбирать область видимости настройки:
PUBLIC
делает настройку видимой для текущей цели и для всех зависящих от неё целейPRIVATE
делает настройку видимой только для текущей целиINTERFACE
делает настройку видимой только для всех зависящих от неё целей
Пример использования областей видимости:
# Каталог include будет добавлен к путям поиска заголовков в текущей цели и во всех зависимых целях
target_include_directories(myTarget PUBLIC ./include)
# Каталог src будет добавлен к путям поиска заголовков только в текущей цели
target_include_directories(myTarget PUBLIC ./src)
Схема зависимостей условного проекта:
Выбор стандарта и диалекта C++
Для настройки стандарта и флагов языка C++ не добавляйте флаги напрямую!
# ! Устаревший метод - прямое указание флага !
target_compile_options(hello PRIVATE -std=c++11)
В CMake версии 3.8+ вы можете прямо потребовать включить нужный стандарт:
# Источник: https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html
# Включаем C++ 2017
target_compile_features(myapp cxx_std_17)
# Альтернатива: включаем C++ 2014
target_compile_features(myapp cxx_std_14)
# Альтернатива: включаем C++ 2011
target_compile_features(myapp cxx_std_11)
В CMake версии до 3.7 включительно можно использовать set_target_properties
(если не работает, то у вас слишком старый CMake):
# Стандарт: C++ 2014, расширения языка от производителя компилятора отключены
set_target_properties(myapp PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
Для разработчиков библиотек есть более тонкий контроль над возможностями языка:
# API библиотеки (т.е. заголовки) требуют лямбда-функций и override,
# реализация библиотеки требует ещё и range-based for.
target_compile_features(mylibrary PUBLIC cxx_override cxx_lambdas PRIVATE cxx_range_for)
Функции в CMake
CMake позволяет объявлять функции командами function(name) / endfunction()
и макросы командами macro(name) / endmacro()
. Предпочитайте функции, а не макросы, т.к. у функций есть своя область видимости переменных, а у макросов — нет.
function(hello_get_something var_name)
...
# Установить переменную в области видимости вызывающей стороны
# можно с помощью тега PARENT_SCOPE
set(${var_name} ${ret} PARENT_SCOPE)
endfunction()
Добавление исходников к цели с target_sources
Лучше добавлять специфичные исходники с помощью target_sources, а не с помощью дополнительных переменных.
add_library(hello hello.cxx)
if(WIN32)
target_sources(hello PRIVATE system_win.cxx)
elseif(UNIX)
target_sources(hello PRIVATE system_posix.cxx)
else()
target_sources(hello PRIVATE system_generic.cxx)
endif()
Интерфейс к утилитам командной строки
Подробнее см. Command-Line Tool Mode
# Создать каталог debug-build
cmake -E make_directory debug-build
# Перейти в каталог debug-build
cmake -E chdir debug-build
Функция find_package
Функция find_package принимает имя библиотеки как аргумент и обращается к CMake, чтобы найти скрипт для настройки переменных данной библиотеки. В итоге при сборке либо возникает ошибка из-за того что пакет не найден, либо добавляются переменные, хранящие пути поиска заголовков, имена библиотек для компоновщика и другие параметры.
Пример подключения Boost, вызывающего встроенный в CMake скрипт FindBoost:
# Весь Boost без указания конкретных компонентов
find_package(Boost REQUIRED)
# Теперь доступны переменные
# - Boost_INCLUDE_DIRS: пути к заголовочным файлам
# - Boost_LIBRARY_DIRS: пути к статическим/динамическим библиотекам
# - Boost_LIBRARIES: список библиотек для компоновщика
# - Boost_<C>_LIBRARY: библиотека для компоновки с компонентом <C> библиотек Boost
Пример подключения библиотеки Bullet с помощью встроенного скрипта FindBullet и компоновки с приложением my_app:
# Вызываем встроенный скрипт FindBullet.cmake
find_package(Bullet REQUIRED)
# Добавляем пути поиска заголовков к цели my_app
target_include_directories(my_app ${BULLET_INCLUDE_DIRS})
# Добавляем список библиотек для компоновки с целью my_app
target_link_libraries(my_app ${BULLET_LIBRARIES})
In this article, you’ll learn how to create a CMake hello world project from scratch using the CMake Tools extension in VS Code.
If you have an existing CMake project that already has a CMakeLists.txt
file in the root directory but no CMake presets, you can skip to Create a CMakePresets.json
file to configure your project with CMake presets.
Otherwise, create a folder for a new project. From the Terminal window, create an empty folder called HelloWorld
, navigate into it, and open VS Code in that folder by entering the following commands:
mkdir helloworld
cd helloworld
code .
The code .
command opens VS Code in the current working folder, which becomes your «workspace».
Create a CMakeLists.txt file
The CMake Tools extension can create the files for a basic CMake project for you.
-
Open the Command Palette (⇧⌘P (Windows, Linux Ctrl+Shift+P)) and run the CMake: Quick Start command:
-
Enter a project name, and select C++ as the project language.
This information will be written to
CMakeLists.txt
and a few initial source files.Note: If you had other source code files in this folder that you wanted to add as targets to the
CmakeLists.txt
, an option to add these would now be given. But for this tutorial, we will stick with just the hello world file. -
Select CTest as an additional option to add support for testing. You can also select CPack for CPack support.
-
Next, select Executable as the project type to create a basic source file (
main.cpp
) that includes a basicmain()
function.Note: If you had wanted to create a basic source and header file, you would have selected Library instead. But for this tutorial, Executable will do. If you are prompted to configure IntelliSense for the folder, select Allow.
This successfully creates the CMakeLists.txt
file, which tells the CMake tools how to build your project.
Create a CMakePresets.json file
Next, continue with the CMake Quick Start to create a CMakePresets.json
file.
-
Select Add a New Preset and Create from Compilers.
The extension automatically scans for kits on your computer and creates a list of compilers found on your system.
-
Select the compiler you want to use.
For example, depending on the compilers you have installed, you might see something like this:
-
Enter a name for this new preset.
The name for the preset will be written to
CMakePresets.json
.
After completing these steps, you should now have a complete hello world CMake project that contains the following files: main.cpp
, CMakeLists.txt
, and CMakePresets.json
.
5/29/2024
Некоторое время назад мы познакомились с Autotools. Несмотря на то, что Autotools до сих пор используется во многих известных проектах с открытым исходным кодом, инструмент этот трудно назвать особо удобным. Кроме того, нормально работает он только в *nix системах, а в каком-нибудь Windows пользоваться Autotools, скажем так, весьма непросто. В общем, Autotools — это легаси, и нормальные программисты в наше время пытаются использовать CMake или, например, SCons. В этой заметке мы познакомимся с CMake.
Говоря простыми словами, CMake — это такая штука, в которой вы описываете проект, а она вам генерирует Makefile’ы в *nix системах, проекты Visual Studio под Windows, файлы конкретных редакторов и IDE, например Sublime Text, Code::Blocks, Eclipse или KDevelop, и так далее. Несмотря на спорный в некоторых моментах синтаксис, в последнее время CMake становится стандартом де-факто в мире C/C++. В частности, CMake используется в LLVM, Qt, MariaDB, Blender, KiCad, GNU Radio и ряде других проектов. Кроме того, в CLion, IDE для C/C++ от компании JetBrains, по умолчанию также создаются проекты, основанные на CMake.
Примечание: В этом контексте вас может заинтересовать заметка Как править в CLion код любых проектов на С++, даже тех, в которых не используется CMake.
Использование CMake в простейшем случае выглядит следующим образом. В корне репозитория создается файл CMakeLists.txt примерно такого содержания:
cmake_minimum_required(VERSION 3.1)
# так пишутся комментарии
project(project_name)
find_library(PTHREAD_LIBRARY pthread)
find_library(PCRE_LIBRARY pcre)
include_directories(include)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_FLAGS «${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror»)
add_executable(main src/Main.cpp src/HttpServer.cpp)
target_link_libraries(main ${PTHREAD_LIBRARY} ${PCRE_LIBRARY})
Хочется надеяться, какая строчка здесь что означает, пояснять не нужно. Затем исходники складываются в каталог src, а заголовочные файлы — в каталог include. Для сборки проекта говорим:
mkdir build
cd build
cmake ..
make
Просто, не правда ли?
Помимо приведенного выше find_library
в CMake есть ряд скриптов для подключения конкретных библиотек. В частности, подключение OpenGL осуществляется как-то так:
find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR})
target_link_libraries(main ${OPENGL_LIBRARY} ${CMAKE_DL_LIBS})
CMake можно указать конкретный тип Makefile’ов, которые вы хотите получить на выходе:
cmake -G «Unix Makefiles» ..
cmake -G «MinGW Makefiles» ..
# для просмотра списка всех доступных генераторов:
cmake -G
В частности, многие программисты для ускорения сборки проектов предпочитают использовать Ninja:
cmake -G Ninja ..
ninja -j1
Выбор между отладочной и релизной сборкой осуществляется так:
cmake -DCMAKE_BUILD_TYPE=Release -G Ninja ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja ..
cmake -DCMAKE_BUILD_TYPE=MinSizeRel -G Ninja ..
# Debug используется по умолчанию
cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja ..
Вместо запуска напрямую make или ninja можно сказать что-то вроде:
cmake —build . —config Release —target main
Можно выбрать конкретный компилятор для сборки проекта
cmake -DCMAKE_C_COMPILER=`which clang` \
-DCMAKE_CXX_COMPILER=`which clang++` -G Ninja ..
… а также указать дополнительные флаги компиляции:
cmake -DCMAKE_C_FLAGS=«-O0 -g» -DCMAKE_CXX_FLAGS=«-O0 -g» ..
Например, для определения степени покрытия кода тестами при помощи lcov нужно сказать что-то вроде:
cmake -DCMAKE_C_FLAGS=«-O0 -g -fprofile-arcs -ftest-coverage» \
-DCMAKE_EXE_LINKER_FLAGS=«-lgcov» ..
В мире C/C++ нередко бывает, что сторонние библиотеки, использующие CMake, подключаются к проекту при помощи сабмодулей Git. Подключение таких библиотек к проекту осуществляется довольно просто:
cmake_minimum_required(VERSION 2.8)
project(c-algorithms-examples)
include_directories(deps/algorithms/include)
add_subdirectory(deps/algorithms/src)
add_executable(rbtree_example rbtree_example.c)
target_link_libraries(rbtree_example CAlgorithms)
В свою очередь, у библиотеки файл src/CMakeList.txt должен быть примерно таким:
cmake_minimum_required(VERSION 2.8)
project(c-algorithms)
add_library(CAlgorithms STATIC
struct/ilist.c
struct/rbtree.c
struct/htable.c
common/utils.c
)
Вообще, add_subdirectory может принимать путь до любого каталога, в котором есть файл CMakeLists.txt. Это позволяет разбивать проект на подпроекты даже в рамках одного репозитория. Опять же, в случае с библиотеками это позволяет поместить тесты в отдельный подпроект, который не будет собираться при подключении библиотеки в сторонние проекты.
Например, в корне библиотеки CMakeList.txt может быть таким:
cmake_minimum_required(VERSION 2.8)
project(c-algorithms-root)
enable_testing()
include_directories(include)
add_subdirectory(src)
add_subdirectory(test)
Непосредственно тесты добавляются в проект следующим образом:
cmake_minimum_required(VERSION 2.8)
project(c-algorithms-struct-tests)
set(CMAKE_C_FLAGS «${CMAKE_C_FLAGS} -O0 -g»)
add_executable(test_htable test_htable.c)
target_link_libraries(test_htable CAlgorithms)
add_executable(test_rbtree test_rbtree.c)
target_link_libraries(test_rbtree CAlgorithms)
add_test(test_htable «./test_htable»)
add_test(test_rbtree «./test_rbtree»)
Запуск тестов осуществляется простой командой:
make test
# или, с включением отладочного вывода:
make test ARGS=«-V»
# или, если используете Ninja:
ninja test
… выполненной в каталоге build. Если вас интересует тема написания модульных тестов на C++, она более подробно раскрыта в заметке Тестирование кода на C++ с помощью Google Test.
Если же вы используете какой-нибудь PyTest, просто допишите в CMakeList.txt что-то вроде:
find_package(PythonInterp REQUIRED)
enable_testing()
add_test(NAME python_test
COMMAND py.test —capture=no ${CMAKE_SOURCE_DIR}/tests/run.py)
Вывод тестов пишется в файл Testing/Temporary/LastTest.log. Кстати, подробности о переменных окружения, доступных в CMake, таких, как CMAKE_SOURCE_DIR, можно найти здесь.
Помимо рассмотренных выше возможностей часто можно встретить поддержку сборки проектов с различными опциями. В частности, это используется в Assimp и LLDB. При сборке проекта опции выбираются так:
cmake -DLLDB_DISABLE_CURSES:BOOL=TRUE …
cmake -DASSIMP_BUILD_ASSIMP_TOOLS=OFF …
Опции обычно описывают в документации, но в крайнем случае их можно посмотреть и через curses-интерфейс:
В рамках одного поста, конечно, не представляется возможным рассмотреть все возможности CMake. Однако представленной выше информации вам должно вполне хватить в 90% случаев. Полноценные рабочие примеры использования CMake вы найдете, например, в этом, этом, а также в этом репозиториях на GitHub. Примеры использования опций и условных операторов можно найти в репозиториях уже упомянутых Assimp и LLDB. Ну и, конечно же, массу полезного вы найдете на официальном сайте CMake.
А пользуетесь ли вы CMake и если да, используете ли какие-то его возможности, о которых не было рассказано выше?
Метки: C/C++, Кроссплатформенность.
Эта статья находится в разработке!
Содержание
- 1 Что это и зачем нужно
- 1.1 Краткое описание
- 2 Старт
- 3 Подробное описание
- 3.1 Указание необходимой версии cmake
- 3.2 Название проекта
- 3.3 Переменные
- 3.4 Устанавливаем команды компилятору
- 3.5 Папка с хедерами
- 3.6 Самое важное — подключение библиотек
- 3.7 Пример хорошего CMakeLists.txt и где он будет лежать
- 3.8 Как создать библиотеку в поддиректории и слинковать ее с основной программой
- 4 Как использовать CMake в связке с QtCreator
- 4.1 Как добавить header в проект, чтобы его было видно в списке файлов
Что это и зачем нужно
CMake — кроссплатформенная автоматизированная система сборки проектов.
Непосредственно сборкой она не занимается, а только генерирует Makefile, который потом будет выполнен утилитой make.
CMake может проверять наличие необходимых библиотек и подключать их, собирать проекты под разными компиляторами и операционными системами. Т.е. у вас есть куча кода и файлик, содержащий информацию для cmake, и чтобы скомпилить
это дело где-нибудь еще, вам нужно просто запустить там cmake, который сделает всё сам. Удобно, полезно, просто.
Краткое описание
Если нет желания/времени/сил читать весь туториал и Вы используете какой-нибудь QtCreator (или любая другая IDE, умеющая работать с cmake), то:
- Создайте в IDE проект под cmake
- Найдите в папке с проектом CMakeFiles.txt
- Пробегитесь глазами по туториалу, соотнося его с вашим CMakeFiles.txt
Про подключение библиотек рекомендуется все-таки прочитать целиком.
Старт
Предполагается, что найти и скачать сам cmake ты, %username%, в состоянии. //а если нет?
Предположим, у Вас есть исходничек «test.cpp» (// а если нет?)(А если нет, то CMake тебе трогать рано).
Для начала нужно создать файлик для cmake, который обычно называют «CMakeLists.txt», и написать туда вот это:
add_executable(test test.cpp)
Теперь запускаем (из консоли) в этой папке команду «cmake CMakeLists.txt» (аргументом можно передавать не только файл, но и директорию, в которой он лежит, тогда cmake найдет его сам).
cmake будет использовать переданный (или найденный) файл проекта (тот самый CMakeLists.txt), и в текущей директории будет создавать проект.
Проект — это много-много файлов и директорий (примечание: поэтому лучше запускать cmake из другой директории, чтобы можно было, например, быстро удалить все бинарники), из которых нас больше всего интересует Makefile.
Makefile — это файл, нужный для утилиты make.
Именно она запускает компиляторы, линковщики и прочие радости. Запускаем make в каталоге сборки (т.е. там же, где Вы запускали cmake).
В консоли вылезет примерно такой текст:
Scanning dependencies of target test [100%] Building CXX object CMakeFiles/test.dir/test.cpp.o Linking CXX executable test [100%] Built target test
А у Вас в папочке появится исполняемый файл «test». Запустите, убедитесь, что это действительно то, что ожидается от компиляции файла «test.cpp».
Подробное описание
Поразбираемся с различными возможностями cmake.
Указание необходимой версии cmake
cmake_minimum_required(VERSION 2.6)
Указывайте высокую минимальную версию CMake.
Если используемая версия cmake меньше 2.6, он не захочет работать. Писать эту команду всегда — хороший стиль (cmake будет пыхтеть и обижаться, если вы не укажете версию, но собирать всё равно всё будет).
Название проекта
project(visualization)
Указывает, что этот cmake-файл является корневым для некоторого проекта. С проектами связаны определенные переменные и поведение cmake (читайте документацию).
Переменные
В cmake можно создавать текстовые переменные. Команда
set(VARIABLE The variable's value)
запишет в переменную «VARIABLE» значение «The variable’s value».
Чтобы где-либо использовать значение этой переменной, нужно написать ${VARIABLE}.
Чтобы добавить к переменной некий текст, можно сделать так:
set(VARIABLE "${VARIABLE} new text")
Как видите, использовать значение можно и внутри кавычек.
Переменные активно используются различными библиотеками — для установки флагов, параметров сборки/линковки и прочих вкусностей, об этом чуть-чуть попозже.
Пример коше’гного проекта со списком сорцов в отдельной переменной:
cmake_minimum_required(VERSION 2.6) set(SOURCES test.cpp lib1.cpp lib2.cpp) add_executable(test ${SOURCES})
Устанавливаем команды компилятору
add_definitions(-DSOME_IMPORTANT_DEFINITION)
Эта команда используется для установки дефайнов, которыe можно проверить в коде через, например, #ifdef SOME_IMPORTANT_DEFINITION.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
Эта команда добавит к флагам, используемым при сборке c++-кода, флаги -std=c++11 и -Wall.
Кто не знает: «-std=c++11» включает в gcc поддержку стандарта c++11, «-Wall» говорит gcc выводить все предупреждения (очень советую, помогает отловить много глупых багов и писать аккуратный код).
Если ваша версия GCC меньше, чем 4.7.0, вместо -std=c++11 нужно использовать -std=c++0x.
В GCC 4.8.0 появился флаг -std=c++1y, в котором начинают реализовывать фичи следующего стандарта.
Папка с хедерами
Допустим, Вы хотите, чтобы хедеры (файлики, подключаемые через #include) искались еще и в каталогах «headers/» и «more_headers/»:
include_directories("headers/" "more_headers/")
Надеюсь, и это понятно.
Самое важное — подключение библиотек
Научимся искать и подключать библиотеки при помощи cmake на примере Boost.
Для начала установим переменные для буста:
set(Boost_USE_STATIC_LIBS OFF) set(Boost_USE_MULTITHREADED ON)
Первое — мы не хотим, чтобы буст подключался к нам статически (т.е. хотим динамическую линковку). Если ты, %username%, не знаешь, что это, пока просто забей и используй этот флаг так, как написано. Но в ближайшее время узнай, о чем речь.
Второй флаг разрешает бусту внутри своих магических реализаций использовать треды для распараллеливания и прочих радостей.
Итак, мы установили флаги. Давайте найдем буст!
Допустим, нам нужны компоненты буста под названием chrono (библиотека для работы со временем) и filesystem (библиотека для работы с файловой системой):
find_package(Boost COMPONENTS chrono filesystem REQUIRED)
Win, будут искаться только нужные библиотеки, и их расположение будет записано в переменную Boost_LIBRARIES.
Опция «REQUIRED» говорит о том, что библиотека необходима проекту.
Без нее cmake решит, что отсутствие данной библиотеки — не так уж и страшно, и будет собирать дальше.
Добавим директории с хедерами буста для поиска в них хедеров:
include_directories(${Boost_INCLUDE_DIRS})
Итак, осталось найденные библиотеки подключить к исполняемому файлу.
target_link_libraries(test ${Boost_LIBRARIES})
В качестве библиотек нужно указать пути к необходимым собранным библиотекам. cmake нашел указанные нами библиотеки и записал в переменную, чем мы и пользуемся.
Заметим, что эту команду нужно вызывать после того, как создан target сборки (через add_executable).
Пример хорошего CMakeLists.txt и где он будет лежать
Итак, полный пример использования всего этого. У нас есть некая директория (отныне считаем ее «/sources»), и в ней лежат исходники
/sources/lib1/main.cpp /sources/lib2/main.cpp /sources/main.cpp
В корне «/» лежит файл «/CMakeLists.txt»:
cmake_minimum_required(VERSION 2.8) project(cmake-example) set(Boost_USE_STATIC_LIBS OFF) set(Boost_USE_MULTITHREADED ON) find_package(Boost COMPONENTS chrono filesystem REQUIRED) set(CMAKE_CXX_FLAGS "$\$${CMAKE_CXX_FLAGS} -std=c++11 -Wall") set(SRC_LIST lib1/main.cpp lib2/main.cpp main.cpp) add_executable($\$${PROJECT_NAME} $\$${SRC_LIST}) target_link_libraries($\$${PROJECT_NAME} $\$${Boost_LIBRARIES})
Если Вам что-то в нём не понятно — перечитайте соответствующую информацию выше.
Создаем директорию «/build» (не «/sources/build»), переходим в нее, запускаем в ней «cmake ..».
«..» — метка родительской директории.
cmake возьмет из нее наш CMakeLists.txt и по нему создаст проект в папке «/build».
Чтобы проект собрать, запускаем «make» в той же папке «/build».
Таким образом, в корне у нас есть:
- CMakeLists.txt
- директория с исходниками
- каталог сборки
Все разделено, автоматизировано и удобно.
Как создать библиотеку в поддиректории и слинковать ее с основной программой
Пусть в ./ лежит основной проект, а в ./subdir мы хотим сделать либу, а в ./build построить проект.
./subdir/CMakeLists.txt
project(MegaLibrary) set(SOURCES lib.cpp) set(HEADERS lib.h) add_library(lib $\$${SOURCES} $\$${HEADERS}) target_include_directories(lib PUBLIC $\$${CMAKE_CURRENT_SOURCE_DIR})
./CMakeLists.txt
project(MainProject) set(MAIN_PROJECT_SRC_LIST main) # Other stuff
add_executable(main $\$${MAIN_PROJECT_SRC_LIST}) add_subdirectory(subdir) target_link_libraries(main lib)
Теперь можно в файлах основного проекта делать #include «lib.h» (см. документацию по target_include_directories).
В ./build запускаем «cmake .. && make» и получаем собранный проект.
Как использовать CMake в связке с QtCreator
Интеграция с cmake у QtCreator не очень тесная, тем не менее, работать с ним можно.
Создаем новый проект без использования Qt, выбираем «Проект на С++ с использованием CMake». Создастся дефолтный файл сборки, который просто добавляет все исходники в директории проекта и компилирует их в один бинарник.
Если вы создали файл header.h в директорию проекта, просто строчку
add_executable($\$${PROJECT_NAME} $\$${SRC_LIST})
измените на
add_executable($\$${PROJECT_NAME} $\$${SRC_LIST} "header.h")