среда, 15 июня 2011 г.

Система CMake - часть 1

Вот мой вариант теста для настоящего программиста: что неприятнее всего не найти? Кредитку, корой только что расплачивался в ресторане, телефон прекрасной девушки, с которой вчера познакомился в метро или файл сборки проекта для той системы, в которой работаешь? Разумеется, ни одна из этих проблем не стоит того, чтобы делать из нее трагедию. Даже если кто-то сумеет воспользоваться вашей кредиткой, деньги, скорее всего, удастся вернуть, над потерей случайного телефонного номера тоже горевать стоит – кто знает, может оно и к лучшему, а отсутствующий в исходниках файл сборки проекта можно восстановить, при условии, что вы понимаете структуру проекта и у вас под рукой есть подходящие инструменты.
Программисты, работающие в Linux (да и в других Unix-системах) традиционно используют для сборки программ инструментарий GNU build system (состоящий из утилит autoconf, automake, libtool, gnulib). Именно с помощью GNU build system создаются знакомые всем нам файлы configure, с помощью которых мы генерируем make-файлы для сборки приложения. Система GNU build system настолько тесно связана с историей Linux и других открытых проектов, что «мантра» configure – make – make install рассматривается некоторыми Unix-программистами как единственно верный способ установки ПО. К недостаткам GNU build system можно отнести ее ориентацию на инструментарий разработчика GNU (GNU make, GCC и т.п.), который очень популярен на открытых Unix-платформах, но малораспространен в других средах. Стремяcь заполнить этот пробел, разработчики из компании Kitware создали свой собственный вариант кросс-платформенного генератора сборочных файлов - CMake – (cross-platform make).
Система CMake выбрана в качестве стандартного средства сборки KDE 4, и уже по одной этой причине заслуживает нашего внимания. Но у CMake есть и собственные достоинства. Прежде всего, CMake является по-настоящему кросс-платформенным генератором проектов, позволяющим создавать единые описания проектов для Linux и других Unix-систем (включая сюда Mac OS X) и Windows. Остановимся на этом подробнее. Важное отличие Windows от Linux (с точки зрения разработчика) заключается в том, что на платформе Windows нет единого стандарта сборочных файлов. MS Visual Studio использует свои файлы проектов, C++Builder – свои, MinGW – свои. Преимущество CMake в том, что эта система способна генерировать «родные» сборочные файлы для всех перечисленных средств разработки (как и для многих других). Кроме того, CMake стремится максимально использовать фирменные средства генерации сборочных файлов – например, для генерации сборочных файлов проекта Qt используется qmake. Помимо прочего CMake обладает интеллектуальной системой поиска инструментов сборки и библиотек на конкретной платформе (интроспекция) и автоматического конфигурирования. Благодаря этому система CMake сама устанавливает многие параметры сборочных файлов, которые в других системах управления сборкой приходится устанавливать вручную. Например, в ОС Linux CMake сам найдет директорию, в которой вы установили требуемый программе набор виджетов, а под Windows вам не придется указывать CMake, где установлены Visual Studio или C++Builder. Все это делает пакет CMake весьма простым и удобным в использовании.
Хотя в этих статьях мы будем рассматривать работу CMake в основном на примере программ, написанных на C++, CMake может пригодиться и тем, кто пишет на Java (а также многих других языков).
Схематично работу CMake можно описать следующим образом: для сборки приложения создается файл CMakeLists.txt, в котором описываются параметры сборки (расположение файлов исходных текстов, требуемые внешние модули, цели сборки). Далее этот файл передается утилите cmake. Результатом работы cmake является файл, содержащий инструкции сборки приложения для конкретной платформы (make-файл GNU make, файл проекта Visual Studio и т.д.). Суть идеи заключается в том, что описание процесса сборки в файле CMakeLists.txt абстрагировано как от конкретных особенностей отдельных систем (расположение файлов, возможности компиляторов), так и целых платформ. Читая общее описание процесса сборки из файла CMakeLists.txt, программа cmake создает файл инструкций сборки, учитывающий специфику конкретной системы.
Как же программе CMake удается быть такой «умной»? В основе CMake лежит мощный язык сценариев, который используется как в файлах CMakeLists.txt, так и в специальных сценариях – модулях. Именно в модулях реализованы такие функции CMake, как поиск файлов и проверка возможностей системы. Файлы модулей расположены в поддиректории Modules директории cmake. Файлы модулей можно разделить на три категории. Одни файлы предназначены для настройки работы системы на конкретной платформе (Linux, Winodws, Mac OS, BeOS и т.д.). Вторая группа модулей предназначена для выполнения интеллектуального поиска средств разработки и различных вспомогательных утилит, а также для проверки возможностей этих средств. Третья группа модулей выполняет поиск и проверяет параметры различных библиотек, требуемых отдельным программам. Важную роль в работе CMake играют так же файлы шаблонов для генерации различных файлов сборки. Поскольку файлы модулей и шаблонов отделены от самой утилиты cmake и написаны на том же языке сценариев, что и файлы описания сборки, расширить возможности CMake очень просто. CMake 2.6 поставляется с тремя сотнями модулей, которые адаптируют CMake для работы с множеством разных инструментов, начиная с компиляторов C++ и Fortran, интерпретаторов Perl и Python и заканчивая такими библиотеками как OpenSceneGraph, SDL, Qt 4 и wxWidgets. Файлы модулей поставляются не только с дистрибутивами CMake, но и вместе с дистрибутивами некоторых библиотек, разработчики которых уже заинтересованы в поддержке CMake.
Хотя основные преимущества CMake связаны с кросс-платформенностью, что и отражено в названии пакета, простота работы с различными библиотеками может сдлеать CMale полезным и для Linux-проектов, которые не предполагают переноса на другие платформы. Благодаря CMake вы получаете простой и стандартный способ сборки приложений, использующих Qt, KDE, GTK+, wxWidgets...

Приступая к работе

Все примеры, рассмотренные в этих статьях, ориентированы на CMake 2.6 или более поздних версий. Если у вас установлена более ранняя версия CMake, обновите ее – это несложно (скачать последнюю версию CMake можно с сайта www.cmake.org). Хотя изложение в этих статьях ориентировано, в основном, на Linux-программиста, мы не будем пренебрегать кросс-платформенными возможностями CMake, так что если вы все еще пользуетесь ОС из Редмонда, рекомендуется установить CMake и на нее тоже. Для изучения кросс-платформенных возможностей CMake мы воспользуемся кросс-платформенным набором виджетов wxWidgets (последнюю версию оного я тоже рекомендую установить на все интересующие вас платформы с сайта wxwidgets.org). Если вы никогда не программировали в wzxWidgets и не намерены делать этого впредь, то я могу вас успокоить – мы не будем заниматься программированием wxWidgets. Мы сейчас изучаем систему сборки программ, а не их написания, поэтому в качестве учебного материала нам вполне подойдут многочисленные примеры готовых программ, входящие в состав дистрибутива wxWidgets. Поскольку мы практически не будем касаться внутренней начинки этих программ, почти все наши рекомендации и инструкции можно рассматривать как универсальные, применимые при работе с любой другой библиотекой виджетов. Нашим основным средством разработки будет, естественно GCC, а в обзоре переноса проектов под Windows мы рассмотрим (очень бегло) сборку проектов с помощью Visual Studio и C++Builder.

Первый пример

В директории samples дистрибутива wxWidgets найдите поддиректорию minimal. Эта поддиректория содержит минимальный пример приложения wxWidgets. В поддиректории minimal содержатся также файлы сборки приложения на все случаи жизни, а вот файла CMakeLists.txt в ней нет. Мы исправим это упущение. Ниже приводится исходный текст файла CMakeLists.txt (который вы найдете на диске) для приложения minimal. Для упрощения работы файл CMakeLists.txt размещается в той же директории, что и исходные тексты приложения.
project(minimal)
cmake_minimum_required(VERSION 2.6)
set(wxWidgets_USE_LIBS base core)
find_package(wxWidgets REQUIRED)
include(${wxWidgets_USE_FILE})
set(minimal_SRCS minimal.cpp)
if(WIN32)
set(minimal_SRCS ${minimal_SRCS} minimal.rc)
endif(WIN32)
add_executable(minimal WIN32 ${minimal_SRCS})
target_link_libraries(minimal ${wxWidgets_LIBRARIES})
Те, кому сразу не терпится попробовать, могут скомандовать в директории minimal
cmake ./
после чего вызывать make. Процесс сборки с помощью make-файла, сгенерированного CMake, выглядит довольно красочно (рис. 1).

рис. 1
Рисунок 1. Make-файлы CMake в консоли Linux.

После окончания сборки можно скомандовать
minimal
и посмотреть на работу программы, которую мы не написали, но собрали (рис. 2).

рис. 2
Рисунок 2. Приложение wxWidgets под Linux.

Прежде чем разбирать этот пример, рассмотрим кратко структуру языка сценариев CMake. На протяжении всех следующих статей мы, в основном будем заниматься изучением этого языка. Элементы языка сценариев CMake можно разделить на три категории: переменные, свойства и команды. Переменные играют в проектах CMake такую же важную роль, как и в файлах make. Настройка параметров сборки проекта выполняется, в основном, путем присвоения переменным различных значений. С помощью свойств проект CMake получает различные сведения о состоянии системы, для которой генерируются сборочные файлы. Свойства можно рассматривать как переменные, доступные только для чтения. Значения свойств устанавливаются средой CMake. Команды, как вы, конечно, догадались, выполняют различные действия с переменными и свойствами CMake и управляют процессом генерации файлов сборки. Важной особенностью языка CMake является его расширяемость. Модули CMake, о которых говорилось выше, позволяют определять новые переменные, свойства и команды. Именно благодаря расширяемости языка CMake этот пакет может использоваться для сборки столь разных проектов.
Где найти документацию
Общее описание синтаксиса языка CMake можно найти в интернете на странице cmake.org/HTML/Documentation.html. Там же есть ссылка на бумажную книгу Bill Hoffman, Mastering CMake (насколько я знаю, на русском языке не издавалась). Что касается расширений CMake, описания многих из них входят в стандартную документацию. Описания остальных расширений следует искать на сайтах тех проектов, для которых они предназначены.
Не следует думать, что расширения языка CMake чрезмерно его усложняют. Базовый набор команд переменных и свойств языка не очень велик, а расширения, как правило, связаны с конкретными пакетами, средами и платформами разработки. Если вы не ставите перед собой цель стать эрудитом в области CMake, вам потребуется знать только те расширения, которые нужны для используемых вами инструментов, а их, как правило, тоже не много.
Перейдем теперь к рассмотрению файла CMakeLists.txt. Описание сборки начинается с команды project()
Команда project() задает имя проекта, а также позволяет указать (в качестве необязательного параметра) язык программирования. В качестве имени проекта мы выбираем имя компилируемой программы (это не обязательное правило CMake, но так удобнее). Язык программирования (С, CXX, Java) можно не указывать. В этом случае CMake попробует «угадать» язык по расширениям файлов исходных текстов. Команда project() – не просто элемент оформления файла CMakeLists.txt. Помимо прочего, эта команда определяет переменные projectname_SOURCE_DIR и projectname_BINARY_DIR. Как нетрудно догадаться, эти переменные содержат имена директории исходных тестов (обычно та же директория, в которой расположен файл CMakeLists.txt) и директории в которой будет сохранен результат сборки. Файлы программ по умолчанию собираются в той же директории, где расположен файл CMakeLists.txt, а файлы библиотек – в поддиректории lib этой директории.
С большой буквы или с маленькой?
Язык CMake обладает частичной регистро-независимостью. Имена команд не зависят от регистра (IF(), If(), if() и iF() – одна и та же команда), в то время как имена свойств и переменных – зависят. В связи с регистро-независимостью имен команд возникает вопрос, как правильнее писать – большой буквы, с маленькой или полностью заглавными буквами. Разные авторы пишут по-разному (в документации CMake имена команд пишутся полностью строчными буквами, а во многих файлах CMake, написанных самими разработчиками – заглавными). Лично я пишу имена команд с маленькой буквы, и буду следовать этому принципу в статьях данной серии.
Команда cmake_minimum_required() позволяет задать минимальный номер версии CMake, требуемый для сборки проекта. Эта команда не является обязательной, но она весьма полезна, если файл CMakeLists.txt использует возможности, появившиеся в последней версии CMake, которая пока что установлена не у всех. Если установленная в системе версия CMake не соответствует минимальным требованиям файла CMakeLists.txt, будет выдано сообщение об ошибке.
Следующая команда, set(), является одной из наиболее часто используемых команд в файлах CMake. Команда set() позволяет присваивать значения переменным. Первым аргументом команды должно быть имя переменной (если переменной с этим именем ранее не существовало, она будет создана), далее следует список значений, которые присваиваются переменной (одной переменной можно присвоить сразу несколько значений). Например, в команде
set(wxWidgets_USE_LIBS base core)
Мы присваиваем переменной wxWidgets_USE_LIBS (которая должна содержать список требуемых программе модулей wxWidgets) значения base и core. Переменные CMake можно использовать рекурсивно, то есть добавлять новые значения к списку уже имеющихся, как например, в строке
set(minimal_SRCS ${minimal_SRCS} minimal.rc)
где к значениям переменной minimal_SRCS добавляется еще одно значение. Обратите внимание на конструкцию ${имя_переменной}. Этак конструкция позволяет получить значение переменной с указанным именем (без символа $ и фигурных скобок CMake «не понял» бы, что тут имеется в виду имя переменной). Правила использования имен и значений переменных в CMake похожи на правила для переменных окружения в сценариях оболочки.
Команда find_package() – одна из важнейших команд CMake. В следующей статье мы остановимся на этой команде подробнее, сейчас скажем только, что find_package() загружает расширения CMake. В нашем примере загружается расширение wxWidgets, предназначенное для генерации файлов сборки программ, использующих wxWidgets. Важно подчеркнуть, что команда find_package() не ищет сами библиотеки, она только загружает расширение CMake. Поиск библиотек, определение их версий и прочие действия, связанные с библиотеками, выполняет загруженное расширение. Естественно, что поскольку система CMake сама не выполняет сборку приложений, библиотеками она «интересуется» лишь с точки зрения генерации инструкций для сборочных файлов. В нашем примере команда find_package() вызывается с опцией REQUIRED. тем самым мы указываем CMake, что если расширение для работы с wxWidgets не найдено, генерация файлов, необходимых для сборки приложения, выполняться не может.
Команда add_executable() относится к числу команд, которые устанавливают цели сборки. Данная команда указывает, что целью сборки является исполнимый файл программы. Если бы мы собирали библиотеку, следовало бы использовать команду add_library(). Команда add_executable() позволяет указать имя собираемого файла и связать с ним исходные тексты. Обратите внимание на опцию WIN32, переданную команде add_executable(). Эту опцию потребовалось упомянуть для того, чтобы при генерации файлов сборки на платформе Windows были созданы файлы сборки графического приложения (а не консольного, как это будет по умолчанию). В Linux нет принципиальной разницы между структурой исполнимого файла графической и консольной программы (в Widows – есть, интересующихся читателей отсылаю к соответствующей литературе). Как же отреагирует CMake для Linux на присутствие опции WIN32? правильный ответ – никак, пакет ее просто проигнорирует. Помимо файлов программ и библиотек в проектах CMake можно указывать и другие цели, например, установку и упаковку приложений.
Последняя команда файла target_link_libraries() указывает CMake, какие инструкции следует добавить в сборочный файл для подключения к проекту сторонних библиотек. Первый аргумент команды – цель, определенная выше командой add_executable() (вообще говоря, целей сборки может быть несколько), далее идет список внешних библиотек, в которых нуждается эта цель. В нашем примере этот список хранится в переменной wxWidgets_LIBRARIES, определенной в расширении wxWidgets. На содержимое переменной wxWidgets_LIBRARIES влияет переменная wxWidgets_USE_LIBS, вот почему значение этой переменной следует присвоить до обращения к команде find_package().

Сборка c помощью CMake на платформе Windows
Наш файл CMakeLists.txt уже содержит некоторые элементы, которые должны пригодиться при генерации сборочных файлов под Windows. Тем не менее, на этой платформе нас ждут некоторые сложности. Вызваны эти сложности тем, что файловая система Windows гораздо более хаотична, чем файловая система Linux. В Unix-системах существуют устойчивые традиции касательно того, где должны быть расположены те или иные файлы. Например, файлы разделяемых библиотек следует искать (и устанавливать) в директориях /lib, /usr/lib, /usr/local/lib. В Windows путаницы гораздо больше. Файлы тех же разделяемых библиотек могут располагаться либо в системных каталогах Windows, либо в собственных каталогах программ. В дополнение к этому файловая система Windows не имеет общего корня, как это принято в Unix-систмах. Все эти особенности приводят к тому, что под Windows пакету CMake гораздо труднее найти места расположения требуемых библиотек. Принимая во внимание этот факт, а также то, что консоль в Windows гораздо менее популярна, в среде Windows рекомендуется использовать специальную утилиту с графическим интерфейсом (рис. 3). Такая же программа есть и в пакете CMake для Linux, но в нашей любимой ОС удобнее пользоваться консолью.

рис. 3
Рисунок 3. CMake GUI для Windows.
Главная задача CMake GUI – генерация файлов сборки приложения на основе файлов CMakeLists.txt для различных средств сборки, используемых на платформе Windows. Попутно эта утилита позволяет присвоить значения некоторым переменным CMake, для которых сам пакет CMake не смог найти подходящие значения (или «не уверен» в их правильности). Отметим, что эти дополнительные настройки влияют на получаемые в результате генерации файлы сборки, но не влияют на содержимое самого файла CMakeLists.txt.
Генерация файлов сборки с помощью CMake GUI состоит из двух этапов: настройки и собственно генерации. Первая операция выполняется по команде Configure, вторая – по команде Generate. В процессе конфигурации утилита позволяет вам выбрать среду разработки из довольно внушительного списка и настроить значения «сомнительных» переменных. В процессе генерации утилита может выявить синтаксические ошибки в файле CMakeLists.txt. Если в качестве целевой среды сборки выбрана среда Visual Studio, CMake для Windows генерирует файл ALL_BUILD.vcproj. Если целью является компилятор Borland C++, в результате создается make-файл для консольной версии компилятора (рис. 4).

рис. 4
Рисунок 4. Консольная сборка под Windows выглядит почти так же, как и под Linux. Вот она, кросс-платформенность! ;)
Выполним сборку, мы получаем то же приложение, что и под Linux (рис. 5)
рис. 5
Рисунок 5. Программа minimal,Windows-версия.
В этой статье мы лишь бегло ознакомились с языком CMake. В следующнй статье нас ждет более подробное знакомство с переменными и командами языка CMake, а так же с ключами, позволяющими управлять консольным вариантом программы.
Пример проекта CMake для этой статьи

Комментариев нет:

Отправить комментарий