What are Cross-Compiling Real-World Packages in Linux-Free Embedded Linux Course

Cross-Compiling Real-World Packages

Theory ends here. Cross-compile a real library, resolve its dependencies with pkg-config, and learn what to do when it all goes wrong.

100+ dependencies are common for graphical or multimedia packages
1 environment variable, PKG_CONFIG_LIBDIR, fixes most pkg-config headaches
7 lectures completed by the end of this one
free linux kernel development course
free embedded linux course
free linux device drivers course
free embedded systems course

What You Will Learn

Cross-compiling zlib, a real package with a non-standard build script
What pkg-config actually solves, and how to point it at your sysroot
Diagnosing the most common cross-compilation failures
Tying every lecture in this toolchain series together

Prerequisites

Everything from the previous six lectures in this series
A working cross toolchain and sysroot you can install into

A Real Package: Cross-Compiling zlib

zlib is small, ubiquitous, and, conveniently for this lecture, a perfect example of a package that looks like Autotools but doesn’t quite behave like it. It ships its own hand-written configure script that predates widespread Autotools adoption in the project.

$ wget https://zlib.net/zlib-1.3.1.tar.gz
$ tar xf zlib-1.3.1.tar.gz
$ cd zlib-1.3.1

zlib’s configure script doesn’t accept the standard --host flag the way real Autotools packages do. Instead, you point it at your cross-compiler directly through the CC variable and skip the machine-role flags entirely:

$ CC=aarch64-linux-gnu-gcc ./configure --prefix=/usr
$ make
$ make DESTDIR=$(aarch64-linux-gnu-gcc -print-sysroot) install

The lesson here generalizes well beyond zlib: always read a package’s INSTALL or README file before assuming it follows textbook Autotools conventions, because plenty of well-established packages predate, or simply diverge from, the standard.

Package Configuration With pkg-config

Once a library is installed into your sysroot, the next problem is knowing exactly which compiler and linker flags any other package needs to use it. Tracking this by hand across dozens of packages is unmanageable, which is exactly the problem pkg-config solves. It reads small .pc metadata files, one per installed library, kept in [sysroot]/usr/lib/pkgconfig:

$ cat $(aarch64-linux-gnu-gcc -print-sysroot)/usr/lib/pkgconfig/zlib.pc
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: zlib
Description: zlib compression library
Version: 1.3.1
Libs: -L${libdir} -lz
Cflags: -I${includedir}

Querying it directly on your host will fail, though, and this catches almost everyone the first time:

$ pkg-config zlib --libs --cflags
Package zlib was not found in the pkg-config search path.

That’s because pkg-config defaults to searching your host’s own .pc database, not your target’s sysroot. Point it at the right place with PKG_CONFIG_LIBDIR:

$ PKG_CONFIG_LIBDIR=$(aarch64-linux-gnu-gcc -print-sysroot)/usr/lib/pkgconfig \
  pkg-config zlib --libs --cflags
-I/usr/include -L/usr/lib -lz

With that working, feeding the output straight into a cross-compile becomes one clean command:

$ PKG_CONFIG_LIBDIR=$(aarch64-linux-gnu-gcc -print-sysroot)/usr/lib/pkgconfig \
  aarch64-linux-gnu-gcc $(pkg-config zlib --cflags --libs) \
  ep_compress_demo.c -o ep_compress_demo

Where ep_compress_demo.c is a small original program calling zlib’s compress() and uncompress() functions to round-trip a short string, useful as a quick smoke test that the library, headers, and linker are all correctly wired together end to end.

When Cross-Compiling Gets Ugly

zlib and SQLite-style libraries cross-compile smoothly. Plenty of packages do not, and the pain almost always falls into one of a few recurring categories:

Symptom Root cause
configure script has hand-rolled logic that ignores –host Custom, pre-Autotools build scripts, as seen with zlib above
configure finds the wrong headers or libraries It’s reading from the host’s own /usr instead of your sysroot, disregarding the cross-compile settings
Build fails trying to execute a just-compiled binary The build system assumes it can run its own output, which is impossible for cross-compiled code
Dozens of failed dependency checks in a chain One missing or mismatched library in a long dependency graph; packages with graphical (GTK/Qt) or multimedia support can easily depend on 100+ libraries

Each of these requires case-by-case detective work: reading configure logs, checking config.log, and sometimes patching the package’s build scripts directly. For any package with a genuinely large dependency graph, manually cross-compiling everything by hand stops being a good use of your time.

The Better Long-Term Answer

This is precisely the pain that Buildroot and the Yocto Project exist to eliminate. Both encode the cross-compilation quirks of hundreds of common packages so you don’t have to rediscover them yourself, generate a matched toolchain and sysroot automatically, and handle dependency graphs that would take weeks to resolve manually. Manual cross-compilation, package by package, is worth doing while you’re learning, exactly as this lecture series has done, but it is rarely the right approach for an entire production root filesystem.

From One Package to a Full System
Manual cross-compile (this lecture) → good for learning, one or two packages
Buildroot / Yocto Project → good for production, dozens to hundreds of packages

Common Mistakes

Mistake Why it hurts
Running pkg-config without setting PKG_CONFIG_LIBDIR It silently queries the host’s package database instead of your target sysroot
Assuming every configure script behaves identically zlib and several other well-known packages use custom, non-standard scripts
Manually cross-compiling a package with a huge dependency tree Can consume days of effort that a build system like Buildroot handles automatically
Never checking config.log after a failed configure run It almost always contains the exact compiler or linker error that the terminal summary hides

Best Practices

Always read INSTALL/README before assuming a package follows standard Autotools conventions
Set PKG_CONFIG_LIBDIR (and PKG_CONFIG_SYSROOT_DIR where supported) for every cross-compile session
Check config.log first when a configure script fails mysteriously
Reach for Buildroot or Yocto once you’re compiling more than a handful of packages
Keep a personal notes file of every package-specific quirk you discover; you will hit it again

Summary and Key Takeaways: the Whole Toolchain Series

Across this series you’ve gone from “what is a toolchain” to actually cross-compiling a real third-party library end to end. A toolchain is binutils, a compiler, a C library, and kernel headers, chosen deliberately and kept consistent for the life of a project. Cross toolchains dominate embedded development, and the ABI, C library, and floating-point choices baked into a toolchain’s target tuple matter more than most beginners expect. You can build a toolchain from source with crosstool-NG, or source a matched one from Bootlin, Arm’s official downloads, or your build system’s SDK. Static and dynamic linking trade size for patchability, and the soname system is what lets shared libraries evolve safely. Cross-compiling individual packages works with plain makefiles, Autotools, or increasingly CMake and Meson, and pkg-config is what stitches dependencies together correctly, as long as you remember to point it at your sysroot. And for anything beyond a handful of packages, a proper build system does this work far more reliably than doing it by hand.

With a solid toolchain foundation in place, the next stage of any embedded Linux project is the bootloader, the piece of software that brings your board to life and hands off control to the kernel you’ll build using exactly the toolchain skills covered here.

FAQ

Why doesn’t zlib’s configure script accept –host?

zlib ships a custom, hand-written configure script rather than a generated Autotools one, so it doesn’t implement the standard –host flag. You set the compiler directly through CC instead.

Why does pkg-config say a library isn’t found even though I just installed it?

By default pkg-config searches your host machine’s own package database, not your target’s sysroot. Set PKG_CONFIG_LIBDIR to your sysroot’s pkgconfig directory to fix this.

When should I stop manually cross-compiling packages and switch to Buildroot or Yocto?

As soon as your dependency count grows past a handful, or you’re dealing with graphical or multimedia libraries that can easily pull in 100+ dependencies. Manual cross-compilation doesn’t scale well past that point.

What’s the first thing to check when a cross-compile fails mysteriously?

config.log, generated by the configure script. It contains the actual compiler and linker commands and error output, which the terminal summary usually only hints at.

Is manual cross-compiling ever the right long-term choice?

For a small handful of packages, or for learning purposes, yes. For a full production root filesystem with dozens or hundreds of dependencies, a build system like Buildroot or Yocto is almost always the better investment of your time.

What comes after learning the toolchain in an embedded Linux course?

The bootloader is the traditional next step, since it’s the first piece of software that runs on the board and is responsible for loading and starting the Linux kernel that this toolchain series has been preparing you to build.

You’ve completed the toolchain series. Ready for bootloaders next?

 

Leave a Reply

Your email address will not be published. Required fields are marked *