Теоретические сведения по C++ для студентов курса “Программирование на основе классов и шабллонов” кафедры ИУ5 МГТУ им. Н.Э. Баумана.
Разработка программного обеспечения требует множества инструментов: компиляторов, сборщиков, тестовых фреймворков и систем упаковки. Кроссплатформенные проекты усложняют этот процесс, так как не всегда можно использовать интегрированные среды разработки вроде Xcode или Visual Studio.
CMake облегчает работу, объединяя настройку, сборку, тестирование и упаковку в едином инструменте. Он генерирует файлы проекта для выбранной платформы и инструмента сборки, а встроенные CTest и CPack позволяют автоматизировать тестирование и создание пакетов. Все этапы управления сборкой можно выполнять напрямую через CMake.
Перед использованием следует установить актуальную версию CMake — многие платформы поставляют устаревшие версии через менеджеры пакетов.
CMake — это генератор систем сборки, а не компилятор.
CMakeLists.txt
↓ (configure)
CMake
↓ (generate)
Makefiles / Ninja / Visual Studio / Xcode
↓
Компилятор (gcc / clang / MSVC)
CMake позволяет описать проект один раз и собирать его на разных платформах.
cmake .
make
bad
cmake -S . -B build
cmake --build build
ok
Преимущества out-of-source:
command(ARG1 ARG2 ...)
Особенности:
set(MY_VAR "hello")
message(STATUS "${MY_VAR}")
Проверка:
if(DEFINED MY_VAR)
endif()
set(SOURCES a.cpp b.cpp c.cpp)
add_executable(app ${SOURCES})
Target — это логическая единица сборки (цель):
add_executable(app main.cpp)
add_library(mylib src.cpp)
target_include_directories(mylib
PUBLIC include
PRIVATE src
)
| Ключ | Назначение |
|---|---|
| PRIVATE | только цель |
| PUBLIC | цель + потребители |
| INTERFACE | только потребители |
set(CMAKE_CXX_STANDARD 20)
bad
target_compile_features(app PRIVATE cxx_std_20)
ok
add_library(static_lib STATIC src.cpp)
add_library(shared_lib SHARED src.cpp)
add_library(header_only INTERFACE)
target_link_libraries(app PRIVATE mylib)
CMake автоматически передаёт include-директории, флаги и определения.
project/
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ └── main.cpp
├── include/
├── tests/
│ └── CMakeLists.txt
Корневой CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
add_subdirectory(src)
add_subdirectory(tests)
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
Использование:
target_link_libraries(app PRIVATE fmt::fmt)
enable_testing()
add_executable(test1 test.cpp)
add_test(NAME unit COMMAND test1)
Запуск:
ctest --test-dir build
option(ENABLE_WARNINGS "Enable warnings" ON)
if(ENABLE_WARNINGS)
target_compile_options(app PRIVATE -Wall -Wextra)
endif()
install(TARGETS mylib EXPORT MyLibTargets)
install(DIRECTORY include/ DESTINATION include)
{
"version": 3,
"configurePresets": [
{
"name": "debug",
"generator": "Ninja",
"binaryDir": "build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
cmake_minimum_required(VERSION 3.20)
project(App LANGUAGES CXX)
add_executable(app src/main.cpp)
target_compile_features(app PRIVATE cxx_std_23)
target_compile_options(app PRIVATE -Wall -Wextra)
От простого к сложному
example1
В проекте есть два файла main.cpp:
#include <iostream>
int main(int, char**){
std::cout << "hello, this is first CMake example!\n";
return 0;
}
и CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(example1)
add_executable(hello main.cpp)
set_target_properties(
hello PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
для запуска CMake, обычно, создают дополнительную директорию (build или temp или с другим именем, куда CMake будет генерировать файлы системы сборки (например, Makefile или файлы для Ninja), которые содержат информацию про ОС, компиляторы, библиотеки на устройстве с которого был запущенн CMake, иначе они будут лежать в корне вашего проекта).
Для запуска cmake из этой директории необходимо перейти в директорию “выше” (..) и в директории еxample1 обратиться к файлу CMakeLists.txt
mkdir build
cd build
cmake ../еxample1/
После того как CMake сгенерирует файлы-системы сборки, можно запустить сборку вашего проекта в соответствии с target-целью поставленной перед CMake командой:
cmake --build .
в результате появиться исполняемый файл hello (в директории build) в соответствии с add_executable(hello main.cpp).
example2
много-файловый проект:
//main.cpp
#include "version.h"
#include <iostream>
int main(int, char**){
std::cout << "hello from the second Cmake example! \n";
std::cout << "Version = " << examples::getVersion() << '\n';
return 0;
}
// version.h
#pragma once
namespace examples{
int getVersion();
}
//version.cpp
#include "version.h"
int examples::getVersion(){
return 0;
}
и CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(example2)
#add_executable(version main.cpp version.cpp version.h)
#add_executable(version main.cpp version.cpp)
set(SOURCES
main.cpp
version.cpp
)
set(HEADERS
version.h
)
add_executable(version ${SOURCES} ${HEADERS})
set_target_properties(
version PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
example3
также много-файловый проект, плюс генерация файла через configure_file
//main.cpp
#include "version.h"
#include <iostream>
int main(int, char**){
std::cout << "hello from the third Cmake example! \n";
std::cout << "Version = " << examples::getVersion() << '\n';
return 0;
}
//__________
// version.h
namespace examples{
int getVersion();
}
//__________
//version.cpp
#include "version.h"
#include "config.h"
namespace examples {
int getVersion(){
return (PROJECT_VERSION_PATCH);
}
}
//__________
//config.h.in
#pragma once
#cmakedefine PROJECT_VERSION @PROJECT_VERSION@
config.h.in - Это шаблон файла, который будет обработан CMake.
если переменная определена — будет создан define - #cmakedefine PROJECT_VERSION @PROJECT_VERSION@
но есть ньюанс
#cmakedefine PROJECT_VERSION @PROJECT_VERSION_PATCH@возвращает"0.0.1", а это строка и поэтому программа выведетVersion = 1, можно получить каждую цифру#cmakedefine PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@,#cmakedefine PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@,#cmakedefine PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@, можно придумать и по другому - :)
После обработки configure_file() CMake создаст файл:
build/config.h
Пример того, что может получиться:
#define PROJECT_VERSION 1
и CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(example3 VERSION 0.0.1)
configure_file(
config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
set(SOURCES
main.cpp
version.cpp
)
set(HEADERS
version.h
)
add_executable(configure ${SOURCES} ${HEADERS})
set_target_properties(
configure PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
target_include_directories(
configure
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
Этот пример показывает три важные вещи CMake:
1 - Переменные проекта
project(example3 VERSION 0.0.1)
создаёт переменные:
PROJECT_VERSION2️ - Генерацию файлов
configure_file(config.h.in config.h)
CMake подставляет значения переменных в шаблон.
3️ - Работа с include директориями
target_include_directories(...)
нужна потому что config.h лежит в build-директории, а не в исходниках.
example4
использование структуры много-модульного проекта и создание статической библиотеки
Структура проекта
example4
│
├── CMakeLists.txt
│
├── lib
│ ├── CMakeLists.txt
│ ├── version.cpp
│ └── version.h
│
└── src
├── CMakeLists.txt
└── main.cpp
lib — создаёт статическую библиотеку
src — создаёт исполняемый файл
главный CMakeLists.txt объединяет всё вместе.
Итак, общий CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(example4 VERSION 0.0.2)
add_subdirectory(lib)
add_subdirectory(src)
add_subdirectory(lib) говорит CMake: перейти в папку lib и выполни файл lib/CMakeLists.txt, добавь созданные targets (например библиотеку) в общий проект.
То же самое происходит с src.
Создание библиотеки
//lib.cpp
#include "lib.h"
#include "config.h"
#include <iostream>
namespace lib {
int makeSameSuperJob(){
std::cout << "Hello from lib\n";
return 42;
}
int getVersion(){
return (PROJECT_VERSION);
}
}
//__________
// lib.h
#pragma once
namespace lib {
int makeSameSuperJob();
int getVersion();
}
//__________
//config.h.in
#pragma once
#cmakedefine PROJECT_VERSION @PROJECT_VERSION@
cmake_minimum_required(VERSION 3.5)
project(library VERSION 0.0.2)
configure_file(
config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
add_library(lib STATIC lib.cpp lib.h)
set_target_properties(
lib PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED
)
target_include_directories(
lib
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
target_include_directories(lib PUBLIC ...) говорит компилятору, где искать заголовки. PUBLIC означает: библиотека сама использует этот include и передаёт его всем target’ам, которые будут линковаться с lib.
создание приложения, использующее написанную библиотеку
//main.cpp
#include "config.h"
#include "lib.h"
#include <iostream>
int main(int, char**)
{
std::cout << "hello from main! \n";
lib::makeSameSupperJob();
std::cout << "Lib version: " << lib::getVersion() << '\n';
return 0;
}
//__________
//config.h.in
#pragma once
#cmakedefine PROJECT_VERSION @PROJECT_VERSION@
cmake_minimum_required(VERSION 3.5)
project(Main VERSION 0.0.5)
configure_file(
config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
add_executable(mainLib main.cpp)
set_target_properties(
mainLib PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
target_include_directories(
mainLib
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_include_directories(
mainLib
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../lib
)
target_link_libraries(
mainLib lib
)
Рекомендации по изучению