UNIX Средства

А теперь мы должны рассмотреть вопросы переносимости программ. Переносимость в мире UNIX имеет чрезвычайно большое значение. Количество реализаций UNIX, её клонов и UNIX-подобных систем весьма велико, и невозможно заранее предвидеть, на какой из них будет работать ваша программа. В разных реализациях могут быть разные версии компилятора, может быть по-разному организована система стандартных каталогов для исполняемых файлов. Предлагать пользователю самостоятельно редактировать make-файлы в соответствии со своей системой – дело недопустимое. Необходимо, чтобы пользователь мог установить программу всего за несколько простых шагов. Следовательно, разработчик должен приложить к программе скрипт, который исследовал бы платформу пользователя и, в соответствии с ней, автоматически сгенерировал наиболее подходящий make-файл.

Ясно, что такой скрипт будет очень сложным по структуре, и написание его вручную чревато многочисленными ошибками. К счастью, этого делать и не надо. В наборе инструментов GNU имеются средства, автоматически создающие такой скрипт (иногда они упоминаются под собирательным названием Autotools).

Итак, пользователь получает ваш дистрибутив, состоящий из файлов исходного кода, среди этих файлов должен быть и тот самый вышеуказанный скрипт. Его общепринятое название – configure.

Далее перед пользователем стоят всего две задачи. Первая – запустить этот скрипт.

./configure

В результате должен сгенерироваться Makefile. А, поскольку есть Makefile, то вторая задача – запустить хорошо знакомую нам программу make.

make

Будет собран запускаемый файл вашей программы. Пользователь может поместить его в стандартный каталог с исполняемыми программами с помощью команды make install (предварительно зайдя в систему в режиме суперпользователя), удалить его оттуда командой make uninstall, очистить дистрибутив от сгенерированных файлов командой make clean и т. д.

Итак, задача разработчика – подготовить скрипт configure.

Вначале создадим в каталоге проекта всего два простых файла: Makefile.am и configure.ac (желательно перед этим удалить оттуда, все файлы, что мы создавали раньше, оставить только problem.h, problem.cpp, main.cpp).

Makefile.am

bin_PROGRAMS=kalkul

kalkul_SOURCES=problem.h problem.cpp main.cpp

configure.ac

AC_INIT(main.cpp)

AM_INIT_AUTOMAKE(kalkul,0.1)

AC_PROG_CC

AC_PROG_CXX

AC_PROG_INSTALL

AC_OUTPUT(Makefile)

Этого вполне достаточно для формирования нужного нам скрипта. Значения этих записей, наверное, интуитивно понятны. bin_PROGRAMS=kalkul указывает, как должен называться конечный запускаемый файл. kalkul_SOURCES=problem.h problem.cpp main.cpp указывает на все исходные файлы, участвующие в сборке этой программы. Если бы наша программа называлась как-нибудь по-другому (например reader), то вместо kalkul_SOURCES стояло бы reader_SOURCES и т. д.

Скрипт configure.ac должен всегда начинаться директивой AC_INIT и заканчиваться AC_OUTPUT. Его команды означают следующее.

AC_INIT(main.cpp) является инициализацией этого скрипта. Ему в качестве параметра передаётся название любого из исходных файлов. Он проверяет, находится ли в данном каталоге такой файл. И, если он находится, значит каталог действительно является рабочим.

AM_INIT_AUTOMAKE(kalkul,0.1) указывает, что мы будем использовать утилиту automake. Параметры указывают на название и версию, которые программа должна получить после сборки.

AC_PROG_CC и AC_PROG_CXX указывают, каким синтаксисом и с применением каких библиотек программа написана. Это помогает выбрать соответствующий компилятор. Наша программа написана целиком на C++ и использует стандартную библиотеку C++, поэтому здесь мы можем оставить только AC_PROG_CXX. Если бы мы использовали предыдущую, C-версию программы, можно было бы оставить только AC_PROG_CC. В крупных программах, где используются элементы и того и другого, пишутся обе директивы.

AC_PROG_INSTALL указывает, что надо в make-файле сформировать цель install, чтобы пользователь мог командой make install установить программу в системный каталог.

AC_OUTPUT завершает скрипт и указывает, что конечный файл должен называться Makefile.

Теперь в консоли заходим в каталог проекта, и даём по очереди следующие команды:

aclocal

autoconf

touch README AUTHORS NEWS ChangeLog

automake -a

./configure

make

После всего этого, если всё прошло нормально, в проекте должен появиться исполняемый файл. Теперь, если вы хотите сформировать дистрибутив для распространения, дайте команду

make dist

В папке проекта должен появиться архив kalkul-0.1.tar.gz

Можете теперь его выкладывать в интернете или рассылать друзьям по электронной почте. :-)

Что же должен сделать получатель вашего творения? Вначале распаковать архив.

gunzip kalkul-0.1.tar.gz

tar xf kalkul-0.1.tar

Затем зайти в каталог с дистрибутивом.

cd kalkul-0.1

Автоматически сформировать Makefile, который будет зависеть от конфигурации его операционной системы.

./configure

Затем собрать программу.

make

И установить её в системном каталоге, предварительно зайдя в режиме суперпользователя.

su

make install

exit

Чтобы удалить её, нужно указать make uninstall в режиме суперпользователя:

su

make uninstall

exit

Разберём команды, которые мы сейчас вводили:

aclocal сканирует файл configure.ac и, в зависимости от его директив, формирует макросы, предназначенные для autoconf и automake; эти макросы сохраняются в файле aclocal.m4;

autoconf формирует скрипт configure в зависимости от содержимого configure.ac;

automake формирует скрипт Makefile.in в зависимости от содержимого Makefile.am; в дальнейшем пользователь, запустив configure, сформирует Makefile на основании Makefile.in. Флаг -a означает, что, если программа не найдёт в каталоге проекта файлов install-sh, missing, INSTALL, COPYING, depcomp, она автоматически создаёт их.

Если мы просто запустим команды в таком порядке, то automake выдаст много «жалоб» и откажется работать. Дело в том, что дополнительная задача automake – проверка соответствия нашего дистрибутива стандартам GNU. В соответствии с этими стандартами, в дистрибутиве обязательно должны присутствовать файлы NEWS, README, AUTHORS, ChangeLog. Чтобы избавить себя от выслушивания жалоб, создадим эти файлы командой touch NEWS README AUTHORS ChangeLog. Системная утилита touch меняет время модификации файла на текущее. Если указанного файла нет, то он создаётся пустым.

Вообще, если уж мы начали разговор о стандартах GNU, нам придётся признать, что мы сейчас сделали всё неправильно. Мы сильно упростили задачу, чтобы сделать её более наглядной.

По стандарту в корневом каталоге проекта расположены только текстовые файлы README, INSTALL, AUTHORS, THANKS, ChangeLog, COPYING, а также файлы, имеющие отношение к сборке программы. Все основные файлы проекта обычно располагаются во вложенных каталогах. Вложенные каталоги обычно следующие:

src – содержит все файлы исходного кода;

lib – необязательный каталог; содержит исходные коды библиотек, используемых сразу несколькими программами, а также участков кода, имеющих отношение к переносимости программ;

doc – содержит документацию к программе;

m4 – содержит макросы для autoconf, которые вы, возможно, захотите передать другим разработчикам;

intl – содержит участки программы, отвечающие за интернационализацию программы, то есть перевода её интерфейса на разные человеческие языки;

po – содержит списки сообщений, с помощью которых программа общается с пользователем; здесь же – варианты этих сообщений на языках разных народов, предназначенные для интернационализации.

Возможны и другие варианты.

Давайте создадим в нашем проекте каталог src и перенесём туда файлы problem.h, problem.cpp, main.cpp. Все остальные файлы и каталоги удалим.

В корневом каталоге проекта создадим файл Makefile.am. Поместим в него следующий текст.

SUBDIRS = src

В каталоге src создадим другой файл с точно таким же названием Makefile.am. Его содержимое:

bin_PROGRAMS=kalkul

kalkul_SOURCES=problem.h problem.cpp main.cpp

В корневом каталоге создадим файл configure.ac. Текст его следующий.

AC_INIT(src/main.cpp)

AM_INIT_AUTOMAKE(kalkul,0.1)

AC_PROG_CC

AC_PROG_CXX

AC_PROG_INSTALL

AC_OUTPUT(Makefile src/Makefile)

Обратите внимание: в начальном макросе AC_INIT мы указываем, что исходные файлы расположены в каталоге src, а в конечном AC_OUTPUT указываем сразу на оба формируемых файла Makefile.

Сгенерируем конфигурационный скрипт обычным образом.

aclocal

autoconf

touch README AUTHORS NEWS ChangeLog

automake -a

Соберём программу.

./configure

make

В папке src появился исполняемый файл. Теперь создадим архив.

make dist

Обратите внимание на такую строку в сгенерированном make-файле:

DEFS = -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"kalkul\" -DVERSION=\"0.1\"

Таким путём make-файл указывает на имя и версию нашего пакета. Мы передали эти параметры с помощью директивы AM_INIT_AUTOMAKE(kalkul, 0.1) в файле configure.ac. Мы можем таким же образом передать и много других параметров. Если их будет слишком много, то эта строка в make-файле станет слишком длинной. Это чревато следующими трудностями. Во-первых, в такой длинной строке трудно визуально найти ошибки. Во-вторых, некоторые UNIX-системы имеют лимит на длину строки в скриптах, и, если мы превысим этот лимит, скрипт не будет обрабатываться.

Существует и другой способ, при котором эти параметры указываются в заголовочном файле config.h с помощью макроса #define. Давайте перепишем сборочные скрипты с использованием этого заголовочного файла. Сам файл будет сформирован автоматически. Нам же нужно внести небольшие изменения в скрипты.

Удалите из проекта все файлы, кроме исходников (problem.h, problem.cpp, main.cpp).

В начало каждого из этих файлов исходников необходимо вставить макрос, требующий включения в них заголовочного файла config.h (и добавить к нему макрос защиты от повторного включения того же файла). Самого файла, правда, пока нет. Ну ничего, к моменту компиляции он будет сгенерирован.

Включите следующий текст в начало файлов main.cpp, problem.cpp, problem.h

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

В корневом каталоге проекта создайте новый configure.ac.

AC_INIT(src/main.cpp)

AM_CONFIG_HEADER(src/config.h)

AM_INIT_AUTOMAKE(kalkul,0.1)

AC_PROG_CC

AC_PROG_CXX

AC_PROG_INSTALL

AC_OUTPUT(Makefile src/Makefile)

От предыдущего он отличается только тем, что содержит директиву AM_CONFIG_HEADER(src/config.h), указывающую, что такой заголовочный файл должен быть сформирован. Оба файла Makefile.am остаются без изменений.

В корневом каталоге проекта:

SUBDIRS = src

В каталоге src:

bin_PROGRAMS=kalkul

kalkul_SOURCES=problem.h problem.cpp main.cpp

Команды по формированию конфигурационного скрипта те же, за исключением того, что к ним добавляется autoheader. Обратите внимание: эти команды должны даваться строго в порядке, указанном ниже:

aclocal

autoconf

touch NEWS README AUTHORS ChangeLog

autoheader

automake -a

В результате, помимо всего прочего, сформировался файл config.h.in, в котором указаны все макросы препроцессора, которые войдут в будущий заголовочный файл config.h. Сам же config.h будет формироваться на машине пользователя по команде ./configure, и его структура будет зависеть от конфигурации компьютера пользователя. Давайте проделаем работу пользователя.

./configure

make

Посмотрим теперь на Makefile. Вместо длинной строки, указанной выше, мы видим компактную запись, указывающую, что необходимую информацию можно найти в заголовочном файле.

DEFS = -DHAVE_CONFIG_H

Теперь, как обычно, создадим дистрибутив.

make dist

Давайте теперь всю эту же процедуру проделаем с предыдущей версией нашего калькулятора, написанной на языке C. Если вы помните, там мы вынуждены были явным образом подключать математическую библиотеку libm, потому что компилятор C неявно её не подключает.

Собственно, здесь нам всего лишь нужно добавить одну строчку. Но рассмотрим всё по порядку.

Создадим новый каталог для проекта kalkulc. Внутри него создадим вложенный каталог src, и перенесём в каталог src все три файла от C-версии нашего калькулятора. Это main.c, calculate.c, calculate.h. Только обратите внимание, что в прошлый раз, работая с этой версией калькулятора, мы не пользовались заголовочным конфигурационным файлом config.h, поскольку мы тогда не пользовались инструментами автогенерации make-файла. Теперь же мы будем пользоваться всеми полученными знаниями в полном объёме, так что этот заголовочный файл нам понадобится.

Вставьте в самое начало каждого из этих трёх файлов (main.c, calculate.c и calculate.h) макрос, требующий включения этого файла. И, заодно, защиту от его повторного включения.

#ifdef HAVE_CONFIG_H

#include <config.h>

#endif

В каталоге проекта создадим файл configure.ac со следующим текстом.

AC_INIT(src/main.c)

AM_CONFIG_HEADER(src/config.h)

AM_INIT_AUTOMAKE(kalkul,0.1)

AC_PROG_CC

AC_PROG_CXX

AC_PROG_INSTALL

AC_OUTPUT(Makefile src/Makefile)

Как видите, он аналогичен тому, что мы делали в C++-версии.

Здесь же создаём файл Makefile.am с одной строчкой.

SUBDIRS = src

А внутри каталога src – ещё один Makefile.am.

bin_PROGRAMS = kalkul

kalkul_SOURCES = calculate.h calculate.c main.c

kalkul_LDADD = -lm

Как видите, что он отличается от C++-версии только строчкой kalkul_LDADD = -lm Эта строчка и указывает, какую библиотеку следует подключить при компиляции.

Все остальные шаги вам уже хорошо знакомы.

aclocal

autoconf

touch NEWS README AUTHORS ChangeLog

autoheader

automake -a

./configure

make