C
July 2013 30

Краткое введение в GNU autoconf

From Sandbox
I saw a book entitled «Die GNU Autotools» and I thought «My feelings exactly». Turns out the book was in German1.

Можно долго рассуждать о несовершенстве сего инструментария, о превосходстве CMake/QMake/подставьте_вашу_любимую систему сборки, но проекты, использующие autotools, окружают нас повсюду, и стоит как минимум знать, что это за зверь и с чем его едят, чтобы при попытке сделать, а то и отправить разработчикам патч, не править автосгенерированные файлы, чем я не так давно занимался.

Так же следует понимать, что именно autoconf системой сборки не является вообще, это система конфигурации перед сборкой. autoconf почему-то многие считают неким монстром, «проверяющим 15 давно несуществующих версий компилятора Fortran, а потом поддержку ключей этими компиляторами», что не совсем верно, ибо оно делает ровно то, что ему скажут. Другое дело, что многие просто копипастят его конфиг из проекта в проект, в итоге результат получается ужасающим.

В данной статье (планируется всё же осилить цикл) я хотел бы рассказать про autoconf, зачем он нужен и как его использовать.

N. B. К статье я подготовил архив с исходниками, можно его скачать.

Лучше всего пояснить, зачем используется autoconf на простом примере сферической программы в POSIX-окружении.

Итак, предположим, у вас есть программа, состоящая из одного исполняемого файла, которая читает строку из конфига и пишет в лог. Это неплохой сферический пример, так как очень многие программы помимо своей полезной нагрузки именно это и делают:
#include <stdlib.h>
#include <stdio.h>

void main (int argc, char**argv[])
{
        FILE* config = fopen ("/etc/hellolog.conf", "r");
        FILE* log = fopen ("/var/log/hellolog.log", "a");
        char*line;
        getline (&line, NULL, config);
        fprintf (log, "Line from config %s", line);
        fclose(config);
        fclose(log);
        free(line);
}


И простой Makefile для её сборки:
#!/usr/bin/make -f
SOURCES = main.c
all: hellolog
hellolog: $(SOURCES)
        gcc -o $@ $(SOURCES)
clean:
        rm hellolog

.PHONY: all clean


Если в процессе прочтения будут какие-то вопросы по Makefile-ам, настоятельно рекомендуется прочитать доку по make.

В принципе, бери да пользуйся, но по-хорошему программу надо бы в систему ещё и установить. Догадаться о том, что надо скопировать исполняемый файл в /bin можно, но лучше всё же сделать цели install и uninstall заодно:

install:
        install hellolog $(DESTDIR)/usr/bin/hellolog
uninstall:
        rm $(DESTDIR)/usr/bin/hellolog


install — *nix-овая утилита, которая помимо копирования файла выполняет манипуляции с правами доступа к нему.
DESTDIR здесь нужен, чтобы была возможность проводить установку не сразу в систему, а во временную директорию, чтобы система сборки пакетов могла их оттуда вычитать и упаковать. Мы же помним, что использовать make install напрямую — это очень плохо, правда?

Остаётся одна немаловажная проблема — все пути у нас захардкодены. Если кому-то нужно установить программу в домашнюю директорию, в /opt/ или просто используется дистрибутив, чихать хотевший на FHS, возникнут проблемы.

В принципе, мы можем принимать пути к нужным директориям в качестве аргументов make как делаем это с DESTDIR (make переопределяет заданные в Makefile значения, так что можно сделать и умолчания. Для начала модифицируем исходный код:

...
#define CONFIG_PATH CONFDIR"/hellolog.conf"
#define LOG_PATH LOCALSTATEDIR"/helloconf.log"

void main (int argc, char**argv[])
{
        printf ("Config %s Log %s\n", CONFIG_PATH, LOG_PATH);
        FILE* config = fopen (CONFIG_PATH, "r");
        FILE* log = fopen (LOG_PATH, "a");
...


Теперь добавим определения нужных путей в Makefile, попутно вынеся их в CFLAGS, чтобы было удобнее реиспользовать при компиляции нескольких файлов, а так же модифицируем цели install и uninstall:

#!/usr/bin/make -f
SOURCES = main.c
prefix = /usr/local
bindir = $(prefix)/bin
sysconfdir = $(prefix)/etc
sharedstatedir = $(prefix)/var
CFLAGS = -DCONFDIR='"$(sysconfdir)"' -DLOCALSTATEDIR='"$(sharedstatedir)"'
all: hellolog
hellolog: $(SOURCES)
        gcc $(CFLAGS) -o $@ $(SOURCES)
clean:
        rm hellolog
install:
        install hellolog $(DESTDIR)$(bindir)/hellolog
uninstall:
        rm $(DESTDIR)$(bindir)/hellolog
.PHONY: all clean install uninstall


Уже намного лучше. Мы можем сделать make prefix=/opt/hellolog && make install prefix=/opt/hellolog и изолировать файлы своей программы в этой директории. Проблема теперь в том, что целей сборки может быть больше, и каждый раз писать кучу параметров не вполне удобно. По-хорошему всё надо вынести в некий настроечный скрипт, который получит параметры конфигурации, а в дальнейшем просто использовать make.

В бородатые времена такие скрипты писались вручную, заодно в целях переносимости кода (это у нас тут только POSIX используется, в настоящих программах ещё куча библиотек, причём некоторые из них взаимозаменяемы в какой-то части) в него включали проверки зависимостей и платформозависимые изменения логики Makefile. В какой-то момент количество скриптового кода, который передовой китайской техникой реиспользования кода под названием «копипаст» переносился из проекта в проект, стало превышать мыслимые пределы. В итоге один находчивый человек решил вынести часто используемые куски в макросы на M4 (M+4 буквы слова Macro, язык макросов разработанный Керниганом и Ритчи), что вылилось в autoconf. В дальнейшем он получил большое распространение, а затем на его инфраструктуре были созданы инструменты automake и libtool. Тем не менее, суть autoconf осталась прежней (набор макросов) и он может с успехом использоваться отдельно.

Посмотрим, что мы можем сделать с нашей игрушечной программой. В принципе, набор изменений крайне небольшой и касается только Makefile. Предопределёные значения путей заменим на placeholder-ы, а сам Makefile переименуем в Makefile.in:

prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
sysconfdir = @sysconfdir@
sharedstatedir = @sharedstatedir@


Так же добавим минимально полезный конфигурационный файл configure.ac:

AC_INIT([hellolog], [1.0])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT


Запускаем autoreconf и получаем в текущей директории файл configure, который имеет всем привычный формат командной строки. Делаем ./configure --prefix=/opt && make && ./hellolog и видим, что все пути прописаны правильно. Теперь посмотрим, что же произошло «под капотом».

Единственный файл, который принимает autoconf — это configure.ac, являющийся обычный Bourne-shell скриптом, использующим макросы, соответственно AC_INIT и AC_OUTPUT являются обязательным скелетом, AC_CONFIG_FILES же указывает список файлов, в которых необходимо провести подстановки. Тут же можно сделать ещё кучу разных действий наподобие проверки наличия зависимостей. Я рекомендую для этого использовать pkg-config, для него существует отдельный набор макросов. Далее генерируется скрипт configure, которому не нужно ничего кроме Bourne-совместимого шелла и awk (раньше использовался sed).

./configure в свою очередь после проверок генерирует скрипт config.status, содержащий нужные параметры подстановки и запускает его. А тот уже в свою очередь генерирует файлы со значениями этих подстановок. Так что если у вас поменялся только Makefile, то достаточно запустить лишь config.status.

Итого, тулчейн выглядит так: autoreconf + configure.ac -> configure -> config.status -> итоговые файлы.

В принципе, ничто не мешает использовать autoconf вместе с вашей любимой средой сборки. Я, например, использую с MSBuild для своих программ, заточенных под Mono, Makefile-враппер для этого тривиален.

Ссылки
Мануал по GNU Make
Мануал по M4
Мануал по Autoconf (на английском)

Примечания
1. die (читается как «ди») — определённый артикль множественного числа в немецком.
+58
32.5k 213
Comments 24
Top of the day