GNU Autotools Mastery: Portable Build Systems from First Principles
Goal
By completing the projects in this guide, you will deeply understand the entire GNU Build System (Autotools) ecosystem—Autoconf, Automake, and Libtool—and why it remains the gold standard for portable C/C++ software distribution after 30+ years. You’ll master the M4 macro language that powers configure scripts, understand how feature detection works across different Unix-like systems, and gain the ability to create professional-grade build systems that work everywhere from embedded Linux to macOS to BSD variants. More importantly, you’ll internalize the philosophy of portable software: writing once and building anywhere without modification.
Why GNU Autotools Matter
The Portability Problem
In theory, C is portable. In practice, building C software across different systems is a nightmare:
The Portability Challenge:
Your C Code
│
▼
┌─────────────────────────────────────────────────────────┐
│ Target Systems │
├─────────────┬──────────────┬─────────────┬─────────────┤
│ Linux │ macOS │ FreeBSD │ Solaris │
├─────────────┼──────────────┼─────────────┼─────────────┤
│ <endian.h> │<machine/ │<sys/ │<sys/ │
│ │ endian.h> │ endian.h> │ isa_defs.h> │
├─────────────┼──────────────┼─────────────┼─────────────┤
│ strlcpy: NO │ strlcpy: YES │ strlcpy: YES│ strlcpy: YES│
├─────────────┼──────────────┼─────────────┼─────────────┤
│ /lib64 │ /usr/lib │ /usr/lib │ /usr/lib │
├─────────────┼──────────────┼─────────────┼─────────────┤
│ gcc │ clang │ clang │ cc │
└─────────────┴──────────────┴─────────────┴─────────────┘
│
▼
How do you write ONE Makefile?
What Autotools Solves
The Autotools Solution:
Developer Machine User Machine
┌────────────────┐ ┌────────────────┐
│ │ │ │
│ configure.ac │ │ ./configure │
│ + │ make dist │ ↓ │
│ Makefile.am │ ─────────────────► │ config.h │
│ ↓ │ (tarball) │ + │
│ autoreconf │ │ Makefile │
│ ↓ │ │ ↓ │
│ configure │ │ make │
│ + │ │ ↓ │
│ Makefile.in │ │ Binary │
│ │ │ │
└────────────────┘ └────────────────┘
Key Insight: The user doesn't need autotools installed!
Only the developer needs it. Users just run ./configure && make
The Build Pipeline in Detail
Complete Autotools Flow:
┌──────────────────────────────────────────────────────────────────┐
│ DEVELOPER SIDE │
├──────────────────────────────────────────────────────────────────┤
│ │
│ configure.ac ─────► autoreconf ─────► configure (shell script) │
│ │ │ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ aclocal.m4 │ │
│ │ (M4 macros) │ │
│ │ │ │
│ ▼ │ │
│ Makefile.am ──► automake ──► Makefile.in │ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌─────────────────────────────┐ │
│ │ │ make dist │ │
│ │ │ (creates tarball) │ │
│ │ └────────────┬────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ project-1.0.tar.gz │
│ │ (ready for distribution) │
│ │
└──────────────────────────────────────────────────────────────────┘
│
│ (download/install)
▼
┌──────────────────────────────────────────────────────────────────┐
│ USER SIDE │
├──────────────────────────────────────────────────────────────────┤
│ │
│ tar xf project-1.0.tar.gz │
│ cd project-1.0 │
│ │
│ ./configure ──────────────────────────────────────────────┐ │
│ │ │ │
│ ├── Check for C compiler │ │
│ ├── Check for required headers │ │
│ ├── Check for required libraries │ │
│ ├── Check for optional features │ │
│ └── Generate: │ │
│ │ │ │
│ ├── config.h (feature detection results) │ │
│ ├── Makefile (from Makefile.in) │ │
│ └── config.status (for reconfiguration) │ │
│ │ │
│ make ──────────────────────────────────────────────────────┘ │
│ │ │
│ └── Compile sources with detected settings │
│ │
│ make install ──────────────────────────────────────────────── │
│ │ │
│ └── Install to /usr/local (or --prefix location) │
│ │
└──────────────────────────────────────────────────────────────────┘
Historical Context
GNU Autotools emerged from a real problem in the 1990s:
Timeline of Build Systems:
1970s: make (Stuart Feldman, Bell Labs)
└── Manual Makefiles, no portability support
1991: Autoconf 1.0 (David MacKenzie)
└── Shell-based feature detection
└── Generated from M4 macros
1994: Automake 1.0 (Tom Tromey)
└── Generate Makefile.in from simple .am files
└── GNU Coding Standards compliance
1996: Libtool 1.0 (Gordon Matzigkeit)
└── Portable shared library creation
└── Versioning and dependency handling
2000s: CMake, SCons emerge as alternatives
└── But Autotools remains dominant for C/C++
└── Still used by: GCC, Linux kernel, GNOME, GTK+
Why Autotools Still Matters in 2024
Despite newer alternatives, Autotools remains essential:
Project Build System Why Autotools?
─────────────────────────────────────────────────────────
GNU Coreutils Autotools Runs on 50+ platforms
GCC Autotools Maximum portability required
Linux Kernel make + Kconfig Custom, but inspired by autoconf
GTK+/GNOME Meson (was AT) Migrated recently, but AT legacy
OpenSSL Custom + AT Hybrid for portability
FFmpeg Custom But configure-like interface
GNU Emacs Autotools Lisp + C portability
When to use Autotools:
- Maximum portability across Unix variants
- Libraries intended for wide distribution
- Projects following GNU standards
- When users expect
./configure && make && make install
Prerequisites & Background Knowledge
Essential Prerequisites
Before starting these projects, you should have:
1. C Programming Fundamentals
- Writing and compiling C programs
- Header files and separate compilation
- Understanding of preprocessor (
#include,#define,#ifdef) - Basic library linking (
-lflags)
2. Make Basics
- Reading and writing simple Makefiles
- Understanding targets, prerequisites, and recipes
- Variables and pattern rules
- Understanding of
make,make clean,make install
3. Shell Scripting Fundamentals
- Variables and substitution
- Conditionals and loops
- Exit codes and error handling
- Basic understanding of
shvsbash
Self-Assessment Questions
Before proceeding, you should be able to answer:
- What does
gcc -c file.c -o file.oproduce, and why is it different fromgcc file.c -o program? - In a Makefile, what does
$(CC) $(CFLAGS) -o $@ $^mean? - What’s the difference between
-I/pathand-L/pathin GCC? - What does
#ifdef HAVE_FEATUREdo in C code? - Why might
/usr/include/stdio.hexist on one system but not another?
Helpful But Not Required
- Prior exposure to M4 macro language
- Experience with shared libraries (
.so,.dylib,.dll) - Knowledge of pkg-config
- Cross-compilation concepts
Development Environment Setup
# Ubuntu/Debian
sudo apt-get install autoconf automake libtool pkg-config build-essential
# Fedora/RHEL
sudo dnf install autoconf automake libtool pkgconfig gcc make
# macOS (with Homebrew)
brew install autoconf automake libtool pkg-config
# Verify installation
autoconf --version # Should show 2.69+
automake --version # Should show 1.16+
libtool --version # Should show 2.4+
Time Investment Expectations
| Project Range | Time Required | Difficulty |
|---|---|---|
| P01-P04 | 3-4 hours each | Beginner |
| P05-P09 | 4-6 hours each | Intermediate |
| P10-P14 | 6-8 hours each | Intermediate-Advanced |
| P15-P17 | 8-12 hours each | Advanced |
Total estimated time: 80-120 hours for complete mastery
Core Concept Analysis
The Three Pillars of Autotools
Autotools Component Architecture:
┌─────────────────────────────────────────────────────────────────┐
│ AUTOCONF │
│ "What features does this system have?" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Input: configure.ac (M4 macros) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ AC_INIT([myproject], [1.0]) │ │
│ │ AC_CONFIG_SRCDIR([src/main.c]) │ │
│ │ AC_PROG_CC │ │
│ │ AC_CHECK_HEADERS([unistd.h sys/types.h]) │ │
│ │ AC_CHECK_FUNCS([strlcpy memset_s]) │ │
│ │ AC_CHECK_LIB([z], [compress]) │ │
│ │ AC_CONFIG_FILES([Makefile src/Makefile]) │ │
│ │ AC_OUTPUT │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Output: configure (portable shell script, ~5000+ lines) │
│ config.h.in (template for feature defines) │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ AUTOMAKE │
│ "How should we build this project?" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Input: Makefile.am (high-level build description) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ bin_PROGRAMS = myprogram │ │
│ │ myprogram_SOURCES = main.c utils.c │ │
│ │ myprogram_LDADD = -lz │ │
│ │ │ │
│ │ lib_LTLIBRARIES = libmylib.la │ │
│ │ libmylib_la_SOURCES = mylib.c │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Output: Makefile.in (template with @substitutions@) │
│ Includes: all, install, clean, dist, check targets │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ LIBTOOL │
│ "How do we build portable shared libraries?" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Problem: Every system creates shared libraries differently │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Linux │ │ macOS │ │ Windows │ │
│ │ libfoo.so │ │ libfoo.dylib │ │ foo.dll │ │
│ │ gcc -shared │ │ -dynamiclib │ │ dllexport │ │
│ │ -Wl,-soname │ │ -install_name │ │ .def files │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ Solution: libtool abstraction │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ libtool --mode=compile gcc -c foo.c │ │
│ │ libtool --mode=link gcc -o libfoo.la foo.lo │ │
│ │ libtool --mode=install install libfoo.la /usr/lib │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Output: .la files (libtool archives with metadata) │
│ Platform-appropriate shared/static libraries │
│ │
└─────────────────────────────────────────────────────────────────┘
M4 Macro Language Fundamentals
M4: The Heart of Autoconf
M4 is a general-purpose macro processor that transforms text:
┌────────────────────────────────────────────────────────────────┐
│ M4 Basics │
├────────────────────────────────────────────────────────────────┤
│ │
│ define([GREET], [Hello, $1!]) ← Define macro │
│ GREET([World]) ← Invoke macro │
│ → Hello, World! ← Result │
│ │
│ Key M4 concepts: │
│ │
│ ┌──────────────────┬──────────────────────────────────────┐ │
│ │ [text] │ Quoted text (delays expansion) │ │
│ │ $1, $2, ... │ Macro arguments │ │
│ │ define(name,val) │ Define a new macro │ │
│ │ dnl │ Delete to newline (comment) │ │
│ │ changequote │ Change quote characters │ │
│ │ include(file) │ Include another M4 file │ │
│ └──────────────────┴──────────────────────────────────────┘ │
│ │
│ Why M4 for Autoconf? │
│ • Allows reusable macro libraries │
│ • Generates portable shell code │
│ • Extensible through custom macros │
│ │
└────────────────────────────────────────────────────────────────┘
How Autoconf Uses M4:
configure.ac (M4 macros)
│
│ m4 + aclocal.m4 + system macros
▼
configure (pure POSIX shell script)
Example transformation:
AC_CHECK_HEADERS([unistd.h])
│
│ Expands to ~50 lines of shell:
▼
ac_fn_c_check_header_mongrel() {
...
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
#include <$ac_header>
_ACEOF
if ac_fn_c_try_compile; then
...
The configure Script Internals
What configure Actually Does:
┌────────────────────────────────────────────────────────────────┐
│ ./configure --prefix=/usr --enable-debug │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ PHASE 1: Parse Arguments │
├────────────────────────────────────────────────────────────────┤
│ │
│ --prefix=/usr → ac_prefix="/usr" │
│ --enable-debug → enable_debug="yes" │
│ --with-ssl=/opt/ssl → with_ssl="/opt/ssl" │
│ │
│ Standard options: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ --prefix Installation prefix (/usr/local) │ │
│ │ --exec-prefix Architecture-dependent files │ │
│ │ --bindir User executables │ │
│ │ --libdir Object code libraries │ │
│ │ --includedir C header files │ │
│ │ --sysconfdir Configuration files (/etc) │ │
│ │ --enable-X Enable feature X │ │
│ │ --disable-X Disable feature X │ │
│ │ --with-X Use external package X │ │
│ │ --without-X Don't use external package X │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ PHASE 2: System Detection │
├────────────────────────────────────────────────────────────────┤
│ │
│ Detect: Build system, host system, target system │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ build: System where compilation happens │ │
│ │ host: System where program will run │ │
│ │ target: System for which compiler generates code │ │
│ │ (only relevant for compilers/toolchains) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Output: config.guess determines system triplet │
│ Example: x86_64-unknown-linux-gnu │
│ aarch64-apple-darwin23.0.0 │
│ │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ PHASE 3: Feature Detection │
├────────────────────────────────────────────────────────────────┤
│ │
│ For each check, configure: │
│ 1. Creates a small test program (conftest.c) │
│ 2. Compiles it with the detected compiler │
│ 3. Checks the result (compile success? link success? run?) │
│ 4. Records the result for later use │
│ │
│ Example: AC_CHECK_HEADERS([sys/mman.h]) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ cat > conftest.c <<EOF │ │
│ │ #include <sys/mman.h> │ │
│ │ int main() { return 0; } │ │
│ │ EOF │ │
│ │ │ │
│ │ if $CC -c conftest.c 2>/dev/null; then │ │
│ │ echo "#define HAVE_SYS_MMAN_H 1" >> config.h │ │
│ │ fi │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ PHASE 4: Output Generation │
├────────────────────────────────────────────────────────────────┤
│ │
│ Generate final files from templates: │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ config.h.in │ → │ config.h │ │
│ │ #undef HAVE_X │ │ #define HAVE_X 1 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Makefile.in │ → │ Makefile │ │
│ │ CC = @CC@ │ │ CC = gcc │ │
│ │ prefix = @prefix@│ │ prefix = /usr │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ Also creates: │
│ • config.status (re-run to regenerate without full configure) │
│ • config.log (detailed log of all checks) │
│ │
└────────────────────────────────────────────────────────────────┘
Makefile.am Syntax Deep Dive
Automake Primaries and Prefixes:
┌────────────────────────────────────────────────────────────────┐
│ PRIMARY (what to build) │
├────────────────────────────────────────────────────────────────┤
│ │
│ PROGRAMS → Executable programs │
│ LIBRARIES → Static libraries (.a) │
│ LTLIBRARIES → Libtool libraries (.la → .so/.dylib) │
│ HEADERS → Header files to install │
│ DATA → Data files to install │
│ SCRIPTS → Scripts to install │
│ MANS → Manual pages │
│ TEXINFOS → Texinfo documentation │
│ │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ PREFIX (where to install) │
├────────────────────────────────────────────────────────────────┤
│ │
│ bin_ → $(bindir) typically /usr/local/bin │
│ lib_ → $(libdir) typically /usr/local/lib │
│ include_ → $(includedir) typically /usr/local/include │
│ sbin_ → $(sbindir) typically /usr/local/sbin │
│ pkglib_ → $(pkglibdir) $(libdir)/$(PACKAGE) │
│ noinst_ → Don't install (internal use only) │
│ check_ → Only for testing │
│ │
└────────────────────────────────────────────────────────────────┘
Combined: prefix_PRIMARY = targets
Examples:
bin_PROGRAMS = myapp → Install myapp to $(bindir)
lib_LTLIBRARIES = libfoo.la → Install libfoo to $(libdir)
noinst_LIBRARIES = libinternal.a → Build but don't install
include_HEADERS = foo.h bar.h → Install headers to $(includedir)
check_PROGRAMS = test_foo → Only built during 'make check'
┌────────────────────────────────────────────────────────────────┐
│ Per-target Variables │
├────────────────────────────────────────────────────────────────┤
│ │
│ Replace - with _ in target name: │
│ │
│ bin_PROGRAMS = my-program │
│ │
│ my_program_SOURCES = main.c utils.c ← Source files │
│ my_program_CFLAGS = -DDEBUG ← Compiler flags │
│ my_program_LDFLAGS = -L/opt/lib ← Linker flags │
│ my_program_LDADD = -lz libinternal.a ← Libraries to link │
│ my_program_DEPENDENCIES = generated.h ← Extra dependencies │
│ │
│ For libraries (Libtool): │
│ │
│ lib_LTLIBRARIES = libfoo.la │
│ │
│ libfoo_la_SOURCES = foo.c bar.c │
│ libfoo_la_LIBADD = -lm ← Libraries │
│ libfoo_la_LDFLAGS = -version-info 1:2:0 ← Versioning │
│ │
└────────────────────────────────────────────────────────────────┘
Concept Summary Table
| Concept | What You Must Internalize |
|---|---|
| configure.ac | The M4 source that becomes the configure script; describes what to detect |
| Makefile.am | High-level build description; Automake generates complex Makefiles from it |
| M4 macros | Text transformation language; AC_* and AM_* macros expand to shell code |
| Feature detection | Compile-time tests that determine what the host system supports |
| config.h | Header file containing #define for each detected feature |
| Libtool | Abstraction layer for building shared libraries portably |
| make dist | Creates distributable tarball with all generated files |
| autoreconf | Runs all auto* tools in correct order to regenerate build system |
| Substitution variables | @VAR@ in templates replaced by configure with detected values |
| pkg-config | Standard way to query installed library compile/link flags |
Deep Dive Reading by Concept
Recommended Books
| Concept | Book | Chapters |
|---|---|---|
| Autotools Overview | Autotools (John Calcote) | Ch 1-3: Introduction and First Project |
| M4 Language | Autotools | Ch 4: M4 Fundamentals |
| Configure Scripts | Autotools | Ch 5-6: Autoconf in Depth |
| Automake | Autotools | Ch 7-8: Automake Mastery |
| Libtool | Autotools | Ch 9-10: Building Libraries |
| Make Fundamentals | The GNU Make Book (Graham-Cumming) | Ch 1-4: Make Basics |
| Advanced Make | The GNU Make Book | Ch 5-8: Functions and Patterns |
| Portability | The Art of Unix Programming (Raymond) | Ch 17: Portability |
Online Resources (Authoritative)
- GNU Autoconf Manual: https://www.gnu.org/software/autoconf/manual/
- GNU Automake Manual: https://www.gnu.org/software/automake/manual/
- GNU Libtool Manual: https://www.gnu.org/software/libtool/manual/
- Autoconf Archive: https://www.gnu.org/software/autoconf-archive/
Quick Start Guide
For learners who feel overwhelmed, here’s your first 48 hours:
Day 1: Understand the Flow
- Read: The “Autotools Solution” diagram above (15 min)
- Install: autoconf, automake, libtool on your system (15 min)
- Project P01: Build your first autotools project (2-3 hours)
- Experiment: Modify configure.ac, run
autoreconf -i, observe changes
Day 2: Go Deeper
- Project P02: Write your first configure.ac from scratch (2-3 hours)
- Project P03: Add Automake with Makefile.am (2-3 hours)
- Experiment: Read the generated
configurescript (yes, all 5000+ lines) - Debug: Intentionally break things, read
config.log
Recommended Learning Paths
Path 1: “Just Make It Work” (Pragmatic)
For developers who need to use Autotools now:
P01 (Build Process) → P02 (configure.ac) → P03 (Automake) →
P06 (Libraries) → P12 (Distribution) → P16 (Migration)
Timeline: 2-3 weeks, ~30 hours
Path 2: Deep Understanding (Thorough)
For developers who want to master the entire system:
P01 → P02 → P03 → P04 (M4) → P05 (Headers) → P06 (Libraries) →
P07 (Functions) → P08 (Libtool) → P09 (Dependencies) →
P10 (Cross-compile) → P11 (Install) → P12 (Dist) →
P13 (Tests) → P14 (Docs) → P15 (pkg-config) → P16 → P17
Timeline: 6-8 weeks, ~100 hours
Path 3: Library Developer Focus
For developers building reusable C libraries:
P01 → P02 → P03 → P06 → P08 (Libtool deep) →
P09 → P15 (pkg-config) → P11 → P12 → P17
Timeline: 3-4 weeks, ~50 hours
Projects
P01: Understanding the Build Process
What You’ll Build
A mental model of the complete Autotools pipeline by dissecting an existing GNU project’s build system, tracing every file transformation from source to installed binary.
Why This Teaches the Concept
Before writing Autotools files, you must understand what they produce. This project forces you to trace the entire flow, seeing how your inputs transform into the final build system.
Core Challenges
- Identifying which files are source (developer-written) vs generated
- Understanding the role of each auto* tool
- Tracing @substitutions@ through the pipeline
- Reading and understanding a real configure script
Difficulty & Time
- Difficulty: Beginner
- Time: 3-4 hours
- Prerequisites: Basic shell and make knowledge
Real-World Outcome
After completing this project, running ./configure && make will no longer be a mystery. You’ll be able to look at any Autotools project and immediately understand its structure.
What you’ll produce:
build-trace/
├── analysis.md # Your written analysis
├── file-map.txt # Source → Generated file mapping
├── configure-excerpt.sh # Annotated configure snippets
└── timeline.txt # Order of tool invocation
The Core Question You’re Answering
What exactly happens between typing ./configure and getting a working binary, and which files drive each step?
Concepts You Must Understand First
- What is the difference between
Makefile.amandMakefile.inandMakefile? - Why does configure need to be a shell script rather than a binary?
- What does “portable” mean in the context of Unix shell scripts?
Questions to Guide Your Design
Analysis Phase:
- Which files in the tarball were generated by autoconf? By automake?
- What M4 macros appear in configure.ac?
- How does configure determine which compiler to use?
Tracing Phase:
- What happens if you delete config.cache and re-run configure?
- What information is stored in config.log?
- How does Makefile.in become Makefile?
Thinking Exercise
Before examining a project, predict the answers:
- Draw a diagram showing: configure.ac → ??? → configure → ??? → Makefile
- List 5 things you think configure checks for
- Predict what would happen if you ran configure on a system without gcc
The Interview Questions They’ll Ask
- “Explain the difference between configure.ac and configure.”
- “Why would you run
autoreconf -iinstead ofautoconf?” - “What is config.status and when would you use it?”
- “How does Autotools handle out-of-tree builds?”
- “What’s in config.log and how do you use it for debugging?”
Hints in Layers
Hint 1 - Starting Point:
Download GNU Hello (ftp://ftp.gnu.org/gnu/hello/). It’s the canonical minimal Autotools example.
Hint 2 - What to Look For:
In the extracted tarball, identify: .ac files, .am files, .in files, and files with no extension that are shell scripts.
Hint 3 - Key Commands:
# See all generated files
find . -name "Makefile.in" -o -name "configure" -o -name "aclocal.m4"
# See the macro expansion
grep -n 'AC_' configure.ac | head -20
# Trace configure's checks
./configure 2>&1 | tee configure.output
Hint 4 - The Timeline:
The developer runs: autoreconf -i which calls:
aclocal→ aclocal.m4autoconf→ configureautoheader→ config.h.inautomake --add-missing→ Makefile.in + support files
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “Cannot find install-sh” | automake not run | Run automake --add-missing |
| configure.ac not found | Wrong directory | Look for configure.ac or configure.in |
| “aclocal: command not found” | automake not installed | Install automake package |
Learning Milestones
- Stage 1: Can identify which files are source vs generated
- Stage 2: Can trace a macro from configure.ac to its shell expansion in configure
- Stage 3: Can explain the full build process to another developer
P02: Writing Your First configure.ac
What You’ll Build
A minimal but complete configure.ac file from scratch for a simple C program, learning each required macro and why it exists.
Why This Teaches the Concept
configure.ac is the heart of Autotools. By writing one from scratch (not copying a template), you internalize what each macro does and why it’s needed.
Core Challenges
- Understanding required vs optional macros
- Proper macro ordering (it matters!)
- Generating config.h for feature defines
- Creating the final configure script
Difficulty & Time
- Difficulty: Beginner
- Time: 3-4 hours
- Prerequisites: P01, basic C programming
Real-World Outcome
myproject/
├── configure.ac # Your hand-written configure.ac
├── config.h.in # Generated by autoheader
├── configure # Generated by autoconf (5000+ lines)
├── src/
│ └── main.c # Uses config.h defines
└── README
Running ./configure && make produces a working binary that adapts to the host system.
The Core Question You’re Answering
What is the minimum set of macros needed for a functional configure.ac, and what does each one do?
Concepts You Must Understand First
- What does AC_INIT do, and what are its required arguments?
- Why is AC_CONFIG_SRCDIR needed?
- What’s the difference between AC_CONFIG_HEADERS and AC_CONFIG_FILES?
Questions to Guide Your Design
Structure:
- What’s the correct order of macros in configure.ac?
- Which macros are always required vs optional?
- How do you specify where source files are?
Output:
- How do you tell configure what files to generate?
- What’s the purpose of AC_OUTPUT?
- Why would you use config.h vs just compiler flags?
Thinking Exercise
Write out (on paper) what you think configure.ac should look like for a program with:
- One source file:
main.c - Version 1.0.0
- Bug report email: bugs@example.com
- Needs to check for
stdlib.h
Then compare with the actual required syntax.
The Interview Questions They’ll Ask
- “What does AC_INIT do and what arguments does it take?”
- “Explain the purpose of AC_CONFIG_SRCDIR.”
- “What’s the difference between configure.ac and configure.in?”
- “How do you add a custom –enable option?”
- “What happens if you forget AC_OUTPUT?”
Hints in Layers
Hint 1 - Minimum Required: Every configure.ac needs at least:
- AC_INIT
- AC_OUTPUT And usually: AC_PROG_CC, AC_CONFIG_FILES
Hint 2 - Basic Template:
AC_INIT([package-name], [version], [bug-email])
dnl ... checks go here ...
AC_OUTPUT
Hint 3 - Generating the Script:
# First time setup
autoreconf -i
# Or manually:
autoconf # Creates configure from configure.ac
autoheader # Creates config.h.in if using AC_CONFIG_HEADERS
Hint 4 - Debug Your configure.ac:
# Check for M4 syntax errors
autom4te --language=autoconf configure.ac
# Verbose autoconf
autoconf --warnings=all
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| configure.ac basics | Autotools (Calcote) | Ch 3: A First Autoconf Project |
| AC_INIT explained | GNU Autoconf Manual | Section 4.1 |
| M4 syntax | Autotools | Ch 4: The M4 Macro Processor |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “possibly undefined macro: AC_PROG_CC” | aclocal not run | Run aclocal first or use autoreconf -i |
| Empty configure script | Missing AC_OUTPUT | Add AC_OUTPUT as last line |
| config.h not generated | Missing AC_CONFIG_HEADERS | Add AC_CONFIG_HEADERS([config.h]) |
Learning Milestones
- Stage 1: Can write a configure.ac that generates a working configure
- Stage 2: Can add custom –enable and –with options
- Stage 3: Can explain what every line does without looking it up
P03: Automake Basics with Makefile.am
What You’ll Build
Transform your manual Makefile into a Makefile.am, letting Automake generate a full-featured build system with make, make clean, make install, make dist, and make check support.
Why This Teaches the Concept
Automake is where most of the build system magic happens. Understanding its high-level syntax and what it generates teaches you why Autotools projects “just work.”
Core Challenges
- Understanding the PREFIX_PRIMARY = targets syntax
- Specifying per-target sources and flags
- Integrating with configure.ac
- Getting proper installation paths
Difficulty & Time
- Difficulty: Beginner-Intermediate
- Time: 4-5 hours
- Prerequisites: P01, P02, basic Makefile knowledge
Real-World Outcome
myproject/
├── configure.ac # Updated with AM_INIT_AUTOMAKE
├── Makefile.am # Your high-level build description
├── Makefile.in # Generated by automake
├── src/
│ ├── Makefile.am # Subdirectory build description
│ └── main.c
└── Makefile # Generated by configure
$ make
$ make install DESTDIR=/tmp/test
$ make dist
$ make distcheck # Verifies distribution is self-contained
The Core Question You’re Answering
How does Automake transform a 10-line Makefile.am into a 1000+ line Makefile.in with full GNU Coding Standards compliance?
Concepts You Must Understand First
- What is the difference between LDADD and LDFLAGS?
- Why do we use
_instead of-in variable names? - What is DESTDIR and why is it important for packaging?
Questions to Guide Your Design
Structure:
- How do you organize a multi-directory project?
- What’s the purpose of SUBDIRS?
- How do you specify which files go into the distribution?
Variables:
- What’s the difference between bin_PROGRAMS and noinst_PROGRAMS?
- How do you add custom CFLAGS for one target only?
- How does Automake find source files for compilation?
Thinking Exercise
Given this directory structure:
project/
├── src/
│ ├── main.c
│ └── utils.c
├── lib/
│ └── libfoo.c
└── tests/
└── test_foo.c
Write out (on paper) what Makefile.am files you’d need and their contents to:
- Build an executable
myappfrom src/ - Build a static library
libfoo.afrom lib/ - Build and run tests from tests/
The Interview Questions They’ll Ask
- “Explain the difference between LDADD and LIBADD.”
- “What does
make distcheckverify?” - “How do you conditionally compile a program based on configure results?”
- “What’s the difference between nodist_ and noinst_ prefixes?”
- “How do you add non-source files to the distribution tarball?”
Hints in Layers
Hint 1 - Basic Syntax:
# Makefile.am
bin_PROGRAMS = myapp
myapp_SOURCES = main.c utils.c
myapp_LDADD = -lm
Hint 2 - Multi-Directory:
# Top-level Makefile.am
SUBDIRS = src lib tests
# Each subdirectory has its own Makefile.am
Hint 3 - Update configure.ac:
AC_INIT([myproject], [1.0])
AM_INIT_AUTOMAKE([foreign -Wall -Werror])
AC_PROG_CC
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT
Hint 4 - Extra Distribution Files:
EXTRA_DIST = README.md doc/manual.txt
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Automake basics | Autotools (Calcote) | Ch 7: Introducing Automake |
| Makefile.am syntax | GNU Automake Manual | Section 3: General Concepts |
| SUBDIRS | Autotools | Ch 8: Automake in Depth |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “required file not found: install-sh” | Missing support files | Run automake --add-missing |
| Installed to wrong directory | Wrong prefix variable | Use proper PREFIX_PRIMARY |
| Source files not found | Wrong SOURCES path | Paths are relative to Makefile.am location |
Learning Milestones
- Stage 1: Can write Makefile.am for a single-directory project
- Stage 2: Can set up multi-directory project with SUBDIRS
- Stage 3: Can pass
make distcheckon first try
P04: Mastering the M4 Macro Language
What You’ll Build
A collection of custom M4 macros that extend Autoconf’s capabilities, including your own AC_CHECK_* style macros that you can reuse across projects.
Why This Teaches the Concept
M4 is the engine that makes Autotools work. Understanding M4 transforms you from someone who copies configure.ac snippets to someone who can create custom feature detection.
Core Challenges
- Understanding quoting rules (the source of 90% of M4 bugs)
- Writing macros that expand to portable shell code
- Properly using AC_DEFINE and AC_SUBST
- Creating reusable macro libraries
Difficulty & Time
- Difficulty: Intermediate
- Time: 5-6 hours
- Prerequisites: P01-P03, shell scripting
Real-World Outcome
project/
├── m4/
│ ├── ax_check_mysystem.m4 # Your custom macros
│ └── ax_require_feature.m4
├── configure.ac # Uses your custom macros
└── aclocal.m4 # Aggregated macros
Your custom macros work identically to built-in AC_* macros.
The Core Question You’re Answering
How does M4’s quoting and expansion model work, and how do you write macros that generate correct shell code?
Concepts You Must Understand First
- What does
[text]mean in M4 vstext? - How does $1, $2, etc. work in M4 macros?
- What is the difference between define and AC_DEFUN?
Questions to Guide Your Design
Quoting:
- When do you need double brackets
[[text]]? - Why does
$1sometimes need to be[$1]? - What does
changequotedo and when would you use it?
Macro Design:
- How do you cache check results?
- How do you provide a default action if check fails?
- How do you make your macro print the “checking for…” message?
Thinking Exercise
Without running M4, predict what this outputs:
define([GREET], [Hello, $1])
define([NAME], [World])
GREET(NAME)
GREET([NAME])
Now trace through what happens when Autoconf processes:
AC_CHECK_HEADERS([sys/types.h])
What shell code does it become?
The Interview Questions They’ll Ask
- “Explain M4 quoting and why it matters for Autotools.”
- “How do you create a custom Autoconf macro?”
- “What’s the difference between AC_DEFUN and AU_DEFUN?”
- “How does AC_REQUIRE work and when would you use it?”
- “How do you debug M4 expansion problems?”
Hints in Layers
Hint 1 - Basic M4:
dnl This is a comment (deleted to newline)
define([MYVAR], [myvalue])
MYVAR → expands to "myvalue"
Hint 2 - Autoconf Macro Structure:
AC_DEFUN([MY_CHECK_FOO],
[AC_MSG_CHECKING([for foo])
if test -f /etc/foo; then
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_FOO], [1], [Define if foo exists])
else
AC_MSG_RESULT([no])
fi])
Hint 3 - Using Caching:
AC_DEFUN([MY_CHECK_FOO],
[AC_CACHE_CHECK([for foo], [my_cv_have_foo],
[if test -f /etc/foo; then
my_cv_have_foo=yes
else
my_cv_have_foo=no
fi])
if test "$my_cv_have_foo" = yes; then
AC_DEFINE([HAVE_FOO], [1], [Define if foo exists])
fi])
Hint 4 - Common Patterns:
dnl Require a prerequisite macro
AC_REQUIRE([AC_PROG_CC])
dnl Use shell variables from previous checks
AS_IF([test "$ac_cv_header_unistd_h" = yes],
[AC_DEFINE([HAVE_UNISTD], [1])])
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| M4 fundamentals | Autotools (Calcote) | Ch 4: M4 Fundamentals |
| Writing macros | GNU Autoconf Manual | Section 10: Writing Autoconf Macros |
| Macro design | Autotools | Ch 6: Autoconf in Depth |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Macro expands too early | Missing quotes | Add quotes around expansion |
| Shell syntax errors | M4 ate special chars | Quote shell commands properly |
| “undefined macro” | Wrong order or missing AC_DEFUN | Check dependencies with AC_REQUIRE |
Learning Milestones
- Stage 1: Understand quoting and can predict expansion
- Stage 2: Can write a custom AC_CHECK_* macro
- Stage 3: Can create a reusable macro library with caching
P05: Checking for Headers and Functions
What You’ll Build
A configure.ac that comprehensively checks for headers and functions, properly handling the case where features are missing by providing fallbacks or disabling functionality.
Why This Teaches the Concept
Feature detection is the core purpose of Autotools. This project teaches you to write robust checks that work across systems and gracefully handle missing features.
Core Challenges
- Understanding the difference between AC_CHECK_HEADERS and AC_CHECK_HEADER
- Handling dependent headers (need X.h to check for Y.h)
- Providing fallback implementations for missing functions
- Using the results in your C code properly
Difficulty & Time
- Difficulty: Intermediate
- Time: 4-5 hours
- Prerequisites: P01-P04, C preprocessor knowledge
Real-World Outcome
// config.h (generated)
#define HAVE_UNISTD_H 1
#define HAVE_SYS_TYPES_H 1
/* #undef HAVE_STRLCPY */
#define HAVE_MEMSET_S 1
// main.c
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char *dst, const char *src, size_t size);
#endif
The Core Question You’re Answering
How do you detect system capabilities at configure time and make that information available to your C code?
Concepts You Must Understand First
- Why can’t you just use
#ifdef linuxin your code? - What’s the difference between compile-time and link-time checks?
- Why does header presence not guarantee function presence?
Questions to Guide Your Design
Headers:
- How do you check for a header that requires another header first?
- What if a header exists but is broken on some systems?
- How do you check for C++ headers?
Functions:
- How do you check if a function exists?
- How do you verify a function’s signature?
- How do you provide a replacement for missing functions?
Thinking Exercise
For the function getaddrinfo():
- What header(s) declare it?
- What library does it live in on different systems? (Linux, macOS, Solaris)
- How would you write a configure check that handles all cases?
The Interview Questions They’ll Ask
- “How do you check for a function that might be in different headers on different systems?”
- “What’s the difference between AC_CHECK_FUNC and AC_SEARCH_LIBS?”
- “How do you handle a function that exists but has a different signature?”
- “Explain how config.h gets generated and used.”
- “How would you provide a fallback implementation for a missing function?”
Hints in Layers
Hint 1 - Basic Header Check:
AC_CHECK_HEADERS([unistd.h sys/types.h sys/stat.h])
Hint 2 - Dependent Headers:
AC_CHECK_HEADERS([sys/socket.h], [], [],
[#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
])
Hint 3 - Function Checks:
AC_CHECK_FUNCS([strlcpy strlcat memset_s])
dnl Check function with specific library
AC_SEARCH_LIBS([gethostbyname], [nsl socket])
Hint 4 - Providing Fallbacks:
# Makefile.am
if !HAVE_STRLCPY
libcompat_a_SOURCES = compat/strlcpy.c
endif
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Header checks | Autotools (Calcote) | Ch 5: Feature Checks |
| Function checks | GNU Autoconf Manual | Section 5.5: Generic Functions |
| Portability patterns | POSIX Programmer’s Guide | Ch on portability |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Header found but code won’t compile | Dependent headers needed | Use AC_CHECK_HEADERS with includes parameter |
| Function check passes but link fails | Function is macro, not real function | Use AC_CHECK_DECL instead |
| Wrong function signature used | Different systems differ | Use AC_CHECK_DECL with prototype |
Learning Milestones
- Stage 1: Can check for common headers and functions
- Stage 2: Can handle dependent headers and library searches
- Stage 3: Can provide complete fallback implementations
P06: Checking for Libraries
What You’ll Build
A configure.ac that detects and links against external libraries like zlib, OpenSSL, or libpng, handling different installation paths, version requirements, and optional vs required dependencies.
Why This Teaches the Concept
Real-world projects depend on external libraries. This project teaches you the patterns for detecting libraries in various installation locations and versions.
Core Challenges
- Finding libraries in non-standard paths
- Checking for specific library versions
- Handling optional vs required dependencies
- Properly setting CFLAGS/LDFLAGS/LIBS
Difficulty & Time
- Difficulty: Intermediate
- Time: 5-6 hours
- Prerequisites: P01-P05, understanding of linking
Real-World Outcome
./configure --with-ssl=/opt/openssl --with-zlib
./configure --without-ssl
./configure --with-ssl=auto # Detect if available
checking for zlib... yes
checking for OpenSSL... yes (version 3.0.2)
The Core Question You’re Answering
How do you reliably detect external libraries across different systems, installation paths, and versions?
Concepts You Must Understand First
- What’s the difference between -L, -l, and -I flags?
- Why can’t you just hardcode
/usr/lib? - What is pkg-config and how does it help?
Questions to Guide Your Design
Detection:
- How do you let users specify a custom library path?
- How do you check if the library works (not just exists)?
- How do you check for a minimum version?
Optional Dependencies:
- How do you make a library optional?
- How do you provide –with-foo and –without-foo?
- How do you set a default (auto-detect)?
Thinking Exercise
For OpenSSL:
- What files would you look for to detect it? (headers, libraries)
- What function would you test to verify it works?
- How would you check the version programmatically?
- What if the user has both OpenSSL 1.1 and 3.0 installed?
The Interview Questions They’ll Ask
- “How do you check for a library that might be in /usr, /usr/local, or /opt?”
- “Explain the difference between –with-foo and –enable-foo.”
- “How do you handle optional vs required library dependencies?”
- “What is AC_SEARCH_LIBS and when would you use it?”
- “How do you check for a specific library version?”
Hints in Layers
Hint 1 - Basic Library Check:
AC_CHECK_LIB([z], [compress],
[LIBS="-lz $LIBS"],
[AC_MSG_ERROR([zlib required but not found])])
Hint 2 - Custom Path Support:
AC_ARG_WITH([ssl],
[AS_HELP_STRING([--with-ssl=PATH], [path to OpenSSL])],
[ssl_prefix="$withval"],
[ssl_prefix=""])
if test -n "$ssl_prefix"; then
CPPFLAGS="$CPPFLAGS -I${ssl_prefix}/include"
LDFLAGS="$LDFLAGS -L${ssl_prefix}/lib"
fi
Hint 3 - Version Checking:
AC_MSG_CHECKING([OpenSSL version >= 1.1.0])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[#include <openssl/opensslv.h>]],
[[#if OPENSSL_VERSION_NUMBER < 0x10100000L
#error "OpenSSL version too old"
#endif]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_MSG_ERROR([OpenSSL 1.1.0+ required])])
Hint 4 - Optional with Auto-Detect:
AC_ARG_WITH([ssl],
[AS_HELP_STRING([--with-ssl], [use OpenSSL (default: auto)])],
[],
[with_ssl=auto])
AS_IF([test "x$with_ssl" != xno],
[AC_CHECK_LIB([ssl], [SSL_new], [have_ssl=yes], [have_ssl=no])],
[have_ssl=no])
AS_IF([test "x$with_ssl" = xyes && test "x$have_ssl" = xno],
[AC_MSG_ERROR([OpenSSL requested but not found])])
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Library detection | Autotools (Calcote) | Ch 5-6: Library Checks |
| AC_ARG_WITH | GNU Autoconf Manual | Section 15.2 |
| Linking | Linkers & Loaders (Levine) | Ch 3 |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Library found but link fails | 32/64-bit mismatch | Check library architecture |
| Header not found after AC_CHECK_LIB | Need -I flag too | Add to CPPFLAGS |
| Multiple library versions conflict | LD_LIBRARY_PATH issues | Use rpath or static linking |
Learning Milestones
- Stage 1: Can detect simple system libraries
- Stage 2: Can handle custom paths and optional dependencies
- Stage 3: Can check versions and handle complex library scenarios
P07: Conditional Compilation
What You’ll Build
A project that uses AM_CONDITIONAL and shell tests to enable/disable features at configure time, resulting in different code paths being compiled based on system capabilities or user choices.
Why This Teaches the Concept
Real software has optional features. This project teaches you to structure your code and build system so features can be cleanly enabled or disabled.
Core Challenges
- Connecting configure options to Makefile conditionals
- Using conditionals in both Makefile.am and C code
- Handling feature dependencies
- Creating sensible defaults
Difficulty & Time
- Difficulty: Intermediate
- Time: 4-5 hours
- Prerequisites: P01-P06
Real-World Outcome
./configure --enable-debug --disable-ssl
./configure --enable-profiling
./configure --enable-feature1 --enable-feature2
config.h:
#define ENABLE_DEBUG 1
/* #undef ENABLE_SSL */
#define ENABLE_FEATURE1 1
Makefile:
# DEBUG_TRUE is empty, DEBUG_FALSE is #
The Core Question You’re Answering
How do you conditionally include source files, set compiler flags, and control code paths based on configure-time decisions?
Concepts You Must Understand First
- What’s the difference between AC_ARG_ENABLE and AC_ARG_WITH?
- How does AM_CONDITIONAL work?
- What are _TRUE and _FALSE suffixes in Automake?
Questions to Guide Your Design
Options:
- When should you use –enable-X vs –with-X?
- How do you provide a default value?
- How do you show options in ./configure –help?
Conditionals:
- How do you conditionally compile entire source files?
- How do you conditionally add CFLAGS?
- How do you conditionally link libraries?
Thinking Exercise
Design the configure options for a network server that:
- Can optionally use SSL (requires OpenSSL)
- Can enable debug mode
- Can enable performance profiling
- Has a feature that requires both SSL and debug mode
What happens if a user enables the SSL+debug feature but not SSL itself?
The Interview Questions They’ll Ask
- “Explain the difference between –enable-X and –with-X options.”
- “How does AM_CONDITIONAL affect the generated Makefile?”
- “How do you handle dependent features (feature B requires feature A)?”
- “What’s the difference between AC_DEFINE and AC_SUBST for conditionals?”
- “How do you make an optional feature default to ‘auto-detect’?”
Hints in Layers
Hint 1 - Enable Option:
AC_ARG_ENABLE([debug],
[AS_HELP_STRING([--enable-debug], [enable debug mode])],
[enable_debug=$enableval],
[enable_debug=no])
if test "x$enable_debug" = xyes; then
AC_DEFINE([DEBUG], [1], [Enable debug mode])
CFLAGS="$CFLAGS -g -O0"
fi
Hint 2 - Automake Conditional:
AM_CONDITIONAL([ENABLE_DEBUG], [test "x$enable_debug" = xyes])
# Makefile.am
if ENABLE_DEBUG
myapp_SOURCES += debug.c
myapp_CFLAGS = -DDEBUG_EXTRA
endif
Hint 3 - Conditional Sources:
bin_PROGRAMS = myapp
myapp_SOURCES = main.c
if HAVE_SSL
myapp_SOURCES += ssl_support.c
myapp_LDADD = $(SSL_LIBS)
myapp_CFLAGS = $(SSL_CFLAGS)
endif
Hint 4 - Feature Dependencies:
AS_IF([test "x$enable_feature_b" = xyes],
[AS_IF([test "x$enable_feature_a" != xyes],
[AC_MSG_ERROR([Feature B requires Feature A (--enable-feature-a)])])])
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Conditionals | Autotools (Calcote) | Ch 8: Automake Conditionals |
| AC_ARG_* | GNU Autoconf Manual | Section 15 |
| Conditional programs | GNU Automake Manual | Section 20.1 |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Conditional always true/false | AM_CONDITIONAL called conditionally | Always call AM_CONDITIONAL |
| Makefile.am syntax error | if/endif without AM_CONDITIONAL | Define conditional in configure.ac |
| Wrong default value | Unquoted shell variable | Quote: test “x$var” = xyes |
Learning Milestones
- Stage 1: Can add simple –enable options
- Stage 2: Can use conditionals in Makefile.am
- Stage 3: Can handle complex feature dependencies
P08: Building Libraries with Libtool
What You’ll Build
A shared library project using Libtool that builds and installs correctly on Linux, macOS, and other Unix variants, with proper versioning and symbol visibility.
Why This Teaches the Concept
Shared libraries are essential for reusable code, but every platform handles them differently. Libtool abstracts these differences away.
Core Challenges
- Understanding .la files and what they contain
- Setting proper version-info for ABI compatibility
- Handling symbol visibility (exports)
- Installing to the correct directories
Difficulty & Time
- Difficulty: Intermediate-Advanced
- Time: 6-8 hours
- Prerequisites: P01-P07, understanding of shared libraries
Real-World Outcome
lib/
├── libmylib.la # Libtool archive (text file with metadata)
├── libmylib.so # Symlink
├── libmylib.so.0 # Symlink
├── libmylib.so.0.0.0 # Actual shared library
├── libmylib.a # Static library
└── .libs/ # Build artifacts
$ libtool --mode=install install libmylib.la /usr/local/lib
The Core Question You’re Answering
How does Libtool create libraries that work identically across platforms with different shared library conventions?
Concepts You Must Understand First
- What’s the difference between SONAME and actual filename?
- What is -version-info CURRENT:REVISION:AGE?
- Why are .la files needed, and what do they contain?
Questions to Guide Your Design
Building:
- How do you compile source files for a shared library (PIC)?
- What’s the difference between LIBADD and LDADD?
- How do you link one libtool library to another?
Versioning:
- How do you set the library version?
- When do you increment CURRENT, REVISION, AGE?
- How does versioning relate to ABI compatibility?
Thinking Exercise
You have libfoo version 1.0.0 with these public functions:
- foo_init()
- foo_process()
- foo_cleanup()
Version 1.1.0 adds:
- foo_set_option()
Version 2.0.0 removes:
- foo_cleanup() (replaced by foo_shutdown())
What should the -version-info be for each release? Why?
The Interview Questions They’ll Ask
- “Explain the CURRENT:REVISION:AGE versioning scheme.”
- “What is a .la file and why is it needed?”
- “How does Libtool handle the differences between Linux .so and macOS .dylib?”
- “What is -rpath and when would you use it?”
- “How do you control which symbols are exported from your library?”
Hints in Layers
Hint 1 - Basic Libtool Library:
# configure.ac
LT_INIT
# Makefile.am
lib_LTLIBRARIES = libmylib.la
libmylib_la_SOURCES = mylib.c
libmylib_la_LDFLAGS = -version-info 0:0:0
Hint 2 - Version Info Rules:
Start with 0:0:0
If source changed: REVISION++
If interfaces added: CURRENT++, REVISION=0, AGE++
If interfaces removed: CURRENT++, REVISION=0, AGE=0
If interfaces changed: CURRENT++, REVISION=0, AGE=0
Hint 3 - Symbol Visibility:
libmylib_la_CFLAGS = -fvisibility=hidden
libmylib_la_LDFLAGS = -export-symbols-regex '^mylib_'
// mylib.h
#define MYLIB_API __attribute__((visibility("default")))
MYLIB_API void mylib_public_func(void);
Hint 4 - Using the Library:
bin_PROGRAMS = myapp
myapp_SOURCES = main.c
myapp_LDADD = libmylib.la # Use .la file
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Libtool basics | Autotools (Calcote) | Ch 9-10: Building Libraries |
| Shared libraries | Linkers & Loaders (Levine) | Ch 9-10 |
| Symbol visibility | Ulrich Drepper’s DSO Guide | All |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “cannot find -lmylib” | Using -l instead of .la | Use libmylib.la for libtool libs |
| Library not found at runtime | rpath not set | Use -rpath or set LD_LIBRARY_PATH |
| Wrong library version loaded | Multiple versions installed | Check ldconfig cache |
Learning Milestones
- Stage 1: Can build a basic libtool library
- Stage 2: Can version the library correctly
- Stage 3: Can control symbol visibility and handle complex dependencies
P09: Handling Dependencies Between Components
What You’ll Build
A multi-component project where internal libraries depend on each other and on external libraries, with proper build ordering and dependency tracking.
Why This Teaches the Concept
Real projects have complex dependency graphs. This project teaches you to structure Autotools projects so dependencies build in the correct order and propagate properly.
Core Challenges
- Ordering subdirectories correctly
- Propagating CFLAGS/LDFLAGS between components
- Handling circular dependencies
- Rebuilding when dependencies change
Difficulty & Time
- Difficulty: Intermediate-Advanced
- Time: 6-8 hours
- Prerequisites: P01-P08
Real-World Outcome
project/
├── lib/
│ ├── libcore/ # Foundation library
│ │ └── libcore.la
│ ├── libutils/ # Depends on libcore
│ │ └── libutils.la
│ └── libplugin/ # Depends on libcore + libutils
│ └── libplugin.la
├── src/
│ └── myapp # Depends on all libraries
└── tests/
└── test_* # Depend on specific libraries
The Core Question You’re Answering
How do you structure an Autotools project so that internal and external dependencies are correctly ordered, propagated, and tracked?
Concepts You Must Understand First
- How does SUBDIRS ordering affect builds?
- What’s the difference between _LIBADD and _LDADD?
- How do .la files encode dependency information?
Questions to Guide Your Design
Ordering:
- In what order should subdirectories be listed?
- What if library A and B depend on each other?
- How do you handle dependencies across SUBDIRS?
Propagation:
- How do you pass compile flags from one library to its dependents?
- How do you ensure a library’s dependencies are linked?
- What about transitive dependencies?
Thinking Exercise
Draw a dependency graph for this scenario:
- libbase: no dependencies
- libmath: depends on libbase and libm (system math library)
- libnetwork: depends on libbase and libssl
- libcore: depends on libmath and libnetwork
- myapp: depends on libcore
Now determine:
- The SUBDIRS order
- What each library’s LIBADD should be
- What happens if libbase.la is modified
The Interview Questions They’ll Ask
- “How do you handle transitive dependencies in Autotools?”
- “What happens if you have a circular dependency between libraries?”
- “How do .la files track dependencies?”
- “How do you ensure dependent targets rebuild when a library changes?”
- “What’s the difference between DIST_SUBDIRS and SUBDIRS?”
Hints in Layers
Hint 1 - SUBDIRS Order:
# Top-level Makefile.am
# Dependencies must come before dependents
SUBDIRS = lib/libbase lib/libmath lib/libnetwork lib/libcore src tests
Hint 2 - Inter-Library Dependencies:
# lib/libmath/Makefile.am
lib_LTLIBRARIES = libmath.la
libmath_la_SOURCES = math.c
libmath_la_LIBADD = $(top_builddir)/lib/libbase/libbase.la -lm
Hint 3 - Header Search Paths:
AM_CPPFLAGS = -I$(top_srcdir)/lib/libbase/include \
-I$(top_srcdir)/lib/libmath/include
Hint 4 - Convenience Libraries:
# Internal library, not installed
noinst_LTLIBRARIES = libinternal.la
libinternal_la_SOURCES = internal.c
# Absorbed into final library
lib_LTLIBRARIES = libpublic.la
libpublic_la_SOURCES = public.c
libpublic_la_LIBADD = libinternal.la
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Multi-directory projects | Autotools (Calcote) | Ch 8: Deep Automake |
| Library dependencies | GNU Automake Manual | Section 8.3 |
| Linking | Linkers & Loaders (Levine) | Ch 7 |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “cannot find libfoo.la” | Wrong path | Use $(top_builddir)/path/lib.la |
| Dependency not rebuilt | Missing in _LIBADD | Add to _LIBADD, not just link command |
| Parallel build fails | Wrong SUBDIRS order | List dependencies first |
Learning Milestones
- Stage 1: Can order SUBDIRS for simple dependencies
- Stage 2: Can propagate flags and libraries between components
- Stage 3: Can handle complex multi-level dependency graphs
P10: Cross-Compilation Support
What You’ll Build
Extend your project to support cross-compilation, where you compile on one system (build) for execution on a different system (host), such as building ARM binaries on x86_64.
Why This Teaches the Concept
Cross-compilation is essential for embedded development and multi-platform support. This project teaches you how Autotools handles the build/host/target distinction.
Core Challenges
- Understanding build vs host vs target
- Separating build tools from host tools
- Running configure for cross-compilation
- Testing cross-compiled binaries
Difficulty & Time
- Difficulty: Advanced
- Time: 6-8 hours
- Prerequisites: P01-P09, cross-compilation toolchain
Real-World Outcome
# Build ARM binaries on x86_64
./configure --build=x86_64-linux-gnu \
--host=arm-linux-gnueabihf \
CC=arm-linux-gnueabihf-gcc
# Build for Windows on Linux
./configure --host=x86_64-w64-mingw32
$ file myapp
myapp: ELF 32-bit LSB executable, ARM, EABI5 ...
The Core Question You’re Answering
How does Autotools distinguish between the compilation system and the execution system, and how do you write a configure.ac that supports both scenarios?
Concepts You Must Understand First
- What is a system triplet (e.g., x86_64-unknown-linux-gnu)?
- What’s the difference between build, host, and target?
- Why can’t you run host programs during the build?
Questions to Guide Your Design
Detection:
- How does configure know it’s cross-compiling?
- What checks can’t work when cross-compiling?
- How do you override detected values?
Build Tools:
- How do you compile tools that run during the build?
- What is AC_CHECK_TOOL vs AC_CHECK_PROG?
- How do you handle #define differences?
Thinking Exercise
You’re building a project that:
- Has a code generator tool that runs during build
- Produces a library for the target platform
- Needs to detect endianness of the target
What problems arise when cross-compiling, and how would you solve them?
The Interview Questions They’ll Ask
- “Explain build, host, and target in Autotools cross-compilation.”
- “What is AC_CHECK_TOOL and how does it differ from AC_CHECK_PROG?”
- “How do you handle run-time tests when cross-compiling?”
- “What is ac_cv_* caching and how does it help cross-compilation?”
- “How do you compile build tools separately from host programs?”
Hints in Layers
Hint 1 - Cross-Compile Basics:
AC_CANONICAL_BUILD
AC_CANONICAL_HOST
# True when cross-compiling
if test "$cross_compiling" = yes; then
AC_MSG_WARN([Cross-compiling: some checks will be skipped])
fi
Hint 2 - Tool Detection:
# Finds arm-linux-gnueabihf-gcc when cross-compiling
AC_PROG_CC
# Finds build system's gcc for build tools
AX_PROG_CC_FOR_BUILD
Hint 3 - Build Tools:
# Compile tool for build system
noinst_PROGRAMS = generator
generator_CC = $(CC_FOR_BUILD)
generator_CFLAGS = $(CFLAGS_FOR_BUILD)
# Run it to generate code for host
generated.c: generator input.def
./generator < input.def > $@
Hint 4 - Pre-set Cache Variables:
# Override run-time checks for cross-compilation
./configure --host=arm-linux-gnueabihf \
ac_cv_sizeof_long=4 \
ac_cv_c_bigendian=no
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Cross-compilation | Autotools (Calcote) | Ch 11: Cross-Compilation |
| System triplets | GNU Autoconf Manual | Section 14 |
| Embedded builds | Embedded Linux Systems (Hollabaugh) | Build system chapter |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “cannot run test program” | Run-time check during cross-compile | Use cache variables |
| Wrong tool found | AC_CHECK_PROG instead of AC_CHECK_TOOL | Use AC_CHECK_TOOL |
| Build tool fails | Compiled for host, not build | Use CC_FOR_BUILD |
Learning Milestones
- Stage 1: Can cross-compile a simple project
- Stage 2: Can handle build-time tools separately
- Stage 3: Can pre-set cache variables for complex targets
P11: Installation Targets and Prefix Handling
What You’ll Build
A project with proper installation handling for binaries, libraries, headers, data files, and configuration files, respecting all standard directory variables and DESTDIR.
Why This Teaches the Concept
Installation is where Autotools really shines. This project teaches you how to install files to the right places on any system, supporting both system-wide and per-user installation.
Core Challenges
- Understanding all the *dir variables
- Using DESTDIR for packaging
- Installing to multiple directories
- Post-install hooks (like ldconfig)
Difficulty & Time
- Difficulty: Intermediate
- Time: 4-5 hours
- Prerequisites: P01-P08
Real-World Outcome
./configure --prefix=/opt/myapp \
--sysconfdir=/etc \
--localstatedir=/var
make install DESTDIR=/tmp/package
$ tree /tmp/package
/tmp/package/
├── etc/
│ └── myapp.conf
├── opt/myapp/
│ ├── bin/myapp
│ ├── lib/libmylib.so.1.0.0
│ ├── include/mylib.h
│ └── share/myapp/templates/
└── var/lib/myapp/
The Core Question You’re Answering
How do you ensure your project installs correctly regardless of the user’s chosen prefix, and how do you support system packaging?
Concepts You Must Understand First
- What is DESTDIR and why do packagers need it?
- What’s the difference between –prefix and –exec-prefix?
- Why would sysconfdir be different from prefix?
Questions to Guide Your Design
Directory Variables:
- What is $(pkgdatadir) vs $(datadir)?
- When would you use $(sysconfdir) vs $(localstatedir)?
- How do you create package-specific directories?
Installation:
- How do you install to multiple directories?
- How do you run post-install commands?
- How do you make installation idempotent?
Thinking Exercise
A user runs:
./configure --prefix=/usr --sysconfdir=/etc
make install DESTDIR=/tmp/pkg
For a project that installs:
- An executable
- A shared library
- Header files
- Configuration file
- Manual page
- Data files
What is the exact path where each file ends up?
The Interview Questions They’ll Ask
- “Explain DESTDIR and why it’s essential for packaging.”
- “What’s the difference between pkglibdir and libdir?”
- “How do you handle configuration files that shouldn’t be overwritten on upgrade?”
- “What install-data-hook and install-exec-hook for?”
- “How do you make installation work with SELinux?”
Hints in Layers
Hint 1 - Standard Directories:
bin_PROGRAMS = myapp # $(bindir)
lib_LTLIBRARIES = libmylib.la # $(libdir)
include_HEADERS = mylib.h # $(includedir)
man1_MANS = myapp.1 # $(man1dir)
sysconf_DATA = myapp.conf # $(sysconfdir)
pkgdata_DATA = templates/* # $(pkgdatadir)
Hint 2 - Custom Directory:
# Create custom directory under datadir
templatesdir = $(pkgdatadir)/templates
templates_DATA = template1.txt template2.txt
Hint 3 - Post-Install Hook:
install-exec-hook:
ldconfig $(DESTDIR)$(libdir)
install-data-local:
$(MKDIR_P) $(DESTDIR)$(localstatedir)/lib/$(PACKAGE)
Hint 4 - Configuration File Handling:
# Don't overwrite existing config
install-data-local:
test -f $(DESTDIR)$(sysconfdir)/myapp.conf || \
$(INSTALL_DATA) myapp.conf $(DESTDIR)$(sysconfdir)/myapp.conf
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Installation | GNU Automake Manual | Section 12: Installation |
| Directory variables | GNU Coding Standards | Section 7.2 |
| Packaging | Autotools (Calcote) | Ch 13: Distribution |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Files installed to wrong place | Wrong PRIMARY prefix | Use correct prefix_ |
| DESTDIR not respected | Hardcoded paths | Use $(DESTDIR)$(*dir) |
| Post-install hook fails | No $(DESTDIR) | Add $(DESTDIR) to paths |
Learning Milestones
- Stage 1: Can install to standard directories
- Stage 2: Can use custom directories and post-install hooks
- Stage 3: Project passes
make distcheckwith installation
P12: Creating Distribution Packages
What You’ll Build
A complete distribution system that produces tarballs, verifies their completeness, and passes the stringent make distcheck test.
Why This Teaches the Concept
Distribution is the final step in sharing your software. This project teaches you to create release tarballs that build correctly on any system.
Core Challenges
- Including all necessary files
- Excluding development-only files
- Passing distcheck
- Setting up version numbering
Difficulty & Time
- Difficulty: Intermediate
- Time: 5-6 hours
- Prerequisites: P01-P11
Real-World Outcome
make dist
ls *.tar.gz
myproject-1.2.3.tar.gz
# Verify distribution is complete and builds correctly
make distcheck
# The distcheck test:
# 1. Extracts tarball to temp directory
# 2. Runs configure
# 3. Runs make
# 4. Runs make check
# 5. Runs make install DESTDIR=...
# 6. Runs make uninstall
# 7. Verifies all files were uninstalled
# 8. Runs make distclean
# 9. Verifies source tree is clean
The Core Question You’re Answering
How do you create a distribution tarball that contains everything needed to build your project, but nothing extra, and how do you verify it’s complete?
Concepts You Must Understand First
- What files are automatically included in dist?
- What is EXTRA_DIST for?
- Why does distcheck build in a subdirectory?
Questions to Guide Your Design
Inclusion:
- How do you include non-source files (README, etc.)?
- How do you include generated files?
- How do you include entire directories?
Exclusion:
- How do you exclude files from distribution?
- What about .gitignore files?
- How do you handle large generated files?
Thinking Exercise
Your project has:
- Source files (automatically included)
- A data/ directory with templates
- Generated documentation in doc/
- Test fixtures in tests/fixtures/
- A scripts/ directory not used by build
- Build cache files you want excluded
Write out the EXTRA_DIST and dist-hook rules needed.
The Interview Questions They’ll Ask
- “What does
make distcheckverify and why is it important?” - “How do you include a directory tree in the distribution?”
- “What’s the difference between EXTRA_DIST and CLEANFILES?”
- “How do you handle generated files in distribution?”
- “How do you exclude files from the distribution?”
Hints in Layers
Hint 1 - Basic Distribution:
EXTRA_DIST = README.md AUTHORS ChangeLog data/templates
Hint 2 - Including Directories:
dist-hook:
cp -r $(srcdir)/scripts $(distdir)/scripts
rm -rf $(distdir)/scripts/.git
# Or use recursive variable
EXTRA_DIST = scripts
Hint 3 - Excluding Files:
dist-hook:
rm -f $(distdir)/src/*.bak
rm -rf $(distdir)/.github
Hint 4 - Version in configure.ac:
AC_INIT([myproject], [1.2.3], [bugs@example.com])
# Version components
AC_DEFINE([VERSION_MAJOR], [1], [Major version])
AC_DEFINE([VERSION_MINOR], [2], [Minor version])
AC_DEFINE([VERSION_PATCH], [3], [Patch version])
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Distribution | Autotools (Calcote) | Ch 13: Creating Distributions |
| distcheck | GNU Automake Manual | Section 14 |
| Release management | GNU Maintainer Guide | All |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “file not found” during distcheck | Missing from EXTRA_DIST | Add to EXTRA_DIST |
| distcheck fails on install | Hardcoded paths | Use configure variables |
| distcheck fails on clean | Files not in CLEANFILES | Add to CLEANFILES or DISTCLEANFILES |
Learning Milestones
- Stage 1: Can create dist tarball with all files
- Stage 2: Can pass distcheck on first try
- Stage 3: Can set up automated release versioning
P13: Adding Tests with Automake
What You’ll Build
A comprehensive test suite integrated with Automake, supporting unit tests, integration tests, and make check with proper test output.
Why This Teaches the Concept
Testing is essential for quality software. This project teaches you to integrate testing into your Autotools build in a standard, portable way.
Core Challenges
- Setting up test programs and scripts
- Handling test dependencies
- TAP (Test Anything Protocol) support
- Parallel test execution
Difficulty & Time
- Difficulty: Intermediate
- Time: 5-6 hours
- Prerequisites: P01-P09, testing concepts
Real-World Outcome
$ make check
Making check in tests
PASS: test_basic
PASS: test_advanced
FAIL: test_edge_cases
============
1 of 3 tests failed
============
See tests/test-suite.log for details
The Core Question You’re Answering
How do you integrate testing into an Autotools project so that make check runs your test suite with proper pass/fail reporting?
Concepts You Must Understand First
- What is the difference between check_PROGRAMS and noinst_PROGRAMS?
- What is TESTS and how does it work?
- What is the Test Anything Protocol (TAP)?
Questions to Guide Your Design
Test Types:
- How do you run compiled test programs?
- How do you run test scripts?
- How do you pass environment variables to tests?
Integration:
- How do tests find the libraries they’re testing?
- How do you run tests in parallel?
- How do you skip tests on certain platforms?
Thinking Exercise
Design a test setup for a library with:
- Unit tests that test individual functions
- Integration tests that test the public API
- Tests that require specific features (e.g., network access)
- Tests that need test data files
The Interview Questions They’ll Ask
- “How do you make test binaries that aren’t installed?”
- “What is the TAP protocol and why would you use it?”
- “How do you run tests in parallel with Automake?”
- “How do you skip tests based on configure results?”
- “How do you debug a failing test in the test suite?”
Hints in Layers
Hint 1 - Basic Test Setup:
# Makefile.am
check_PROGRAMS = test_basic test_advanced
test_basic_SOURCES = test_basic.c
test_basic_LDADD = $(top_builddir)/src/libmylib.la
TESTS = test_basic test_advanced
Hint 2 - Test Scripts:
TESTS = test_basic.sh test_integration.sh
EXTRA_DIST = test_basic.sh test_integration.sh
# Set up environment
AM_TESTS_ENVIRONMENT = \
PATH="$(top_builddir)/src:$$PATH"; \
export PATH;
Hint 3 - TAP Support:
AM_CPPFLAGS = -I$(top_srcdir)/tap
check_LTLIBRARIES = libtap.la
libtap_la_SOURCES = tap/tap.c tap/tap.h
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \
$(top_srcdir)/build-aux/tap-driver.sh
Hint 4 - Conditional Tests:
if HAVE_NETWORK
TESTS += test_network
endif
# Skip test at runtime
test_requires_root:
test `id -u` -eq 0 || exit 77 # 77 = skip
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Automake testing | GNU Automake Manual | Section 15: Tests |
| TAP | TAP specification | https://testanything.org |
| Test strategies | xUnit Patterns | Ch on test organization |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Test can’t find library | Wrong LDADD path | Use $(top_builddir)/path/lib.la |
| Test exits 77 | Test was skipped | Check test prerequisites |
| Parallel tests fail | Tests interfere | Use unique temp files per test |
Learning Milestones
- Stage 1: Can run basic tests with
make check - Stage 2: Can use TAP and test scripts
- Stage 3: Can handle conditional and parallel tests
P14: Documentation with Texinfo
What You’ll Build
Professional documentation using Texinfo that produces Info, HTML, PDF, and man page output from a single source, integrated with make doc.
Why This Teaches the Concept
GNU projects use Texinfo for documentation. This project teaches you to write documentation that integrates with the GNU ecosystem.
Core Challenges
- Texinfo markup language
- Multiple output formats
- Integration with Automake
- Cross-references and indices
Difficulty & Time
- Difficulty: Intermediate
- Time: 5-6 hours
- Prerequisites: P01-P03, documentation concepts
Real-World Outcome
doc/
├── myproject.texi # Texinfo source
├── myproject.info # Info format (for `info myproject`)
├── myproject.html # HTML format
├── myproject.pdf # PDF format
└── version.texi # Generated version info
$ info myproject
$ man myproject
$ xdg-open doc/myproject.html
The Core Question You’re Answering
How do you write documentation once and produce Info, HTML, PDF, and man pages from the same source?
Concepts You Must Understand First
- What is the Info format and why does GNU use it?
- How does Texinfo differ from Markdown?
- What is makeinfo?
Questions to Guide Your Design
Texinfo:
- What are the required structural elements?
- How do you create cross-references?
- How do you document functions and commands?
Integration:
- How do you include version info from configure?
- How do you install Info files?
- How do you generate man pages from Texinfo?
Thinking Exercise
Design the documentation structure for:
- A command-line tool with 10 subcommands
- A C library with 20 public functions
- Conceptual documentation with 5 chapters
What sections would your Texinfo file have?
The Interview Questions They’ll Ask
- “Why does GNU use Texinfo instead of Markdown?”
- “How do you generate multiple formats from Texinfo?”
- “How do you keep version numbers synchronized with configure.ac?”
- “What is install-info and why is it needed?”
- “How do you generate man pages from Texinfo?”
Hints in Layers
Hint 1 - Basic Texinfo:
\input texinfo
@setfilename myproject.info
@settitle MyProject Manual
@node Top
@top MyProject
This is the manual for MyProject.
@menu
* Introduction:: Getting started
* Usage:: How to use MyProject
@end menu
@node Introduction
@chapter Introduction
Welcome to MyProject...
@bye
Hint 2 - Automake Integration:
# doc/Makefile.am
info_TEXINFOS = myproject.texi
myproject_TEXINFOS = version.texi
# Generate version.texi
version.texi: $(top_srcdir)/configure.ac
echo "@set VERSION $(PACKAGE_VERSION)" > $@
echo "@set UPDATED $(shell date +'%B %Y')" >> $@
Hint 3 - Man Page from Texinfo:
# Use help2man for simple tools
dist_man_MANS = myproject.1
myproject.1: $(top_builddir)/src/myproject
help2man --no-info $< > $@
Hint 4 - HTML Output:
html-local: myproject.html
myproject.html: myproject.texi
$(MAKEINFO) --html --no-split $<
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Texinfo | GNU Texinfo Manual | All |
| Documentation | GNU Maintainer Guide | Documentation sections |
| Info reader | GNU Info Manual | User’s Guide |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “undefined cross-reference” | Missing @node | Add @node before referenced section |
| Info not installed | install-info not run | Add to install-data-hook |
| PDF fails | Missing TeX | Install texlive package |
Learning Milestones
- Stage 1: Can write basic Texinfo that produces Info
- Stage 2: Can generate HTML and PDF
- Stage 3: Can integrate with Automake and produce man pages
P15: pkg-config Integration
What You’ll Build
A library with full pkg-config support, generating .pc files that let other projects easily find and use your library.
Why This Teaches the Concept
pkg-config is the standard way to distribute library compile and link information. This project teaches you to both produce and consume pkg-config files.
Core Challenges
- Writing .pc.in templates
- Handling private dependencies
- Supporting both installed and uninstalled usage
- Versioning in pkg-config
Difficulty & Time
- Difficulty: Intermediate
- Time: 4-5 hours
- Prerequisites: P01-P08, library concepts
Real-World Outcome
$ pkg-config --cflags --libs mylib
-I/usr/local/include -L/usr/local/lib -lmylib -lz
$ pkg-config --modversion mylib
1.2.3
# In another project's configure.ac
PKG_CHECK_MODULES([MYLIB], [mylib >= 1.0])
The Core Question You’re Answering
How do you make your library discoverable and usable by other Autotools projects through pkg-config?
Concepts You Must Understand First
- What is a .pc file and what does it contain?
- What’s the difference between Libs and Libs.private?
- What is PKG_CONFIG_PATH?
Questions to Guide Your Design
Production:
- How do you create the .pc file during configure?
- How do you handle optional dependencies?
- Where should .pc files be installed?
Consumption:
- How do you use PKG_CHECK_MODULES?
- How do you handle missing pkg-config?
- How do you allow fallback to manual detection?
Thinking Exercise
Your library:
- Links publicly to libz (user’s code needs zlib headers too)
- Links privately to libm (internal use only)
- Has optional SSL support
- Needs custom CFLAGS
Write out the complete .pc.in file.
The Interview Questions They’ll Ask
- “What’s the difference between Libs and Libs.private in .pc files?”
- “How do you handle optional features in pkg-config?”
- “What is an uninstalled .pc file and when would you use it?”
- “How do you use PKG_CHECK_MODULES in configure.ac?”
- “How do you make pkg-config optional in your build?”
Hints in Layers
Hint 1 - Basic .pc.in:
# mylib.pc.in
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: mylib
Description: My awesome library
Version: @PACKAGE_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lmylib
Hint 2 - configure.ac:
AC_CONFIG_FILES([mylib.pc])
# Install .pc file
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = mylib.pc
Hint 3 - Using PKG_CHECK_MODULES:
# In consuming project's configure.ac
PKG_CHECK_MODULES([MYLIB], [mylib >= 1.0],
[have_mylib=yes],
[have_mylib=no])
# Use MYLIB_CFLAGS and MYLIB_LIBS in Makefile.am
Hint 4 - Optional Fallback:
PKG_CHECK_MODULES([MYLIB], [mylib >= 1.0],
[have_mylib=yes],
[AC_MSG_WARN([pkg-config not found, trying manual detection])
AC_CHECK_LIB([mylib], [mylib_init],
[MYLIB_LIBS="-lmylib"; have_mylib=yes],
[have_mylib=no])])
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| pkg-config | Autotools (Calcote) | Ch 12: pkg-config |
| Library best practices | Ulrich Drepper’s DSO Guide | Section on discovery |
| .pc file format | freedesktop.org pkg-config guide | All |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| “Package not found” | Wrong PKG_CONFIG_PATH | Set PKG_CONFIG_PATH properly |
| Link errors with private libs | Using Libs instead of Libs.private | Move internal libs to Libs.private |
| Version mismatch | @PACKAGE_VERSION@ not substituted | Ensure AC_CONFIG_FILES includes .pc |
Learning Milestones
- Stage 1: Can create and install .pc file
- Stage 2: Can consume pkg-config in configure.ac
- Stage 3: Can handle optional features and fallbacks
P16: Migrating a Manual Makefile Project
What You’ll Build
Take an existing project with a hand-written Makefile and convert it to a full Autotools build system, maintaining identical functionality.
Why This Teaches the Concept
Migration is a common real-world task. This project teaches you to systematically convert existing builds while preserving behavior.
Core Challenges
- Analyzing the existing build
- Mapping manual targets to Automake
- Handling custom build rules
- Testing equivalence
Difficulty & Time
- Difficulty: Advanced
- Time: 8-10 hours
- Prerequisites: P01-P15
Real-World Outcome
# Before
$ make # Works
$ make install # Maybe works?
$ make dist # Doesn't exist
# After
$ ./configure --prefix=/opt/app
$ make
$ make check
$ make install DESTDIR=/tmp/pkg
$ make distcheck # Passes!
The Core Question You’re Answering
How do you systematically migrate a project from manual Makefiles to Autotools while maintaining build behavior and adding new capabilities?
Concepts You Must Understand First
- How do you identify all build targets in a Makefile?
- What Autotools features provide value over manual Makefiles?
- How do you maintain backward compatibility?
Questions to Guide Your Design
Analysis:
- What are all the current build targets?
- What external dependencies exist?
- What platform-specific code exists?
Migration:
- Which targets map directly to Automake?
- What requires custom rules?
- How do you verify identical output?
Thinking Exercise
Given this Makefile:
CC = gcc
CFLAGS = -Wall -O2
LDFLAGS = -lz -lm
SRCS = main.c utils.c network.c
OBJS = $(SRCS:.c=.o)
myapp: $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) myapp
install: myapp
cp myapp /usr/local/bin/
Write out the equivalent configure.ac and Makefile.am.
The Interview Questions They’ll Ask
- “Walk me through migrating a simple Makefile to Autotools.”
- “How do you handle custom build rules that Automake doesn’t support?”
- “How do you verify the migrated build produces identical output?”
- “What benefits does migration provide over the manual Makefile?”
- “How do you handle shell-specific or OS-specific code in Makefiles?”
Hints in Layers
Hint 1 - Inventory First:
# List all targets
grep '^[a-zA-Z].*:' Makefile
# List all dependencies
grep -E '^[A-Z]+\s*=' Makefile
# Find platform-specific code
grep -i 'uname\|linux\|darwin\|BSD' Makefile
Hint 2 - Map to Automake:
Manual Makefile Autotools
────────────────────────────────────────
CC = gcc AC_PROG_CC
CFLAGS = -Wall AM_CFLAGS = -Wall
LDFLAGS = -lz -lm myapp_LDADD = -lz -lm
SRCS = main.c utils.c myapp_SOURCES = main.c utils.c
myapp: $(OBJS) bin_PROGRAMS = myapp
%.o: %.c (automatic)
clean: (automatic)
install: (automatic with bin_PROGRAMS)
Hint 3 - Custom Rules:
# When Automake doesn't have a built-in rule
BUILT_SOURCES = generated.h
generated.h: template.def gen_tool
./gen_tool < template.def > $@
Hint 4 - Verification Script:
#!/bin/bash
# Compare old and new builds
make -f Makefile.old clean && make -f Makefile.old
md5sum myapp > old.md5
make distclean
./configure && make
md5sum myapp > new.md5
diff old.md5 new.md5
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Migration strategy | Autotools (Calcote) | Ch 2: A First Project |
| Make patterns | The GNU Make Book | Ch on conversion |
| Build comparison | N/A | Compare outputs |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Different binary produced | CFLAGS differ | Match original flags |
| Missing file in dist | EXTRA_DIST needed | Add custom files |
| Custom rule not working | Wrong Automake syntax | Use proper BUILT_SOURCES |
Learning Milestones
- Stage 1: Can convert simple single-file project
- Stage 2: Can handle multi-directory with libraries
- Stage 3: Can migrate complex project with custom rules
P17: Best Practices and Common Patterns
What You’ll Build
A template repository demonstrating all Autotools best practices, reusable across future projects, with documentation of patterns and anti-patterns.
Why This Teaches the Concept
After learning the pieces, you need to see how they fit together. This project consolidates everything into reusable patterns.
Core Challenges
- Creating a project template
- Documenting patterns and anti-patterns
- Setting up CI/CD for Autotools
- Maintaining build system as code evolves
Difficulty & Time
- Difficulty: Advanced
- Time: 10-12 hours
- Prerequisites: P01-P16
Real-World Outcome
project-template/
├── configure.ac # Well-documented, follows all best practices
├── Makefile.am # Clean, modular structure
├── m4/ # Reusable custom macros
│ └── ax_*.m4
├── build-aux/ # Helper scripts
├── doc/ # Documentation setup
├── src/ # Source code
├── lib/ # Libraries
├── tests/ # Test suite
├── .github/
│ └── workflows/
│ └── build.yml # CI/CD pipeline
├── HACKING.md # Build system documentation
└── README.md
The Core Question You’re Answering
What are the established patterns for Autotools projects, and how do you create a reusable template that new projects can start from?
Concepts You Must Understand First
- What makes an Autotools project maintainable?
- What are the common anti-patterns?
- How do you automate build system testing?
Questions to Guide Your Design
Structure:
- What’s the ideal directory layout?
- How do you organize configure.ac?
- What goes in m4/ vs inline in configure.ac?
Maintenance:
- How do you keep Autotools files up to date?
- How do you test the build system itself?
- How do you document for future maintainers?
Thinking Exercise
Review 5 major GNU projects’ configure.ac files:
- GNU Coreutils
- GNU Grep
- GNU Make
- GCC (bootstrap)
- Emacs
What patterns appear in all of them? What differs?
The Interview Questions They’ll Ask
- “What does a well-structured Autotools project look like?”
- “How do you test an Autotools build system in CI/CD?”
- “What are the most common Autotools mistakes?”
- “How do you keep the build system maintainable as code grows?”
- “When would you recommend Autotools vs CMake vs Meson?”
Hints in Layers
Hint 1 - Template configure.ac:
AC_PREREQ([2.69])
AC_INIT([project], [1.0.0], [bugs@example.com])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIRS([m4])
AM_INIT_AUTOMAKE([foreign -Wall -Werror subdir-objects])
AM_SILENT_RULES([yes])
# Checks for programs
AC_PROG_CC
AC_PROG_CXX
AM_PROG_AR
LT_INIT
# Checks for libraries
# ... (using m4 macros from m4/)
# Checks for headers
AC_CHECK_HEADERS([...])
# Feature options
AC_ARG_ENABLE([...])
AC_ARG_WITH([...])
# Output
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
src/Makefile
lib/Makefile
tests/Makefile
doc/Makefile
$PACKAGE_NAME.pc
])
AC_OUTPUT
Hint 2 - CI/CD Template:
# .github/workflows/build.yml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
sudo apt-get install -y autoconf automake libtool
else
brew install autoconf automake libtool
fi
- name: Bootstrap
run: autoreconf -i
- name: Configure
run: ./configure
- name: Build
run: make
- name: Test
run: make check
- name: Distcheck
run: make distcheck
Hint 3 - Common Anti-Patterns:
❌ Anti-Pattern ✅ Best Practice
─────────────────────────────────────────────────────
Hardcoded paths Use $(prefix) variables
Inline shell in Makefile.am Put in build-aux/ scripts
Duplicated checks Create m4/ macros
Missing from EXTRA_DIST Run distcheck regularly
Version in multiple places Single source in configure.ac
Hint 4 - Maintainer Documentation:
# HACKING.md
## Build System
### Regenerating Build Files
autoreconf -i
### Adding New Source Files
1. Add to appropriate *_SOURCES in Makefile.am
2. Run make to verify
### Adding New Configure Checks
1. Create m4/ax_check_foo.m4
2. Add to configure.ac
3. Run autoreconf -i
### Release Process
1. Update version in configure.ac
2. Update ChangeLog
3. Run make distcheck
4. Tag release
Books That Will Help
| Topic | Book | Section |
|---|---|---|
| Best practices | Autotools (Calcote) | All chapters |
| Maintainability | GNU Maintainer Guide | All |
| CI/CD | N/A | Platform documentation |
Common Pitfalls & Debugging
| Problem | Cause | Fix |
|---|---|---|
| Bootstrap fails in CI | Missing autoreconf deps | Install autoconf, automake, libtool |
| distcheck fails only in CI | Path differences | Use $(srcdir) properly |
| Template becomes outdated | No maintenance | Schedule periodic reviews |
Learning Milestones
- Stage 1: Can create project from template
- Stage 2: Can document build system for new contributors
- Stage 3: Can set up CI/CD and maintain long-term
Project Comparison Table
| Project | Difficulty | Time | Key Concepts | Real-World Applicability |
|---|---|---|---|---|
| P01: Build Process | Beginner | 3-4h | Overview, file relationships | Understanding any Autotools project |
| P02: configure.ac | Beginner | 3-4h | AC_* macros, M4 basics | Writing configure scripts |
| P03: Automake | Beginner-Int | 4-5h | Makefile.am, targets | Any Autotools project |
| P04: M4 Mastery | Intermediate | 5-6h | Quoting, custom macros | Advanced customization |
| P05: Headers/Functions | Intermediate | 4-5h | Feature detection | Portable code |
| P06: Libraries | Intermediate | 5-6h | External dependencies | Real-world projects |
| P07: Conditionals | Intermediate | 4-5h | AM_CONDITIONAL | Optional features |
| P08: Libtool | Int-Advanced | 6-8h | Shared libraries | Library development |
| P09: Dependencies | Int-Advanced | 6-8h | Build ordering | Large projects |
| P10: Cross-Compile | Advanced | 6-8h | Build/host/target | Embedded development |
| P11: Installation | Intermediate | 4-5h | Directory variables | Packaging |
| P12: Distribution | Intermediate | 5-6h | make dist/distcheck | Release management |
| P13: Testing | Intermediate | 5-6h | make check, TAP | Quality assurance |
| P14: Documentation | Intermediate | 5-6h | Texinfo | GNU projects |
| P15: pkg-config | Intermediate | 4-5h | .pc files | Library distribution |
| P16: Migration | Advanced | 8-10h | Conversion strategy | Legacy projects |
| P17: Best Practices | Advanced | 10-12h | Patterns, templates | All future projects |
Summary
Learning Path Overview
Foundation (P01-P03)
│
├─────────────────────────────────────────────┐
▼ │
M4 & Detection (P04-P07) │
│ │
├─────────────────────────────────────────────┤
▼ │
Libraries & Deps (P08-P09) │
│ │
├─────────────────────────────────────────────┤
▼ │
Distribution (P10-P15) │
│ │
├─────────────────────────────────────────────┤
▼ │
Mastery (P16-P17) ◄────────────────────────┘
Expected Outcomes by Project
| After Completing | You Can |
|---|---|
| P03 | Build any simple Autotools project from scratch |
| P07 | Create projects with optional features and dependencies |
| P09 | Structure complex multi-component projects |
| P12 | Create release tarballs that pass distcheck |
| P15 | Publish libraries consumable by other Autotools projects |
| P17 | Start any new project with production-ready build system |
Key Takeaways
- Autotools is about portability - Write once, build anywhere
- M4 is the secret sauce - Understanding it unlocks advanced customization
- distcheck is your friend - If it passes, your distribution is complete
- Libtool abstracts complexity - Shared libraries become portable
- pkg-config enables discovery - Libraries become easily consumable
- Templates save time - Create once, reuse forever
When to Use Autotools
Use Autotools when:
- Maximum portability across Unix variants is required
- You’re building C/C++ libraries for wide distribution
- The project follows GNU standards
- Users expect
./configure && make && make install
Consider alternatives when:
- Windows is a primary target (CMake)
- You want simpler syntax (Meson)
- You’re building non-C/C++ projects
- Rapid iteration is more important than portability
Further Resources
Official Documentation
Books
- Autotools: A Practitioner’s Guide to GNU Autoconf, Automake, and Libtool by John Calcote (2010) - The definitive guide
- The GNU Make Book by John Graham-Cumming (2015) - Essential Make knowledge
- Managing Projects with GNU Make by Robert Mecklenburg (2004) - Classic Make reference
Example Projects to Study
- GNU Hello - Minimal canonical example
- GNU Coreutils - Complex multi-program project
- GLib - Modern library with full pkg-config support
- FFmpeg - Shows configure-like without full Autotools
Community Resources
- Autotools Mythbuster - Practical guide by Diego E. Pettenò
- Autoconf Archive - Reusable macros
This guide follows the project-based learning philosophy: you learn by building, not by reading. Each project produces a working artifact that demonstrates mastery of its concepts. The best way to learn Autotools is to pick a project and start typing AC_INIT.