Основы сборки проектов при помощи Autotools

29 августа 2016

Поговорим о системах сборки, а конкретнее — одной из них, Autotools (также известной под названием GNU Build System). Если вы когда-нибудь собирали программу при помощи волшебной последовательности команд ./configure && make && sudo make install, значит вы использовали Autotools. Откровенно говоря, в новых проектах я бы рекомендовал использовать CMake, ну или хотя бы SCons. Но по историческим причинам многие проекты все еще используют Autotools. Также некоторые люди ошибочно считают Autotools чем-то типа стандарта де-факто. А значит, было бы неплохо примерно понимать, как им пользоваться.

Матчасть

Существует много *nix систем, и все они между собой немного различаются. Например, используют разные компиляторы, хранят заголовочные файлы установленных библиотек по разным путям, и так далее. Autotools нужен для того, чтобы один и тот же проект можно было собрать при помощи уже упомянутой последовательности команд на любой *nix системе.

Заметьте, что речь идет исключительно о *nix системах. Другими словами, проект, использующий Autotools, можно собрать на *BSD, MacOS, различных дистрибутивах Linux и под Cygwin, но не под обычной Windows с Visual Studio и вот этим всем. Например, в PostgreSQL для того, чтобы проект собирался и под Windows, имеется специальный набор скриптов на Perl. Еще из недостатков Autotools, как вы и сами скоро убедитесь, можно отметить использование довольно стремного синтаксиса. Именно поэтому в третьем тысячелетии, вообще-то говоря, лучше использовать какой-нибудь CMake.

Autotools содержит в себе три важных компонента:

  • Autoscan — эта штука сканирует код проекта и выдает на выходе заготовку для будущего файла configure.ac.
  • Autoconf — генерирует из написанного программистом файла configure.ac портабельный скрипт configure, запускаемый при сборке проекта.
  • Automake — помогает в генерации Makefile при запуске скрипта configure. На вход Automake принимает написанный программистом файл Makefile.am, а на выходе выдает Makefile.in, который затем используется configure для генерации мейкфайла.

Разумеется, есть множество не менее важных компонентов, но обычно почему-то выделяют именно эти три. Если пока что не очень понятно, не переживайте. Давайте рассмотрим пример, и все сразу станет ясно.

Типичный пример — сборка GCC

Для начала рассмотрим сборку готового проекта. Сборка PostgreSQL ранее уже подробно рассматривалась в этом блоге, поэтому данный пример не интересен. Рассмотрим лучше сборку GCC.

Установка зависимостей:

sudo apt-get install libmpc-dev libgmp-dev libmpfr-dev dejagnu

Тянем исходники из Subversion репозитория:

svn co svn://gcc.gnu.org/svn/gcc/trunk
cd trunk

Собственно сборка:

./configure
make -j2 all-gcc all-gdb
make check-gcc
# sudo make install

Вот так примерно собираются и устанавливаются проекты, использующие Autotools.

Autotools на примере нового небольшого проекта

Небольшой проект, рассмотренный ранее в заметке Не самый тривиальный пример использования libcurl, как раз использует Autotools. Хотя далее и будет рассмотрено создание совершенно нового проекта с нуля, вы можете ориентироваться на GitHub-репозиторий с исходным кодом проекта из той заметки. Пример еще одного проекта, использующего Autotools, вы найдете в посте Перехват сетевого трафика при помощи библиотеки libpcap. Репозиторий на GitHub с исходниками к посту находится здесь.

Итак, создаем первый файл с исходным кодом на C. Пусть будет shownotegen.c, пока что такого содержания:

#include <stdio.h>
#include <config.h>

int main()
{
  printf("Hello, this is " PACKAGE_STRING "\n");
}

Также создаем Makefile.am:

AM_CFLAGS = -std=c99 -Wall -Wextra -Werror

bin_PROGRAMS=shownotegen
shownotegen_SOURCES=shownotegen.c

Если у вас в проекте несколько программ, то пишем что-то вроде:

AM_CFLAGS = -std=c99 -Wall -Wextra -Werror

bin_PROGRAMS=compress decompress
compress_SOURCES=compress.c
decompress_SOURCES=decompress.c

После создания *.c файлов говорим:

autoscan

Будут созданы файлы autoscan-2.69.log (пустой) и configure.scan. В последнем нужно поправить название пакета, его версию и контактный email, а также дописать после строчки AC_PROG_CC (это важно; если дописать, например, просто в конце файла, то все сломается):

AM_INIT_AUTOMAKE

Переименовываем файл:

mv configure.scan configure.ac

Создаем файлы, необходимые согласно GNU coding standards (без них откажется работать утилита autoreconf):

touch NEWS README AUTHORS ChangeLog

Важно! Сейчас самое время добавить все полученные файлы в Git. Потом будет автоматически сгенерировано много «лишних» файлов.

Теперь, имея configure.ac, создаем кучу разных файлов, не исключая и тот самый configure:

autoreconf -iv

Проверяем, что все работает (под FreeBSD вместо make говорим gmake):

./configure
make

Если все было сделано правильно, появится бинарничек:

./shownotegen

… который можно установить:

sudo make install

… или удалить:

sudo make uninstall

Пакет, который любой может распаковать и сказать ./configure && make && sudo make install со всем необходимым создается так:

make distcheck

Не так уж и сложно!

Тестирование

В некоторых проектах можно прогонять тесты, сказав make check. Чем наш проект хуже?

В Makefile.am дописываем в конце список тестов, например:

TESTS=test.sh

Снова делаем autoreconf, configure и make, как было описано выше. Затем:

make check

Вывод тестов пишется в (название_теста).log. Результат выполнения всех тестов пишется в test-suite.log.

Про зависимости

Также Autotools позволяет проверять наличие в системе зависимостей. Давайте для эксперимента добавим в наш проект зависимость от libcurl.

Поправим shownotegen.c таким образом:

#include <stdio.h>
#include <config.h>
#include <curl/curl.h>

int main()
{
  printf("Hello, this is " PACKAGE_STRING
         ", curl version: %s\n", curl_version());
}

Для поиска библиотек и заголовочных файлов в configure.ac добавляем что-то вроде:

AC_CHECK_HEADER([curl/curl.h])
AC_CHECK_LIB([curl], [curl_version])

У макроса AC_CHECK_LIB первый аргумент — это название библиотеки (как -lcurl), второй аргумент — это название какой-то одной процедуры из библиотеки. Autotools попытается собрать простую программу, дабы убедиться, что в библиотеке есть такая процедура. Это довольно странный способ проверки зависимостей, но вот в Autotools так принято.

Заметьте, что при такой конфигурации скрипт configure просто запишет в config.h:

#define HAVE_LIBCURL 1

… если libcurl будет найден, и не запишет иначе. Если нужно, чтобы configure завершался с ошибкой в случае отсутствия зависимости, пишем:

AC_CHECK_HEADER([curl/curl.h], [], [
  AC_MSG_ERROR([Unable to find curl/curl.h])
])

AC_CHECK_LIB([curl], [curl_version], [], [
  AC_MSG_ERROR([Unable to find libcurl])
])

После правки configure.ac не забываем сказать:

autoreconf -iv

Хочется отметить один интересный момент. Если взять, к примеру, FreeBSD, то в этой системе все устанавливаемые с помощью пакетов заголовочные файлы складываются в /usr/local/include. Но при этом Autotools по дэфолту ничего не ищет в /usr/local/include! Решить эту проблему можно несколькими способами. Например, можно дописать в configure.ac перед AC_CHEK_HEADER и AC_CHECK_LIB что-то вроде:

CFLAGS="$CFLAGS -I/usr/local/include"
CPPFLAGS="$CPPFLAGS -I/usr/local/include"
LDFLAGS="$LDFLAGS -L/usr/local/lib"

Некоторые делают так, а кто-то считает, что все переменные окружения нужно выставить вручную при запуске configure.

Заключение

Выше, конечно, были описаны далеко не все возможности и особенности Autotools. Но этих знаний достаточно примерно в 90% случаев. В остальных же 10%, как всегда, приходится курить методичку.

С одной стороны, не так страшен черт, как его малюют. С другой, как по мне, уж проще описать все зависимости в файле REAMDE.md и написать простенький скрипт make.sh (или make.py), чем использовать Autotools. А вы как считаете?

Метки: , .


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