Система сборки

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

Система сборки представляет собой набор Make-файлов и утилит, организованных в одном каталоге, который монтируется в исходное дерево разрабатываемого продукта.

Основной задачей системы сборки является автоматизация всех этапов создания программного продукта, начиная с организации работ с архивами исходных текстов сторонних производителей и заканчивая выпуском собственного отчуждаемого дистрибутива.

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

Introduction

Система сборки представляет собой набор Make-файлов и скриптов, написанных на языках Bash и Perl. На следующем листинге приведено краткое дерево системы сборки. Основу системы составляет файл core.mk, именно в нем содержатся все основные правила сборки, позволяющие упростить создание пользовательских Make-файлов.

 .
 ├── Makefile
 ├── _kxLab.pm
 ├── apply_patches
 ├── build-config.mk.template
 ├── build_packages_list
 ├── build_pkg_requires
 ├── build_requires
 ├── build_requires_tree
 ├── build_src_requires
 ├── canonical-build
 ├── constants.mk
 ├── core.mk
 ├── dist_clean
 ├── downloads_clean
 ├── global_clean
 ├── install_pkgs
 ├── install_targets
 ├── rootfs_clean
 ├── target-setup.mk
 ├── tree-build-system.mk
 ├── tree-src.mk
 ├── tree.mk
 ├── 3pp
 │   └── fakeroot
 │       └── 0.18
 │           └── Makefile
 ├── pkgtool
 │   ├── change-refs
 │   ├── check-db-integrity.in
 │   ├── check-package.in
 │   ├── check-requires.in
 │   ├── install-package.in
 │   ├── install-pkglist
 │   ├── make-package
 │   ├── make-pkglist
 │   ├── pkginfo
 │   ├── pkglog
 │   └── remove-package.in
 ├── html
 │   └── requires_tree_html.template
 └── scripts
     ├── fill_align
     └── fill_to_size

Обычного пользователя системы сборки из всего набора файлов интересуют лишь файлы constants.mk и core.mk, которые необходимо включать с помощью директивы include в пользовательские Make-файлы.

Дерево системы сборки монтируется в каталог build-system общего дерева исходных текстов собираемых пакетов программ для различных устройств. Общее дерево может выглядеть, например, так:

 .
 ├── .svn
 ├── app
 ├── base
 ├── boot
 ├── build-system
 ├── dev
 ├── doc
 ├── hal
 ├── libs
 ├── net
 ├── products
 ├── secure
 ├── share
 ├── sources
 ├── .svnignore
 └── Makefile

Система сборки не имеет никаких предположений по поводу структуры общего дерева исходных текстов за исключением нескольких каталогов. Рассмотрим эти каталоги.

Directory sources

В каталоге sources предусмотрено создание Make-файлов, с помощью которых осуществляется загрузка исходных пакетов программ для последующей сборки. Структура Make-файлов здесь коренным образом отличается от остальных каталогов, поскольку они предназначены не для сборки компонентов программ, а лишь для загрузки архивов с FTP сервера.

Directory dist

Каталог dist – это временный каталог. Он создается автоматически и предназначен для хранения результатов сборки. Структуру данного каталога мы рассмотрим, когда опишем некоторые возможности системы сборки.

Hello, World

Как известно, наилучшим способом изучения какой-либо системы является практика. Поэтому, прежде чем мы перейдем к подробному описанию системы сборки, приступим сразу к написанию простых программ и Make-файлов.

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

COMPONENT_TARGETS  = $(HARDWARE_BUILD)
COMPONENT_TARGETS += $(HARDWARE_AT91S)

Здесь мы описали две цели: во-первых, это машина разработчика HARDWARE_BUILD, на которой можно сразу проверить полученную программу на работоспособность и, во-вторых, плата, построенная на основе микроконтроллера Atmel AT91SAM7S (HARDWARE_AT91S). Используемые имена целевых устройств можно увидеть в файле build-system/constants.mk. И нам, естественно, необходимо иметь в нашем Make-файле определения, декларированные в constants.mk. Для этого мы воспользуемся директивой include.

COMPONENT_TARGETS  = $(HARDWARE_BUILD)
COMPONENT_TARGETS += $(HARDWARE_AT91S)

include ../../build-system/constants.mk

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

Поскольку мы работаем на машине с архитектурой x86_64, было бы неплохо одновременно построить и 32-битную версию нашей программы. Для этого зададим еще один вариант целевого устройства, но уже с помощью такого понятия системы сборки, как FLAVOUR. Такая возможность предусмотрена в основном для того, чтобы разработчик мог различать некоторые модификации целевых устройств в рамках одной линейки продуктов, построенных на одном и том же центральном процессоре, но имеющих некоторые отличия в периферии. Разумеется, путь, по которому мы идем здесь для создания 32-битного приложения не совсем пригоден для этой работы при создании "боевых" комплексов программ, и здесь мы его применили лишь для демонстрации возможностей системы. Итак, определим еще одну модификацию программы:

COMPONENT_TARGETS  = $(HARDWARE_BUILD)
COMPONENT_TARGETS += $(HARDWARE_AT91S)

include ../../build-system/constants.mk

BUILD_FLAVOURS = hello-32compat

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

 .
 ├── .at91sam7s-newlib
 │   └── at91s
 ├── .build-machine
 │   └── build
 │       └── hello-32compat
 ├── Makefile
 └── main.c

Чтобы не утруждать пользователя необходимостью указывать полные пути к созданным каталогам, система сборки автоматически создает переменную TARGET_BUILD_DIR. Значение данной переменной меняется в зависимости от того, на какую именно цель в данный момент идет сборка нашей программы main.c. Так во время сборки программы на цель HARDWARE_AT91S значение переменной TARGET_BUILD_DIR равно .at91sam7s-newlib/at91s. Во время сборки на машину разработчика HARDWARE_BUILD значением переменной TARGET_BUILD_DIR является .build-machine/build. И, наконец, когда идет сборка 32-битной версии программы, переменной TARGET_BUILD_DIR присваивается значение .build-machine/build/hello-32compat. Стоит отметить, что путь TARGET_BUILD_DIR считается от текущего каталога, в котором расположен наш Make-файл. Пользователь может узнать абсолютное значение текущего пути по значению переменной $(CURDIR), за которую отвечает утилита Make.

Продолжим написание нашего Make-файла. Наша программа "Здравствуй, Мир" состоит всего лишь из одного файла main.c, и список исходных файлов будет коротким:

bin_srcs = main.c

SRCS = $(bin_srcs)

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

Для того чтобы получаемые объектные файлы были расположены в каталоге TARGET_BUILD_DIR, о котором мы говорили ранее, нам надо определить список этих объектных файлов:

bin_objs = $(addprefix $(TARGET_BUILD_DIR)/,$(bin_srcs:.c=.o))

Естественно, теперь нам необходимо определить имя результирующей программы:

bin_target = $(TARGET_BUILD_DIR)/main

Поскольку для сборки программы под 32-битную Intel архитектуру мы используем компилятор, который установлен на машине разработчика, и способный генерировать код под две архитектуры, нам понадобится переопределить управления компилятора во время сборки x86_32 модификации нашей программы:

ifeq ($(FLAVOUR),hello-32compat)
ARCH_FLAGS := -m32 -march=i486 -mtune=i686
CFLAGS     += -DFLAVOUR=32
LDFLAGS    += $(ARCH_FLAGS)
endif

Теперь мы можем определить заключительные строки нашего Make-файла, в которых, используя директиву include, включим в наш файл ядро системы сборки build-system /core.mk.

Приведем Make-файл целиком:

COMPONENT_TARGETS  = $(HARDWARE_BUILD)
COMPONENT_TARGETS += $(HARDWARE_AT91S)

include ../../build-system/constants.mk

BUILD_FLAVOURS = hello-32compat

bin_srcs = main.c

SRCS = $(bin_srcs)

bin_objs = $(addprefix $(TARGET_BUILD_DIR)/,$(bin_srcs:.c=.o))

bin_target = $(TARGET_BUILD_DIR)/main

ifeq ($(FLAVOUR),hello-32compat)
ARCH_FLAGS := -m32 -march=i486 -mtune=i686
CFLAGS     += -DFLAVOUR=32
LDFLAGS    += $(ARCH_FLAGS)
endif

BUILD_TARGETS = $(bin_target)

include ../../build-system/core.mk

$(bin_target): $(bin_objs)
       $(LINK)

Итак, наш Make-файл готов. Отдельно надо упомянуть переменную BUILD_TARGETS. Список BUILD_TARGETS является ключевым. Для целей, упомянутых в этом списке, система сборки не производит никаких действий, однако до тех пор, пока эти цели не построены, система сборки не может приступить к остальным задачам. Основная работа по сборке объектного кода осуществляется с помощью вызова $(LINK). Команда $(LINK) является ключевой и описана в секции общих правил системы сборки. Данные правила предназначены для сборки пользовательских программ, находящихся непосредственно в дереве исходных текстов, в отличие от сторонних программ, которые, как правило, поступают на сборку в виде архивированных исходных пакетов.

Для выполнения сборки нашей программы по запросу $(LINK) система автоматически, в зависимости от содержимого списка $(SRCS), выберет соответствующий компилятор и компоновщик, вызовет команду, представленную значением переменной $(LINK), дождётся ее выполнения, а именно, выполнения всех целей из списка BUILD_TARGETS, и завершит работу.

Теперь, выполнив команду make, мы получим набор файлов, который легче всего представить в виде дерева:

 .
 ├── .at91sam7s-newlib
 │   └── at91s
 │       ├── main
 │       ├── main.d
 │       ├── main.linkmap
 │       └── main.o
 ├── .build-machine
 │   └── build
 │       ├── hello-32compat
 │       │   ├── main
 │       │   ├── main.d
 │       │   ├── main.linkmap
 │       │   └── main.o
 │       ├── main
 │       ├── main.d
 │       ├── main.linkmap
 │       └── main.o
 ├── .makefile
 ├── .src_requires
 ├── .src_requires_depend
 ├── Makefile
 └── main.c

Если наша исходная программа main.c выглядела следующим образом:

#include <stdlib.h>
#include <stdio.h>

int main()
{
#if FLAVOUR == 32
   printf( "\nHello, World! (x86_32 object built for running on x86_64 machine)\n\n");
#else
   printf( "\nHello, World!\n\n" );
#endif
   return( 0 );
}

то выполнение программы main в каталоге .build-machine/build/hello-32compat даст следующий результат:

$ cd .build-machine/build/hello-32compat
$ ./main

Hello, World! (x86_32 object built for running on x86_64 machine)


$

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

Targets and Directories

В предыдущем разделе мы упомянули о таких понятиях как временный каталог сборки TARGET_BUILD_DIR и список главных целей сборки BUILD_TARGETS, в понимании утилиты Make. Рассмотрим эти понятия более подробно.

Temporary Build Directories

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

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

TARGET_BUILD_DIR = .$(TOOLCHAIN)/$(HARDWARE)$(if $(FLAVOUR),/$(FLAVOUR)))

Естественно, пользователю всегда доступны переменные TOOLCHAIN, HARDWARE, FLAVOUR, которые имеют актуальные значения, соответствующие текущей цели. Анализируя значения этих переменных в собственном Make-файле, пользователь может узнать, например, с помощью какого toolchain-а идет сборка в данный момент. Здесь также необходимо подчеркнуть, что имя каталога формируется относительно текущего каталога, в котором расположен Make-файл пользователя, и начинается с точки. С точки зрения Unix-подобных систем, каталоги и файлы, имена которых начинаются с точки, являются скрытыми. Использование скрытых каталогов позволяет не загромождать список файлов пользователя и, кроме того, уберечь систему от потенциальных ошибок при очистке (при выполнении команды make clean), когда, в общем случае, можно обойтись удалением скрытых файлов и каталогов.

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

Reserved Temporary Files

Необходимо сказать, что система сборки создает в каталоге TARGET_BUILD_DIR собственные временные файлы. Пользователь не имеет права создавать или изменять зарезервированные системой сборки временные файлы.

$(TARGET_BUILD_DIR)/.dist
$(TARGET_BUILD_DIR)/.rootfs
$(TARGET_BUILD_DIR)/.requires
$(TARGET_BUILD_DIR)/.requires_depend

Кроме того, в текущем каталоге, где располагается Make-файл пользователя, система сборки может создавать файлы, в которых описаны зависимости текущего пакета от исходных архивов.

$(CURDIR)/.src_requires
$(CURDIR)/.src_requires_depend

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

Main Targets

Система сборки подразумевает наличие трех списков основных целей, в понимании утилиты Make.

Самым важным и обязательным списком, который должен быть определен в пользовательском Make-файле, является список целей сборки BUILD_TARGETS. Если этот список пуст, система сборки не произведет никаких действий. В общем случае, главной целью должно быть создание пакета, который, в свою очередь, может являться предметом поставки потребителю и быть инсталлирован в корневую файловую систему целевого устройства. Здесь следует отметить, что система сборки всего лишь добивается выполнения целей, указанных в списке BUILD_TARGETS и не предпринимает более никаких действий.

Два других списка не являются обязательными и связаны лишь с инсталляцией готовых пакетов. К ним относятся список продуктов PRODUCT_TARGETS и список целей, предназначенных для инсталляции во временную корневую систему устройства, а именно ROOTFS_TARGETS.

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

Destination Directories

Для временного хранения результатов сборки в корне исходного дерева создается каталог dist. Этот каталог содержит подкаталоги трех типов:

  1. Каталоги для временного хранения окружения разработки.
  2. Каталоги для временного хранения готовых пакетов.
  3. Каталоги для временного хранения образов корневых файловых систем с инсталлированными пакетами программ в том виде, в котором они будут находиться в целевой файловой системе.

Рассмотрим более подробно структуру и назначение каталогов.

К первому типу относятся каталоги, имена которых определяются переменной TARGET_DEST_DIR.

Target Destination Directory

Каталог TARGET_DEST_DIR служит для временного хранения окружения разработчика целевой платформы. Туда следует инсталлировать все программы и заголовочные файлы, необходимые для сборки программного обеспечения. Полное имя данного каталога формируется следующим образом:

TARGET_DEST_DIR = $(TOP_BUILD_DIR_ABS)/dist/.$(TOOLCHAIN)/$(HARDWARE)

Здесь переменная TOP_BUILD_DIR_ABS указывает полный путь к корню дерева исходников. Этот путь определяется системой сборки автоматически, что позволяет хранить локальные копии веток репозитория в любом месте файловой системы без дополнительных настроек системы со стороны пользователя.

Инсталлировать результаты сборки в каталог TARGET_DEST_DIR можно двумя способами. Первый способ заключается в использовании команды cp, например, следующим образом:

$ cp -a $(TARGET_BUILD_DIR)/build/libintl.h $(TARGET_DEST_DIR)/usr/include

Однако в случае использования команды cp пользователю необходимо явно создать целевой каталог:

$ mkdir -p $(TARGET_DEST_DIR)/usr/include

перед тем, как копировать файлы.

Наилучшим способом копирования файлов в каталоги TARGET_DEST_DIR является использование утилиты install_targets системы сборки. Утилита install_targets представляет собой простой скрипт на языке Perl. В Make-файлах пользователю рекомендуется указывать полный путь при вызове утилиты install_targets. Для этого в системе сборки предусмотрена переменная BUILDSYSTEM, указывающая путь к каталогу build-system текущего дерева исходников. Так, для копирования файла '$(TARGET_BUILD_DIR)/build/libintl.h' во временное хранилище достаточно записать:

       $(BUILDSYSTEM)/install_targets        \
         $(TARGET_BUILD_DIR)/build/libintl.h \
         $(TARGET_DEST_DIR)/usr/include $(HARDWARE)

Утилита install_targets позволяет инсталлировать множество файлов воспроизводя исходное дерево каталогов в TARGET_DEST_DIR. Приведем более сложный пример использования утилиты install_targets.

       @cd $(TARGET_BUILD_DIR) && \
         CWD=$(CURDIR)            \
           $(BUILDSYSTEM)/install_targets --preserve-source-dir=true \
             $$(find * -path 'lib*' -type f -print) $(TARGET_DEST_DIR) $(HARDWARE)

Здесь происходит копирование всех файлов из каталогов, начинающихся с lib, такими каталогами могут быть, напрмер, '$(TARGET_BUILD_DIR)/lib' и '$(TARGET_BUILD_DIR)/libexec'.

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

Еще более простым способом инсталляции файлов во временное окружение разработки является использование функции install-into-devenv. В разделе User's Makefile приведен пример использования данной функции системы сборки.

Вернемся к нашему примеру "Здравствуй, Мир" и добавим в Make-файл строчку, отвечающую за инсталляцию нашей программы в каталог $(TARGET_DEST_DIR):

$(bin_target): $(bin_objs)
       @$(LINK)
       @CWD=$(CURDIR) $(BUILDSYSTEM)/install_targets $(bin_target) \
                                        $(TARGET_DEST_DIR)/bin $(HARDWARE)

Тогда после выполнения мы получим следующее дерево каталогов:

 .
 └── dist
     ├── .at91sam7s-newlib
     │   └── at91s
     │       └── bin
     │           └── main
     └── .build-machine
         └── build
             └── bin
                 └── main

Здесь важно пояснить, что система сборки не поддерживает модификации устройств (FLAVOURS) с точки зрения автоматической инсталляции. Это связано с тем, что для отдельных модификаций нет необходимости строить полный набор целевого программного обеспечения. Как правило, от конкретной модификации зависит лишь ограниченный набор программ или драйверов устройств. Поэтому пользователь должен самостоятельно определять, какая именно модификация программы должна быть установлена в систему. Принимать такого рода решения следует на этапе формирования дистрибутива из общего набора пакетов.

Target Products Directory

Каталог PRODUCTS_DEST_DIR предназначен для хранения собранных пакетов прикладных программ. Пакеты, собранные для последующего распространения, инсталлируются в данный каталог согласно именам групп. Для каждой группы пакетов создается отдельный подкаталог в каталоге PRODUCTS_DEST_DIR. Имя данного каталога формируется автоматически, согласно текущей цели.

PRODUCTS_DEST_DIR = $(TOP_BUILD_DIR_ABS)/dist/products/$(TOOLCHAIN)/$(HARDWARE)

Пользователю не надо явным образом создавать данный каталог. Достаточно лишь задать список PRODUCT_TARGETS, и система сборки автоматически создаст необходимые каталоги и поместит в них целевые файлы.

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

pkg_basename    = $(NAME)-$(VERSION)-$(ARCH)-$(DISTRO_NAME)-$(DISTRO_VERSION)

pkg_archive     = $(TARGET_BUILD_DIR)/$(GROUP)/$(pkg_basename).$(pkg_arch_suffix)
pkg_signature   = $(call sign-name,$(pkg_archive))
pkg_description = $(call desc-name,$(pkg_archive))
products        = $(call pkg-files,$(pkg_archive))

PRODUCT_TARGETS = $(products)

Здесь ключевыми являются:

Имя группы пакета $(GROUP) определяется пользователем.

  1. переменная pkg_arch_suffix, определяющая расширение имени пакета. На данный момент, расширение равно txz;
  2. функция sign-name, заменяющая суффикс $(pkg_arch_suffix) на sha256;
  3. функция desc-name, заменяющая суффикс $(pkg_arch_suffix) на txt;
  4. функция pkg-files, формирующая полный список файлов, предназначенных для инсталляции в PRODUCTS_DEST_DIR.

Более подробную информацию о пакетах и группах пакетов можно получить в разделе, посвященном утилитам менеджера пакетов pkgtool.

Target Root File System Directory

Каталог ROOTFS_DEST_DIR содержит целевую корневую файловую систему. Имя этого каталога формируется по правилу:

ROOTFS_DEST_DIR = $(TOP_BUILD_DIR_ABS)/dist/rootfs/$(TOOLCHAIN)/$(HARDWARE)

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

ROOTFS_DEST_DIR = $(pkg_archive)

Здесь переменная pkg_archive содержит относительное имя файла пакета.

Service Targets

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

$ make help

Данная команда выведет на экран список всех допустимых целей и, кроме того, выведет список устройств, для которых разработан Make-файл текущего каталога.

Requires Tree

После выполнения всех целей сборки существует возможность создания представленного в JSON-формате дерева зависимостей пакетов или каталогов исходного дерева друг от друга. При этом создается HTML-документ, который сопровождается структурой данных JSON, расположенной в отдельном файле с расширением '.json'.

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

$ make requires_tree

Если не указать целевое устройство, то данная команда построит деревья для всех типов устройств, поддерживаемых Make-файлом текущего каталога.

На рис.1 приведен пример дерева зависимостей пакета bind-9.10.1. Мы построили его с помощью команды

$ HARDWARE=omap5uevm make requires_tree

После выполнения данной команды в каталоге TARGET_BUILD_DIR будет получено три файла с именами omap5uevm.html, omap5uevm.json и omap5uevm.min.json. Последний из которых является сжатой копией JSON-файла.

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

Рис.1. Дерево межпакетных зависимостей

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

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

$ make requires_tree

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

Для того чтобы избежать попыток автоматического построения деревьев по окончании сборки, перед выполнением цели requires_tree осуществляется проверка наличия файла $(TARGET_BUILD_DIR)/.requires, причем проверка эта делается посредством функции GNU Make $(wildcard ...), что обуславливает необходимость для пользователя повторного запуска утилиты Make непосредственно из командной строки.

Packages List

Цель packages_list предусмотрена для создания списка пакетов, предназначенных для инсталляции на целевое устройство.

Для получения такого списка необходимо выполнить следующую команду:

$ make packages_list

Если не указать целевое устройство, то данная команда построит списки для всех типов устройств, поддерживаемых Make-файлом текущего каталога.

Разумеется, посредством переменной HARDWARE можно задать имя одного целевого устройства:

$ HARDWARE=omap5uevm make packages_list

После выполнения данной команды в каталоге TARGET_BUILD_DIR будет получен файл с именем omap5uevm.pkglist, представляющий собой список пакетов, организованный в порядке инсталляции на целевую систему и предназначенный для использования утилитой install-pkglist, входящей в состав набора pkgtool.

Так же как и построение дерева межпакетных зависимостей, создание списка пакетов возможно только в том случае, когда файл $(TARGET_BUILD_DIR)/.requires существует и выполнение основных целей Make-файла данного каталога завершено.

Devices Table

Перед тем как создавать образы файловых систем с помощью таких утилит как genext2fs или populatefs, разработчику необходимо иметь список файлов устройств, которые будут созданы в каталоге /dev. Файлы некоторых устройств необходимы на начальной стадии загрузки системы, когда еще не развернута виртуальная файловая система /dev. Кроме того, файлы устройств могут понадобиться при инсталляции загрузчиков; например LILO, часто используемый на Intel-машинах, требует наличия файлов устройств в каталоге /dev даже в том случае, когда инсталляция LILO осуществляется не на рабочей машине, а в chroot-окружении.

Утилиты genext2fs и populatefs принимают таблицу устройств в виде файла примерно следующего содержания:

# device table

# <name>         <type>   <mode>   <uid>   <gid>   <major>   <minor>   <start>   <inc>   <count>
/dev/console      c        0600     0       0       5         1
/dev/fb0          c        0660     0       18      29        0
/dev/fb1          c        0660     0       18      29        1

/dev/initctl      p        0600     0       0
/dev/kmem         c        0640     0       9       1         2
/dev/kmsg         c        0644     0       0       1         11
/dev/kvm          c        0600     0       0       10        232
/dev/mem          c        0640     0       9       1         1
/dev/mmcblk0      b        0660     0       0       179       0
/dev/mmcblk0p1    b        0660     0       0       179       1
/dev/mmcblk0p2    b        0660     0       0       179       2
/dev/mmcblk0p3    b        0660     0       0       179       3
/dev/mmcblk0p4    b        0660     0       0       179       4

/dev/mtd0         c        0660     0       0       90        0

/dev/null         c        0666     0       0       1         3
/dev/ppp          c        0660     0       16      108       0
/dev/ram0         b        0640     0       6       1         0

/dev/random       c        0666     0       0       1         8
/dev/sda          b        0660     0       6       8         0
/dev/sda1         b        0660     0       6       8         1
/dev/sda2         b        0660     0       6       8         2
/dev/sda3         b        0660     0       6       8         3
/dev/sda4         b        0660     0       6       8         4

/dev/tty          c        0666     0       5       5         0
/dev/ttyS0        c        0660     0       16      4         64
/dev/ttyS1        c        0660     0       16      4         65

/dev/urandom      c        0666     0       0       1         9
/dev/zero         c        0666     0       0       1         5
/var/log/wtmp     f        0644     0       22
/var/run/utmp     f        0644     0       22

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

Рассмотрим теперь, чем занимается система сборки во время выполнения следующей команды пользователя:

$ make devices_table

Просматривая все пакеты из списка $(HARDWARE).pkglist в каталоге $(PRODUCTS_DEST_DIR), система сборки выявляет в них:

  • файлы устройств;
  • файлы, имеющие нестандартные атрибуты, например, файлы с установленными suid, sgid или sticky битами;
  • файлы, принадлежащие не только суперпользователю, но и специальным группам пользователей, например, такие как /var/log/wtmp, uid:gid которого равен root:utmp;

и делает соответствующие записи в выходном файле $(TARGET_BUILD_DIR)/.DEVTABLE .

Кроме пакетов, находящихся в списке $(HARDWARE).pkglist, система сборки проверит наличие пакета $(PRODUCTS_DEST_DIR)/base/init-devices-*.txz, и если данный пакет существует, то система сборки занесет в результирующий список файлы, входящие в данный пакет и удовлетворяющие условиям, описанным выше.

Пакет init-devices-*.txz является специальным пакетом, не предназначенным для инсталляции в автоматическом режиме, и используется только в двух случаях:

  1. Формирование списка устройств для каталога /dev, используемого во время создания образа корневой файловой системы посредством утилиты populatefs .
  2. Инсталляция системы в интерактивном режиме в заданный каталог или на заданный раздел носителя информации от имени суперпользователя.

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

В качестве примера, создания пакетов, содержащих файлы специальных устройств, от имени непривилегированного пользователя, можно рассмотреть файл base/init-devices/Makefile, из репозитория платформы Radix.Linux.

Root Ext4 FS Image

Во время сборки системы, как мы говорили ранее, все пакеты инсталлируются во временный каталог $(ROOTFS_DEST_DIR), представляющий собой некий образ содержимого корневой файловой системы для целевого устройства. И хотя данный каталог вполне пригоден для монтирования на целевую машину по NFS протоколу, он остается непригодным для создания файловой системы на диске или другом носителе информации. Дело в том, что поскольку сборка и инсталляция пакетов происходила от имени непривилегированного пользователя, все файлы каталога $(ROOTFS_DEST_DIR) имеют некорректные права доступа и не принадлежат суперпользователю. Разумеется, в данном образе нет и необходимых файлов устройств в каталоге /dev, которые, опять таки, могут быть созданы только от имени суперпользователя.

Для того чтобы автоматизировать создание корневой файловой системы, удовлетворяющей всем требованиям среды Linux, система сборки предоставляет возможность создания образа Ext4 файловой системы в обычном файле, пригодном для записи на целевой носитель посредством команды dd.

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

$ make ext4fs_image

Разумеется, если необходимо создать файловую систему лишь для одного целевого устройства, например для платы MIPS Creator CI20, то можно явно задать имя целевой машины:

$ HARDWARE=ci20 make ext4fs_image

Если на момент создания цели ext4fs_image в каталоге $(TARGET_BUILD_DIR) отсутствует таблица устройств (файл .DEVTABLE), то данная таблица будет создана автоматически, так как цель ext4fs_image зависит от цели devices_table.

Размер целевой файловой системы можно задать с помощью аргумента size, например, следующим образом:

$ make ext4fs_image size=14.49G

Размер может быть определен в байтах, килобайтах, мегабайтах и гигабайтах, путем добавления соответствующего суффикса, K, M или G, соответственно, следующего без пробелов и занимающего лишь один символ в верхнем регистре. Естественно, если размер задается в байтах, суффикс не используется.

Следует отметить, что размер целевой файловой системы не может быть меньше размера содержимого каталога $(ROOTFS_DEST_DIR) плюс 40% и, если затребованный пользователем размер окажется меньше допустимого, то файловая система будет создана без учета требований пользователя.

В дополнение к образу корневой файловой системы, будет создана загрузочная запись MBR (Master Boot Records), которая будет содержать таблицу разделов целевого носителя информации, при этом первые 446 байтов в MBR будут заполнены нулями. Таким образом, в результате выполнения команды

$ HARDWARE=ci20 make ext4fs_image

в каталоге $(TARGET_BUILD_DIR) будет получено два файла: $(HARDWARE).ext4fs и $(HARDWARE).SD.MBR .

Products Release

Цель products_release предусмотрена для инсталляции файлов $(HARDWARE).ext4fs, $(HARDWARE).SD.MBR и файла $(HARDWARE).pkglist в каталог $(PRODUCTS_DEST_DIR). Выполнение команды

$ make products_release

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

$ HARDWARE=ci20 make products_release

даст нам не только набор отчуждаемых пакетов для устройства MIPS Creator CI20, полученный во время сборки, но и список, в котором все пакеты представлены в порядке, учитывающем межпакетные зависимости. Кроме того, поскольку в каталоге $(PRODUCTS_DEST_DIR) будет размещен образ целевой файловой системы, программа инсталляции сможет предложить пользователю просто скопировать файловую систему на целевой диск вместо того, чтобы инсталлировать пакеты поочередно.

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

dist/products/jz47xx-glibc/ci20/ci20.SD.MBR
dist/products/jz47xx-glibc/ci20/ci20.ext4fs
dist/products/jz47xx-glibc/ci20/ci20.pkglist

Суффикс .SD.MBR выбран не случайно. Система сборки создает таблицы разделов целевого носителя из расчета, что в основном будут использоваться карты SDHC, для которых в системе Linux по умолчанию выбирается четыре считывающие головки и 16 секторов на трек. Однако данный выбор ни сколько не ограничивает пользователя в применении других носителей информации.

Кроме того, подразумевается, что начало файловой системы на целевом носителе должно быть расположено по смещению 1 048 576 байтов или 2048 секторов.

Для записи на карту SDHC, которой, например, соответствует дескриптор /dev/mmcblk0, пользователю достаточно выполнить две команды:

# dd if=ci20.SD.MBR of=/dev/mmcblk0  bs=512 count=1
# dd if=ci20.ext4fs of=/dev/mmcblk0 obs=512k seek=2

Оставшееся пространство на карте пользователь может отдать дополнительным разделам, воспользовавшись командой fdisk.

Кроме рассмотренных здесь файлов, система сборки ориентируется на возможное наличие в каталоге $(PRODUCTS_DEST_DIR) файла $(HARDWARE).boot-records, который может содержать загрузчик U-Boot.

Если на момент выполнения команды

$ make products_release

файл $(HARDWARE).boot-records будет существовать, то система сборки поместит в его начало таблицу разделов и для пользователя процесс записи файлов на SD карту будет упрощен до следующих команд:

# cat ci20.boot-records ci20.ext4fs > SDHC.img
# dd if=SDHC.img of=/dev/mmcblk0 obs=512k

Создание загрузочных записей мы рассмотрим в разделах, посвященных поддерживаемым целевым устройствам. Здесь же мы можем предложить самостоятельно рассмотреть данный процесс в файле boot/u-boot/ci20/2013.10-20150826/Makefile и, чуть более сложную процедуру, связанную с созданием FAT32 загрузочного раздела, в файле boot/u-boot/omap543x/2013.04-20140216/Makefile.

В заключение, следует отметить, что суффиксы .ext4fs, .SD.MBR и .boot-records являются ключевыми для системы сборки.

Naming Convention

В именах toolchain-ов, устройств и модификаций устройств нельзя применять символы подчеркивания '_'. Исключением является имя toolchain-а, в которое входит аббревиатура x86_64, например, x86_64-eglibc. Такие имена обрабатываются системой сборки корректно.

Toolchains

Toolchain – набор пакетов программ, необходимых для компиляции и генерации выполняемого кода из исходных текстов. Здесь и далее будут рассматриваться наборы программ, предназначенные для кросс-компиляции и генерации кода для различных целевых платформ, работающие на персональных машинах (как правило x86_64) и составленные в рамках проекта GNU. GNU Toolchain в общем случае состоит из кросс-компилятора GCC, компоновщика объектных кодов (входящего в состав пакета binutils) и библиотек, предназначенных для работы на целевой машине. Основной библиотекой, без которой не может работать ни одна программа, является стандартная библиотека языка C. Данная библиотека должна входить в состав toolchain-а и, кроме того, быть инсталлирована на целевую систему. Важным здесь является то, что на целевой системе должна присутствовать именно та библиотека, с которой были связаны программы, созданные с помощью toolchain-a. Для обеспечения такого соответствия существует два способа:

  1. Копирование библиотеки из toolchain-а на целевую систему.
  2. Сборка новой библиотеки и использование ее при сборке всех программ вместо той, которая присутствует в toolchain-е.

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

Второй путь связан с необходимостью указания компилятору каталога для поиска библиотек, с которыми ему надлежит связывать строящиеся программы. Эта функциональность заложена в компилятор GCC и, может быть задействована как на этапе сборки самого компилятора, так и при его использовании. В первом случае каталог поиска библиотек задается на этапе конфигурирования с помощью опции configure скрипта:

$ ./configure --with-sysroot=DIR

а во втором случае – с помощью управления --sysroot:

$ gcc -c --sysroot=DIR -o main main.c

Допустим, что наши toolchain-ы расположены в каталоге /opt/toolchain. Тогда полное имя cross-gcc может быть, например, таким:

/opt/toolchain/arm-OMAP543X-linux-eglibc/1.0.7/bin/arm-omap543x-linux-gnueabihf-gcc

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

/opt/toolchain/arm-OMAP543X-linux-eglibc/1.0.7/arm-omap543x-linux-gnueabihf/sys-root

Здесь следует отменить, что путь /opt/toolchain/arm-OMAP543X-linux-eglibc/1.0.7 является произвольным, т.е. выбран произвольно для размещения toolchain-а на машине разработчика. Имя каталога, выделенное синим цветом, arm-omap543x-linux-gnueabihf, определено автоматически и говорит о том, что при создании toolchain-а на этапе конфигурирования перед сборкой была задана архитектура целевого процессора с помощью следующей опции:

$ ./configure ... --target=arm-omap543x-linux-gnueabihf ...

И наконец, имя каталога, выделенное красным цветом, выбрано автором сборки посредством опции:

$ ./configure \
     --with-sysroot=$(TOOLCHAIN_PATH)/$(TOOLCHAIN_VERSION)/$(TARGET)/sys-root

Все это мы рассказали для того, чтобы перейти к описанию процесса подключения новых toolchain-ов к системе сборки.

Connection of the New Toolchain to Build System

Для подключения нового toolchain-а к системе сборки необходимо добавить несколько строк в файл build-system/constants.mk. Рассмотрим это на примере.

В начале файла build-system/constants.mk определен путь ко всем toolchain-ам, используемым в системе сборки:

TOOLCHAINS_BASE_PATH = /opt/toolchain

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

Поскольку мы добавляем новый toolchain, мы вероятно, уже определились с кодовыми названиями целевых устройств, для которых будем вести разработку. Допустим это платы OMAP5 uEVM (Pandaboard 2) и DRA7XX EVM от Texas Instruments. Следовательно, нам необходимо определить две новые переменные, определяющие названия наших машин:

HARDWARE_OMAP5UEVM = omap5uevm
HARDWARE_DRA7XXEVM = dra7xxevm

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

OMAP5UEVM_ID_STD = 70
DRA7XXEVM_ID_STD = 71

Здесь важно отметить, что при задании переменных необходимо обеспечить точное соответствие имен и значений переменных следующим требованиям:

Имя целевого устройства должно быть равно суффиксу имени переменной 'HARDWARE_...', записанному в нижнем регистре. Другими словами, части имен, выделенные красным цветом, должны быть равны между собой и, кроме того, если эти части перевести в нижний регистр, мы должны получить имя устройства, выделенное синим цветом:

HARDWARE_OMAP5UEVM = omap5uevm
OMAP5UEVM_ID_STD = 70

Теперь мы можем описать непосредственно toolchain с кодовым именем omap543x-eglibc:

TOOLCHAIN_OMAP543X_EGLIBC    = omap543x-eglibc

OMAP543X_EGLIBC_ARCH         = arm-omap543x-linux-gnueabihf
OMAP543X_EGLIBC_VERSION      = 1.0.7
OMAP543X_EGLIBC_DIR          = arm-OMAP543X-linux-eglibc
OMAP543X_EGLIBC_PATH         = $(TOOLCHAINS_BASE_PATH)/$(OMAP543X_EGLIBC_DIR)

OMAP543X_EGLIBC_ARCH_DEFS    = -D__OMAP543X__=1
OMAP543X_EGLIBC_ARCH_FLAGS   = -march=armv7-a -mtune=cortex-a15     \
                               -mfloat-abi=hard  -mfpu=neon-vfpv4   \
                               -mabi=aapcs-linux -fomit-frame-pointer

OMAP543X_EGLIBC_SYSROOT      = sys-root
OMAP543X_EGLIBC_DEST_SYSROOT = yes

OMAP543X_EGLIBC_HARDWARE_VARIANTS := $(HARDWARE_OMAP5UEVM) \
                                     $(HARDWARE_DRA7XXEVM)

Заметим, что здесь соблюдается тот же принцип формирования имен переменных, который мы использовали при определении кодовых имен целевых устройств, с тем лишь дополнением, что при переводе в нижний регистр символ подчеркивания '_' переводится в дефис '-', и наоборот. Если говорить на языке команд Linux, то перевод из нижнего регистра в верхний можно осуществить так:

$ OMAP543X_EGLIBC=`echo omap543x-eglibc | tr '[a-z-]' '[A-Z_]'`

Рассмотрим подробно заданные переменные и их значения. Прежде всего, кодовое имя toolchain-а, выбираемое пользователем, в нашем случае omap543x-eglibc. Это имя должно быть кратким, но информативным. Данное имя говорит о том, что toolchain предназначен для сборки программ на линейку устройств, основанных на SoCs OMAP5430 и OMAP5432 от Texas Instruments. Кроме того, целевые программы компонуются с EGLIBC [www.eglibc.org], которая является доработанной версией GNU Glibc специально для встраиваемой техники. И, наконец, то, что целевой системой является операционная система на базе ядра Linux, понятно из контекста.

Поддержка EGLIBC прекращена на версии 2.19, поскольку цели и задачи ее разработки перешли в основной проект GLIBC.

Переменная OMAP543X_EGLIBC_VERSION определяет версию toolchain-а и одновременно подкаталог 1.0.7, в котором расположен toolchain. Система сборки автоматически использует эту переменную для определения полного имени кросс компилятора или, другими словами, пути к кросс-компилятору. Пользователю не надо использовать эту переменную напрямую. Переменные OMAP543X_EGLIBC_DIR, OMAP543X_EGLIBC_PATH задаются пользователем согласно реальному размещению toolchain-а на машине разработчика или build-сервере. Переменная OMAP543X_EGLIBC_SYSROOT задает имя финального подкаталога пути к системному корневому каталогу, который был задан при создании toolchain-а с помощью опции --with-sysroot скрипта конфигурирования. Значение переменной OMAP543X_EGLIBC_ARCH должно в точности соответствовать значению управления --target, используемого при создании кросс-компилятора. В рассматриваемом нами случае целевая архитектура описана строкой arm-omap543x-linux-gnueabihf.

Следующий рисунок поясняет использование этих переменных.

Рис.2. Системный каталог кросс-компилятора

На рис.2 синим цветом выделены переменные, задаваемые пользователем в файле build-system/constants.mk. Переменные TOOLCHAIN_PATH и TARGET автоматически определяются системой сборки согласно текущей цели и могут быть использованы в собственных Make-файлах. В дальнейшем мы рассмотрим использование данных переменных.

Переменные OMAP543X_EGLIBC_ARCH_DEFS, OMAP543X_EGLIBC_ARCH_FLAGS предназначены для задания дополнительных управлений кросс-компилятору во время его использования в составе системы сборки. Так, например, переменная OMAP543X_EGLIBC_ARCH_FLAGS предназначена для задания архитектуры процессора целевого устройства. Причем управления, заданные с помощью переменной OMAP543X_EGLIBC_ARCH_FLAGS, соответствуют тем, которые использовались во время сборки самого кросс-компилятора.

Отдельно следует пояснить значение переменной OMAP543X_EGLIBC_DEST_SYSROOT. Если значение данной переменной равно yes, то при вызове кросс-компилятора будет использовано управление --sysroot=DIR, причем DIR будет представлять собой не путь к системному каталогу toolchain-а, а так называемый TARGET_DEST_DIR путь, определяющий положение целевого каталога, который используется во время сборки всех программ в качестве временного системного окружения, в котором представлена будущая целевая система. Таким образом, при конфигурировании и сборке программ на цель будут использоваться заголовочные и объектные файлы, которые представляют текущее состояние целевой системы, и именно с ними будут связаны строящиеся программы.

Разумеется, такое перенаправление системного каталога целесообразно при сборке пользовательских приложений, а в случае сборки ядра, модулей ядра или системных загрузчиков не нужно, так как построение такого рода системного программного обеспечения не нуждается в использовании других библиотек. Для того чтобы собрать целевые программы при заданной переменной OMAP543X_EGLIBC_DEST_SYSROOT со значением yes, но без использования управления --sysroot=DIR, пользователь может отменить такое перенаправление в отдельно взятом Make-файле, определив в нем переменную USE_TARGET_DEST_DIR_SYSROOT со значением, отличным от слова yes. Например так:

USE_TARGET_DEST_DIR_SYSROOT = no

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

Если же значение переменной OMAP543X_EGLIBC_DEST_SYSROOT равно no,

OMAP543X_EGLIBC_DEST_SYSROOT = no

то управление --sysroot=DIR использоваться не будет. Такое поведение характерно при сборке программного обеспечения для систем, основанных на микроконтроллерах с использованием библиотек предоставленных в составе toolchain-а или вообще без каких-либо системных библиотек.

И наконец, переменная OMAP543X_EGLIBC_HARDWARE_VARIANTS служит для того, чтобы задать список устройств, программное обеспечение для которых должно собираться с использованием данного кросс-компилятора.

В качестве итога, приведем полный список переменных, которые мы ввели в файле build-system/constants.mk для подключения нового toolchain-а:

HARDWARE_OMAP5UEVM = omap5uevm
HARDWARE_DRA7XXEVM = dra7xxevm

OMAP5UEVM_ID_STD = 70
DRA7XXEVM_ID_STD = 71

TOOLCHAIN_OMAP543X_EGLIBC    = omap543x-eglibc

OMAP543X_EGLIBC_ARCH         = arm-omap543x-linux-gnueabihf
OMAP543X_EGLIBC_VERSION      = 1.0.7
OMAP543X_EGLIBC_DIR          = arm-OMAP543X-linux-eglibc
OMAP543X_EGLIBC_PATH         = $(TOOLCHAINS_BASE_PATH)/$(OMAP543X_EGLIBC_DIR)

OMAP543X_EGLIBC_ARCH_DEFS    = -D__OMAP543X__=1
OMAP543X_EGLIBC_ARCH_FLAGS   = -march=armv7-a -mtune=cortex-a15     \
                               -mfloat-abi=hard  -mfpu=neon-vfpv4   \
                               -mabi=aapcs-linux -fomit-frame-pointer

OMAP543X_EGLIBC_SYSROOT      = sys-root
OMAP543X_EGLIBC_DEST_SYSROOT = yes

OMAP543X_EGLIBC_HARDWARE_VARIANTS := $(HARDWARE_OMAP5UEVM) \
                                     $(HARDWARE_DRA7XXEVM)

Using Hardware IDs in source code

При написании собственных программ может возникнуть необходимость изменить определенную часть кода в соответствии с архитектурой того или иного целевого устройства. Допустим, основная часть программы остается независимой и может выполняться на всех устройствах, которые мы определили ранее в файле build-system/constants.mk. Но некоторая часть кода является зависимой от конкретного устройства и, следовательно, при сборке программы инженеру необходимо знать, для какого устройства в данный момент идет сборка. Для этого в системе сборки предусмотрено автоматическое создание и использование макро-определений, которые подаются на вход компилятору. Итак, первое макро-определение, которое создается для всех устройств, это переменная __HARDWARE__. Данной переменной присваивается значение текущего идентификатора устройства, для которого в данный момент идет компиляция. Кроме того, для всех устройств, декларированных в файле build-system/constants.mk, создаются переменные, имя которых равно имени устройства, переведенному в верхний регистр. Так, в нашем случае,

HARDWARE_OMAP5UEVM = omap5uevm
HARDWARE_DRA7XXEVM = dra7xxevm

OMAP5UEVM_ID_STD = 70
DRA7XXEVM_ID_STD = 71

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

$ gcc -D__HARDWARE__=70 -DOMAP5UEVM=70 -DDRA7XXEVM=71

для устройства omap5uevm, и с управлениями

$ gcc -D__HARDWARE__=71 -DOMAP5UEVM=70 -DDRA7XXEVM=71

для устройства dra7xxevm.

Используя данные переменные, инженер в собственном коде может делать проверки, например, такие как показано на следующем листинге.

/* HW independent code */

#if __HARDWARE__ == OMAP5UEVM

/* OMAP5UEVM specific code */

#elif __HARDWARE__ == DRA7XXEVM

/* DRA7XXEVM specific code */

#endif

Используя переменную OMAP543X_EGLIBC_ARCH_DEFS, пользователь системы сборки вправе задавать любые переменные. Однако следует помнить, что заданные пользователем переменные ни в коем случае не должны совпадать с предопределенными макро, как самим компилятором, так и другими собираемыми компонентами.

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

$ gcc -dM -E -x c /dev/null

Здесь, управление -dM отвечает за вывод предопределенных макро-определений, управление -E инициирует печать в стандартный поток вывода и, наконец, -x c выбирает язык C.

CCACHE

Система сборки использует утилиту CCACHE(1) для сокращения времени сборки. В файле build-system/constants.mk определена переменная

CACHED_CC_OUTPUT = /opt/extra/ccache

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

Для вывода статистики работы пользователь системы сборки может выполнить команду make ccache_stats в любом каталоге дерева.

Установить максимальный размер кэша можно с помощью команды:

$ CACHE_DIR=/opt/extra/ccache ccache -M 64G

где, размер можно задавать в килобайтах, мегабайтах и гигабайтах, используя суффиксы числового значения K, M, G, соответственно.

Versioning

Версии системы сборки нумеруются стандартным способом, при котором номер версии состоит из трех цифр: major.minor.maintenance.

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

Прежде чем использовать ту или иную версию системы сборки, пользователь может удостовериться какие именно устройства поддерживаются и какие версии toolchain-ов подключены к работе. Для этого достаточно рассмотреть файл build-system/constants.mk, например, файл constants.mk, соответствующий тегу tags/build-system-1.1.5.

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

Continuous Branches

Обычный процесс разработки подразумевает непрерывное развитие транка и поддержание его стабильности. Как правило, непосредственная разработка ведется на отдельных ветках. По окончанию разработки новой функциональности, изменения поступают на транк. Для фиксации стабильных ревизий продукта создаются теги, которые представляют собой копии состояния транка в определенные моменты времени. На следующей схеме (рис.3), до момента времени Tn, показано создание тегов транка так, как это происходит в условиях стандартной политики управления конфигурациями продукта.

Рис.3. Постоянные ветви

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

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

Решение здесь состоит в создании ветки build-system-1.1.x, которая сохранит текущую функциональность системы сборки и обеспечит возможность ее совершенствования на фиксированном наборе toolchain-ов и поддерживаемых устройств.

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

В момент времени Tn+1 ветка build-system-1.1.x перейдет в так называемое CONTINUOUS состояние, которое характеризуется тем, что все изменения, производимые на ней, более никогда не должны попадать непосредственно в транк. Теги, фиксирующие состояния разработки, теперь будут создаваться как непосредственные копии ветки build-system-1.1.x. На рис.3 это показано на примере тега с номером 1.1.7.

Ветка build-system-1.2.x, созданная в момент времени Tn+1, в дальнейшем тоже перейдет в CONTINUOUS состояние, но только тогда, когда возникнет необходимость поставки на транк новых ключевых изменений. Например, в тот момент, когда будет создана ветка build-system-1.3.x или build-system-2.x.x. Здесь переменные x характеризуют вариабельность соответствующего компонента номера версии системы сборки.