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 make commands 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 program

What 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:

make

Make 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
	recipe

Let's break that down:

PartWhat It IsExample
TargetWhat you want to build (usually a file name)hello
PrerequisitesWhat the target depends on (files it needs)hello.c
RecipeThe shell commands to run to build the targetgcc 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 hello

This 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:

ToolPrimary UseWhy It Exists
MakeGeneral build automationUniversal, fast, dependency-aware, been around since 1976
CMakeGenerates Makefiles and project filesMakes C/C++ builds work across different operating systems
npm scriptsNode.js task runningBuilt into the JavaScript ecosystem
GradleJava/Android buildsHandles Java's complex build requirements
CargoRust buildsBuilt 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:

  1. Getting Started — Installing Make and writing your first Makefile
  2. Core Concepts — Variables, automatic variables, and pattern rules that make your Makefiles flexible
  3. Advanced Features — Functions, conditionals, multi-directory projects, and advanced techniques
  4. 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-tests

By 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:

  1. What is the primary problem that Make solves?
  2. What are the three parts of a Makefile rule?
  3. Why is Make still relevant when newer build tools exist?
  4. Can Make be used for tasks other than compiling code?
Show Solution
  1. 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.

  2. 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.

  3. 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 test interface.

  4. 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.

Was this page helpful?
SR

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 →