Copyright (C) А.Гавва V-0.4w май 2004

8. Построение больших проектов

Утилита gnatmake, которая входит в состав системы компилятора GNAT, является прекрасным инструментом для построения небольших проектов. Однако при построении больших проектов или в случае каких-либо особенностей разрабатываемого проекта (например, при использовании большого количества внешних функций языка C) может возникнуть желание использовать для управления построением проекта каких-либо дополнительных средств.

Данная глава предоставляет некоторую информацию по совместному использованию с GNAT достаточно широко распространенной утилиты управления сборкой проектов GNU make, а также пакетов GNU Autoconf и GNU Automake. Следует заметить, что здесь не преследуется цель описать все возможности и варианты использования этих программных инструментов GNU. Поэтому для получения более подробных сведений необходимо обратиться к соответствующей документации GNU. Кроме того, следует учитывать, что утилита GNU make не предназначена для замены утилиты gnatmake, которая входит в систему компилятора GNAT.

8.1 Использование утилиты GNU make

Прежде чем рассматривать какие-либо примеры, следует заметить, что все приведенные далее примеры относятся непосредственно к утилите GNU make. Хотя make является стандартной утилитой и базовый язык оформления файлов управления сборкой проекта одинаков, демонстрируемые примеры используют некоторые развитые свойства, которые характерны именно для версии GNU make.

8.1.1 Общие сведения о GNU make

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

Предположим, что Ада-программа называется dbase и в ее состав входят следующие файлы с исходными текстами: common.adb, scanner.adb и parser.adb. В этом случае файл Makefile может содержать в себе следующее правило:


dbase: common.o scanner.o parser.o
        gnatlink -o dbase

Это правило говорит о том, что исполняемый файл dbase зависит от трех файлов с исходными текстами на языке Ада, и чтобы выполненить обновление файла dbase программа make должна заново скомпоновать его из указанных объектных файлов с помощью команды gnatlink.

Следует заметить, что при написании Ада-программ, которые использует файлы с исходными текстами на языке C основополагающая стратегия использования утилиты GNU make с компилятором GNAT заключается в создании правил, которые гарантируют правильную компиляцию файлов с исходными текстами на языке C, а затем завершают построение всего проекта с помощью выполнения команды gnatmake.

Рассмотрим пример простого файла управления сборкой проекта (Makefile), который будет осуществлять компиляцию Ада-программы main.adb, а также любых Ада-пакетов, которые используются в программе main.adb. Этот пример будет работать со всеми небольшими проектами. Для этого понадобиться отредактировать содержимое переменной OBJS, которая содержит перечень всех объектных файлов соответствующих Ада-пакетов используемых в реальной программе.

Для использования этого файла управления сборкой проекта необходимо просто выполнить команду make, которая осуществит автоматическую сборку проекта, или выполнить команду make clean для удаления всех промежуточных файлов, которые были сгенерированы компилятором.


# Пример простого файла управления сборкой проекта Makefile
#
# Предполагается, что головная программа имеет имя "main.adb"
#

OBJS = main.o somepackage.o

# Правило компиляции файлов с исходными тектами на языке Ада
.adb.o:
        gcc -c $<

.SUFFIXES: .adb .o

# Правило компоновки головной программы
main: $(OBJS)
        gnatbind -xf main.ali; gnatlink main.ali

clean:
        rm *.o *.ali core

8.1.2 Использование утилиты gnatmake в файлах Makefile

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

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


## Этот Makefile предназначен для использования при следующей структуре
## каталогов:
##  - Исходные тексты разделены на серию компонентов программного обеспечения
##    csc (computer software components)
##    Каждый из этих csc помещается в своем собственном каталоге.
##    Их имена соответствуют названиям каталогов.
##    Они будут компилироваться в динамически загружаемые библиотеки
##    (хотя они могут работать и при статической компоновке)
##  - Головная программа (и возможно другие пакеты, которые не являются
##    частями каких-либо csc, располагаются в каталоге верхнего уровня
##    (там где располагается Makefile).
##       toplevel_dir __ first_csc  (sources) __ lib (will contain the library)
##                    \_ second_csc (sources) __ lib (will contain the library)
##                    \_ ...
##
## Хотя этот Makefile предназначен для построения динамически загружаемых
## библиотек, его достаточно легко модифицировать для построения
## частично скомпонованых объектов (необходимо модифицировать показанные ниже
## строки с -shared и gnatlink)
##
## При использовании этого Makefile, можно изменить любой существующий файл
## системы или добавить какой-либо новый файл, и все будет корректно
## перекомпилировано (фактически, будут перекомпилированы только объекты,
## которые нуждаются в перекомпиляции и будет осуществлена перекомпоновка
## головной программы).
##

# Список компонентов программного обеспечения csc проекта.
# Он может быть сгенерирован автоматически.
CSC_LIST=aa bb cc

# Имя головной программы (без расширения)
MAIN=main

# Для построения объектов с опцией -fPIC
# необходимо раскомментировать следующую строку
#NEED_FPIC=-fPIC

# Следующая переменная должна указывать каталог
# в котором расположена библиотека libgnat.so
# Узнать расположение libgnat.so можно с помощью команды
# 'gnatls -v'. Обычно, это последний каталог в Object_Path.
GLIB=...

# Каталоги для библиотек. Этот макрос расширяется в список CSC,
# который перечисляет динамически загружаемые библиотеки.
# Возможно использование простого перечисления:
#   LIB_DIR=aa/lib/libaa.so bb/lib/libbb.so cc/lib/libcc.so
LIB_DIR=${foreach dir,${CSC_LIST},${dir}/lib/lib${dir}.so}

${MAIN}: objects ${LIB_DIR}
	gnatbind ${MAIN} ${CSC_LIST:%=-aO%/lib} -shared
	gnatlink ${MAIN} ${CSC_LIST:%=-l%}

objects::
	# перекомпиляция исходных текстов
	gnatmake -c -i ${MAIN}.adb ${NEED_FPIC} ${CSC_LIST:%=-I%}

# Примечание: в будущих версиях GNAT следующие команды будут упрощены
#             с помощью использования нового инструментального средства
#             gnatmlib
${LIB_DIR}:
	mkdir -p ${dir $ }
	cd ${dir $ }; gnatgcc -shared -o ${notdir $ } ../*.o -L${GLIB} -lgnat
	cd ${dir $ }; cp -f ../*.ali .

# Зависимости для модулей
aa/lib/libaa.so: aa/*.o
bb/lib/libbb.so: bb/*.o
bb/lib/libcc.so: cc/*.o

# Перед запуском программы необходимо убедиться в том,
# что динамически загружаемые библиотеки указаны в пути поиска
run::
	LD_LIBRARY_PATH=pwd/aa/lib:pwd/bb/lib:pwd/cc/lib ./${MAIN}

clean::
	${RM} -rf ${CSC_LIST:%=%/lib}
	${RM} ${CSC_LIST:%=%/*.ali}
	${RM} ${CSC_LIST:%=%/*.o}
	${RM} *.o *.ali ${MAIN}

8.1.3 Автоматическое создание списка каталогов

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

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

Второй способ - более общий. Он требует использования внешней программы find, которая стандартна для большинства систем UNIX. Все подкаталоги, которые расположены внутри корневого каталога проекта, будут добавлены в список каталогов проекта.


# Показанный ниже пример основывается на следующей иерархии каталогов,
# причем, все каталоги могут содержать произвольное количество файлов:
# ROOT_DIRECTORY ->  a  ->  aa  ->  aaa
#                       ->  ab
#                       ->  ac
#                ->  b  ->  ba  ->  baa
#                       ->  bb
#                       ->  bc
# Этот файл Makefile создает переменную с именем DIRS, которая может
# быть использована в любой момент, когда необходим список каталогов
# (см. другие примеры)

# Корневой каталог иерархии каталогов проекта
ROOT_DIRECTORY=.

####
# Первый способ: явно определяет список каталогов.
# Он позволяет определять любое подмножество необходимых каталогов.
####

DIRS := a/aa/ a/ab/ b/ba/

####
# Второй способ: использует шаблоны
# Примечательно, что аргументы показанных ниже шаблонов
# должны звканчиваться символом '/'.
# Поскольку шаблоны также возвращают имена файлов, их необходимо
# отфильтровывать, чтобы избежать дублирование имен каталогов.
# Для этого используются встроенные функции make dir и sort.
# Это устанавливает переменную DIRS в следующее значение (примечательно, что
# каталоги aaa и baa не будут указаны до тех пор, пока не будут
# изменены аргументы шаблона).
# DIRS= ./a/a/ ./b/ ./a/aa/ ./a/ab/ ./a/ac/ ./b/ba/ ./b/bb/ ./b/bc/
####

DIRS := ${sort ${dir ${wildcard ${ROOT_DIRECTORY}/*/ ${ROOT_DIRECTORY}/*/*/}}}

####
# Третий способ: использует внешнюю программу
# Эта команда будет более быстро выполняться на локальных дисках.
# В результате выполнения этой команды переменная DIIRS будет
# установлена в следующее значение:
# DIRS= ./a ./a/aa ./a/aa/aaa ./a/ab ./a/ac ./b ./b/ba ./b/ba/baa ./b/bb ./b/bc
####

DIRS := ${shell find ${ROOT_DIRECTORY} -type d -print}

8.1.4 Генерация опций командной строки для gnatmake

После создания списка каталогов, как было описано выше, можно легко сгенерировать аргументы командной строки, которые будут переданы утилите gnatmake.

С целью полноты этот пример подразумевает, что путь к исходным текстам не соответствует пути к объектным файлам. Таким образом, имеется два различных списка каталогов.


# см. "Автоматическое создание списка каталогов"
# для создания этих переменных
SOURCE_DIRS=
OBJECT_DIRS=

GNATMAKE_SWITCHES := ${patsubst %,-aI%,${SOURCE_DIRS}}
GNATMAKE_SWITCHES += ${patsubst %,-aO%,${OBJECT_DIRS}}

all:
        gnatmake ${GNATMAKE_SWITCHES} main_unit

8.1.5 Преодоление ограничения на длину командной строки

Многие операционные системы ограничивают длину командной строки. Это может вызвать некоторые трудности при работе над большим проектом, поскольку затрудняет передачу списков каталогов, которые содержат файлы с исходными текстами и объектные файлы, в командной строке утилиты gnatmake.

Показанный ниже пример демонстрирует то, как можно установить переменные окружения, которые позволят утилите gnatmake выполнять обработку также как и в случае указания списков каталогов в командной строке запуска gnatmake. При этом ограничения операционной системы позволяют использовать более длинные списки каталогов или, как в большинстве систем, длина списка будет не ограничена.

Подразумевается, что для создания списка каталогов в файле Makefile используются ранее рассмотренные способы. С целью полноты примера предполагается, что путь к объектным файлам (где также располагаются ALI файлы) не соответствует пути к файлам с исходными текстами.

Следует обратить внимание на небольшую хитрость, которая использована в файле Makefile: для повышения эффективности создаются две временные переменные (SOURCE_LIST и OBJECT_LIST), которые немедленно расширяются утилитой GNU make. Примечательно, что этот способ перекрывает стандартное поведение утилиты make, когда переменные расширяются только в момент их фактического использования.


# В этом примере создаются две переменные окружения
# ADA_INCLUDE_PATH и ADA_OBJECT_PATH.
# Они обладают таким же эффектом как и опции -I в командной строке.
# Эквивалентом использования -aI в командной строке будет
# только указание переменной окружения ADA_INCLUDE_PATH,
# а эквивалентом использования -aO является ADA_OBJECT_PATH.
# Естественно, что для этих переменных необходимо использование
# двух различных значений.
#
# Примечательно также, что необходимо сохранение предыдущих значений
# этих переменных, поскольку они могут быть определены перед запуском
# 'make' с целью указания установленных библиотек для GNAT.


# см. "Автоматическое создание списка каталогов"
# для создания этих переменных
SOURCE_DIRS=
OBJECT_DIRS=

empty:=
space:=${empty} ${empty}
SOURCE_LIST := ${subst ${space},:,${SOURCE_DIRS}}
OBJECT_LIST := ${subst ${space},:,${OBJECT_DIRS}}
ADA_INCLUDE_PATH += ${SOURCE_LIST}
ADA_OBJECT_PATH += ${OBJECT_LIST}
export ADA_INCLUDE_PATH
export ADA_OBJECT_PATH

all:
        gnatmake main_unit

8.2 Переносимость в UNIX, пакеты GNU Automake и GNU Autoconf

Если вы используете систему Linux и желаете создать проект, который будет выполняться на различных платформах UNIX, а не только в Linux, то вам необходимо использовать пакеты GNU Autoconf и GNU Automake. Следует заметить, что свободно распространяемое программное обеспечение GNU использует средства этих пакетов очень интенсивно.

Поставляемый в системе Linux пакет GNU Autoconf позволяет создать скрипт командного интерпретатора с именем configure, который настроен под конкретный проект. Когда этот скрипт выполняется, он осуществляет сканирование средств той системы UNIX на которой он выполняется. В результате, этот скрипт осуществляет необходимую подстройку файлов Makefile, которые управляют сборкой проекта. В качестве необязательного дополнения он может генерировать C-файл config.h, который содержит информацию об обнаруженных средствах.

Пакет GNU Automake является дополнением к пакету GNU Autoconf. С помощью утилиты automake он позволяет создавать файлы Makefile.in из шаблонных файлов Makefile.am. Как только работа утилиты automake завершена, для получения окончательных версий файлов Makefile необходимо запустить скрипт configure. После этого остается только выполнить команду make, которая построит проект для любой версии UNIX.

Существует возможность использования средств пакетов GNU Autoconf и GNU Automake с файлами Makefile, которые используются для управления сборкой проектов программ на языке Ада. Для получения подробных сведений об использовании этих пакетов следует обратиться к соответствующей документации или использовать команды "info autoconf" и "info automake".


Copyright (C) А.Гавва V-0.4w май 2004