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

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 (читается как «ди») — определённый артикль множественного числа в немецком.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 24

    +25
    Эпиграф просто прекрасен! :)
      +2
      Последний раз читал про autoconf тут: Поколение, затерянное на базаре.
        +2
        Как по мне краткое введение не нужно, примеров предостаточно, да и они настолько тривиальны, что пояснения не нужны. А вот что-то более сложное, с зависимостями, раздельной компиляцией, проверкой версий и т.п. было бы полезнее. Лично я сколько раз не брался, столько-же раз бросал и продолжаю писать простые для понимания Makefile'ы или *sh скрипты.
          +3
          В одной статье всего не уместить, а писать про действительно сложные вещи имеет смысл только в рамках цикла статей, и далеко не в первую очередь, ибо требования к целостности и последовательности изложения никто не отменял, в противном случае всё превратится в бессвязный набор «полезных советов», которые могут быть поняты только теми, кто и так уже в теме.
          +5
          M4 это то кошмарное чудо, с помощью которого генерировался конфиг sendmail'а? И где для обозначения строки используются разнонаправленные кавычки? Жесть… Честно признаться не понимаю как это дожило до сих времён. Оно должно было умереть вместе с sendmail'ом…
          Почему они не стали использовать хотя бы препроцессор С. Благо он везде есть…
            +2
            Оно самое, да. Макросы autoconf при этом достаточно короткие, т. к. используется модульный подход, а их вызовы так и вовсе однострочны в большинстве случаев. А для строк используются не кавычки, а [квадратные скобки], что, в принципе, является не сколько обозначением строки, сколько инструкцией не пытаться распознать содержимое как вызов макроса.
              +6
              sendmail не умер, к сожалению.
                +3
                Ну, если кто-то еще никак не может закопать обратно стюардессу, то это их проблемы.
                Именно из-за безумного конфига sendmail' и столь же безумного генератора конфигов на М4 я свалил на postfix. Но sendmail и М4 мне еще долго снились… Я отлично понимаю почему народ тупо копипастит конфиги m4.
                  +2
                  Они не считают, что это в принципе «проблемы». Могу привести хороший пример: FreeBSD. Стюардесса не менее мертва, но у нее несть числа поклонников в России.
                    0
                    Я сам адепт FreeBSD. ) Sendmail там оставляют только те, кому от него ничего большего не требуется, чем он может в стандартной конфигурации. Все, кто более-менее серьезно работает с почтой, сразу его меняют на postfix или qmail.

                    Впрочем… Но мы его к почтовым серверам не подпускаем…
              0
              Мне стыдно. Топик это одно из того, что никак не смог осилить полностью. Конечно мог изменить существующие конфиги под себя. Но не смотря на понимание отдельных моментов — в целом, никак не лезет в голову. Особенно проблемы на подобии: если есть lib1 использовать её, иначе lib2… в крайнем случае lib5.

              Прочитал статью, и вспомнились тёплые ламповые времена. Осознал, на сколько продвинулись среды и тулчейны разработки.
                +1
                Эм, оно жеж config.h с информацией о наличии библиотек генерирует, дальше можно определиться на ifdef-ах.
                +3
                Есть хороший мануал в виде презентации по autoconf: PDF
                Читается быстро и легко, разъясняются многие тонкие моменты и то, как происходит все внутри. Очень рекомендую новичкам.
                • UFO just landed and posted this here
                    0
                    Я бы посоветовал использовать install -D

                    Это пригодится, если вы решите поставить проект в несуществующую директорию, например в /home/username/pkgs

                    И еще, кажется здесь
                    install:
                    install hellolog $(DESTDIR)$(bindir)/hellolog

                    вы хотели написать prefix вместо DESTDIR
                      0
                      prefix уже содержится в bindir, его туда autoconf кладёт. DESTDIR нужен для того, чтобы скрипт сборки пакета мог установить файлы во временную директорию.
                        0
                        Честно говоря не совсем понимаю зачем это нужно, мы же и так можем регулировать куда положить пакет с помощью ./configure --prefix=something, зачем нужен еще и$(DESTDIR)?
                          +1
                          Ну вот смотрите. Допустим, мы хотим, чтобы программа была установлена в /opt/prog, тогда конфигурационные файлы будут лежать в /opt/prog/etc. Этот путь (заданный через параметры ./configure) оказывается вкомпилированным в исполняемый файл, он там потом их будет после запуска искать. Теперь вспоминаем про то, что большинство дистрибутивов укомплектованы пакетными менеджерами, оперирующими бинарными пакетами, и, по-хорошему, нам вместо того, чтобы засирать систему, нужно использовать их возможности. При сборке пакета, чтобы в нём не оказалось ничего лишнего, относящиеся нему файлы надо положить в отдельную директорию, откуда они потом попадут в архив (внутри deb помимо метаданных и скриптов лежит обычный tar.gz с файлами, в RPM используется cpio). Соответственно надо как-то проинструктировать make install о том, куда ему класть файлы. Через --prefix мы этого сделать не можем, потому что тогда скомпилированный исполняемый файл будет искать свои данные в /tmp/debuild-root/mypackage/debian/tmp/opt/prog/etc, где их после установки пакета, естественно, не будет. Потому нужен какой-то иной способ указать, куда именно сейчас складывать файлы. Этим способом и является переменная DESTDIR.
                            0
                            Все понял идею, спасибо за объяснение. Я честно говоря не занимался создание deb пакетов, поэтому с данной схемой не знаком. Еще раз спасибо за разъяснение.
                              0
                              Да эта схема во всех бинарных дистрибутивах одна и та же. Единственная альтернатива — смонтировать куда-нибудь корень через --rbind, поверх него unionfs, сделать туда chroot, а потом уже внутри make install. Но это слишком заморочено и плохо переносимо между операционками, потому все пользуются стандартными инструментами сред сборки.
                      0
                      die — артикль не множественного числа, а женского рода в номинативе.
                      Множественное число определяется окончанием, причем у разных слов разным.
                        0
                        Да вы что? (подсказка: смотреть правый верхний угол). Я, может, многое успел забыть, но два курса проведённых на инязе даром не проходят.
                        • UFO just landed and posted this here
                            0
                            Autotools — название/имя собственное, их не переводят. И тогда уж не Autotoolsen, а Selbstwerkzeuge

                      Only users with full accounts can post comments. Log in, please.