Before Beginning
Перед использованием системы сборки, естественно, надо получить хотябы один кросс-компилятор и настроить окружение. Более подробно, аспекты построения среды разработки, мы рассматривали в разделе Development Environment. Здесь мы ограничимся лишь элементарными действиями, которые необходимо сделать перед началом работы.
Install CCACHE
Для ускорения процесса сборки большого количества пакетов мы настоятельно рекомендуем установить утилиту CCACHE(1). Большинство дистрибутивов Linux имеют данную утилиту.
Мы уже рассматривали использование CCACHE во вводной статье. Здесь мы просто напомним, что если не менять значение переменной CACHED_CC_OUTPUT в файле 'build-system/constants.mk', то необходимо позаботиться о создании каталога '/opt/extra/ccache'. Сделать это нужно от имени администратора системы. Кроме того, необходимо предоставить права доступа на каталог '/opt/extra' группе разработчиков, поскольку работа в системе не должна осуществляться от имени администратора.
$ sudo mkdir -p /opt/extra/ccache $ sudo chown -R developer:developers /opt/extra
Getting Toolchains
Основным средством разработки, естественно, является кросс-компилятор. Кросс компиляторы, по умолчанию, располагаются в каталоге '/opt/toolchains' и, если не менять значение переменной TOOLCHAINS_BASE_PATH в файле 'build-system/constants.mk', то необходимо создать данный каталог, присвоив права доступа группе разработчиков.
$ sudo mkdir -p /opt/toolchains $ sudo chown -R developer:developers /opt/toolchains
В принципе, более ни каких действий, со стороны пользователя, не требуется. Дело в том, что если перед началом сборки какого-либо пакета, необходимый toolchain не будет обнаружен в соответствующем каталоге, система сборки начнет загрузку toolchain-а с FTP-сервера и, по окончанию загрузки, распакует toolchain в каталог '/opt/toolchains'.
Установить toolchain-ы можно заранее. Для этого надо выполнить команды, подобные следующим, для каждого toolchain-а.
$ cd /opt/toolchains $ wget ftp://ftp.radix.pro/toolchains/x86_64/1.0.9/arm-RK328X-linux-glibc-1.0.9.tar.gz $ tar xzf arm-RK328X-linux-glibc-1.0.9.tar.gz
First Package
Рассмотрим работу системы сборки на простейшем примере, который, несмотря на свою простоту, позволит нам изучить все основные этапы создания отчуждаемых пакетов.
Создадим каталог проекта:
$ mkdir project $ cd project
Воспользуемся следующим сценарием для получения системы сборки.
#!/bin/sh VERSION=1.1.0 wget ftp://ftp.radix.pro/radix/build-system/releases/build-system-${VERSION}.tar.xz wget ftp://ftp.radix.pro/radix/build-system/releases/build-system-${VERSION}.tar.xz.sha256sum sha256sum --check build-system-${VERSION}.tar.xz.sha256sum tar xJf build-system-${VERSION}.tar.xz mv build-system-${VERSION} build-system exit 0
Чтобы не создавать собственный проект с нуля, возьмем готовый пакет из репозитория платформы Radix.Linux. Пусть это будет pkgtool. Дело в том, что это базовый пакет, который не требует загрузки исходных текстов, так как они уже присутствуют в системе сборки. Кроме того, пакет pkgtool не зависит ни от каких пакетов в системе и, для его работы достаточно тех средств, которые присутствуют на машине разработчика.
Итак, для получения необходимых файлов обратимся к репозиторию.
#!/bin/sh REVISION=255 svn co -r${REVISION} svn://radix.pro/platform/trunk/base base rm -rf .svn exit 0
Напомним, что мы ведем нашу работу в каталоге 'project' и, следовательно, после получения системы сборки и Make-файла пакета pkgtool, мы имеем следующее дерево каталогов.
project │ ├── base │ └── pkgtool │ ├── Makefile │ ├── pkg-description.in │ └── pkg-install.sh │ └── build-system
Перейдем в каталог 'base/pkgtool'
$ cd base/pkgtool
и выполним команду
$ make help
В конце полученного листинга, мы увидим список вариантов целевых устройств, для которых приготовлен Make-файл 'base/pkgtool/Makefile'. Разумеется, мы не будем собирать пакет для всех возможных устройств, а выберем, например, только плату Firefly-RK3288.
Соберем пакет pkgtool для устройства Firefly-RK3288:
$ HARDWARE=ffrk3288 make
В завершение, система сборки выдаст сообщение о том, что пакет pkgtool успешно инсталлирован.
Package creation complete. ####### ####### Install packages into 'dist/rootfs/rk328x-glibc/ffrk3288/...' file system... ####### Installing package pkgtool... |======================================================================| pkgtool 1.1.0 (Package Tools) This is a set of scripts used for package creation, install, etc. Uncompressed Size: 168K Compressed Sise: 24K |======================================================================| make[2]: Leaving directory `project/base/pkgtool' make[1]: Leaving directory `project/base/pkgtool' bash-4.2$
Посмотрим теперь, какие изменения произошли в каталоге нашего проекта.
project │ ├── base │ └── pkgtool │ ├── Makefile │ ├── pkg-description.in │ └── pkg-install.sh │ ├── build-system │ └── dist │ ├── products │ └── rk328x-glibc │ └── ffrk3288 │ └── base │ ├── pkgtool-1.1.0-rk328x-glibc-radix-1.0.sha256 │ ├── pkgtool-1.1.0-rk328x-glibc-radix-1.0.txz │ └── pkgtool-1.1.0-rk328x-glibc-radix-1.0.txt │ └── rootfs └── rk328x-glibc └── ffrk3288 ├── sbin │ └── pkgtool │ ├── change-refs │ ├── check-db-integrity │ ├── check-package │ ├── check-requires │ ├── install-package │ ├── install-pkglist │ ├── make-package │ ├── make-pkglist │ ├── pkginfo │ ├── pkglog │ ├── remove-package │ └── update-package └── var └── log └── radix ├── packages │ └── pkgtool-1.1.0-rk328x-glibc-radix-1.0 ├── removed_packages └── setup └── setup.log
Во-первых, отчуждаемый пакет помещен в каталог 'dist/products/rk328x-glibc/ffrk3288/base'. Здесь имена подкаталогов выбраны следующим образом.
Если обратиться к файлу 'build-system/constants.mk', то в нем можно найти значения соответствующих переменных.
HARDWARE_FFRK3288 = ffrk3288 TOOLCHAIN_RK328X_GLIBC = rk328x-glibc
Таким образом, по мере сборки пакетов, входящих в дистрибутив, каталог 'dist/products' наполняется новыми пакетами для отчуждения. Структура каталогов построена таким образом, что фактически готова для формирования репозитория отчуждаемых пакетов, и может быть скопирована на FTP-сервер без каких-либо изменений, так как все пакеты разделены по целевым устройствам.
Во-вторых, в каталоге 'dist/rootfs/rk328x-glibc/ffrk3288' началось формирование временной корневой файловой системы, которую, по мере инсталлирования новых пакетов, можно тестировать на целевом устройстве, монтируя каталог 'dist/rootfs/rk328x-glibc/ffrk3288' по NFS.
Разумеется, после инсталляции первого пакета, в каталоге 'var/log/radix' корневой файловой системы, создана информационная база пакетов и, в файле 'var/log/radix/setup/setup.log' мы видим,
[07-Jul-2015 18:21:10] Checking: pkgtool-1.1.0-rk328x-glibc-radix-1.0: SUCCESS: Package is not installed. [07-Jul-2015 18:21:10] Installing: pkgtool-1.1.0-rk328x-glibc-radix-1.0.txz: SUCCESS: Package has been installed.
что сначала была осуществлена проверка, не инсталлирован ли уже пакет pkgtool, а затем произведена инсталляция пакета. После инсталляции, вся информация о пакете pkgtool была записана в log-файл 'var/log/radix/packages/pkgtool-1.1.0-rk328x-glibc-radix-1.0'.
Поскольку утилиты, входящие в пакет pkgtool, итак находятся в системе сборки, инсталлировать их в кросс-окружение не нужно. Поэтому каталог 'dist/.rk328x-glibc/ffrk3288' не создан. Как только в одном из пакетов мы воспользуемся функцией системы сборки install-into-devenv, начнется формироание системного каталога нашего кросс-окружения, сведения о котором даны во вводной статье.
Choose Hardware Targets
Прежде чем рассматривать выбор целевых устройств, необходимо сказать несколько слов об инициализации самой системы сборки. Дело в том, что в предыдущем примере, мы не обращали на это внимания поскольку перед сборкой любого пакета, такая инициализация происходит автоматически. Но если мы только распаковали исходный архив системы или сняли исходный код из репозитория, система сборки не готова к работе.
Инициализировать систему сборки просто. Для этого надо перейти в каталог 'build-system' и выполнить команду
$ make
Итак, в предыдущем примере, для выбора целевого устройства, мы использовали переменную HARDWARE.
$ HARDWARE=ffrk3288 make
Это удобно лишь в том случае, если надо собрать пакет лишь для одного устройства. Если же необходимо собрать пакет одновременно для нескольких целевых устройств и, при этом, мы не хотим собирать пакет для всех возможных устройств, надо воспользоваться командой
$ make configure_targets
в каталоге 'build-system'.
Данная команда дает пользователю возможность выбора устройств, для которых будет осуществляться сборка.
По умолчанию, разрешены все устройства декларированные в системе сборки. Дело в том, что после приготовления системы сборки к работе, создается файл 'build-system/build-config.mk'. Данный файл не хранится в репозитории системы сборки, а просто является копией файла 'build-system/build-config.mk.template'.
Любой разработчик может держать у себя собственный файл 'build-config.mk' и использовать его для своих проектов.
Здесь еще необходимо напомнить связи целевых устройств с toolchain-ами. С помощью одного и того же toolchain-а может осуществляться сборка для нескольких устройств. Так например, мы можем использовать toolchain 'a2x-glibc' для сборки пакетов на Cubieboard 2 и Cubietruck. Привязка этих целевых устройств к toolchain-у осуществлена в файле 'build-system/constants.mk' следующим образом.
TOOLCHAIN_A2X_GLIBC = a2x-glibc A2X_GLIBC_HARDWARE_VARIANTS := $(HARDWARE_CB2X) $(HARDWARE_CB3X)
Поэтому, если мы, без дополнительных настроек в файле 'build-system/build-config.mk', выполним команду
$ TOOLCHAIN=a2x-glibc make
для сборки пакета, который может работать на обоих устройствах Cubieboard 2 и Cubietruck. То будет выполнена сборка этого пакета сразу для двух целевых устройств.
Flavours
Для того, чтобы более полно раскрыть возможности системы сборки, мы приготовили специальный пример в каталоге 'base/examples/hello' платформы Radix.Linux. Здесь используются toolchain-ы, созданные для архитектур x86_32 и x86_64. Напомним, что в разделе Getting Toolchains приведен пример загрузки toolchain-ов с нашего FTP-сервера и, перед тем как изучать данный пример, необходимо инсталлировать toolchain-ы i486-PC-linux-glibc и x86_64-PC-linux-glibc. Мы выбрали эти toolchain-ы потому, что, полученные примеры, можно будет выполнять на Intel-машине разработчика, хотя они и будут собраны с помощью кросс-компилятора.
Cross Environment
Поскольку, в данном примере, потребуется наличие GNU Libc в кросс-окружении, нам необходимо сделать некоторые приготовления. В основном мы делаем их для того, чтобы при рассмотрении работы системы сборки, мы могли иметь краткие логи, не загроможденные информацией о приготовлении GNU Libc.
Преде всего надо получить копию исходных файлов Radix.Linux платформы
$ svn co svn://radix.pro/platform/trunk platform
Затем перейти в каталог 'base/examples/hello' и выполнить команду Make так, как показано на следующем листинге.
$ cd platform/base/examples/hello $ make -j8
Теперь можно отвлечься на некоторое время, пока система сборки будет загружать исходные пакеты, инициализировать собственные средства, готовить GNU Libc и собирать наш пример.
Время загрузки исходных пакетов можно сократить до нескольких минут, если организовать собственный FTP-сервер в локальной сети. Ускорить сборку пакетов можно лишь с помощью аппаратных средств машины разработчика.
По окончанию сборки, мы получим готовые пакеты нашего примера, но на данном этапе они нас не интересуют. Сборку мы производили лишь для того, чтобы наполнить кросс-окружение. Очистим теперь временную корневую файловую файловую систему и кросс-окружение от пакетов hello и hello-dll. Для этого выполним следующие команды.
$ cd base/examples/hello $ make local_rootfs_clean $ make local_dist_clean $ cd base/examples/hello-dll $ make local_rootfs_clean $ make local_dist_clean
Посмотрим теперь на состояние каталога 'platform/dist'.
Мы имеем кросс-окружение, необходимое для сборки нашего примера, в каталогах 'platform/dist/.i686-glibc/pc32' и 'platform/dist/.x86_64-glibc/pc64', заполненное для архитектур x86_32 и x86_64, соответственно.
Временные корневые файловые системы, для выбранных нами архитектур, расположены в каталогах 'platform/dist/rootfs/i686-glibc/pc32' и 'platform/dist/rootfs/x86_64-glibc/pc64'.
Кроме того, отчуждаемые пакеты, которые были построены с целью обеспечения работы программы hello, размещены в каталоге 'platform/dist/products'.
Итак мы готовы к рассмотрению нашего примера. Для простоты, мы рассматриваем механизм FLAVOURS не на различных вариантах целевых устройств, а на искусственном примере различных модификаций программы, связанных с локализацией приложений.
Library
Библиотека hello.so пакета hello-dll содержит лишь одну функцию print_msg( char *s ), которая, в качестве аргумента, принимает строку и выводит на консоль приветственное сообщение. Если, например, вызвать функцию print_msg( "Jane" ) с аргументом "Jane", то, для французского варианта библиотеки, на экран будет выведено сообщение
fr: Bonjour, Jane!
Допустим, что нас интересуют три варианта библиотеки hello.so. Во-первых, вариант не учитывающий локализацию и разговаривающий только на английском языке. Назовем этот вариант базовым и будем собирать данный вариант в том случае, если в командной строке переменная FLAVOUR не имеет значения. Во-вторых, немецкий вариант, который мы будем собирать, если переменная FLAVOUR имеет значение FLAVOUR=de. И, наконец, в-третьих, вариант французский, который будет собран при значении переменной FLAVOUR равном FLAVOUR=fr.
От нашего Make-файла 'base/examples/hello-dll/Makefile' мы хотим добиться следующего поведения.
При сборке с помощью команды
$ make
должны собираться все варианты.
Если же мы зададим конкретное значение переменной FLAVOUR,
$ make FLAVOUR=de
то собираться должен лишь один вариант, соответствующий значению переменной FLAVOUR.
Более того, мы хотим, чтобы во временную корневую файловую систему инсталлировался только французский вариант нашей библиотеки, но отчуждаемые пакеты готовились для всех вариантов.
Сделать это можно с помощью механизма FLAVOURS. Для этого, в Make-файле определим список возможных вариантов
FLAVOURS = de fr
Здесь, базовый вариант указывать не надо.
Для того, чтобы инсталлировался лишь французский вариант, воспользуемся условными директивами GNU Make
ifeq ($(FLAVOUR),fr) ROOTFS_TARGETS = $(pkg_archive) endif
При этом, в нашем Make-файле, список ROOTFS_TARGETS будет определен лишь во время сборки французского варианта. Для всех остальных вариантов, список целей для инсталляции будет пуст.
Вот собственно и все, что необходимо сделать в теле нашего Make-файла.
Остается позаботится о том, чтобы во время сборки библиотеки, а именно, во время трансляции файла 'base/examples/hello-dll/hello.c' в объектный код, мы могли достоверно определить, для какого именно варианта, в данный момент, осуществляется сборка.
Следовательно, нам необходимо задать препроцессору дополнительные флаги. Сделать это можно, например, так
ifneq ($(FLAVOUR),) CFLAGS += -fPIC -I$(CURDIR) -D__FLAVOUR__=\"$(FLAVOUR)\" else CFLAGS += -fPIC -I$(CURDIR) endif
Здесь переменная CFLAGS, являясь стандартной переменной окружения, и воспринимаемая практически всеми средствами автоматизации, уже содержит необходимые флаги, которые она получила от системы сборки. Поэтому, чтобы не разрушить существующее значение $(CFLAGS), мы использовали оператор '+='.
Необходимо отметить важный факт. В случае использования механизма FLAVOURS, задание макро-определений для препроцессора, полностью зависит от автора Make-файла. Однако для задания подобных макро-определений в переменной CFLAGS, характеризующих текущие значения имени toolchain-а и идентификатора целевого устройства, система сборки имеет собственный механизм, описанный во вводной статье.
Итак, теперь, когда мы имеем макро-определение __FLAVOUR__, в тексте файла 'base/examples/hello-dll/hello.c', можно легко определить текущий вариант сборки, например, следующим образом.
#if defined( __FLAVOUR__ ) if( !strcmp( "de", __FLAVOUR__ ) ) { (*printf_call)( "\n%s: Hallo, %s!\n\n", __FLAVOUR__, s ); } else if( !strcmp( "fr", __FLAVOUR__ ) ) { (*printf_call)( "\n%s: Bonjour, %s!\n\n", __FLAVOUR__, s ); } else { (*printf_call)( "\n%s: Hello, %s!\n\n", "unknown", s ); } #else (*printf_call)( "\nHello, %s!\n\n", s ); #endif
Все достаточно просто. Тепрь надо подумать о размещении готовых пакетов в условиях использования механизма FLAVOURS.
Единственным отличием процедуры создания пакетов в нашем Make-файле, от аналогичных процедур других Make-файлов, является вызов утилиты make-package
. . . $(MAKE_PACKAGE) --flavour=$(FLAVOUR) . . .
где мы использовали управление '--flavour'. Именно эта функциональность необходима нам для того, чтобы пакеты, соответствующие всем вариантам отличным от базового, размещались в собственных подкаталогах. Более подробно, управление '--flavour' рассматривается в разделе Package Tools.
Соберем, теперь, все возможные варианты пакета hello-dll и посмотрим на изменения в каталоге 'platform/dist'.
Мы видим, что все варианты пакета hello-dll инсталлированы и, кросс-окружение готово для того, чтобы создавать программы на основе библиотеки hello-dll.
Если мы посмотрим на лог сборки, то увидим в какой последовательности собирались варианты пакета. Фактически, система сборки вызывала наш Make-файл шесть раз, подобно тому, как показано на следующем листинге.
$ make TOOLCHAIN=i686-glibc HARDWARE=pc32 FLAVOUR= $ make TOOLCHAIN=i686-glibc HARDWARE=pc32 FLAVOUR=de $ make TOOLCHAIN=i686-glibc HARDWARE=pc32 FLAVOUR=fr $ make TOOLCHAIN=x86_64-glibc HARDWARE=pc64 FLAVOUR= $ make TOOLCHAIN=x86_64-glibc HARDWARE=pc64 FLAVOUR=de $ make TOOLCHAIN=x86_64-glibc HARDWARE=pc64 FLAVOUR=fr
Разумеется при параллельной сборке, все подобные вызовы осуществляются практически одновременно, ну или в зависимости от того, сколько ядер насчитывает процессор машины разработчика.
В файле 'build-system/core.mk' можно видеть алгоритм перебора целей.
Если в командной строке не заданы значения переменных TOOLCHAIN, HARDWARE, FLAVOUR, то будет осуществляться сборка всех возможных вариантов, которые, в свою очередь, определяются исходя из списков COMPONENT_TARGETS и FLAVOURS пользовательского Make-файла. Напомним здесь, что полный набор вариантов может быть ограничен с помощью файла 'build-system/build-config.mk', рассмотренного нами в разделе Choose Hardware Targets.
Если не задано лишь одно из значений переменных TOOLCHAIN, HARDWARE, FLAVOUR, например, FLAVOUR, то система сборки будет пребирать варианты одного целевого устройства, которое, в свою очередь, задано переменными TOOLCHAIN и HARDWARE.
В случае, когда надо явно указать, что переменная задана и ее значение является пустым, надо поступить следующим образом.
$ make FLAVOUR=
При таком определении варианта, будет собираться лишь базовый. Заметим, что для переменных TOOLCHAIN и HARDWARE данный способ не работает, так как сборка не может осуществляться в условиях, когда целевое устройство не определено.
Application
Make-файл 'base/examples/hello/Makefile' построен на тех же принципах, что и Make-файл пакета hello-dll. Отличие состоит в том, что список зависимостей формируется динамически, исходя из текущего варианта сборки.
FLAVOURS = de fr include ../../../build-system/constants.mk ifeq ($(FLAVOUR),de) REQUIRES = base/examples/hello-dll^de endif ifeq ($(FLAVOUR),fr) REQUIRES = base/examples/hello-dll^fr endif ifeq ($(FLAVOUR),) REQUIRES = base/examples/hello-dll endif
Здесь необходимо заметить, что ссылка на вариант сборки необходимого пакета осуществляется посредством символа '^'.
Вообще, данный пример создан специально для того, чтобы пользователи системы сборки могли самостоятельно опробовать различные конфигурации, проследили порядок работы системы и, на практике, освоили основные приемы работы. Кроме того, самостоятельное изучение исходных текстов системы сборки может дать неизмеримо больше информации, нежели любые рукописные документы.
Hardware Specific Flavours
До сих пор, мы рассматривали общие варианты целевых устройств, задаваемые списком FLAVOURS. Заданные таким способом варианты, влияют на все устройства, для которых создан Make-файл. Так, в нашем примере, будет собираться по три варианта, как для машины с архитектурой x86_32, так и для машины с архитектурой x86_64.
Если же необходимо задать список вариантов, который будет присущь только устройству с идентификатором HARDWARE_PC64=pc64, надо определить список, имя которого имеет префикс, равный суффиксу в имени HARDVARE_PC64. Следующий листинг дает исчерпывающее объяснение по поводу префиксов и суффиксов переменных.
HARDWARE_PC64 = pc64 PC64_FLAVOURS = de fr
Первая переменная представлена в файле 'build-system/constants.mk', вторая, — представляет собой HW-специфичный список вариантов и, задается автором Make-файла.
HW-специфичные списки имеют более высокий приоритет чем общие.
Это означает, что если, для какого-либо устройства, определен HW-специфичный список, то общий список, для этого устройства игнорируется.
Относительно примера hello-dll, это означает, что если наряду с общим списком FLAVOURS определить список принадлежащий лишь устройству pc64, например так,
FLAVOURS = de fr PC64_FLAVOURS = fr
то для устройства pc64 будут возможны лишь два варианта пакета hello-dll, а именно базовый и французский. И если, при этом, попытаться собрать пакет с помощью команды,
$ make HARDWARE=pc64 FLAVOUR=de
то система сборки выдаст сообщение об ошибке и прекратит свою работу.
Parallel Building
Использование механизма FLAVOURS накладывает ограничения на параллельную сборку, поэтому, как только система сборки увидит ненулевой список вариантов, для текущего Make-файла, параллельная сборка будет отменена.
Связано это, в основном, с тем, что как правило, все варианты пакета имеют одинаковые требования к базовым системным пакетам. Представим, что все варианты пакета hello-dll требуют наличия в системе пакета pkgtool. Тогда, при параллельной сборке hello-dll, все варианты, одновременно, затребуют сборку пакета pkgtool, в его единственном, базовом варианте. Естественно при этом возникнут коллизии потому, что пакет pkgtool не имеет вариантов и, более того, создавать искуственно варианты пакета pkgtool для удовлетворения потребностей всевозможных вариантов других пакетов, не имеет никакого смысла. Другие способы разрешения подобных коллизий приведут к необходимости применения нестандартных, для GNU Make механизмов, что все равно приведет к замедлению работы.
Ну и еще одно замечание, касающееся наполнения кросс-окружения.
В нашем примере, инсталляция объектного и заголовочного файлов пакета hello-dll в кросс-окружение осуществляется для всех вариантов.
$(install_target): $(bin_target) . . . $(call install-into-devenv, $(HELLO_PKG)) . . .
Разумеется, автор платформы, может устранить данную погрешность, например, следующим образом.
$(install_target): $(bin_target) . . . ifeq ($(FLAVOUR),) $(call install-into-devenv, $(HELLO_PKG)) endif . . .
Но в этом случае, если пользователь решит собрать лишь один вариант пакета
$ make FLAVOUR=fr
кросс-окружение останется пустым и, при сборке программы hello, возникнет ошибка.
Вывод здесь прост. Нам, как создателям системы сборки, необходимо предоставить максимальные возможности и предупредить об особенностях, на первый взгляд незаметных. А вот в каком объеме эти возможности будут задействованы, предстоит решать пользователям, исходя из собственных потребностей.