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:
- Binutils pass 1
- GCC pass 1
- Linux API headers
- Glibc
- Libstdc++ from GCC
- Binutils pass 2
- GCC pass 2
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 hereand 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 - -vProduced 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++ - -vMind 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++ - -vNotes
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-gnuLet'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=$TARGETIt'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=$TARGETBinutils 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=$TARGETCross 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).