Введение в CMake
CMake — кроcсплатформенная утилита для автоматической сборки программы из исходного кода. При этом сама CMake непосредственно сборкой не занимается, а представляет из себя front-end. В качестве back-end`a могут выступать различные версии make и Ninja. Так же CMake позволяет создавать проекты для CodeBlocks, Eclipse, KDevelop3, MS VC++ и Xcode. Стоит отметить, что большинство проектов создаются не нативных, а всё с теми же back-end`ами.
Для того что бы собрать проект средствами CMake, необходимо в корне дерева исходников разместить файл CMakeLists.txt, хранящий правила и цели сборки, и произвести несколько простых шагов.
Разберёмся на примерах.
Пример 1. Hello, World:
#include int main(int argc, char** argv)
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake. # Если версия установленой программы # старее указаной, произайдёт аварийный выход. add_executable(main main.cpp) # Создает исполняемый файл с именем main # из исходника main.cpp
Синтаксис CMake похож на синтаксис bash, всё что после символа «#» является комментарием и обрабатываться программой не будет. CMake позволяет не засорять дерево исходных кодов временными файлами — очень просто и без лишних телодвижений сборка производится «Out-of-Source».
Создадим пустую директорию для временных файлов и перейдём туда.
fshp@panica-desktop:~/tmp$ cmake ~/cmake/example_1/
…
— Build files have been written to: /home/fshp/tmp
fshp@panica-desktop:~/tmp$
fshp@panica-desktop:~/tmp$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
fshp@panica-desktop:~/tmp$
Видим, что в папке появилось несколько временных файлов, необходимых для сборки проекта.
Теперь можно запустить непосредственно make:
fshp@panica-desktop:~/tmp$ make
Scanning dependencies of target main
[100%] Building CXX object CMakeFiles/main.dir/main.cpp.o
Linking CXX executable main
[100%] Built target main
fshp@panica-desktop:~/tmp$ ./main
Hello, World!
fshp@panica-desktop:~/tmp$
Итак, наша программа собралась.
Папку tmp можно очищать\удалять без риска поломать исходники. Если CMakeLists.txt был изменен, то вызов make автоматически запустит cmake. Если исходники были перемещены, то нужно очистить временную директорию и запустить cmake вручную.
Пример 2. Библиотеки:
#include "foo.h" int main(int argc, char** argv)
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake. # Если версия установленой программы # старее указаной, произайдёт аварийный выход. project(hello_world) # Название проекта set(SOURCE_EXE main.cpp) # Установка переменной со списком исходников для исполняемого файла set(SOURCE_LIB foo.cpp) # Тоже самое, но для библиотеки add_library(foo STATIC $) # Создание статической библиотеки с именем foo add_executable(main $) # Создает исполняемый файл с именем main target_link_libraries(main foo) # Линковка программы с библиотекой
set(SOURCE main.cpp foo.cpp) set(HEADER main.h foo.h)
Итак, эта версия нашего проекта включает в себя одну статическую библиотеку, собираемую из исходников. Если заменить «STATIC» на «SHARED», то получим библиотеку динамическую. Если тип библиотеки не указать, по умолчанию она соберётся как статическая.
При линковке указываются все необходимые библиотеки:
target_link_libraries(main foo ogg vorbis)
Как и при ручной компиляции, имена библиотек указываются без стандартного префикса «lib».
Итак, сборка библиотек с CMake не вызывает проблем, при этом тип библиотеки статическая\динамическая меняется лишь одним параметром.
Пример 3. Подпроекты:
Подпроекты очень удобны, если ваша программа разбита на несколько библиотек или же проект состоит из нескольких программ.
Каждый подпроект является по сути полноценным проектом и может использоваться самостоятельно.
Теперь у нас «foo» находится в субдирректории и там же находится CMakeLists.txt подпроекта.
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake. # Если версия установленой программы # старее указаной, произайдёт аварийный выход. project(hello_world) # Название проекта set(SOURCE_EXE main.cpp) # Установка переменной со списком исходников include_directories(foo) # Расположение заголовочных файлов add_executable(main $) # Создает исполняемый файл с именем main add_subdirectory(foo) # Добавление подпроекта, указывается имя дирректории target_link_libraries(main foo) # Линковка программы с библиотекой
#include "foo.h" int main(int argc, char** argv)
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake. # Если версия установленой программы # старее указаной, произайдёт аварийный выход. project(foo) # Название проекта set(SOURCE_LIB foo.cpp) # Установка переменной со списком исходников add_library(foo STATIC $)# Создание статической библиотеки
В файле подпроекта ничего нового для вас нет. А вот в основном файле новые команды:
main.cpp мы не меняли, а foo.h перенесли. Команда указывает компилятору, где искать заголовочные файлы. Может быть вызвана несколько раз. Хидеры будут искаться во всех указаных директориях.
Указываем директорию с подпроектом, который будет собран как самостоятельный.
Вывод: проекты на CMake можно объединять в довольно сложные иерархические структуры, причем каждый подпроект в реальности является самостоятельным проектом, который в свою очередь может сам состоять из подпроектов. Это позволяет легко разбить вашу программу на необходимое количество отдельных модулей. Примером такого подхода может служить KDE.
Пример 4. Поиск библиотек:
CMake обладает достаточно развитыми средствами поиска установленых библиотек, правда они не встроеные, а реализованы в виде отдельных модулей. В стандартной поставке довольно много модулей, но некоторые проекты (например Ogre) поставляют свои. Они позволяют системе автоматически определить наличие необходимых для линковки проекта библиотек.
На debian модули располагаются в /usr/share/cmake-2.8/Modules/ (у вас версия может отличаться). За поиск библиотек отвечают модули, называющиеся FindNAME.cmake, где NAME — имя библиотеки.
find_package(SDL REQUIRED) if(NOT SDL_FOUND) message(SEND_ERROR "Failed to find SDL") return() else() include_directories($) endif() ########################################################## find_package(LibXml2 REQUIRED) if(NOT LIBXML2_FOUND) message(SEND_ERROR "Failed to find LibXml2") return() else() include_directories($) endif() ########################################################## find_package(Boost COMPONENTS thread-mt REQUIRED) if(NOT Boost_FOUND) message(SEND_ERROR "Failed to find boost::thread-mt.") return() else() include_directories($) endif() ########################################################## target_link_libraries($ $ $ $)
Думаю, смысл должен быть понятен. Первый и второй блок — поиск библиотеки. Если в системе её нет, выведется сообщение об ошибке и завершается выполнение cmake. Третий блок похож, только он ищет не целый пакет библиотек, а лишь необходимый компонент. Каждый такой автоматизированый поиск определяет после выполнения как минимум 3 переменные:
SDL_FOUND, LIBXML2_FOUND, Boost_FOUND — признак присутствия бибилиотеки;
SDL_LIBRARY, LIBXML2_LIBRARIES, Boost_LIBRARIES — имена библиотек для линковки;
SDL_INCLUDE_DIR, LIBXML2_INCLUDE_DIR, Boost_INCLUDE_DIRS — пути к заголовочным файлам.
Если с первыми более или менее понятно, то вторые и третьи мне доставили много хлопот — половина имеет имена в единственном числе, половина — во множественном. Но оказалось, это легко отследить. В каждом модуле вначале есть коментарии, там описаны определяемые переменные. Посмотрите, например, /usr/share/cmake-2.8/Modules/FindLibXml2.cmake
Как видите, CMake способен сам определить наличие и местоположение необходимых библиотек и заголовочных файлов. В принципе, это должна уметь любая система автоматической сборки, иначе смысл в ней?
Пример 5. Внешние библиотеки и объектные файлы:
Если вы пишите для «дяди», а злой «дядя» любит самописные библиотеки и делиться исходниками не желает, поэтому присылает готовую библиотеку, то вы по адресу.
Объектные файлы в CMake стоят на ряду с исходниками — достаточно включить объектник в список файлов для компиляции.
С библиотеками потуже. Как известно, статическая библиотека это не что иное, как ar-архив, внутри которого лежат обычные объектники, никак не связаные между собой. Вы, наверное, уже догадались, как я поступал сначала. Да, просто потрошил библиотеку. Но потом был найден способ поэлегантнее:
add_library(netutil STATIC IMPORTED) set_property(TARGET netutil PROPERTY IMPORTED_LOCATION Binary/game_client/libnetutil.a)
Слово «IMPORTED», указывает, что библиотека берётся извне.
В CMake каждая цель имеет параметры, а set_property позволяет их изменять.
Линкуется такая библиотека стандартно:
target_link_libraries($ netutil)
Для динамических библиотек все аналогично, только тип «SHARED», расширение — «.so».
К сожалению, поддержка несистемных библиотек реализована немного костыльно. Возможно, я просто не знаю правильного варианта, поэтому буду рад, если «ткнете мордочкой». С другой стороны это не навороченый экзоскелет с системой жизнеобеспечения, а простейший костыль из двух строк.
Генераторы:
Как было сказано в начале, CMake умеет генерировать множество различных видов проектов. Это удобно и позволяет использовать CMake для практически любой популярной IDE.
Если запустить cmake без параметров, в конце будут описаны доступные генераторы. Пользоваться так:
Заключение:
Это не перевод мануала, а результат использования CMake в одном коммерческом проекте. Буду рад, если статья поможет хотя бы одному человеку — на русском языке подобной документации довольно мало.
- один проект — один файл. Не нужно хранить кучу скриптов настройки, сборки и прочего хлама;
- Скорость работы в сравнении с autotools;
- простой и понятный синтаксис, конечно с элегантностью питона не потягаться, но и не брейнфак, в конце концов.;
- является front-end`ом для множества IDE;
- отображение прогресса — довольно удобно;
- цветной вывод — в серые будни немного краски не помешает;