peTux LFS-based core system

peTux is not about precompiled binaries (can you trust an anonymous pet?), nor about exact instructions. It's about ideas and artifacts pet managed to collect from humans. Basically, everyone is free to choose any distro for the base system. Pet walks its own way following the most attractive scent of freedom. And the most amazing artifact on this way is LFS. It's like a catnip that makes pet high.

All the notes below are applicable to Linux From Scratch Version r12.3-68 Published June 5th, 2025.

Prerequisites

Host system: some she-master's linuxmint. Pet did not want to install any development packages on the host system, so it used LXC.

Give LFS a try

It's not feasible to experiment with LFS without some automation. ALFS does not look an option to pet. Instead, it uses its own simple shell scripts.

Scripts for each packages are located in packages/{name} subdirectory. Normally they are invoked by top level driver scripts, but also can be run manually. Without arguments they build and install a package, but with an argument they take specific action, either build or install. It's important to set environment variables before running scripts manually. For example, binutils pass 1 build only would require the following commands:

. ./environment-lfs
cd packages/binutils
./build-lfs-pass1 build

-e option for sh in shebang makes script to fail if any command fails. Setting pipefail is important as well. To understand why, try the following:

false
echo $?

false | tee /dev/null
echo $?

See the difference?

Package scripts save config.log file and the top-level driver script saves output of all scripts it runs. Logs along with diff utility help a lot to find source of problems while playing with configure options.

Also, pet's scripts use install-strip instead of bare install wherever possible.

Let fun begin

When playing with options it does not make sense to build entire toolchain with all those m4, make, grep, etc. Here's the minimal set of packages to focus on:

They build GCC with mpc, mpfr, and gmp unpacked in the GCC source tree. Pre-installing or building these libraries separately could save compile time when playing with GCC options, but pet lacks experience to do that in the first place.

Checkpoint 1

After GCC pass 1 it's worth to try running it:

echo | $LFS/tools/bin/$LFS_TGT-gcc -v -x c -

Take a look at include search paths below this line

#include <...> search starts here

and at ignoring nonexistent directory above it. Some of those nonexistent directories will exist after installing Glibc.

Same for C++ compiler:

echo | $LFS/tools/bin/$LFS_TGT-gcc -v -x c++ -

The output explains the format of include path they provide to libstdc++ using --with-gxx-include-dir option and it's helpful if you want to play with locations of include directories.

Checkpoint 2

When Glibc is in place we can make sure C compiler is working by trying

echo -e '#include <stdio.h>\nint main(){puts("hello");return 1;}' | $LFS/tools/bin/$LFS_TGT-gcc -x c - -v

Produced a.out is intended to run in chrooted environment. On the host system it may end up with segfault.

Checkpoint 3

When libstdc++ is in place we can make sure C++ compiler is working by trying

echo -e '#include <iostream>\nint main(){std::cout<<"hello\\n";return 1;}' | $LFS/tools/bin/$LFS_TGT-g++ -x c++ - -v

Mind g++. Although C++ is specified by -x, the linker will fail if $LFS_TGT-gcc is used instead of $LFS_TGT-g++.

Same as at checkpoint 2, produced a.out is intended to run in chrooted environment. On the host system it most likely will end up with segfault.

In details

GCC's Configure Terms and History defines term cross. Yes, just cross. It's when --build equals to --host but --target is different.

This means that a tool built with --target option will generate code and build executables for that target. For GCC this means building cross compiler, for binutils, this is building a cross linker as LFS book says in binutils pass 1 section. However, cross compilation does not take place here. Cross-compiler would be involved only when --build and --host are different. This can be checked in config.log. Most of pet's mistakes were caused by wrong triplets passed to these options.

Note that --target does not make sense for anything that does not generate code, i.e. Glibc, libstdc++, make, grep, m4, etc.

Triplet for --build is usually guessed by config.guess script. On Debian system some confusion might take place because config.guess returns full triplet like x86_64-pc-linux-gnu, but gcc -v says it was configured with x86_64-linux-gnu, i.e. without -pc particle.

This particle is vendor and some systems omit it. This is best explained in https://wiki.osdev.org/Target_Triplet.

Basically, it does not matter if --build and --host aren't equal to what preinstalled compiler was configured with. The key point is that they equal to each other. Pet tried absolutely different vendor, e.g. --build == --host == x86_64-wtf-linux-gnu. Result is the same: it works and produces correct binutils assuming --target=x86_64-lfs-linux-gnu.

Binutils pass 1

No cross-compilation here. The only option is --target=$LFS_TGT.

Options --build and --host are not specified and default to value returned by config.guess.

As long as no GCC executables with target prefix exist yet, host compiler will be used.

Installed executables must have target prefix. If they haven't, something went wrong.

GCC pass 1

Same as in binutils pass 1, we do not cross-compile here yet, we build cross compiler.

Linux API headers

No compilation takes place here at all, we just copy.

Glibc

This is where cross-compilation begins. LFS uses two options for this: --host=$LFS_TGT and --build=$(../scripts/config.guess). The option --target does not make sense for Glibc.

configure script will find newly compiled GCC thanks to PATH variable that includes path to the tools directory in the first place and prefix that matches --host.

Libstdc++ from GCC

Same as for Glibc, we cross-compile it. Note that installation path matches directory where g++ expects include files (see Checkpoint 1). This is set by --with-gxx-include-dir=/tools/$LFS_TGT/include/c++/$PKGVERSION_GCC`.

Binutils pass 2

All the same, cross-compile it. Pass 2 unbinds binutils from host libraries so they can run in chrooted environment. Binutils are installed in /usr. What looks strange to pet is that they don't have target triplet prefix as in pass 1. Adding --target=$LFS_TGT as for GCC pass 2 has no effect. Pet is unable to comprehend this.

GCC pass 2

This option is important for building internal executables: --with-build-sysroot=$LFS. Replacing it with --with-sysroot causes errors.

GCC installs executables with and without target prefix.

Checkpoint 4

After GCC pass 2 compilers can be checked in chrooted environment:

cp -a $LFS $LFS-copy
chown -R root:root $LFS-copy
echo -e '#include <stdio.h>\nint main(){puts("hello");return 1;}' | chroot $LFS-copy /usr/bin/gcc -x c - -v
echo -e '#include <iostream>\nint main(){std::cout<<"hello\\n";return 1;}' | chroot $LFS-copy /usr/bin/g++ -x c++ - -v

Notes

Cross-compiler in /tools directory is still required to build missing utilities to run configure scripts in chrooted environment. They are listed in LFS book Chapter 6. "Cross Compiling Temporary Tools" and have build scripts in packages repository.

LFS host/target tweaks

All those --build, --host, and --target options are confusing. Pet wants clarity so let's start from small tweaks bearing cross-compilation to different architecture in mind.

First, explicitly define HOST and TARGET environment variables. We do not need BUILD, we assume BUILD is same as HOST because we're building both toolchain and the final system on the same machine. We won't run toolchain on different machine, so we need only two variables.

Strictly speaking we should define BUILD instead of HOST, but BUILD is an awkward name in this context, whereas HOST sounds more naturally.

HOST can be read from preinstalled GCC output as

HOST=$(gcc -v 2>&1 | grep -E "^Target: (.*?)"| cut -f2 -d' ')

TARGET is where we want the system to run. We should add something unique to the triplet to make cross things working. Let it be petux we'll get rid of this later in the final system:

TARGET=${DEST_ARCH}-petux-linux-gnu

Let's leave DEST_ARCH same as uname -m for now to avoid unnecessary problems.

Binutils pass 1

configure options will be:

--build=$HOST
--host=$HOST
--target=$TARGET

It's not cross-compilation, so --build and --host are the same and equal to the triplet of machine we're building on.

--target defines final target which can be different architecture. The resulting compiler will generate code for it but it will run on the machine defined by --host, i.e. this one we're building on.

Glibc and Libstdc++

We're cross-compiling Glibc to run on TARGET (i.e. chrooted environment in our exercise), so configure options are:

--build=$HOST
--host=$TARGET

Binutils pass 2

Same as libraries above, however pet is still unable to comprehend why --target option has no effect here.

GCC pass 2

We're cross-compiling GCC to run on TARGET and generate code for TARGET. So, configure options are:

--build=$HOST
--host=$TARGET
--target=$TARGET

Cross compiling other tools

Now it's time to run function build_utils in the build-lfs script. It will compile all the rest packages listed in LFS Chapter 6. "Cross Compiling Temporary Tools".

Organizing packages

Pet does not like piles of files scattered across root file system. Clever humans invented NIX, others have to live with FHS. Pet has to stick to FHS too but it fancies to easily know without a package manager (LFS does not provide any) to which package any given file belongs to.

Symbolic links could be a solution. Pet is trying this approach (WIP).