This tutorial demonstrates the basics of any build system on simple example. Here we shall build program grep in LFS environment at stage one or pass1.

That early stage goes right with your host tools. It does not require chrooting into container and much easier to study.

Preparation

Working directory

Download sources for your working directory, if you don't have it:

any-lfs-current

Unpack the archive:

tar xf any-lfs-*.tgz
cd any-lfs-*

Get ready build commands

You'll need executables of the build engine. The most simple way is to use them right from working directory:

PATH=$(pwd)/rrr/bin:$PATH

After that you will have available all build commands, provided by any. The assignment above is needed in every shell session you use for building.

Launch commands

To build package grep-3.4 in LFS environment, launch:

any do lfs,pass1 grep-3.4

-
any do is the command to build packages.
-
lfs,pass1 is the name of the configuration, used for the build. The main part is lfs, and word pass1 is added to set up the unique settings for first temporary pass.
-
grep-3.4 is the name of the package you wish to build. Program grep is chosen as simple sample, it has no special purpose. This argument you may change the most often in day-to-day debug.

Paths and objects

If you are building in a fresh-created working directory and have issued the previous commands exactly, the building will end up with failure. The status will be printed along with log file:

./build/log/pass1-lfs/grep-3.4.log

That log file keeps the record of entire process. When building fails, the path to log is printed out, so that the user could investigate the problem.

If we open the log, we can see something like that in the end:

./configure: No such file or directory

It seems the sources for the package are missing, as we haven't downloaded them. Let's fetch source archive grep-3.4.tar.xz from the official page:

http://ftp.gnu.org/gnu/grep/grep-3.4.tar.xz

The engine searches for archives with sources here:

./ports/srcstatic/

Put the source archive there. Launch the build again. Now build should procceed successfully.

After the build the following directories and files are created:

./build/image/pass1-lfs/grep-3.4/
directory with installed program, ready for packaging.
./build/work/pass1-lfs/grep-3.4/
directory with sources, where the compilation goes.
./build/pack/pass1-lfs/grep-3.4-x86_64-linux.tgz
binary installation archive for the package.

The shell code, which describes specific actions to build grep-3.4, is located in:

ports/packages/grep-3.4/grep.build

That file is the main tool of maintaining the package.

Also the patches for packages, if they were present, would be located here:

ports/packages/grep-3.4/patch/

Package grep does not have patches, but some other packages do.

General mechanism

Build system works by the following essentials steps:

-
it reads the input data from configuration files and options. Argument lfs,pass1 in the launch above is the pointer, what exactly to read from config files. They contain different data for various platforms. Our actual data is stored under sections [lfs] and [pass1].

All elements of configuration are printed with the word AUSE, when some build is launched.

All found data are transformed into variables in shell.

-
the system creates build script. It is written into directory ./var/dump/. For our example the build script would reside in ./var/dump/grep-3.4_lfs_pass1/dump.sh.

That build script includes the data from configuration files and libraries of the engine. That libraries reside in any/ subdirectory. Also some shell code is located in lfs/ subdirectory.

Build script is individual for each package.

-
inside the build script there is a block, which includes individual build files from ports/packages/. So the script uses generic libraries and functions of the engine, common for all packages, and individual build functions of the package, written by its maintainer.

Common functions perform tasks, which are similar for the vast majority of programs, for example, extraction of sources or creation of binary archive with the result.

Individual functions describe everything, related to the package: typically configuration, compilation, installation.

There is a possibility to redefine almost every typical function and make it individual for the package. Individual variant for grep-3.4 in this case should be saved into ports/packages/grep-3.4/grep.build.

-
The sequence of build functions is launched. They are common and individual functions, described above. At the end of successfull build the binary archive with package is formed.

The sequence is stored in method map in file any/corelib/map.sh. That method just launches all build functions one after another.

Here is the full sequence of build methods:

pkg_pretend
pkg_setup
pkg_context
src_fetch
src_prepare
src_config
src_compile
pkg_rminstall
src_install
pkg_install
pkg_config
pkg_root
pkg_pack

Not all of them are actually used in LFS building.

LFS envrionment at pass1 launches these methods additionally:

lfs_install_pass1

LFS envrionment at main stage launches these methods additionally:

lfs_install

Typically individual build file defines methods:

src_config
src_compile
src_install

Debugging

As the entire build process is shell code, it is enough to load that code in current shell session and repeat needed actions manually, exactly as they are in build files.

To load the environment, created by the system for the package grep-3.4 in our example:

. ./var/dump/grep-3.4_lfs_pass1/dump.sh

Now we need to turn off the mode exit-on-error. It is useful in automatic building not to miss an error, but makes debug impossible. To turn the mode off:

set +e

Finally, we should move to the directory with source files:

tobuild

Here we are ready to reproduce build errors. We may manually launch any strings from build files, including variables:

make ${MAKEOPTS}

We may just view the value of variables:

echo ${MAKEOPTS}

We may launch methods, defined in build files:

src_config

...and do anything else we need.

Build variables

The techniques of build code are explained in this chapter. That code has the same effect for all packages, so no sense to explain it in individual comments for every package.

Package variables: P, PN, PV

Variable P contains the name of the current package. So, while building zlib-1.2.11, for the code:

    echo ${P}

We have output:

zlib-1.2.11

Variable PN contains only the name of a package without version. For zlib-1.2.11 its value would be zlib.

Variable PV contains only the version of a package. For zlib-1.2.11 its value would be 1.2.11.

Variables in this section are assigned automatically and cannot be changed manually.

The PREFIX variable

Variable PREFIX contains the base directory for directory structure of a package. Its libraries are located in $PREFIX/lib, executables in $PREFIX/bin, and so on. Almost all system packages in linux are configured with PREFIX=/usr.

For tools build mode, used in LFS before version 10.0, the pass1 phase of building assigns full local dir + ./tools to the PREFIX. For example, if you are building in /home/halva/lfs, prefix will be /home/halva/lfs/tools. That's done for the ability to install temporary packages of pass1 phase into user-owned directory, without root privileges.

For crosstch build mode, used in LFS since version 10.0 and later on, standard PREFIX is always /usr.

Using PREFIX instead of plain text allows to apply the same code for pass1 and main stages of LFS.

To change the variable, set up option any_program_prefix in rdd.def config file:

[lfs]
any_program_prefix=/opt

The D variable

Variable D contains the empty directory for package installation, personal for each one. For example, zlib-1.2.11 package will have installation directory, pointing to ./build/image/lfs/zlib-1.2.11 (the actual path in variable will be absolute).

After installing packages to empty directory, it is possible to create binary archive, which contains files of only that package and nothing else.

Variable is assigned automatically and should not be changed manually.

The MAKEOPTS variable

MAKEOPTS contains user-defined options for make utility. The most widespread use of it are parameters of parallel build: -j5. The value -j5 is set up by the build system. User may change it globally or individually for some packages. It can be useful in case of errors with parallel build, turned on.

To change the variable, set up option any_make_opts in rdd.def config file:

[lfs]
any_make_opts = -j1

The meaning of MY_P, MY_PD

MY_P variable contains the name of archive with source code of a package (without file extension like .tar.xz). By default it equals to P.

If archive with sources has different name, we can point to it by assigning the variable at the beginning of the build file:

MY_P=Python-3.8.1

Package variables can help to make that assignment more generic and flexible:

MY_P=Python-${PV}

Now the code above can be used for all future versions of Python.

MY_PD variable contains the name of directory with patches of a package. By default it equals to P.

As the patches are located by your own project and not by author of another packages, like for MY_P, it is more rarely needed to change MY_PD. It is used for packages with non-standard names, relating to general packages, like pass1-binutils.

MY_PD=binutils-${PV}
MY_P=binutils-${PV}

Thus we say the package pass1-binutils to use source archives and patches from binutils.

The PASS1PREFIX variable

That variable contains chrooted path to temporary filesystem, constructed at pass1 stage of LFS. The value is /tools.

This variable is initialised only at main (second) stage of LFS build, as it relies on the work within a chroot.

The LFS_TGT variable

LFS_TGT variable is explained in original LFS book. It contains special triplet string, which identifies the architecture of our build.

The LFS_VERSION variable

LFS_VERSION variable contains the version of original LFS, which is used in current environment: 9.1, 8.0. It allows to use the same set of sources (ports, build system) to construct several LFS versions.