Component Targets

The structure of user Makefiles is quite simple. After reading the introductory article where we studied the "Hello, World" example you already have the first impression about the main sections that must be present in any Makefile. Let's examine the main user Makefile sections in more details using the sox (universal sound converter, player, and recorder) package building as an example.

Normally, when building any product, the main goal is to create an application that is able to run on a certain target device. Users prepare Makefiles for the purpose of building certain applications for certain devices. One Makefile can be used to build the same application for the whole device line having different architectures.

This is why the build system includes such notions as toolchain and hardware. Toolchain is, obviously, a tool, and hardware, in turn, is a target. For the build system, a "target" is, in fact, a target device identifier using which it performs a set of required actions. In short, the build system chooses the needed tools and sets up the environment so that the source code of an application could be translated to object code able to run on the target device having the respective identifier.

In other words, the user creates a general Makefile and specifies the target devices, and the build system chooses the matching toolchain and sets up the environment in such a way that the standard operations described in the user Makefile result in the target application creation.

The key phrase from the description above is "specifies the target devices" which means defining a list of target device identifiers the current Makefile is intended for.

So, every Makefile shall start with a list of target devices, for example:

COMPONENT_TARGETS  = $(HARDWARE_PC32)
COMPONENT_TARGETS += $(HARDWARE_PC64)
COMPONENT_TARGETS += $(HARDWARE_CB1X)
COMPONENT_TARGETS += $(HARDWARE_CB3X)
COMPONENT_TARGETS += $(HARDWARE_FFRK3288)
COMPONENT_TARGETS += $(HARDWARE_VIP1830)
COMPONENT_TARGETS += $(HARDWARE_BEAGLE)
COMPONENT_TARGETS += $(HARDWARE_OMAP5UEVM)
COMPONENT_TARGETS += $(HARDWARE_B74)
COMPONENT_TARGETS += $(HARDWARE_CI20)

Since the final target of any Makefile is some component of the created system, this list is called COMPONENT_TARGETS.

To make the constants defined in the build system available for usage in the text of a Makefile, the file build-system/constants.mk shall also be included. This inclusion must be done using a relative path. This way, if our source tree looks like on the following listing:

  .
  ├── app
  │   └── sox
  │        └── 14.4.1
  │            └── Makefile
  │
  └── build-system

then the include directive must be written as follows:

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

because the Makefile is located on the third nesting level relative to the root directory where the build system mount point is located.

Requires

In the requires section the SOURCE_REQUIRES list can be defined and, if needed, additionally the REQUIRES list. These lists define the package's dependencies: first, dependencies on the source code texts, and second, dependencies on other components of the created product (but this is for the operating stage already).

This section must end with a line containing the special identifier __END_OF_REQUIRES__, for example:

# ======= __END_OF_REQUIRES__ =======

in any case, whether the REQUIRES list is present in the Makefile or not.

The inter-package dependencies lists REQUIRES can be prepared with the help of GNU Make's conditional directives: depending on the target device and the current toolchain, using build system variables HARDWARE and TOOLCHAIN, correspondingly. Moreover, if the FLAVOURS mechanism is also utilized, then it is possible to use the FLAVOUR variable in the user Makefiles. This variable specifies the current target device variant, so conditional lists can be prepared, for example, like in the following listing:

COMPONENT_TARGETS  = $(HARDWARE_PC32)
COMPONENT_TARGETS += $(HARDWARE_PC64)

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

# ======= __END_OF_REQUIRES__ =======

In addition, this example also illustrates the build system's FLAVOURS mechanism usage, more specifically, how a link to a target device's variant can be specified using the '^' character. Of course, here we illustrate the FLAVOURS mechanism using an example with different localizations of the same application, and not with different variants of the same target device. But this doesn't downplay the essence and purpose of the FLAVOURS mechanism.

It is worth noting that the directory names in the dependencies lists should contain paths relative to the project root directory.

Source Requires

The SOURCE_REQUIRES list, if defined, should contain at least one directory, for example,

SOURCE_REQUIRES = sources/packages/m/sox

where the archive file containing the built package's source texts is stored after downloading. If this list isn't defined, then it's the Makefile developer's responsibility to take care about the source code location. Usually, when third-party packages are built, the SOURCE_REQUIRES list is not empty.

In general, when a distributive is built, in addition to getting the source package archives from third-party developers, another important task is to take care about modifying such packages and saving the changes in a proper way. The following rules are defined for the Radix.pro platform:

  1. The sources directory contains Makefiles that are responsible for downloading source packages and, if needed, preparing changes for them.
  2. Prepared changes are never saved in the repository in the form of patch-files. Instead the way to prepare such changes is saved in the respective subdirectory.
  3. The subdirectory name where the patch-file is created must conform to the create-*-patch pattern.

In addition, all source packages are downloaded from the same source represented by the build system's DOWNLOAD_SERVER variable. It is recommended to place such server in your own network to speed up the source package download process.

To better understand how Makefiles in the sources directory work, it is recommended to examine some files from the platform repository. For example, the Makefile from the sources/packages/a/acl directory. And the PATCHES file where the patch-file prepared in the sources/packages/a/acl directory is used.

Runtime Requires

As mentioned above, after the requires lists a special identifier shall be added to denote the end of the Requires section.

# ======= __END_OF_REQUIRES__ =======

This identifier is needed to shorten the time spent on dependencies gathering. The point is that the user can define different dependency lists for different target devices using GNU Make's conditional directives. But the build system cannot get the dependency list by simply parsing the Makefile, which means that this list can only be finalized when the Make utility is already running. Because of this, to get the dependency lists, the build system "plays out" user Makefiles in GNU Make debug mode, therefore providing additional possibilities to create Makefiles supporting different target system configurations. Such debugging Makefile playback takes a lot of time, but only if the complete file is played out. Since it isn't needed to playback the whole file on the dependency gathering stage, the special identifier __END_OF_REQUIRES__ is used providing the ability to save time. Time savings are so large that it simply cannot be ignored. For example, for a system containing 700 packages that is built for 10 target devices, the full Makefile playback takes several hours (3—4 hours on Intel Core i7). On the contrary, partial Makefile playback for such setup only takes three to four seconds.

Such dependency gathering mechanism is only used for inter-package dependency lists. The SOURCE_REQUIRES lists are processed by simply parsing the Makefiles, because every package is built from the same source code, and the parts of code depending on the target device architecture are processed already on the package and its build system level.

It is probably a good idea to talk now shortly about inter-package dependencies. The point is that currently, it isn't possible to formalize inter-package dependencies building. In other words, it's impossible to create a formal algorithm for automatic dependency tree creation. Because of this, the build system only provides a mechanism that uses the dependency lists provided by users to automatically determine the package build and installation order and, in addition, to build trees similar to the one presented in the introductory article.

Service Variables

This section contains variable definitions needed for the build system functions operation, such as UNPACK_SRC_ARCHIVE and APPLY_PATCHES. In addition, here variables are defined that serve as targets for the Make utility and the list of patch-files that may be necessary to apply to the unpacked source archive.

Let's talk about these variables in more details.

version            = 14.4.1
tar_bz2_archive    = $(SRC_PACKAGE_PATH)/packages/m/sox/sox-$(version).tar.bz2
SRC_ARCHIVE        = $(tar_bz2_archive)
SRC_DIR            = $(TARGET_BUILD_DIR)/sox-$(version)
src_dir_name       = sox-$(version)
src_done           = $(TARGET_BUILD_DIR)/.source_done

PATCHES = PATCHES

build_dir          = $(TARGET_BUILD_DIR)/build
build_target       = $(TARGET_BUILD_DIR)/.build_done
install_target     = $(TARGET_BUILD_DIR)/.install_done

Here, SRC_ARCHIVE, SRC_DIR and PATCHES are the key build system variables. In fact, their values are the arguments for the UNPACK_SRC_ARCHIVE and APPLY_PATCHES functions. If a source package doesn't require any modifications, then the PATCHES variable may be omitted or its value can be empty.

The PATCHES variable defines an arbitral name of the file containing the list of applied changes, i.e. patch-files. All patch-file paths in the list must be relative to the current directory, which is the directory where the Makefile is located. Since the sox package doesn't require any changes, let's examine patch-file lists in the context of the bzip2 package:

  .
  ├── app
  │   └── bzip2
  │       └── 1.0.6
  │           ├── Makefile
  │           └── PATCHES
  │
  └── build-system

Here, the PATCHES file contains the following line:

../../../sources/packages/a/bzip2/patches/bzip2-1.0.6-cross.patch -p0

defining a relative path to the patch-file and the -p0 directive for the patch utility. It is easy to see that the patch-file was prepared in the sources/packages/a/bzip2 directory and temporarily stored in the patches subdirectory.

The SRC_ARCHIVE variable defines the path to the source archive that will be unpacked into the $(SRC_DIR) directory with the help of the build system's UNPACK_SRC_ARCHIVE function. Note that the SRC_DIR variable is defined as

SRC_DIR = $(TARGET_BUILD_DIR)/sox-$(version)

which means it defines the directory name where the unpacked archive files are located, and not just the $(TARGET_BUILD_DIR) directory. The point is that the UNPACK_SRC_ARCHIVE function unpacks the source archive into the base directory, i.e. the target directory for unpacking is, in fact, `dirname $(SRC_DIR)`. This is done for the sake of convenience: usually, source archives only contain one directory. The name of this directory is defined by the formula package-version. Because of this, it is convenient to have the name of the directory containing the package's source files as the SRC_DIR variable's value. This allows using the SRC_DIR variable both as a pointer to the location where the archive is unpacked and as the valid path to the source files.

Looking ahead, it makes sense to say that using the SRC_ARCHIVE, SRC_DIR, PATCHES variables and the UNPACK_SRC_ARCHIVE, APPLY_PATCHES functions makes source text preparation to subsequent building a very simple task that is executed identically for all user Makefiles:

src_done = $(TARGET_BUILD_DIR)/.source_done

$(src_done): $(SRC_ARCHIVE) $(PATCHES_DEP)
       $(UNPACK_SRC_ARCHIVE)
       $(APPLY_PATCHES)
       @touch $@

The $(src_done) target is defined as a file for a reason. In general, it should be made a rule to only use files, and not directories, at targets, for example, as the following listing illustrates.

$(SRC_DIR): $(SRC_ARCHIVE) $(PATCHES_DEP)
       $(UNPACK_SRC_ARCHIVE)
       $(APPLY_PATCHES)
       @touch $@

Using a directory as a target will inevitably result in errors, especially when building is done in parallel, because the Make utility is not able to determine a directory's creation time correctly.

To conclude the section dedicated to service variables, it should be said that the user can create any variables as necessary. Two of them are worth mentioning separately:

build_target     = $(TARGET_BUILD_DIR)/.build_done
install_target   = $(TARGET_BUILD_DIR)/.install_done

Further in our example we will use them to specify the main Makefile's targets.

Package Information

To automate target package preparation, several variables need to be created:

PKG_GROUP = app

SOX_PKG_NAME                = sox
SOX_PKG_VERSION             = 14.4.1
SOX_PKG_ARCH                = $(TOOLCHAIN)
SOX_PKG_DISTRO_NAME         = $(DISTRO_NAME)
SOX_PKG_DISTRO_VERSION      = $(DISTRO_VERSION)
SOX_PKG_GROUP               = $(PKG_GROUP)
###                          |---handy-ruler-------------------------------|
SOX_PKG_SHORT_DESCRIPTION   = universal sound sample translator
SOX_PKG_URL                 = $(BUG_URL)
SOX_PKG_LICENSE             = GPLv2
SOX_PKG_DESCRIPTION_FILE    = $(TARGET_BUILD_DIR)/$(SOX_PKG_NAME)-pkg-description
SOX_PKG_DESCRIPTION_FILE_IN = $(SOX_PKG_NAME)-pkg-description.in
SOX_PKG_INSTALL_SCRIPT      = $(SOX_PKG_NAME)-pkg-install.sh

Variable names consist of three parts divided with the underscore character ('_'). The first part is an optional prefix denoting the package name, the second part is the required name component PKG, and then a suffix follows that represents the variable's purpose. This convention will be discussed later, in the Collecting Requires section.

The name prefix can be selected arbitrarily, which means it doesn't have to be unique within project. For example, in all Makefiles of the X11/X.org directory such variable names start with the XORG prefix, which is convenient for automatic Makefile template generation.

It is important to note that the values of the following variables:

SOX_PKG_NAME                = sox
SOX_PKG_VERSION             = 14.4.1
SOX_PKG_SHORT_DESCRIPTION   = universal sound sample translator
SOX_PKG_LICENSE             = GPLv2

must be defined explicitly, without using a macro. Expressions like the following:

SOX_PKG_NAME                = $(pkg_name)
SOX_PKG_VERSION             = $(version)

are completely unacceptable. The reason is that during inter-package dependencies gathering as well as during inter-package dependency trees building the build system's functions read the Makefiles directly to get the values of the following variables:

*_PKG_NAME
*_PKG_VERSION
*_PKG_SHORT_DESCRIPTION
*_PKG_LICENSE

In addition, the user should always remember about special characters in the package's short description that must be escaped.

Variables defining the long package description and the installation scenario that we discussed in the Package Tools section are worth mentioning separately.

The user may need to modify these files before they are stored in the target package's root directory. Because of that, it is recommended to store such files' templates as follows:

  app
  └── sox
      └── 14.4.1
          ├── Makefile
          ├── PATCHES
          ├── sox-pkg-description.in
          └── sox-pkg-install.sh

Here, the sox-pkg-description.in file is a template, which is confirmed by the .in suffix, and the sox-pkg-install.sh file only needs to be copied; of course, it must be marked as executable with the respective attribute.

In the Makefile the corresponding variables are defined as:

SOX_PKG_DESCRIPTION_FILE    = $(TARGET_BUILD_DIR)/$(SOX_PKG_NAME)-pkg-description
SOX_PKG_DESCRIPTION_FILE_IN = $(SOX_PKG_NAME)-pkg-description.in
SOX_PKG_INSTALL_SCRIPT      = $(SOX_PKG_NAME)-pkg-install.sh

Later, the following procedures will be used for the target package preparation:

$(SOX_PKG_DESCRIPTION_FILE): $(SOX_PKG_DESCRIPTION_FILE_IN)
       @cat $< | $(SED) -e "s/@VERSION@/$(version)/g" > $@

$(pkg_archive):   .   .   .
       @cp $(SOX_PKG_DESCRIPTION_FILE) $(SOX_PKG)/.DESCRIPTION
       @cp $(SOX_PKG_INSTALL_SCRIPT) $(SOX_PKG)/.INSTALL

It shows that the current package version it inserted into the package's long description template, and only after that the file is copied into the target package's root directory.

Main Targets Definition

In the Package Tools section we described the package creation procedure and talked about creating a temporary directory to store the package files. If the package is prepared using a Makefile, it is recommended to define the name of this temporary directory using the respective variable:

SOX_PKG = $(CURDIR)/$(TARGET_BUILD_DIR)/$(SOX_PKG_NAME)-package

We also need to specify the base name of the resulting package:

pkg_basename = $(SOX_PKG_NAME)-$(SOX_PKG_VERSION)-$(SOX_PKG_ARCH)-$(SOX_PKG_DISTRO_NAME)-$(SOX_PKG_DISTRO_VERSION)

The make-package utility creates three files: an archive file, a file containing the sha sum and a file containing text description for the package. To make it more convenient to specify the main targets, the build system provides several functions allowing easy creation of target file lists:

pkg_archive     = $(TARGET_BUILD_DIR)/$(PKG_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))

We won't examine in details how these functions operate. They are defined in the constants.mk file and we have discussed them in the introductory article. The user can figure out their purpose by himself. The main advantage of using these functions, as opposed to defining lists manually, is that if the need arises to change the file extensions for the files created by the make-package utility, it won't require updating all user Makefiles in the project, it will be enough to only modify functions in the constants.mk file.

So, now we are going to discuss how to define the main targets for the build system, namely:

BUILD_TARGETS     = $(build_target)
BUILD_TARGETS    += $(install_target)

PRODUCT_TARGETS   = $(products)

ROOTFS_TARGETS    = $(pkg_archive)

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

In the introductory article, we have already discussed the main target lists. The only thing left is to examine how to prepare the three Makefile targets that are also included in the build system targets, namely, $(build_target), $(install_target) and $(pkg_archive).

Main Targets Building

The first target is the source package building, and in general, if the package is built on basis of autotools, it is quite simple:

$(build_target): $(src_done)
       @mkdir -p $(build_dir)
       @cd $(build_dir) && \
          $(BUILD_ENVIRONMENT) ../$(src_dir_name)/configure \
          --prefix=/usr               \
          --build=$(BUILD)            \
          --host=$(TARGET)            \
          $(extra_configure_switches)
       @cd $(build_dir) && $(BUILD_ENVIRONMENT) $(MAKE)
       @touch $@

There is really nothing difficult here. First, the standard configure procedure is run, and then the package is built.

Installation

When the $(install_target) target is executed, the main task is to populate the target package temporary directory $(SOX_PKG). For this, the DESTDIR variable is used:

env_sysroot = DESTDIR=$(SOX_PKG)

$(install_target): $(build_target)
       @mkdir -p $(SOX_PKG)
       @cd $(build_dir) && $(BUILD_ENVIRONMENT) $(MAKE) install $(env_sysroot)

For source packages that use autotools, this is the standard way to specify the target installation directory.

In general, if we are installing a package on a working machine, then the standard make install procedure is sufficient, and no other actions on our side are required. But in our case there is much more.

Since we are creating a boot image for a target machine and use a cross-compiler for that end, we need to take care about a wide range of factors including populating the temporary development environment with newly created libraries and utilities.

Our task can be divided into two parts. The first is related to setting-up the target package itself to ensure its correct behavior in the system, and the second is related to setting-up our cross-environment to make sure the following packages, that can often be dependent on the current package, are created.

Radix.pro accepts the following documentation conventions regarding the documents that shall be included in delivery. First, licenses accompanying the package must be residing in the /usr/doc/$(pkgname)-$(version) directory of the target filesystem. Second, all non-standard documentation must be residing in the /usr/share/doc/$(pkgname)-$(version) directory. Third, standard documentation must be installed in accordance to FHS. In addition, man pages and info files must be archived. During the sox package creation we executed the following operations to adhere to these requirements:

$(install_target): $(build_target)
       @mkdir -p $(SOX_PKG)
       @cd $(build_dir) && $(BUILD_ENVIRONMENT) $(MAKE) install $(env_sysroot)
       # ======= Install Documentation =======
       @if [ -d $(SOX_PKG)/usr/share/man ] ; then \
         ( cd $(SOX_PKG)/usr/share/man ; \
           for manpagedir in `find . -type d -name "man*"` ; do \
             ( cd $$manpagedir ; \
               for eachpage in `find . -type l -maxdepth 1` ; do \
                 ln -s `readlink $$eachpage`.gz $$eachpage.gz ; \
                 rm $$eachpage ; \
               done ; \
               gzip -9 *.? ; \
             ) \
           done \
         ) \
        fi
       @mkdir -p $(SOX_PKG)/usr/doc/$(src_dir_name)
       @cp -a $(SRC_DIR)/AUTHORS $(SRC_DIR)/COPYING \
              $(SOX_PKG)/usr/doc/$(src_dir_name)
       @mkdir -p $(SOX_PKG)/usr/share/doc/$(src_dir_name)
       @( cd $(SRC_DIR) ; \
          cp -a AUTHORS COPYING INSTALL LICENSE* NEWS README \
                $(SOX_PKG)/usr/share/doc/$(src_dir_name) \
        )
       @rm -f $(SOX_PKG)/usr/share/doc/$(src_dir_name)/FLAC.tag
       @( cd $(SRC_DIR) ; \
          if [ -r ChangeLog ] ; then \
            DOCSDIR=`echo $(SOX_PKG)/usr/share/doc/$(src_dir_name)` ; \
            cat ChangeLog | head -n 1000 > $$DOCSDIR/ChangeLog ; \
            touch -r ChangeLog $$DOCSDIR/ChangeLog ; \
          fi \
        )

Since a cross-compiler was used to build the package, and it was set to use the TARGET_DEST_DIR directory as the system directory, the installed linker scenarios (files with the .la extension) contain library paths that aren't correct in the target system. This situation must be fixed.

In our case, it is enough to simply delete unwanted directories from the full filenames:

       # ======= remove target path from target libtool *.la files =======
       @( cd $(SOX_PKG)/usr/lib$(LIBSUFFIX) ; \
          sed -i "s,$(TARGET_DEST_DIR),,g" libsox.la \
        )

Now, in the $(SOX_PKG) directory we have a package image that is fully prepared for usage in the created operating system.

Next, we need to take care about the cross-environment, more specifically, about installing the package content into the $(TARGET_DEST_DIR) directory. The build system provides the install-into-devenv function that takes the only argument: the source directory. This is why it is very easy to utilize this function:

       # ======= Install the same to $(TARGET_DEST_DIR) =======
       $(call install-into-devenv, $(SOX_PKG))

We strongly recommend using the install-into-devenv function instead of simply copying the files as shown on the following listing:

       # ======= Install the same to $(TARGET_DEST_DIR) =======
       @mkdir -p $(TARGET_DEST_DIR)
       @cd $(SOX_PKG) && cp -rf * $(TARGET_DEST_DIR)

As opposed to simply copying the files, the install-into-devenv function not only copies the files but also maintains the list of installed files saving it in the $(TARGET_BUILD_DIR)/.dist file. The file $(TARGET_BUILD_DIR)/.dist is used when the following command is called:

$ make dist_clean

to clean up the cross-environment when necessary. The cross-environment must be cleaned up properly to support inter-package dependency debugging during a package development.

So, by using the install-into-devenv function, we expanded the capabilities of our cross-environment, but again – this time already in the cross-environment – we received incorrect linker scenarios (files with the .la extension) and pkg-config utility's configuration files (files with the .pc extension). If we don't take care about modifying these files, then in future, when other packages are built that depend on the current one, we will see a lot of error. Note that searching for the error cause in a system containing several hundreds of packages can be rather difficult. To eliminate possible problems we will again use the sed editor.

       # ======= tune libtool *.la search path to the target destination for development =======
       @( cd $(TARGET_DEST_DIR)/usr/lib$(LIBSUFFIX) ; \
          sed -i "s,/usr,$(TARGET_DEST_DIR)/usr,g" libsox.la ; \
          sed -i "s,L/lib,L$(TARGET_DEST_DIR)/lib,g" libsox.la \
        )
       # ======= tune pkg-config *.pc search path to the target destination for development =======
       @( cd $(TARGET_DEST_DIR)/usr/lib$(LIBSUFFIX)/pkgconfig ; \
          sed -i "s,/usr,$(TARGET_DEST_DIR)/usr,g" sox.pc \
        )

To finish the setup process, we can remove unneeded information from the target package's object files:

       # ======= Strip binaries =======
       @( cd $(SOX_PKG) ; \
          find . | xargs file \
                 | grep "executable" \
                 | grep ELF \
                 | cut -f 1 -d ':' \
                 | xargs $(STRIP) --strip-unneeded 2> /dev/null ; \
          find . | xargs file \
                 | grep "shared object" \
                 | grep ELF \
                 | cut -f 1 -d ':' \
                 | xargs $(STRIP) --strip-unneeded 2> /dev/null ; \
          find . | xargs file \
                 | grep "current ar archive" \
                 | cut -f 1 -d ':' \
                 | xargs $(STRIP) -g 2> /dev/null \
        )
       @touch $@

Package Creation

The final target is the package creation. This procedure is simple and almost identical for all packages.

$(pkg_signature)   : $(pkg_archive) ;
$(pkg_description) : $(pkg_archive) ;

$(pkg_archive): $(install_target) $(SOX_PKG_DESCRIPTION_FILE) $(SOX_PKG_INSTALL_SCRIPT)
       @cp $(SOX_PKG_DESCRIPTION_FILE) $(SOX_PKG)/.DESCRIPTION
       @cp $(SOX_PKG_INSTALL_SCRIPT) $(SOX_PKG)/.INSTALL
       @$(BUILD_PKG_REQUIRES) $(SOX_PKG)/.REQUIRES
       @echo "pkgname=$(SOX_PKG_NAME)"                            >  $(SOX_PKG)/.PKGINFO ; \
        echo "pkgver=$(SOX_PKG_VERSION)"                          >> $(SOX_PKG)/.PKGINFO ; \
        echo "arch=$(SOX_PKG_ARCH)"                               >> $(SOX_PKG)/.PKGINFO ; \
        echo "distroname=$(SOX_PKG_DISTRO_NAME)"                  >> $(SOX_PKG)/.PKGINFO ; \
        echo "distrover=$(SOX_PKG_DISTRO_VERSION)"                >> $(SOX_PKG)/.PKGINFO ; \
        echo "group=$(SOX_PKG_GROUP)"                             >> $(SOX_PKG)/.PKGINFO ; \
        echo "short_description=\"$(SOX_PKG_SHORT_DESCRIPTION)\"" >> $(SOX_PKG)/.PKGINFO ; \
        echo "url=$(SOX_PKG_URL)"                                 >> $(SOX_PKG)/.PKGINFO ; \
        echo "license=$(SOX_PKG_LICENSE)"                         >> $(SOX_PKG)/.PKGINFO
       @$(FAKEROOT) sh -c "cd $(SOX_PKG) && chown -R root:root . && $(MAKE_PACKAGE) --linkadd yes .."

As the previous listing illustrates, we simply create the required files and call the make-package utility.

Collecting Requires

To create the package for inter-package dependency gathering, we used the build system's function BUILD_PKG_REQUIRES. In general, in the $(BUILDSYSTEM)/core.mk file four variables are provided to call the build system's scenario $(BUILDSYSTEM)/build_pkg_requires with various parameters.

    BUILD_PKG_REQUIRES = $(BUILDSYSTEM)/build_pkg_requires $(REQUIRES)
BUILD_ALL_PKG_REQUIRES = $(BUILDSYSTEM)/build_pkg_requires --pkg-type=all $(REQUIRES)
BUILD_BIN_PKG_REQUIRES = $(BUILDSYSTEM)/build_pkg_requires --pkg-type=bin $(REQUIRES)
BUILD_DEV_PKG_REQUIRES = $(BUILDSYSTEM)/build_pkg_requires --pkg-type=dev $(REQUIRES)

These functions are used to build the .REQUIRES file using the $(REQUIRES) list defined in the beginning of the Makefile.

Calling the $(BUILDSYSTEM)/build_pkg_requires scenario without arguments is equal to calling it with the --pkg-type=all argument; it triggers dependency gathering from any types of packages. The last two calls trigger searching for packages of the bin and dev type, according to the value of the --pkg-type directive.

Here the package types have an unusual meaning.

Usually distributors separate packages into binary packages and development packages by dividing every package into two parts. The first part – the binary package – contains executable files and libraries required for running in the system. The second part – the development package – contains header and configuration files required for other applications creation that use the binary package's object files.

The Radix.pro platform is based on other principles and doesn't divide packages into parts. The Radix.pro approach is described below:

Such notions as "applicable for application development" and "intended only for running in the system" are applied to the whole distributive, but not to individual packages included in the distributive.

If a general purpose distributive is created, for example, a distributive for personal computers, then packages are provided in one piece, which means they contain all components created by the author.

If an operating environment is created for embedded systems, then the distributive author has the right to decide what exactly will be included in this or that application. For example, after the package was installed in the target directory, before creating the target archive, the distributive author can delete all header files, all linker files, all configuration files for the pkg-config utility, therefore, decreasing the package size. In addition, the author can remove some object files that, in his opinion, are not required on the target machine.

In both cases, the build system treats the created package as a common package.

But during distributive building sometimes the need arises to decide if a certain package should be installed by default or if it is enough to only create the package and let the user decide whether to install is or not.

For example, to build GNU Libc, GCC compiler's runtime libraries are required. But there are two ways to get the compiler's libraries for the target system. The first way is to simply copy the runtime libraries from the toolchain that is used for system building, and the second is to build the compiler itself as a stand-alone package for the target system.

A package created using the first approach is required to build GNU Libc and, consequently, must be installed in the cross-environment before GNU Libc is built. But the decision whether to install this package on the target machine depends on our intention to install the full gcc package. In any case, we don't want the GNU Libc package to depend either on gcc-runtime package or the gcc package. Because otherwise, to ensure proper operation of the system installer that must follow all inter-package dependencies, we will have to make GNU Libc dependent on the gcc-runtime package. In addition, we will have to divide the gcc package into two parts: gcc-runtime and gcc-without-runtime, which is already unnatural. Plus, the worst thing is that it will result in two identically named but different in content gcc-runtime packages.

Isn't it better to simply know that the gcc-runtime package is created by copying libraries from toolchain, and the gcc package contains the full set of compilers from the GNU collection? In addition, it will make it easier to make decisions on any stage of distributive creation, installation and usage.

The build system provides the possibility to resolve such collisions. Let's examine this matter using the same gcc and gcc-runtime packages as an example.

We will assign the type bin to the pkgtool package because, basically, it's exactly what it is. For this, we defined the name of the variable defining the package name in the base/pkgtool/Makefile file as shown below:

BIN_PKG_NAME = pkgtool

This means that the middle part of the name is BIN_PKG.

The gcc-runtime and gcc packages will receive the type dev:

GCC_RT_DEV_PKG_NAME = gcc-runtime

and

GCC_DEV_PKG_NAME = gcc

(see dev/gcc-runtime/4.9.2/Makefile and dev/gcc/4.9.2/Makefile, correspondingly).

The type of the GNU Libc package is not important.

In the libs/glibc/2.21/Makefile file we will define the dependency list as follows:

REQUIRES   = dev/gcc-runtime/4.9.2
REQUIRES  += base/pkgtool

And we will create the .REQUIRES file with the help of the BUILD_BIN_PKG_REQUIRES function:

$(pkg_archive):   .   .   .
          .   .   .
       @$(BUILD_BIN_PKG_REQUIRES) $(GLIBC_PKG)/.REQUIRES
          .   .   .

This way, the .REQUIRES file will contain the only line:

pkgtool=1.0.6

which means that from the perspective of the system installation utility, the GNU Libc package only depends on the pkgtool package, so pkgtool must already be installed in the system by the time building the GNU Libc package is started. At the same time, during the build process all dependencies will be handled correctly, and runtime libraries of the GCC compiler will be added to the cross-environment before compiling GNU Libc.

Resume

We have examined the structure of a typical Makefile that uses the build system, as well as the main functions that make it possible to automate routine operations. Of course, it is impossible to foresee and describe all source packages that a build system user may encounter. But the Radix.pro system already includes hundreds of packages, and the user can always find interesting and useful examples in the platform's repository.

In conclusion, it should be said that in big projects containing thousands of Makefiles it is especially important to always be consistent about templates and algorithms used in project's Makefiles. Especially in environments with large development teams. Following simple and common rules will help you to avoid a lot of manual efforts to modify Makefiles in case the need arises, because in situations where all Makefiles have the same structure it is possible to update them automatically using sed and awk. It is important to understand that application package creation isn't really a creative job; it is more a routine requiring increased focus and attention. Only scrupulous fulfillment of simple requirements can help you to shorten the time needed to find future errors. It is also a good idea to have a CM manager as a team member with iron nerves and a motto similar to this: "A programmer is allowed to make mistakes, but only in his own repository branch."