What is make?

Picture a pile of Lego bricks scattered across the floor. You want a spaceship, not a cleanup headache. make is the friend who reads the instructions, remembers what you built yesterday, and only asks you to rebuild the wing your dog stepped on.

Under the hood, make reads a Makefile and decides:

  • Which targets are out of date and need rebuilding.
  • Which commands to run—and in what order.
  • Whether it should also tidy up, set permissions, or run your tests.

Many IDEs quietly call make for you. Using it directly feels like hiring the butler instead of hoping he overhears you.


Command-line Flags

Some popular make flags:

  • -f file: Use a specific Makefile (or - for stdin).
  • -i: Ignore errors.
  • -s: Silent mode (no command echo).
  • -r: Disable implicit rules.
  • -n: Dry run (show commands, don’t execute).
  • -t: Touch targets.
  • -q: Just check if things are up-to-date.
  • -p: Print all macros and rules.
  • -d: Debug output (your console will cry).

Example:

make -f custom.mk
make "CUST=PROJECT" library

What is a Makefile?

A Makefile is like the recipe book for your software. It describes:

  • Targets: What to cook (output files).
  • Dependencies: Ingredients required.
  • Commands: How to cook (shell commands).

Format:

target : dependencies
<Tab> commands

Yes, that Tab is mandatory. Forget it, and make will scold you.


How does make work?

  1. Read all Makefiles.
  2. Expand variables and rules.
  3. Build a dependency graph.
  4. Rebuild only what’s outdated.
  5. Execute the necessary commands.

Think of it as a picky chef who refuses to recook soup unless the carrots are fresher than the broth.


Rules

Example:

hello.o : hello.c
    gcc -c hello.c

If hello.c is newer than hello.o, make runs the command. Rules are recursive, dependencies are checked until everything is fresh.


Dependencies

Multiple files can depend on the same ingredient:

a.obj b.obj : inc.h

Update inc.h and both a.obj and b.obj get rebuilt. Efficient and unapologetic.


Macros (a.k.a Variables)

Macros save typing and mistakes. Use $(NAME) to expand:

GNU_BASE = /usr/local/arm/csky-elfabiv2-tools
GNU_BIN  = $(GNU_BASE)/bin/csky-elfabiv2-
CC       = $(GNU_BIN)gcc   # ARM compiler

Now you can change paths once instead of hunting them like Easter eggs.


Inference Rules

Wildcards save you from writing rules for every file:

%.obj : %.c
    $(CC) $(FLAGS) -c $<

All .c files compile into .obj without further instructions. Not magic, just pattern rules.


Comments

Only # works. Example:

# This is a comment
project.exe : main.obj io.obj  # another comment

Automatic Variables

  • $@: Target filename
  • $^: All dependencies
  • $<: First dependency
  • $?: Dependencies newer than target
  • $+: Like $^ but keeps duplicates
  • $*: Target name without extension

Example:

hello.o : hello.c
    gcc -c $<

Useful Functions

  • subst: String substitution
  • foreach: Loop through a list
  • wildcard: Expand matching filenames
  • notdir: Remove paths
  • basename, suffix, addsuffix, addprefix
  • join, strip, findstring, filter, sort
  • if-then-else, call, origin

In short: yes, you can write something that looks suspiciously like Lisp inside your Makefile.


“Makefile: because life’s too short to compile everything manually.”