Cmake что это
Перейти к содержимому

Cmake что это

  • автор:

Записки программиста

Некоторое время назад мы познакомились с 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.

Использование CMake в простейшем случае выглядит следующим образом. В корне репозитория создается файл CMakeLists.txt примерно такого содержания:

cmake_minimum_required ( VERSION 3.1 )

# так пишутся комментарии

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 » $ -Wall -Wextra -Werror» )

add_executable ( main src/Main.cpp src/HttpServer.cpp )

Хочется надеяться, какая строчка здесь что означает, пояснять не нужно. Затем исходники складываются в каталог src, а заголовочные файлы — в каталог include. Для сборки проекта говорим:

mkdir build
cd build
cmake ..
make

Просто, не правда ли?

Помимо приведенного выше find_library в CMake есть ряд скриптов для подключения конкретных библиотек. В частности, подключение OpenGL осуществляется как-то так:

find_package ( OpenGL REQUIRED )

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» ..

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 )

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 )

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 )

add_subdirectory ( src )
add_subdirectory ( test )

Непосредственно тесты добавляются в проект следующим образом:

cmake_minimum_required ( VERSION 2.8 )

set ( 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 )

add_test ( NAME python_test
COMMAND py.test —capture=no $ /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 и если да, используете ли какие-то его возможности, о которых не было рассказано выше?

Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.

Что это и зачем нужно

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». Чтобы где-либо использовать значение этой переменной, нужно написать $.

Чтобы добавить к переменной некий текст, можно сделать так:

set(VARIABLE "$ new text")

Как видите, использовать значение можно и внутри кавычек. Переменные активно используются различными библиотеками — для установки флагов, параметров сборки/линковки и прочих вкусностей, об этом чуть-чуть попозже.

Пример коше’гного проекта со списком сорцов в отдельной переменной:

cmake_minimum_required(VERSION 2.6) set(SOURCES test.cpp lib1.cpp lib2.cpp) add_executable(test $)

Устанавливаем команды компилятору

add_definitions(-DSOME_IMPORTANT_DEFINITION)

Эта команда используется для установки дефайнов, которыe можно проверить в коде через, например, #ifdef SOME_IMPORTANT_DEFINITION.

set(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($)

Итак, осталось найденные библиотеки подключить к исполняемому файлу.

target_link_libraries(test $)

В качестве библиотек нужно указать пути к необходимым собранным библиотекам. 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 "$\$$ -std=c++11 -Wall") set(SRC_LIST lib1/main.cpp lib2/main.cpp main.cpp) add_executable($\$$ $\$$) target_link_libraries($\$$ $\$$)

Если Вам что-то в нём не понятно — перечитайте соответствующую информацию выше.

Создаем директорию «/build» (не «/sources/build»), переходим в нее, запускаем в ней «cmake ..». «..» — метка родительской директории. cmake возьмет из нее наш CMakeLists.txt и по нему создаст проект в папке «/build». Чтобы проект собрать, запускаем «make» в той же папке «/build».

Таким образом, в корне у нас есть:

  • CMakeLists.txt
  • директория с исходниками
  • каталог сборки

Все разделено, автоматизировано и удобно.

Как создать библиотеку в поддиректории и слинковать ее с основной программой

Пусть в ./ лежит основной проект, а в ./subdir мы хотим сделать либу, а в ./build построить проект.

project(MegaLibrary) set(SOURCES lib.cpp) set(HEADERS lib.h) add_library(lib $\$$ $\$$) target_include_directories(lib PUBLIC $\$$)
project(MainProject) set(MAIN_PROJECT_SRC_LIST main) # Other stuff
add_executable(main $\$$) add_subdirectory(subdir) target_link_libraries(main lib)

Теперь можно в файлах основного проекта делать #include «lib.h» (см. документацию по target_include_directories).

В ./build запускаем «cmake .. && make» и получаем собранный проект.

Как использовать CMake в связке с QtCreator

Интеграция с cmake у QtCreator не очень тесная, тем не менее, работать с ним можно.

Создаем новый проект без использования Qt, выбираем «Проект на С++ с использованием CMake». Создастся дефолтный файл сборки, который просто добавляет все исходники в директории проекта и компилирует их в один бинарник.

Как добавить header в проект, чтобы его было видно в списке файлов

Если вы создали файл header.h в директорию проекта, просто строчку

add_executable($\$$ $\$$)
add_executable($\$$ $\$$ "header.h")

Cmake что это

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

Интерфейс к утилитам командной строки

# Создать каталог 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__LIBRARY: библиотека для компоновки с компонентом библиотек 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>) 

PS-Group

  • PS-Group
  • sshambir@gmail.com
  • ps-group
  • image/svg+xml sshambir

Для чего нужен CMake?

Сначала был компилятор. Консольная программа, которой на вход даешь файл с кодом, а она тебе выдает файл-бинарник, который можно запустить. Для Unix-like это выглядело вот так:

gcc main.c 

а для MSVC вот так:

cl.exe main.c 

Однако, любой мало-мальски сложный проект содержит не один, а несколько (много!) файлов с исходным кодом. Можно было бы создать простенький скрипт, в котором было бы написано

gcc file1.c file2.c . file123.c 

Однако каждый раз, внося изменения лишь в один из файлов с кодом, вам нужно будет перекомпилировать программу, а эта команда будет пересобирать все 123 файла. Это медленно и неудобно. Требуется нечто, что будет детектировать измененные файлы и пересобирать только их.

make

Одним из таких решений является утилита make . Если вдаваться в подробности, то существует несколько диалектов make : на Linux используется т.н. GNU Make, на FreeBSD — BSD Make, а на винде — NMake.

make принимает на вход файл, описывающий правила. Каждое правило — это просто одна или несколько команд, которые производят какой-либо файл. Правила могут использовать файлы, получившиеся в результате выполнения других правил, и, таким образом, формируется дерево зависимостей. Все конечные результирующие файлы объединяются в правило all , поэтому команда make all позволяет «собрать» весь проект.

Я взял слово «собрать» в кавычки потому что, на самом деле, make не обязательно используется только с компиляторами. Мануал по make в заголовке гласит:

make – maintain program dependencies

Т.е. make — это программа для управления зависимостями, причем не важно зависимостями чего от чего.

Конфигурация проекта

Беды на этом не кончились. Языки C и С++ устроены таким образом, что чтобы воспользоваться какой-либо сторонней библиотекой, вам необходимо указать компилятору путь до

  1. Директории, содержащей заголовочные файлы библиотеки.
  2. Директории, содержащей саму библиотеку.

Заголовочные файлы указываются флагом -I в Unix-подобных ОС и флагом /I в компиляторе Microsoft:

gcc -I/full/path/to/vasyanlib/include . cl.exe /I"C:\Program Files\VasyanLib\Include" . 

Аналогично, директории для библиотек указываются с помощью -L / /LIBPATH .

Но вот незадача — на разных компьютерах VasyanLib может быть установлена в разные директории! Это означает, что если вы захотите дать ваш код другому человеку, или просто самому скомпилировать ваш код на другой машине, то вам придется редактировать свой скриптик сборки (читай, команду вызова компилятора).

Это особенно неудобно, если ваш скрипт сборки находится под управлением системы контроля версий ( git , hg , svn и т.д.), ведь вам придется либо каждый раз коммитить эти изменения, либо они будут «висеть» у вас незакомиченными.

И фиг бы с ними с библиотеками, но ведь иногда требуется отыскать сам компилятор, или какие-то файлы. А еще иногда требуется определить тип и версию ОС, поддерживаемые флаги компилятора, причем некоторые добавить, в зависимости от поддержки, и т.д.

Таким образом, помимо этапа сборки проекта появляется этап конфигурации. На этом этапе с помощью каких-либо скриптов или программ производятся необходимые проверки и поиски, а результат заносится в Makefile (ну, на сегодняшний день этот Makefile просто генерируется целиком) и/или оформляется в виде заголовочного файла с рядом директив #define , позволяющих в коде определить наличие того или иного файла или фичи.

Системы сборки

На данном этапе становится понятна роль программного обеспечения, называемого системами сборки. Они

  1. Определяют структуру проекта, т.е. из каких «целей» он состоит. Например, проект может состоять из библиотеки, приложения, использующего эту библиотеку, и набора приложений-тестов.
  2. Решают задачу конфигурации проекта.
  3. Решают задачу сборки проекта.
  4. Решают дополнительные задачи: очистка — удаление результатов сборки, тестирование — запуск тестов определенным образом, упаковка — создание релизных архив и инсталляторов, и т.д.

Пожалуй, первой системой сборки был Autotools. Сейчас их уже тьма-тьмущая и CMake, на мой взгляд, является наиболее универсальной из них. Остальные системы сборки либо зависимы от ОС, либо зависимы от языка программирования.

CMake vs IDE

Вот теперь, рассмотрев всю историю, можно разобрать и вопрос о том, является ли CMake и вообще системы сборки «наследием», и обошли ли их IDE.

Ответ — конечно нет. Подтверждение этому можно найти не только в том, что системы сборки постоянно и активно развиваются, но и в том что даже в Visual Studio, той самой IDE, которой не требовалась никакая система сборки, была добавлена поддержка CMake.

Пара слов о Visual Studio. Эта IDE имела свой формат проектов, и, по сути, свою собственную систему сборки. Проекты студии в функциональном смысле были эквивалентны Makefile ам или CMakeLists.txt ам. Неудобство пользованиями ими, на мой взгляд, заключается в следующем:

  1. Отсутствует этап конфигурации. На сегодняшний день мне неизвестно как в С++ проекте студии указать, что ему требуется VasyanLib , да так чтобы при попытке собрать проект, студия бы спросила меня о местонахождении этой библиотеки, вместо того чтобы плеваться ошибками сборки.
  2. Сложно использовать сторонние утилиты в процессе сборки. Например, у меня есть проект, который сначала собирает программу на Хаскелле, запускает ее, а она производит код на С++, который уже превращается в конечную программу. На CMake это заняло строчек 10, а на студии мне неизвестно как это реализовать.

Философия

На самом деле, файлы проектов IDE не нужны. Они не нужны по той причине, что файлы CMakeLists.txt — это уже файлы проекта. Причем они универсальны и не привязаны не только к ОС, но и к используемой IDE — в настоящее время CMake умеет генерить ,помимо Makefile, проекты Visual Studio и Eclipse.

Первая IDE, на мой взгляд, которая поняла эту глубокую мысль — KDevelop. В ней файлы проекта отсутствуют в принципе. Это не только классное решение с принципиальной точки зрения, но и имеет практическую ценность — например, KDevelop «видит» файлы проекта точно так же как сам CMake. В частности, если какой-то #include у вас подчеркивается красным, то это означает что вы накосячили в коде CMake. А в студии эта ошибка могла бы пройти мимо глаз, и проявиться только на другом компьютере с другой конфигурацией директорий.

Надеюсь, за такой развернутый пост мне простят беззастенчивую рекламу KDevelop. Но это и вправду замечательная IDE, на разработку которой я потратил немало времени.

Отслеживать
ответ дан 28 ноя 2019 в 18:55
1,642 11 11 серебряных знаков 13 13 бронзовых знаков
то есть это просто продвинутая система сборки я так понимаю.
– user252359
29 ноя 2019 в 13:30

«Сложно использовать сторонние утилиты в процессе сборки.» — в VS довольно давно (а может и всегда) были шаги prebuild и postbuild. Так что хаскельный генератор кода просто прописывается в prebuild и будет запускаться когда надо. Вроде там даже больше триггеров есть.

29 ноя 2019 в 15:29

@Qwertiy [были шаги prebuild и postbuild] Так давно все сделано! Впрочем это логично, сделать это в IDE ничего не стоит. Вот насколько это никому не нужно, что я даже и не обращал внимания что там есть шаги prebuild и postbuild. 🙂

29 ноя 2019 в 16:25

@Rundogie Rundogie [почему мы программисты должны возиться] А никто и не возится. со всякими сборками. Даже Юниксоиды используют свои юниксоидные IDE (типа упомянутого KDevelop) которые суть надстройки над их любимым Cmake. И эти юниксоидные IDE позволяют Юниксоидам править конфиги к Cmake просто выставляя галочки (какой позор для ревнителей древлего юниксового благочестия :-)) как в Visual Studio. Разница в том, что конфиги VS бинарные и закрытые. А конфиги KDevelop текстовые и открытые. С текстовыми конфигами труднее работать программе, но удобнее работать людям. А с бинарными наоборот.

1 дек 2019 в 7:59

@RundogieRundogie Потратили вы два дня потому что у вас опыта мало. Ну и отчасти потому что там система сборки какая-то неведомая — premake. Еще раз, если бы 100% разработчиков пользовали бы студию, то нужды в CMake бы не было. Но объективная реальность такова, что в ней куча IDE и у всех свои форматы проектов. Нужен какой-то общий знаменатель и этот знаменатель — CMake.

1 дек 2019 в 8:15

CMake (равно как и make, qmake, gmake, consul итд) нужен для описания правил сборки проекта. То есть он призван отвечать на вопросы

  • Где у проекта лежат исходники?
  • Какое имя бинарника проекта?
  • Какие имена библиотек проекта?
  • Какие внешние библиотеки проект использует и где они находятся?
  • Сборка в дебаг или релиз?
  • Какие действия выполнить перед сборкой? А после?
  • Что нибудь ещё.

К тому же CMake это не просто декларативное описание, это вполне себе язык, на котором пишется код, который собирает проект, при этом у CMake уже готово множество методов для автоматизации поиска библиотек, текущей архитектуры и так далее.

При этом файл CMake намного более читабелен и понятен, чем, например, solution в visual studio.

Я, например, в одном из своих pet-проектов про arduino одним CMake-файлом собираю демона для linux, прошивку для arduino и заливаю эту прошивку в arduino.

Отслеживать
ответ дан 28 ноя 2019 в 12:36
3,116 12 12 серебряных знаков 31 31 бронзовый знак

Для организации C/C++ проекта. Достаточно зайти на официальный сайт https://cmake.org/.

CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.

Отслеживать
ответ дан 28 ноя 2019 в 8:24
user361503 user361503
11 1 1 бронзовый знак
То есть утилита бесполезная если я уже знаю какой компилятор я буду использовать?
– user252359
28 ноя 2019 в 8:29

@RundogieRundogie Я в Сях не шарю и заглянул сюда лишь из любопытства, но насколько мне известно, билдер и компилятор — это совершенно разные вещи. Компилятор превращает в исполняемый код программу, которая собирается билдером из файлов проекта.

28 ноя 2019 в 9:28
Да, но то есть в IDE у меня получается уже есть и билдер и компилятор.
– user252359
28 ноя 2019 в 9:40

@RundogieRundogie зависит от IDE. В Visual Studio — да. Но есть IDE, которые используют компилятор и систему сборки, поставленные отдельно.

28 ноя 2019 в 13:37

Для создания программ под микроконтроллеры альтернативе утилитам make и cmake иногда просто нету. IDE от производителя вставляют в код большой объем не используемого кода и удалить его из программы средствами IDE не возможно. Приходится собирать проект без IDE.

Отслеживать
ответ дан 3 дек 2019 в 8:26
3,020 1 1 золотой знак 11 11 серебряных знаков 20 20 бронзовых знаков

не надо путать make и CMake. Если грубо, make исполняет MAKEFILE, CMake — создаёт MAKEFILE, чтобы make мог его выполнить. CMake is an open-source, cross-platform family of tools designed to build, test and package software. И про IDE: приведите пример.

6 дек 2019 в 9:58

Утилиты make и Cmake это наследие Unix с тех времен, когда не было IDE, а в качестве терминала была пишущая машинка Консул(!). 🙂 В принципе, Unix-оиды до сих пор тащат эту методологию разработки и построения проектов, как наиболее общую. Иногда без нее не обойтись, но ее доля в реальной жизни снижается. Все работают на IDE и не заморачиваются с make и Cmake.

Вот еще для чего может понадобится Cmake, так это для распространения программ в виде Open Source. Если автор предоставляет Cmake для проекта, то пользователю проще на своей машине перестроить проект. И пользователю не надо в этом случае разбираться где там исходники, где хедеры, где библиотеки и прочее. Если же распространять с проектом не Cmake, а конфигурационный файл для IDE, то во-первых легко перестроить проект получится только с таким же IDE. А во-вторых IDE на машине пользователя должно стоять на том же диске и с теми же путями, что и на машине автора проекта.

то этот пост все равно бы не объяснял — почему?

ТС и не спрашивал «почему». ТС спрашивал «зачем». ТС спрашивал зачем ему да и всем нам нужен CMake, если давно есть удобные IDE. Я попытался ответить зачем сейчас, в 2019 году, может быть нужен CMake. Ничего более убедительного, чем распространение Open Source проектов не вспомнилось.

Ответ же «почему» очевиден — потому что с IDE проще работать. При использовании IDE не надо вручную править конфигурацию CMake каждый раз при добавлении файла исходников в проект. И в IDE все в одном флаконе — и редактор, и отладчик, и компилятор и подсветка синтаксиса и форматирование исходников и подсветка ошибок и профилятор и еще куча всего. Всего этого нет в CMake и вообще всего этого нет в старой методологии разработки через командную строку. Именно поэтому CMake «загнан в темный угол». Именно поэтому 99% программистов работают только с IDE.

Отсутствует этап конфигурации. На сегодняшний день мне неизвестно как в С++ проекте студии указать, что ему требуется VasyanLib, да так чтобы при попытке собрать проект, студия бы спросила меня о местонахождении этой библиотеки, вместо того чтобы плеваться ошибками сборки.

Не критично. 99% проектов обходятся статическим указанием на местоположение библиотек. Если будет критично, то эту функцию добавят в IDE.

Сложно использовать сторонние утилиты в процессе сборки. Например, у меня есть проект, который сначала собирает программу на Хаскелле, запускает ее, а она производит код на С++, который уже превращается в конечную программу. На CMake это заняло строчек 10, а на студии мне неизвестно как это реализовать.

То же самое — не критично. 99% проектов обходятся без сторонних утилит в процессе сборки. Если будет критично, то эту функцию добавят в IDE. Предварительный запуск программы на Хаскелле для написания программы на С++ для последующей трансляции это такая ЭКЗОТИКА о которой и говорить не стоит. Так работают даже не 1% программистов, так работают 0.00001% программистов. Да вобщем-то и сейчас никто не обломится, запустив перед сборкой/после сборки отдельный батник со сторонними утилитами.

Все упомянутые «неудобства» пользования IDE не перевешивают «удобств» пользования IDE. А править вручную конфигурацию для Cmake это то еще удовольствие. Да еще при этом помнить все птичьи правила этого птичьего языка конфигураций.

В общем резюмируя — сборка Unix-style и вообще работа Unix-style времен батьки Денниса Ритчи это для олдскульного поколения, которое успело выучить язык конфигураций make и Cmake до того, как придумали IDE (спасибо за это кстати приснопамятной фирме Borland). Чем дальше, тем меньше остается таких динозавров что и к лучшему. Никто же сейчас не программирует в кодах. А когда-то были специалисты, которые помнили наизусть все коды(!) (даже не мнемоники ассемблера, а КОДЫ) всей системы команд PDP-11. 🙂

Он является генератором сценариев сборки, и таким образом, делает проект переносимым между разными IDE и системами сборки,

Это все очень хорошо, но 99% проектов не нуждаются в переноске между разными IDE и системами сборки. Поэтому это свойство Cmake не востребовано в 99% случаев.

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

Очень сомнительное утверждение.

Основная беда проектных файлов того же вижака — это то, что они отвратно версионируются, а для больших проектов это создает проблемы.

И каким образом использование Cmake помогает версионировать проекты? К тому же для версионирования сейчас используются всякие системы контроля версий типа того же git. Которые тоже ни разу не инструменты командной строки.

так как противопоставлять CMake и IDE неправильно. Это система сборки, которая может использоваться и с IDE.

Может использоваться. А может и не использоваться. И 99% проектов не используют CMake. И 99% программистов не используют CMake. И чем дальше, тем эта цифра будет только расти. А вопрос был от человека, который счастливо живет под IDE и вдруг узнает что есть еще CMake. Естественно у него возникает вопрос — как же он жил до этого и не пользовался CMake и все у него работало? 🙂 А просто время CMake ушло и CMake остался только для старой гвардии, которая умирает но не сдается (С) Наполеон Бонапарт. 🙂

С развитием хороших и разных IDE роль CMake и благословенной командной строки все время снижается. 🙂

Довольно парадоксально выглядит, что ТС вполне удовлетворен ответом, а общественность продолжает негодовать и рассказывать, что собой представляет Cmake. Вопрос был не о том, что собой представляет Cmake, а вопрос был о том, зачем нужен Cmake, если есть IDE, которые умеют все, что умеет Cmake и таким образом IDE делает Cmake не нужным.

Автомобили заменили лошадей, а IDE вполне заменили Cmake. Это называется технический прогресс. Можно и сейчас ездить на телеге, но зачем? Можно и сейчас собирать проекты с помощью Cmake, но зачем?

Ну то есть понятно зачем. Действительно, существует 1% проектов, которые должны быть переносимы между разными IDE и платформами. Для таких проектов, возможно, применение Cmake и оправдано. Но это не мейнстим сейчас. Это глубоко нишевое применение.

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

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