Make
The original Unix build automation tool whose dependency-and-recipe model has shaped how software is compiled for nearly fifty years
Created by Stuart Feldman
Make is the original build automation tool of the Unix world: a program that reads a description of how files depend on one another and runs only the commands needed to bring everything up to date. Created by Stuart Feldman at Bell Labs around 1976, it introduced a deceptively simple idea — a rule pairing a target, its prerequisites, and a recipe — that has proven durable enough to underpin nearly five decades of software construction. Its citation for the 2003 ACM Software System Award put it bluntly: “there is probably no large software system in the world today that has not been processed by a version or offspring of MAKE.”
History & Origins
Make was born from a specific, maddening frustration. As the story is usually told, Steve Johnson — the author of yacc — stormed into Stuart Feldman’s office at Bell Labs, furious at having wasted a morning debugging a program that was already correct. The bug had been fixed in the source, but the corresponding object file had not been recompiled, so the broken binary persisted. The dependency between source and object had been tracked only in a programmer’s head, and that head had forgotten.
Feldman, who joined Bell Labs’ Computing Sciences Research Center around 1973 and worked alongside Unix pioneers such as Ken Thompson, Dennis Ritchie, and Doug McIlroy, realized the dependency relationships could be written down once and acted on mechanically. An early working version of Make was completed around April 1976. Three years later, in April 1979, Feldman described the tool formally in the paper “Make — A Program for Maintaining Computer Programs,” published in Software: Practice and Experience.
Make spread with Unix itself. Vendors produced their own variants — Sun’s DevPro Make, the BSD lineage, and others — and the core behavior was eventually codified in the POSIX.2 standard. In 1988, the GNU Project produced GNU Make, written by Richard Stallman and Roland McGrath, which became the dominant implementation on Linux and, later, macOS.
Design Philosophy
Make’s enduring appeal comes from a single, clear conceptual model that sits between declarative and imperative styles:
- Declarative dependencies. You describe what depends on what, not the order in which to do things. Make computes the order itself by examining the dependency graph.
- Incremental by default. Make compares file modification timestamps. A target is rebuilt only if it is missing or older than one of its prerequisites, so unchanged work is skipped automatically.
- Recipes are just shell. The commands that build a target are ordinary shell lines. Make does not need to understand compilers, linkers, or any particular language — it orchestrates whatever programs you name.
This separation of dependency structure from build commands is the heart of Make’s design. It is general enough to compile C, typeset documents, generate code, run tests, or deploy a website — any task expressible as “to produce X from Y, run this command.”
Core Concepts
Rules
The fundamental unit is the rule. It names a target, the prerequisites it depends on, and a recipe — a sequence of shell commands, each of which must be indented with a tab character (one of Make’s most famous gotchas):
| |
This says: hello depends on hello.c; if hello is missing or older than hello.c, run cc to rebuild it.
Phony Targets
Not every target is a file. Conventional tasks like clean or all are declared .PHONY so Make never confuses them with real files of the same name:
| |
Variables
Variables reduce repetition and make a Makefile configurable:
| |
Pattern Rules and Automatic Variables
Pattern rules describe how to build a whole class of files, using automatic variables such as $@ (the target), $< (the first prerequisite), and $^ (all prerequisites):
| |
This single rule teaches Make how to turn any .c file into the corresponding .o file, which is why a well-written Makefile can stay short even for a large project.
Putting It Together
A small but complete Makefile for a multi-file C program illustrates the model:
| |
Running make builds only the object files whose sources changed, then relinks program. Running make clean removes the build artifacts.
Evolution
For its first decade, Make was a traditional Unix utility, carried along with each vendor’s system and gradually accreting incompatible extensions. The arrival of GNU Make in 1988 reshaped that landscape. Under long-time maintainer Paul D. Smith (who took over in 1997), GNU Make accumulated a powerful set of features that went well beyond the original: a large library of text-manipulation functions, conditional directives, include of other makefiles, automatic dependency generation, and robust parallel execution via make -j and the jobserver protocol that coordinates parallelism across recursive invocations.
Major modern milestones include GNU Make 4.0 (October 2013), which introduced embedded GNU Guile scripting, output synchronization for cleaner parallel logs, and a loadable-object interface for native extensions. GNU Make 4.4 (2022) refined how MAKEFLAGS propagate and improved jobserver behavior, and GNU Make 4.4.1 (February 2023) followed as a bug-fix release that remains the current stable version.
Alongside GNU Make, the BSD make family evolved independently. The makes in FreeBSD, NetBSD, and OpenBSD share Make’s core model but use a distinct dialect (notably .for loops and != shell assignments), and they build their respective base systems wholesale. Microsoft’s nmake and various other implementations round out a diverse ecosystem unified only by the POSIX common subset.
Current Relevance
Despite decades of would-be replacements, Make remains everywhere. The Linux kernel is built by it, GCC requires it, and the GNU Autotools workflow that produces ./configure && make rests entirely on it. Even projects that adopt newer tools often meet Make at the end of the chain: CMake, Autoconf/Automake, and qmake are frequently configured to generate Makefiles, while faster successors like Ninja were designed explicitly to occupy Make’s niche.
In everyday practice, Make has also enjoyed a quiet renaissance as a generic task runner. Many developers keep a small Makefile in a project root simply so that make test, make lint, make build, and make deploy provide a uniform, language-agnostic entry point — a role that requires none of Make’s compilation heritage but benefits from its ubiquity and zero-configuration availability on virtually every Unix-like machine.
Why It Matters
Make’s importance is hard to overstate. It established the dependency graph plus recipes model that every later build system has had to reckon with, either by adopting it (Apache Ant, Rake, MSBuild) or by reacting against its quirks (Ninja’s speed focus, Bazel’s hermeticity). It demonstrated that build orchestration is a problem worth solving with a dedicated, declarative tool rather than ad-hoc scripts.
It is also a cautionary tale studied as carefully as it is praised. The mandatory tab character, the subtleties of recursive Make, timestamp-based staleness checks that can be fooled, and the tension between portability and GNU extensions are all classic lessons in language and tool design. That a program written in 1976 still sits at the center of how the world compiles software — largely unchanged in its core idea — is among the strongest testaments in computing to getting the fundamental abstraction right.
Learning Resources
- GNU Make Manual — https://www.gnu.org/software/make/manual/
- “Make — A Program for Maintaining Computer Programs” by Stuart I. Feldman (the original 1979 paper)
- Managing Projects with GNU Make by Robert Mecklenburg (O’Reilly)
- POSIX
makespecification — part of the IEEE Std 1003.1 utilities
Make rewards a little study: once the rule model and timestamp logic click, it becomes a remarkably general engine for turning one set of files into another — and a direct window into one of the foundational ideas of software engineering.
Timeline
Notable Uses & Legacy
Linux Kernel
The kernel's kbuild system is built on GNU Make; building Linux from source is fundamentally a very large, recursive make invocation.
GNU Compiler Collection (GCC)
GCC requires GNU Make to build, and Make has long been the canonical tool for compiling the C/C++ projects GCC targets.
BSD Operating Systems
FreeBSD, NetBSD, and OpenBSD build their entire base systems with BSD make, a separate lineage from GNU Make with its own makefile dialect.
GNU Autotools
Automake generates portable Makefile.in templates that configure expands into concrete Makefiles, making make the execution engine behind ./configure && make.
Embedded and Systems Software
Toolchains for microcontrollers, firmware, and operating systems routinely rely on hand-written Makefiles for fine-grained control over compilation and flashing.
Language Influence
Influenced
Running Today
Run examples using the official Docker image:
docker pull alpine:latestExample usage:
docker run --rm -v $(pwd):/app -w /app alpine:latest sh -c 'apk add --no-cache make && make'