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 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).