Cross-Compiling With GNU Autotools
Most open source packages weren’t written with your target board in mind. Here’s how to cross-compile them anyway.
free embedded linux course
free embedded systems course
What You Will Learn
What Autotools actually is and why it exists
The build/host/target distinction that trips up beginners
Configuring an Autotools package for cross-compilation correctly
Where CMake and Meson fit into the picture today
Prerequisites
Comfortable with static vs dynamic linking concepts
Having a Toolchain Is Only the Beginning
A working cross toolchain gets you nowhere by itself. The real work is cross-compiling the dozens or hundreds of open source packages your embedded Linux system actually depends on, and almost none of them were originally written with cross-compilation as the primary use case. Each build system handles this differently, and knowing which one you’re dealing with saves hours of guessing.
The Easy Case: Plain Makefiles
Some of the most important pieces of an embedded Linux system, including the Linux kernel itself, the U-Boot bootloader, and BusyBox, use hand-written makefiles that already understand cross-compilation through one convention: a CROSS_COMPILE make variable holding your toolchain’s prefix, trailing dash included.
$ make CROSS_COMPILE=aarch64-linux-gnu-
Or, exported once for a whole session:
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make
For the kernel and U-Boot specifically, you also need to set the target ARCH, which selects the correct architecture-specific source directories:
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
Autotools: the Older, Still-Common Standard
Autotools is not one tool but a family: Autoconf, Automake, Libtool, and Gnulib, working together to smooth over differences between build environments, compiler versions, and library locations across many different Linux and Unix systems. Packages that use it ship a configure script that probes your environment and generates the right makefiles.
$ ./configure --help
For a native build, the classic three-step dance is enough:
$ ./configure
$ make
$ sudo make install
Cross-compiling needs a few more environment variables set correctly:
| Variable | Purpose |
|---|---|
| CC | The C compiler command, pointed at your cross-compiler |
| CFLAGS | Extra compiler flags |
| LDFLAGS | Extra linker flags, such as -L<dir> for a non-standard library path |
| LIBS | Additional libraries to pass to the linker |
| CPPFLAGS | Preprocessor flags, such as -I<dir> for a non-standard header path |
Why –host Matters More Than CC Alone
Setting CC alone is sometimes enough, but you will frequently hit an error like this:
checking whether we are cross compiling... configure: error:
cannot run C compiled programs.
If you meant to cross compile, use '--host'.
The reason: configure often tests the toolchain’s capabilities by compiling small snippets and actually running them, which is impossible when the output binary is built for a different architecture than the host running the script. Autotools distinguishes three machine roles to solve this properly:
| Role | Meaning | Typical value here |
|---|---|---|
| Build | The machine running configure and make right now | x86_64 desktop (default, usually left unset) |
| Host | The machine the resulting program will run on | Your cross toolchain’s target tuple |
| Target | The machine code the resulting program itself will generate | Only relevant when building a compiler or similar tool |
Setting --host correctly is what actually tells configure to trust your explanation instead of trying to execute cross-compiled test binaries:
$ CC=aarch64-linux-gnu-gcc \
./configure --host=aarch64-linux-gnu --prefix=/usr
The --prefix=/usr matters too: left at its default, Autotools installs into <sysroot>/usr/local, which most systems don’t search by default. Setting the prefix to /usr ensures headers and libraries land where the rest of your sysroot expects to find them.
Try It: Cross-Compiling a Small Original Library
Rather than reusing any specific book example, build a tiny original Autotools-style project, ep_greet, and install it directly into your toolchain’s sysroot for testing:
$ CC=aarch64-linux-gnu-gcc \
./configure --host=aarch64-linux-gnu --prefix=/usr
$ make
$ make DESTDIR=$(aarch64-linux-gnu-gcc -print-sysroot) install
Setting DESTDIR is essential here. Without it, make install tries to write into your host machine’s real /usr directory, which is never what you want when cross-compiling. With it, the freshly built library and headers land inside your toolchain’s sysroot instead, exactly where later builds will expect to find them.
Where CMake and Meson Fit In
Autotools was the default for decades, but it is no longer the only serious option. CMake has become extremely common for C and C++ projects, including many modern embedded frameworks, and it handles cross-compilation through a toolchain file rather than environment variables:
$ cmake -DCMAKE_TOOLCHAIN_FILE=aarch64-toolchain.cmake -B build
$ cmake --build build
A minimal toolchain file just points CMake at your cross-compiler and sysroot:
# aarch64-toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_FIND_ROOT_PATH /opt/toolchains/aarch64-linux-gnu/aarch64-linux-gnu/sysroot)
Meson, a newer build system that generates Ninja build files under the hood, takes a similar cross-file approach and has been gaining adoption for its speed and simpler syntax. None of these has fully displaced Autotools, since thousands of existing packages still use it, but for new embedded projects it’s worth knowing all three exist rather than assuming Autotools is the only path.
Common Mistakes
| Mistake | Why it hurts |
|---|---|
| Setting only CC and skipping –host | configure tries to run cross-compiled test binaries and fails with a cross-compiling error |
| Forgetting DESTDIR during make install | The build silently tries to overwrite files in your host’s real /usr directory |
| Leaving –prefix at its Autotools default | Files install to /usr/local inside the sysroot, where nothing else looks for them by default |
| Assuming every package’s configure script behaves like textbook Autotools | Some packages, zlib among them, ship a configure script that doesn’t follow the standard Autotools conventions at all |
Best Practices
Use DESTDIR plus -print-sysroot to install cleanly into your toolchain
Read a package’s INSTALL file before assuming standard Autotools behavior
For new projects, evaluate CMake or Meson rather than defaulting to Autotools out of habit
Let Buildroot or Yocto handle cross-compilation flags automatically once your package count grows past a handful
Summary and Key Takeaways
Kernel-style makefiles need only CROSS_COMPILE and, for the kernel and U-Boot, ARCH. Autotools packages need CC and, critically, –host, plus a correctly set –prefix and DESTDIR when installing into a sysroot. And while Autotools still underpins a huge share of the open source world, CMake and Meson have earned real footing in newer embedded projects and are worth knowing alongside it, not instead of it.
In the final lecture of this series, we cross-compile a real third-party package end to end, resolve its dependencies with pkg-config, and cover what to do when cross-compilation doesn’t go smoothly.
FAQ
Why do I get a “cannot run C compiled programs” error from configure?
Because configure is trying to compile and execute a test program to probe your toolchain’s behavior, which fails when the resulting binary is for a different architecture than the machine running configure. Passing –host tells configure you’re cross-compiling instead.
What is the difference between build, host, and target in Autotools?
Build is the machine running configure right now. Host is the machine the compiled program will run on. Target is only relevant when you’re building a compiler or similar tool, referring to the machine code that tool itself will generate.
Why does make install need DESTDIR when cross-compiling?
Without DESTDIR, make install writes to the host’s real system directories, such as /usr, by default. DESTDIR redirects the install to a staging directory, typically your toolchain’s sysroot, instead.
Should I use CMake instead of Autotools for a new embedded project?
Either can work well. CMake and Meson have strong modern tooling and IDE support and are common in newer embedded frameworks, while Autotools remains the standard for a huge share of existing open source packages you’ll likely still need to cross-compile.
Does every package’s configure script support –host correctly?
No. Most well-behaved Autotools packages do, but some, including zlib, ship custom configure scripts that don’t follow standard Autotools conventions and need special handling.
Next: cross-compile a real package end to end with pkg-config
