Введение в 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:
Для начала напишем простейший хеловорлд:
main.cpp
#include <iostream>
int main(int argc, char** argv)
{
std::cout << "Hello, World!" << std::endl;
return 0;
}
и файл для сборки:
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
# Если версия установленой программы
# старее указаной, произайдёт аварийный выход.
add_executable(main main.cpp # Создает исполняемый файл с именем main
# из исходника main.cpp
Синтаксис CMake похож на синтаксис bash, всё что после символа "#" является комментарием и обрабатываться программой не будет. CMake позволяет не засорять дерево исходных кодов временными файлами — очень просто и без лишних телодвижений сборка производится «Out-of-Source».
Создадим пустую директорию для временных файлов и перейдём туда.
fshp@panica-desktop:~$ mkdir tmp
fshp@panica-desktop:~$ cd tmp/
fshp@panica-desktop:~/tmp$
Теперь запустим команду cmake, передав ей в качестве параметра путь к папке с исходниками:
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. Библиотеки:
Усложним пример.
foo.h
void hello_world();
foo.cpp
#include <iostream>
void hello_world()
{
std::cout << "Hello, World!" << std::endl;
}
main.cpp
#include "foo.h"
int main(int argc, char** argv)
{
hello_world();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
# Если версия установленой программы
# старее указаной, произайдёт аварийный выход.
project(hello_world) # Название проекта
set(SOURCE_EXE main.cpp) # Установка переменной со списком исходников для исполняемого файла
set(SOURCE_LIB foo.cpp) # Тоже самое, но для библиотеки
add_library(foo STATIC ${SOURCE_LIB}) # Создание статической библиотеки с именем foo
add_executable(main ${SOURCE_EXE}) # Создает исполняемый файл с именем main
target_link_libraries(main foo) # Линковка программы с библиотекой
Переменные могут хранить списки значений, разделённых пробеламитабуляциямипереносами:
set(SOURCE main.cpp foo.cpp)
set(HEADER main.h
foo.h)
Оба варианта правильные
Что бы получить значение переменной ипользуем конструкцию:
${var_name}
Итак, эта версия нашего проекта включает в себя одну статическую библиотеку, собираемую из исходников. Если заменить «STATIC» на «SHARED», то получим библиотеку динамическую. Если тип библиотеки не указать, по умолчанию она соберётся как статическая.
При линковке указываются все необходимые библиотеки:
target_link_libraries(main foo
ogg
vorbis)
Как и при ручной компиляции, имена библиотек указываются без стандартного префикса «lib».
Пример 3. Подпроекты:
Подпроекты очень удобны, если ваша программа разбита на несколько библиотек или же проект состоит из нескольких программ.
Каждый подпроект является по сути полноценным проектом и может использоваться самостоятельно.
Теперь у нас «foo» находится в субдирректории и там же находится CMakeLists.txt подпроекта.
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
# Если версия установленой программы
# старее указаной, произайдёт аварийный выход.
project(hello_world) # Название проекта
set(SOURCE_EXE main.cpp) # Установка переменной со списком исходников
include_directories(foo) # Расположение заголовочных файлов
add_executable(main ${SOURCE_EXE}) # Создает исполняемый файл с именем main
add_subdirectory(foo) # Добавление подпроекта, указывается имя дирректории
target_link_libraries(main foo) # Линковка программы с библиотекой
foo/CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # Проверка версии CMake.
# Если версия установленой программы
# старее указаной, произайдёт аварийный выход.
project(foo) # Название проекта
set(SOURCE_LIB foo.cpp) # Установка переменной со списком исходников
add_library(foo STATIC ${SOURCE_LIB})# Создание статической библиотеки
В файле подпроекта ничего нового для вас нет. А вот в основном файле новые команды:
include_directories(foo)
main.cpp мы не меняли, а foo.h перенесли. Команда указывает компилятору, где искать заголовочные файлы. Может быть вызвана несколько раз. Хидеры будут искаться во всех указаных директориях.
add_subdirectory(foo)
Указываем директорию с подпроектом, который будет собран как самостоятельный.
Пример 4. Поиск библиотек:
CMake обладает достаточно развитыми средствами поиска установленых библиотек, правда они не встроеные, а реализованы в виде отдельных модулей. В стандартной поставке довольно много модулей, но некоторые проекты (например Ogre) поставляют свои.
На debian модули располагаются в /usr/share/cmake-2.8/Modules/ (у вас версия может отличаться).
find_package(SDL REQUIRED)
if(NOT SDL_FOUND)
message(SEND_ERROR "Failed to find SDL")
return()
else()
include_directories(${SDL_INCLUDE_DIR})
endif()
##########################################################
find_package(LibXml2 REQUIRED)
if(NOT LIBXML2_FOUND)
message(SEND_ERROR "Failed to find LibXml2")
return()
else()
include_directories(${LIBXML2_INCLUDE_DIR})
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(${Boost_INCLUDE_DIRS})
endif()
##########################################################
target_link_libraries(${TARGET}
${SDL_LIBRARY}
${LIBXML2_LIBRARIES}
${Boost_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
Пример 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(${TARGET} netutil)
Генераторы:
Как было сказано в начале, CMake умеет генерировать множество различных видов проектов.
Если запустить cmake без параметров, в конце будут описаны доступные генераторы. Пользоваться так:
fshp@panica-desktop:~/tmp$ cmake ~/cmake/example_3/ -G «KDevelop3 — Unix Makefiles»
Заключение:
Это не перевод мануала, а результат использования CMake в одном коммерческом проекте. Буду рад, если статья поможет хотя бы одному человеку — на русском языке подобной документации довольно мало.
Чем понравился CMake лично мне:
- один проект — один файл. Не нужно хранить кучу скриптов настройки, сборки и прочего хлама;
- Скорость работы в сравнении с autotools;
- простой и понятный синтаксис;
- является front-end`ом для множества IDE;
- отображение прогресса — довольно удобно;
- цветной вывод — в серые будни немного краски не помешает;
Для Sublime Text есть плагин, добавляющий подсветку синтаксиса CMake, он так и называется — «CMake».
http://www.pvsm.ru/sborka-proekta/17594