Plain Makefile projects are the easy case. A huge slice of the open-source world, however, ships with
Autotools — the GNU Autoconf/Automake/Libtool family that generates a configure
script for you. Autotools projects need a different mental model than a simple Makefile, because
configure actively probes the toolchain by compiling and running test programs. In this lecture of
our free embedded Linux course, we’ll unpack exactly why that probing breaks under cross
compilation, and how to fix it correctly.
configure –host
build host target
DESTDIR sysroot install
free linux device drivers course
What You Will Learn
- What Autoconf, Automake, Libtool, and Gnulib each actually do
- Why configure fails with “cannot run C compiled programs” when cross compiling
- The build / host / target model Autotools uses to reason about toolchains
- Configuring, building, and installing a real Autotools package for an ARM target
- Using DESTDIR correctly so files land in your sysroot, not your host /usr
Prerequisites
Before You Start
- A working cross toolchain on PATH (see earlier lectures in this course)
- Comfort with the CROSS_COMPILE workflow covered in the previous lecture
- A Linux host with internet access for downloading a sample package
What Autotools Actually Is
“Autotools” isn’t one tool — it’s a family of separate GNU utilities that work together to make a project
portable across compilers, library versions, and header locations without the maintainer manually accounting for
every possible target system:
| Component | Role |
|---|---|
| Autoconf | Generates the configure script from a template describing what to detect |
| Automake | Generates portable Makefile.in templates from a simplified Makefile.am |
| Libtool | Abstracts away platform differences in building shared vs static libraries |
| Gnulib | Supplies portable replacement implementations for functions missing on some systems |
The upshot for you as a builder is simple: any project distributed with a configure script is an
Autotools project, and the usual native build sequence is:
$ make
$ sudo make install
Why Cross Compiling Breaks configure
The configure script doesn’t just check for header files — it frequently compiles small test
programs and runs them to see what happens, to detect things like endianness or library behavior that
can’t be determined from headers alone. That works fine natively. Under cross compilation, the compiled test
program is built for your target’s instruction set, which your host CPU cannot execute — so configure fails with
an error like this:
…
checking whether we are cross compiling… configure: error: in ‘/home/dev/build/example-1.0’:
configure: error: cannot run C compiled programs.
If you meant to cross compile, use ‘–host’.
See ‘config.log’ for more details
The error message actually tells you the fix. Setting only CC tells configure which compiler to
use, but it still assumes it’s allowed to execute whatever that compiler produces. You need to explicitly
tell it that this is a cross build.
The Build / Host / Target Model
Autotools reasons about three distinct machines that can be involved in any single build:
| Term | Meaning | Default |
|---|---|---|
| Build | The machine doing the compiling right now | Your current host |
| Host | The machine the resulting program will run on | Same as build, for a native compile |
| Target | The machine the program will generate code for (relevant only when building compilers) | Same as host |
For an ordinary application or library, you only care about host. Setting --host
to your toolchain’s triplet is what tells configure “don’t try to execute what you build — you’re cross
compiling”:
./configure –host=arm-linux-gnueabihf
Useful configure Environment Variables
- CC — the C compiler command to use
- CFLAGS — additional compiler flags
- CPPFLAGS — preprocessor flags, e.g. -I<dir> for non-standard header locations
- LDFLAGS — linker flags, e.g. -L<dir> for non-standard library locations
- LIBS — extra libraries to link, e.g. -lm for the math library
- CPP — the C preprocessor command to use
Hands-On: Cross Compiling A Small Autotools Package
We’ll use libtomcrypt‘s smaller cousin isn’t ideal for a quick demo, so instead let’s walk through
the well known SQLite amalgamation package, which is a good teaching example precisely because it exposes the
DESTDIR install step clearly. Download and unpack it fresh:
$ tar xf sqlite-autoconf-3450000.tar.gz
$ cd sqlite-autoconf-3450000
Configure it for your ARM target, giving both the compiler and the host triplet:
./configure –host=arm-linux-gnueabihf –prefix=/usrchecking build system type… x86_64-pc-linux-gnu
checking host system type… arm-unknown-linux-gnueabihf
checking whether we are cross compiling… yes
…
config.status: creating Makefile
Note --prefix=/usr — without it, Autotools defaults to installing under
/usr/local, which usually isn’t where your target’s runtime linker expects to find shared
libraries. Now build:
arm-linux-gnueabihf-gcc -DHAVE_CONFIG_H -I. -O2 -c sqlite3.c
arm-linux-gnueabihf-gcc -o sqlite3 shell.o .libs/libsqlite3.a -lpthread -ldl -lm
…
Installing Into Your Sysroot With DESTDIR
Running make install as-is would try to copy files into your host’s own /usr
directory — completely wrong for a cross build. The fix is DESTDIR, which redirects the install
root without changing any of the paths baked into the binaries or pkg-config files:
$ make DESTDIR=$(arm-linux-gnueabihf-gcc -print-sysroot) install
installing sqlite3 to /opt/toolchain/arm-linux-gnueabihf/sysroot/usr/bin
installing libsqlite3.so.0.8.6 to …/sysroot/usr/lib
installing sqlite3.h to …/sysroot/usr/include
After this you should find, inside your sysroot:
| Location | Contents |
|---|---|
| <sysroot>/usr/bin | sqlite3 command-line binary, ready to copy onto the target |
| <sysroot>/usr/lib | Shared and static libraries (.so, .a) |
| <sysroot>/usr/lib/pkgconfig | .pc file, so other packages’ configure scripts can find this one |
| <sysroot>/usr/include | Header files for anything that links against this library |
Two problems commonly show up at this final step. A read-only toolchain sysroot (common with some
crosstool-NG builds) will reject the install with a permissions error — make it writable, or configure your
toolchain build to leave it writable. And forgetting DESTDIR entirely will silently pollute your
host system’s own /usr with target-architecture files that your host can’t even execute.
Common Mistakes And Troubleshooting
Frequent Pitfalls
- Setting only CC and forgetting –host, triggering the “cannot run C compiled programs” error
- Omitting DESTDIR and accidentally installing ARM binaries into the host’s own /usr
- Forgetting –prefix=/usr, ending up with files under /usr/local that the target won’t search
- Running make install without root/write permission on a read-only sysroot
- Not re-running ./configure after changing CROSS_COMPILE-style variables between builds
Best Practices
- Always pass –host explicitly, even if CC alone seems to work for a given package
- Use “gcc -print-sysroot” so DESTDIR always matches your actual toolchain’s sysroot path
- Keep a per-package build log; config.log is invaluable when configure fails mysteriously
- Build in a separate out-of-tree directory when a package supports it, to keep source trees clean
Summary And Key Takeaways
- Autotools projects need –host, not just CC, because configure tries to execute test binaries
- Build, host, and target describe three potentially different machines in the toolchain model
- DESTDIR redirects “make install” into your sysroot without altering baked-in paths
- Always set –prefix=/usr so installed files land where the target’s runtime expects them
Together with the previous lecture’s CROSS_COMPILE workflow, you now have the two most common build-system
patterns you’ll meet cross compiling real-world software for an embedded Linux target.
FAQ
Why does configure fail with “cannot run C compiled programs”?
Configure compiles small test programs to detect target behavior, then tries to execute them. Under cross
compilation the resulting binary targets a different CPU than your host, so it can’t be executed — you need to
pass –host so configure knows not to try.
What’s the difference between build, host, and target?
Build is the machine compiling right now, host is the machine the program will run on, and target is only
relevant when building a cross compiler itself — the machine that compiler’s output will run on.
Why do I need DESTDIR instead of just running make install directly?
Without DESTDIR, make install copies files to your host’s own /usr, which is architecture-mismatched and
pollutes your host system. DESTDIR redirects the install root to your sysroot instead.
Why set –prefix=/usr explicitly?
The Autotools default install prefix is /usr/local, but most embedded root filesystems only search /usr for
libraries and binaries, so files installed under /usr/local would never be found at runtime.
What if make install fails with a permissions error?
Some toolchain sysroots, especially ones built with crosstool-NG, are read-only by default. Make the sysroot
writable, or reconfigure your toolchain build to leave it writable.
Do all open-source packages use Autotools?
No — many modern projects use CMake or Meson instead. If a package ships a configure script, it’s Autotools;
if it ships a CMakeLists.txt, it’s CMake, which follows a different cross-compilation model.
Continue Your Embedded Linux Journey
Next up: an introduction to cross compiling CMake-based projects.
