Build Repeatable File Workflows with GNU make
GNU make rebuilds a target only when its prerequisites are newer, so you stop retyping the same build commands by hand. You will finish with a small Makefile that does that, plus a dry-run mode and a safe cleanup target. The examples use GNU Make 4.3 from the installed make package, version 4.3-4.1build2; gmake is an alias for the same program on this system.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes. You need a shell, GNU make and a project directory where you can create files. The normal build commands are unprivileged. Do not run a Makefile with sudo just because a recipe failed: make runs shell commands, so a bad or untrusted recipe can change far more than the intended build output.
1. Check the Installed Command
Confirm which executable your shell will use and note its version:
$ command -v make
/usr/bin/make
$ make --version | sed -n '1,2p'
GNU Make 4.3
Built for x86_64-pc-linux-gnu
The command also answers to gmake on this machine. If your system has a different implementation, check its manual before relying on GNU-specific features such as .PHONY or --trace.
Checkpoint
The version command succeeds, and you know which project directory will hold the Makefile.
2. Describe One Target and Its Prerequisite
Create a file named Makefile in the project directory. A rule has a target, a colon, zero or more prerequisites, and indented recipe lines. That indentation before printf must be a tab, not spaces:
.PHONY: report
report: input.txt
printf 'input is ready\n' > report.txt
That first example hides a deliberate mismatch, useful for spotting the pattern: the target is named report, but the recipe creates report.txt. Match the names in a real rule. Use this working version instead:
.PHONY: report
report.txt: input.txt
printf 'input is ready\n' > report.txt
Create a harmless input file, then ask make to build the target:
$ printf '%s\n' 'source data' > input.txt
$ make report.txt
printf 'input is ready\n' > report.txt
$ cat report.txt
input is ready
Make compares modification times: it builds a missing target, or rebuilds an existing one when a prerequisite is newer. If the target is already newer, a second make report.txt just prints make: 'report.txt' is up to date. and skips the recipe.
3. Add an Aggregate Target
A named aggregate target lets one command build several outputs. It needs no file of its own, so mark it phony, since phony targets are always considered out of date:
.PHONY: all clean
all: report.txt
report.txt: input.txt
printf 'input is ready\n' > report.txt
clean:
rm -f report.txt
Now make with no target uses the first one, all, after checking for default file names in this order: GNUmakefile, makefile, then Makefile. Running make all explicitly is clearer in scripts and avoids confusion when a directory has more than one makefile.
Safety checkpoint
make clean deletes the generated file. Read every cleanup recipe before you run it, especially one with variables or wildcards. This particular example is reversible, since running make again just rebuilds from input.txt.
4. Preview Work Before You Allow It
Use --dry-run, or -n, to print recipes without running them:
$ make --dry-run all
printf 'input is ready\n' > report.txt
A dry run is a review step, not proof the real command will succeed: make still reads the makefile and resolves prerequisites. If the output shows a surprising path, stop and inspect the rule before running ordinary make.
For a more focused view of why a target is stale, use the GNU-specific trace option:
$ make --trace report.txt
Makefile:6: update target 'report.txt' due to: input.txt
printf 'input is ready\n' > report.txt
Line numbers and exact wording vary with the makefile. What matters is which prerequisite triggered the rebuild.
5. Pass a Value Through a Variable
Variables keep repeated values in one place. A command-line assignment overrides a := assignment in the file, unless the makefile itself uses a special override:
NAME := default
.PHONY: greet
greet:
printf 'hello, %s\n' '$(NAME)'
$ make greet
printf 'hello, %s\n' 'default'
hello, default
$ make greet NAME=reader
printf 'hello, %s\n' 'reader'
hello, reader
Quote values in the recipe when they are data. Treat command-line variables as input, never as trusted shell syntax: do not drop an unreviewed value into eval, a shell command substitution, or a destructive path.
6. Check Whether Work Is Needed
Question mode, --question or -q, runs no recipes and prints nothing. It returns zero when the requested targets are up to date, one when make would rebuild them, and two for an error:
$ make --question report.txt
$ printf 'status: %s\n' "$?"
status: 0
Use it in a script when the distinction matters:
if make --question report.txt; then
printf '%s\n' 'report is current'
else
status=$?
if [ "$status" -eq 1 ]; then
printf '%s\n' 'report needs rebuilding'
else
printf 'make check failed with status %s\n' "$status" >&2
exit "$status"
fi
fi
Common Traps
- "No rule to make target". Usually a missing prerequisite that no rule can create. Check the spelling, current directory and file permissions.
- "missing separator". A recipe line started with spaces instead of a tab. Display the file with something that makes tabs visible, or retype the indentation.
- Make rebuilds every single time. Check the output path and timestamps, and confirm a recipe actually creates the target its rule names. Also check for a stray
.PHONY: correct for an action likeclean, wrong for a generated file whose timestamp should drive rebuilding. - One target fails, others should still run.
make --keep-goingor-kcontinues with other possible targets. It does not repair the failed one, and it can leave a partial build, so review generated files before trusting them.
Done means
- Checked the version. You confirmed the installed GNU make version.
- Wrote a working rule. A Makefile names a target and its prerequisite, with tab-indented recipes.
- Proved the freshness check. A second build skips an unchanged target, while a newer prerequisite triggers a rebuild.
- Previewed before running.
--dry-runreviewed the commands first. - Kept cleanup safe. The cleanup target is explicit, understood and recoverable from the source input.
- Used question mode where it fits. A script can test freshness with
-qand the exit status.