Introduction to Make
Summary: in this tutorial, you will learn discover what gnu make is, why build automation matters, and how makefiles save you from repetitive manual compilation and task execution.
Introduction to Make
Every software project reaches a point where building it by hand becomes painful. You compile one file, then another, then link them together, then run tests — and if you change a single line of code, you do it all over again. This is exactly the problem that Make was designed to solve.
What you'll learn in this tutorial:
- What Make is and what problem it solves
- Why Make is still one of the most widely used tools in software development
- How Make decides what to rebuild (and why that's powerful)
- The basic structure of a Makefile rule
- What you'll learn in the rest of this tutorial series
What Is Make?
Make is a build automation tool. It reads instructions from a file called a Makefile and automatically figures out which parts of a project need to be rebuilt. Instead of recompiling everything from scratch, Make checks what changed and only rebuilds what's necessary.
What does "build" mean? In programming, "building" means transforming your source code (the files you write) into something that can actually run. For a C program, this means compiling .c files into an executable. For a website, it might mean processing CSS, bundling JavaScript, or generating HTML. Any time you need to run a series of commands to turn your source files into a finished product, that's a "build."
Think of Make like a smart chef in a kitchen:
- Without Make: You cook the entire meal from scratch every time, even if you only changed the seasoning on one dish
- With Make: The chef checks what ingredients changed and only remakes the affected dishes
Make was originally created by Stuart Feldman at Bell Labs in 1976. The most widely used version today is GNU Make, which ships with nearly every Linux distribution and is available on macOS and Windows.
Why Should You Learn Make?
1. It's Everywhere
Make is one of the most common tools in software development:
- Nearly every C/C++ project uses Make (or a tool that generates Makefiles)
- Major projects like the Linux kernel, Git, and Python are built with Make
- Many non-C projects (Go, LaTeX, documentation) use Make for task automation
- Docker images frequently use
makecommands in their build steps
Even if you don't write C code, you'll encounter Makefiles in many open-source projects.
2. It Automates Repetitive Work
Without Make, building a C program with three source files might look like this:
gcc -c main.c -o main.o # Compile main.c into main.o
gcc -c utils.c -o utils.o # Compile utils.c into utils.o
gcc -c database.c -o database.o # Compile database.c into database.o
gcc main.o utils.o database.o -o myapp # Link all .o files into the final programWhat are these commands? gcc is a C compiler — it turns human-readable C code into machine code your computer can run. The -c flag means "compile only" (create an .o object file). The last line without -c links all the object files together into a single executable program called myapp. Don't worry if you don't know C — the same concepts apply to any build process.
Every time you change utils.c, you'd have to remember which commands to re-run. With Make, you just type:
makeMake reads the Makefile, checks which files changed, and runs only the necessary commands. If you only changed utils.c, it only recompiles utils.c and re-links — skipping main.c and database.c entirely.
3. It Understands Dependencies
Make understands dependencies — relationships between files. If main.c includes utils.h (a header file), Make knows that changing utils.h means main.c needs to be recompiled too. This dependency tracking is automatic and precise.
What are dependencies? A dependency is a file that another file needs. If file A requires file B to be built first, then B is a "dependency" of A. For example, a compiled program depends on its source code files. If a source file changes, the program needs to be rebuilt.
4. It's Not Just for Compiling
While Make was originally designed for compiling code, it's actually a general-purpose task runner. People use it for all sorts of things:
- Running test suites
- Generating documentation
- Deploying applications to servers
- Managing Docker containers
- Running database migrations
- Any repetitive task that has dependencies between steps
How Does Make Work?
The core concept is simple. A Makefile contains rules, and each rule has three parts:
target: prerequisites
recipeLet's break that down:
| Part | What It Is | Example |
|---|---|---|
| Target | What you want to build (usually a file name) | hello |
| Prerequisites | What the target depends on (files it needs) | hello.c |
| Recipe | The shell commands to run to build the target | gcc hello.c -o hello |
The recipe MUST be indented with a tab character, not spaces! This is a strict rule in Make. If you use spaces, you'll get a confusing "missing separator" error. We'll cover this more in the installation tutorial.
Here's a concrete example:
hello: hello.c
gcc hello.c -o helloThis rule says: "To build the file hello, you need the file hello.c. If hello.c is newer than hello (or hello doesn't exist yet), run gcc hello.c -o hello."
When you type make in your terminal, here's what happens:
Step 1: Make reads the Makefile and finds the first rule
Step 2: Make checks if the prerequisite (hello.c) is newer than the target (hello)
Step 3: If yes (or if hello doesn't exist), Make runs the recipe (gcc hello.c -o hello)
Step 4: If no (nothing changed), Make says make: 'hello' is up to date. and does nothing
This "only rebuild what changed" behavior is what makes Make so efficient for large projects.
Make Compared to Other Tools
You might wonder why there are so many build tools. Here's how Make compares:
| Tool | Primary Use | Why It Exists |
|---|---|---|
| Make | General build automation | Universal, fast, dependency-aware, been around since 1976 |
| CMake | Generates Makefiles and project files | Makes C/C++ builds work across different operating systems |
| npm scripts | Node.js task running | Built into the JavaScript ecosystem |
| Gradle | Java/Android builds | Handles Java's complex build requirements |
| Cargo | Rust builds | Built into the Rust programming language |
Make is often used as the glue that connects other tools. Even if your main build tool is Cargo or npm, you might still use a Makefile as a simple task runner that wraps those commands — giving everyone a consistent make build, make test, make deploy interface.
What You'll Learn in This Series
This tutorial series takes you from zero to proficient with Makefiles:
- Getting Started — Installing Make and writing your first Makefile
- Core Concepts — Variables, automatic variables, and pattern rules that make your Makefiles flexible
- Advanced Features — Functions, conditionals, multi-directory projects, and advanced techniques
- Real-World Projects — Complete Makefiles for C projects, Docker workflows, documentation builds, and more
You don't need to know C programming to learn Make. While many examples use C compilation (because that's Make's origin), the concepts apply to any task automation. We'll explain every command so you can follow along even without C experience.
A Taste of What's Coming
Here's a preview of what a real-world Makefile looks like. Don't worry about understanding it now — we'll cover every piece:
CC := gcc
CFLAGS := -Wall -Wextra -O2
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:src/%.c=build/%.o)
TARGET := bin/myapp
.PHONY: all clean test
all: $(TARGET)
$(TARGET): $(OBJS) | bin
$(CC) $(CFLAGS) $^ -o $@
build/%.o: src/%.c | build
$(CC) $(CFLAGS) -c $< -o $@
bin build:
mkdir -p $@
clean:
rm -rf build bin
test: $(TARGET)
./$(TARGET) --run-testsBy the end of this series, you'll understand every line and be able to write Makefiles like this for your own projects.
Summary
GNU Make is a build automation tool that:
- Reads instructions from a file called a
Makefile - Tracks dependencies between files (which files need which other files)
- Only rebuilds what's necessary when something changes
- Works as both a build system and a general-purpose task runner
- Is available on virtually every Unix-like system
- Has been trusted by developers for nearly 50 years
In the next tutorial, you'll install Make on your system and verify it's working correctly.
Quick Check
Before moving on, answer these questions to test your understanding:
- What is the primary problem that Make solves?
- What are the three parts of a Makefile rule?
- Why is Make still relevant when newer build tools exist?
- Can Make be used for tasks other than compiling code?
Show Solution
-
Make solves the problem of repetitive manual building. Instead of running compilation commands by hand and figuring out what needs rebuilding after every change, Make automates the process and only rebuilds what actually changed.
-
The three parts are: target, prerequisites, and recipe. The target is what you want to build (a file), prerequisites are what it depends on (other files), and the recipe is the shell commands to run to build it. The recipe must be indented with a tab character.
-
Make is still relevant because it's universal, fast, and simple. It's installed virtually everywhere, it serves as a great task runner even for non-C projects, and it often works as the "glue" that connects other tools behind a simple
make build/make testinterface. -
Yes, Make can automate any task — running tests, generating documentation, deploying applications, managing Docker containers, database migrations, and more. It's a general-purpose dependency-aware task runner, not just a compiler tool.
Written by the ShellRAG Team
The ShellRAG editorial team writes practical, beginner-friendly Makefile tutorials with tested code examples and real-world use cases. Every article is technically reviewed for accuracy and updated regularly.
Learn more about us →