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 (-l flags)

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 sh vs bash

Self-Assessment Questions

Before proceeding, you should be able to answer:

  1. What does gcc -c file.c -o file.o produce, and why is it different from gcc file.c -o program?
  2. In a Makefile, what does $(CC) $(CFLAGS) -o $@ $^ mean?
  3. What’s the difference between -I/path and -L/path in GCC?
  4. What does #ifdef HAVE_FEATURE do in C code?
  5. Why might /usr/include/stdio.h exist 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

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)

  1. GNU Autoconf Manual: https://www.gnu.org/software/autoconf/manual/
  2. GNU Automake Manual: https://www.gnu.org/software/automake/manual/
  3. GNU Libtool Manual: https://www.gnu.org/software/libtool/manual/
  4. 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

  1. Read: The “Autotools Solution” diagram above (15 min)
  2. Install: autoconf, automake, libtool on your system (15 min)
  3. Project P01: Build your first autotools project (2-3 hours)
  4. Experiment: Modify configure.ac, run autoreconf -i, observe changes

Day 2: Go Deeper

  1. Project P02: Write your first configure.ac from scratch (2-3 hours)
  2. Project P03: Add Automake with Makefile.am (2-3 hours)
  3. Experiment: Read the generated configure script (yes, all 5000+ lines)
  4. Debug: Intentionally break things, read config.log

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

  1. Identifying which files are source (developer-written) vs generated
  2. Understanding the role of each auto* tool
  3. Tracing @substitutions@ through the pipeline
  4. 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

  1. What is the difference between Makefile.am and Makefile.in and Makefile?
  2. Why does configure need to be a shell script rather than a binary?
  3. 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:

  1. Draw a diagram showing: configure.ac → ??? → configure → ??? → Makefile
  2. List 5 things you think configure checks for
  3. Predict what would happen if you ran configure on a system without gcc

The Interview Questions They’ll Ask

  1. “Explain the difference between configure.ac and configure.”
  2. “Why would you run autoreconf -i instead of autoconf?”
  3. “What is config.status and when would you use it?”
  4. “How does Autotools handle out-of-tree builds?”
  5. “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:

  1. aclocal → aclocal.m4
  2. autoconf → configure
  3. autoheader → config.h.in
  4. automake --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

  1. Understanding required vs optional macros
  2. Proper macro ordering (it matters!)
  3. Generating config.h for feature defines
  4. 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

  1. What does AC_INIT do, and what are its required arguments?
  2. Why is AC_CONFIG_SRCDIR needed?
  3. 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

  1. “What does AC_INIT do and what arguments does it take?”
  2. “Explain the purpose of AC_CONFIG_SRCDIR.”
  3. “What’s the difference between configure.ac and configure.in?”
  4. “How do you add a custom –enable option?”
  5. “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

  1. Understanding the PREFIX_PRIMARY = targets syntax
  2. Specifying per-target sources and flags
  3. Integrating with configure.ac
  4. 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

  1. What is the difference between LDADD and LDFLAGS?
  2. Why do we use _ instead of - in variable names?
  3. 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:

  1. Build an executable myapp from src/
  2. Build a static library libfoo.a from lib/
  3. Build and run tests from tests/

The Interview Questions They’ll Ask

  1. “Explain the difference between LDADD and LIBADD.”
  2. “What does make distcheck verify?”
  3. “How do you conditionally compile a program based on configure results?”
  4. “What’s the difference between nodist_ and noinst_ prefixes?”
  5. “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 distcheck on 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

  1. Understanding quoting rules (the source of 90% of M4 bugs)
  2. Writing macros that expand to portable shell code
  3. Properly using AC_DEFINE and AC_SUBST
  4. 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

  1. What does [text] mean in M4 vs text?
  2. How does $1, $2, etc. work in M4 macros?
  3. What is the difference between define and AC_DEFUN?

Questions to Guide Your Design

Quoting:

  • When do you need double brackets [[text]]?
  • Why does $1 sometimes need to be [$1]?
  • What does changequote do 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

  1. “Explain M4 quoting and why it matters for Autotools.”
  2. “How do you create a custom Autoconf macro?”
  3. “What’s the difference between AC_DEFUN and AU_DEFUN?”
  4. “How does AC_REQUIRE work and when would you use it?”
  5. “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

  1. Understanding the difference between AC_CHECK_HEADERS and AC_CHECK_HEADER
  2. Handling dependent headers (need X.h to check for Y.h)
  3. Providing fallback implementations for missing functions
  4. 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

  1. Why can’t you just use #ifdef linux in your code?
  2. What’s the difference between compile-time and link-time checks?
  3. 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():

  1. What header(s) declare it?
  2. What library does it live in on different systems? (Linux, macOS, Solaris)
  3. How would you write a configure check that handles all cases?

The Interview Questions They’ll Ask

  1. “How do you check for a function that might be in different headers on different systems?”
  2. “What’s the difference between AC_CHECK_FUNC and AC_SEARCH_LIBS?”
  3. “How do you handle a function that exists but has a different signature?”
  4. “Explain how config.h gets generated and used.”
  5. “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

  1. Finding libraries in non-standard paths
  2. Checking for specific library versions
  3. Handling optional vs required dependencies
  4. 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

  1. What’s the difference between -L, -l, and -I flags?
  2. Why can’t you just hardcode /usr/lib?
  3. 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:

  1. What files would you look for to detect it? (headers, libraries)
  2. What function would you test to verify it works?
  3. How would you check the version programmatically?
  4. What if the user has both OpenSSL 1.1 and 3.0 installed?

The Interview Questions They’ll Ask

  1. “How do you check for a library that might be in /usr, /usr/local, or /opt?”
  2. “Explain the difference between –with-foo and –enable-foo.”
  3. “How do you handle optional vs required library dependencies?”
  4. “What is AC_SEARCH_LIBS and when would you use it?”
  5. “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

  1. Connecting configure options to Makefile conditionals
  2. Using conditionals in both Makefile.am and C code
  3. Handling feature dependencies
  4. 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

  1. What’s the difference between AC_ARG_ENABLE and AC_ARG_WITH?
  2. How does AM_CONDITIONAL work?
  3. 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:

  1. Can optionally use SSL (requires OpenSSL)
  2. Can enable debug mode
  3. Can enable performance profiling
  4. 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

  1. “Explain the difference between –enable-X and –with-X options.”
  2. “How does AM_CONDITIONAL affect the generated Makefile?”
  3. “How do you handle dependent features (feature B requires feature A)?”
  4. “What’s the difference between AC_DEFINE and AC_SUBST for conditionals?”
  5. “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

  1. Understanding .la files and what they contain
  2. Setting proper version-info for ABI compatibility
  3. Handling symbol visibility (exports)
  4. 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

  1. What’s the difference between SONAME and actual filename?
  2. What is -version-info CURRENT:REVISION:AGE?
  3. 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

  1. “Explain the CURRENT:REVISION:AGE versioning scheme.”
  2. “What is a .la file and why is it needed?”
  3. “How does Libtool handle the differences between Linux .so and macOS .dylib?”
  4. “What is -rpath and when would you use it?”
  5. “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

  1. Ordering subdirectories correctly
  2. Propagating CFLAGS/LDFLAGS between components
  3. Handling circular dependencies
  4. 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

  1. How does SUBDIRS ordering affect builds?
  2. What’s the difference between _LIBADD and _LDADD?
  3. 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:

  1. The SUBDIRS order
  2. What each library’s LIBADD should be
  3. What happens if libbase.la is modified

The Interview Questions They’ll Ask

  1. “How do you handle transitive dependencies in Autotools?”
  2. “What happens if you have a circular dependency between libraries?”
  3. “How do .la files track dependencies?”
  4. “How do you ensure dependent targets rebuild when a library changes?”
  5. “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

  1. Understanding build vs host vs target
  2. Separating build tools from host tools
  3. Running configure for cross-compilation
  4. 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

  1. What is a system triplet (e.g., x86_64-unknown-linux-gnu)?
  2. What’s the difference between build, host, and target?
  3. 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:

  1. Has a code generator tool that runs during build
  2. Produces a library for the target platform
  3. 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

  1. “Explain build, host, and target in Autotools cross-compilation.”
  2. “What is AC_CHECK_TOOL and how does it differ from AC_CHECK_PROG?”
  3. “How do you handle run-time tests when cross-compiling?”
  4. “What is ac_cv_* caching and how does it help cross-compilation?”
  5. “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

  1. Understanding all the *dir variables
  2. Using DESTDIR for packaging
  3. Installing to multiple directories
  4. 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

  1. What is DESTDIR and why do packagers need it?
  2. What’s the difference between –prefix and –exec-prefix?
  3. 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

  1. “Explain DESTDIR and why it’s essential for packaging.”
  2. “What’s the difference between pkglibdir and libdir?”
  3. “How do you handle configuration files that shouldn’t be overwritten on upgrade?”
  4. “What install-data-hook and install-exec-hook for?”
  5. “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 distcheck with 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

  1. Including all necessary files
  2. Excluding development-only files
  3. Passing distcheck
  4. 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

  1. What files are automatically included in dist?
  2. What is EXTRA_DIST for?
  3. 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

  1. “What does make distcheck verify and why is it important?”
  2. “How do you include a directory tree in the distribution?”
  3. “What’s the difference between EXTRA_DIST and CLEANFILES?”
  4. “How do you handle generated files in distribution?”
  5. “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

  1. Setting up test programs and scripts
  2. Handling test dependencies
  3. TAP (Test Anything Protocol) support
  4. 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

  1. What is the difference between check_PROGRAMS and noinst_PROGRAMS?
  2. What is TESTS and how does it work?
  3. 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

  1. “How do you make test binaries that aren’t installed?”
  2. “What is the TAP protocol and why would you use it?”
  3. “How do you run tests in parallel with Automake?”
  4. “How do you skip tests based on configure results?”
  5. “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

  1. Texinfo markup language
  2. Multiple output formats
  3. Integration with Automake
  4. 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

  1. What is the Info format and why does GNU use it?
  2. How does Texinfo differ from Markdown?
  3. 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

  1. “Why does GNU use Texinfo instead of Markdown?”
  2. “How do you generate multiple formats from Texinfo?”
  3. “How do you keep version numbers synchronized with configure.ac?”
  4. “What is install-info and why is it needed?”
  5. “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

  1. Writing .pc.in templates
  2. Handling private dependencies
  3. Supporting both installed and uninstalled usage
  4. 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

  1. What is a .pc file and what does it contain?
  2. What’s the difference between Libs and Libs.private?
  3. 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

  1. “What’s the difference between Libs and Libs.private in .pc files?”
  2. “How do you handle optional features in pkg-config?”
  3. “What is an uninstalled .pc file and when would you use it?”
  4. “How do you use PKG_CHECK_MODULES in configure.ac?”
  5. “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

  1. Analyzing the existing build
  2. Mapping manual targets to Automake
  3. Handling custom build rules
  4. 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

  1. How do you identify all build targets in a Makefile?
  2. What Autotools features provide value over manual Makefiles?
  3. 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

  1. “Walk me through migrating a simple Makefile to Autotools.”
  2. “How do you handle custom build rules that Automake doesn’t support?”
  3. “How do you verify the migrated build produces identical output?”
  4. “What benefits does migration provide over the manual Makefile?”
  5. “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

  1. Creating a project template
  2. Documenting patterns and anti-patterns
  3. Setting up CI/CD for Autotools
  4. 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

  1. What makes an Autotools project maintainable?
  2. What are the common anti-patterns?
  3. 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

  1. “What does a well-structured Autotools project look like?”
  2. “How do you test an Autotools build system in CI/CD?”
  3. “What are the most common Autotools mistakes?”
  4. “How do you keep the build system maintainable as code grows?”
  5. “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

  1. Autotools is about portability - Write once, build anywhere
  2. M4 is the secret sauce - Understanding it unlocks advanced customization
  3. distcheck is your friend - If it passes, your distribution is complete
  4. Libtool abstracts complexity - Shared libraries become portable
  5. pkg-config enables discovery - Libraries become easily consumable
  6. 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


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.