No matching examples. Try another query or clear the filters.
Copy link A visual reference · GNU Make
GNU Make, one change at a time
From a first rule to reliable builds, LaTeX documents and publishing workflows.
Complete source. A small change. Its actual result.
The Org file is the canonical source. Read the HTML guide beside your work,
or download its PDF and source for offline reading.
Last changes: 2026-10-11. Unified tour–chapter–example IDs, numbered chapter anchors and updated direct links, downloads and comparisons. Manual references now use descriptive links. Reader pages now omit publishing instructions and highlight PDF downloads.
90 worked examples
14 topic families
1 canonical Org source
Copy link Reading the tour
Three ways to see a change
Makefile
Read the exact Makefile used by the example, including targets, prerequisites and recipes.
Build result
Compare captured output and exit status with the files and commands declared by the rule.
Complete workflow
Expand the runnable reproducer or download its script to recreate all local inputs.
The primary panel shows the exact Makefile used by the runnable example.
Expand Complete runnable example for setup files, invocation and checks,
or download its .mk and .sh files. The PDF keeps the Makefile and output
together; its downloads and attached Org source provide the full reproducer.
The LaTeX section covers documents, bibliography tools, generated figures,
draft/final builds, cleanup and a source package that rebuilds successfully.
Read from the beginning or jump to a stable example ID. Each example includes
its complete input and captured result. Copy link preserves the address of the
guide you are reading, including its current domain and path.
How to use this tour
Learn GNU Make through 90 complete examples in 14 topics. Read the Makefile,
then compare the captured output. Every example has a MAK identifier, a direct
URL anchor and a Copy link button in the HTML guide. Tabs introduce ordinary
recipes; the visible-prefix example shows an alternative.
Requirements: GNU Make 4.3 or newer, Bash, and common Unix tools. The final three
workflow examples also use AWK and gnuplot. The LaTeX section requires
latexmk, pdfLaTeX, BibTeX, Biber with biblatex, and pdftotext. On macOS, /usr/bin/make is often
GNU Make 3.81; install a current GNU Make with brew install make and use gmake.
The runnable examples prefer gmake when it exists and otherwise use make.
Each example writes to its own newly created temporary directory. The directory
is removed on exit. To retain its artifacts when running a downloaded script,
use KEEP_WORK=1 bash MAK-13-004.sh. These are original examples, not extracts from
the GNU manual. Content is CC0; the tour structure is CC BY 4.0.
The primary panel shows the exact Makefile used by the runnable example.
Expand Complete runnable example for setup files, invocation and follow-up
checks, or download its .sh file. The PDF keeps the Makefile and output together;
its download links and attached Org source provide the complete reproducer.
Parallel recipe output ordering may vary; MAK-11-003 accepts both complete block
orders rather than pretending the scheduler has a fixed order.
Concepts to keep in mind
Make first reads and expands its configuration, then walks the dependency graph.
A real file target is stale when missing or older than a normal prerequisite.
A phony goal names an action. Order-only prerequisites establish ordering
without making timestamp changes propagate. A recipe usually uses one shell
per logical line. Make variables and shell variables have different expansion
rules. Declare real dependencies before enabling parallel execution.
Read the GNU Make manual for the complete reference.
Selected manual sections:
Copy link MAK-01-001 A target and its recipe A rule names a target before the colon. The following tab-indented recipe runs when that target is requested. Run: make hello.
Makefile hello :
printf "%s\n" "Hello from Make"
Actual output Captured run · exit 0
printf "%s\n" "Hello from Make"
Hello from Make
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
hello:
printf "%s\n" "Hello from Make"
MAKEFILE
m hello
Download: MAK-01-001.mk · MAK-01-001.sh
Copy link MAK-01-002 Silence recipe echo with @ Add @ to hide the recipe command while retaining its output. Compare with MAK-01-001. Run: make hello.
Makefile hello :
@printf "%s\n" "Hello from Make"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
hello:
@printf "%s\n" "Hello from Make"
MAKEFILE
m hello
Download: MAK-01-002.mk · MAK-01-002.sh
Copy link MAK-01-003 Choose the default goal .DEFAULT_GOAL makes the default explicit even when a helper rule comes first. Run: make.
Makefile .DEFAULT_GOAL := all
helper :
@printf "helper\n"
all :
@printf "default all\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.DEFAULT_GOAL := all
helper:
@printf "helper\n"
all:
@printf "default all\n"
MAKEFILE
m
Download: MAK-01-003.mk · MAK-01-003.sh
Copy link MAK-01-004 Request a named target Only the requested target and its prerequisites are considered. The default goal is irrelevant to make check.
Makefile all :
@printf "build\n"
check :
@printf "check\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
@printf "build\n"
check:
@printf "check\n"
MAKEFILE
m check
Download: MAK-01-004.mk · MAK-01-004.sh
Copy link MAK-01-005 Request two goals Specify both goals on the command line. This serial invocation prints their results in the requested order.
Makefile one :
@printf "one\n"
two :
@printf "two\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
one:
@printf "one\n"
two:
@printf "two\n"
MAKEFILE
m one two
Download: MAK-01-005.mk · MAK-01-005.sh
Copy link MAK-01-006 Use another makefile name -f selects a makefile explicitly. The runnable script renames the displayed file to rules.mk. Run: make -f rules.mk hello.
Makefile hello :
@printf "rules.mk selected\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
hello:
@printf "rules.mk selected\n"
MAKEFILE
mv Makefile rules.mk
m -f rules.mk hello
Download: MAK-01-006.mk · MAK-01-006.sh
Copy link MAK-02-001 Create a missing output The missing report.txt must be built from input.txt. The setup file contains a single record, 7. Run: make report.txt, then cat report.txt.
Makefile report.txt : input .txt
@printf "building report\n"
@cp input.txt report.txt
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_7_1'
7
INPUT_7_1
cat > Makefile <<'MAKEFILE'
report.txt: input.txt
@printf "building report\n"
@cp input.txt report.txt
MAKEFILE
m -s report.txt
cat report.txt
Download: MAK-02-001.mk · MAK-02-001.sh
Copy link MAK-02-002 Skip an up-to-date output The second invocation has nothing to rebuild. A label separates the two runs; make -s keeps the no-op quiet.
Makefile report.txt : input .txt
@printf "building report\n"
@cp input.txt report.txt
Actual output Captured run · exit 0
first run
building report
second run
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_8_1'
7
INPUT_8_1
cat > Makefile <<'MAKEFILE'
report.txt: input.txt
@printf "building report\n"
@cp input.txt report.txt
MAKEFILE
printf "first run\n"
m -s report.txt
printf "second run\n"
m -s report.txt
Download: MAK-02-002.mk · MAK-02-002.sh
Copy link MAK-02-003 Rebuild after a newer prerequisite Changing the input only triggers a rebuild when its timestamp is newer. The script assigns explicit past timestamps to avoid sleeps and filesystem-resolution races.
Makefile report.txt : input .txt
@printf "building report\n"
@cp input.txt report.txt
Actual output Captured run · exit 0
building report
building report
9
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_9_1'
7
INPUT_9_1
cat > Makefile <<'MAKEFILE'
report.txt: input.txt
@printf "building report\n"
@cp input.txt report.txt
MAKEFILE
m -s report.txt
printf "9\n" > input.txt
touch -t 202001010101 report.txt
touch -t 202101010101 input.txt
m -s report.txt
cat report.txt
Download: MAK-02-003.mk · MAK-02-003.sh
Copy link MAK-02-004 Content changes are not enough Make compares timestamps, not file contents. The script replaces the input but dates it earlier than the report; the report keeps its old value. Content hashing needs an explicit design.
Makefile report.txt : input .txt
@cp input.txt report.txt
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_10_1'
7
INPUT_10_1
cat > Makefile <<'MAKEFILE'
report.txt: input.txt
@cp input.txt report.txt
MAKEFILE
m -s report.txt
printf "9\n" > input.txt
touch -t 202001010101 input.txt
m -s report.txt
cat report.txt
Download: MAK-02-004.mk · MAK-02-004.sh
Copy link MAK-02-005 Build a dependency chain Make completes the intermediate table before its dependent report. The order comes from the graph, not the order of recipe text.
Makefile report.txt : table .txt
@printf "report\n"
@cp table.txt report.txt
table.txt : input .txt
@printf "table\n"
@cp input.txt table.txt
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_11_1'
7
INPUT_11_1
cat > Makefile <<'MAKEFILE'
report.txt: table.txt
@printf "report\n"
@cp table.txt report.txt
table.txt: input.txt
@printf "table\n"
@cp input.txt table.txt
MAKEFILE
m -s report.txt
Download: MAK-02-005.mk · MAK-02-005.sh
Copy link MAK-02-006 Share one prerequisite Two outputs depend on the same prepared file. It is made once per invocation. Run: make all.
Makefile all : left .txt right .txt
left.txt right.txt : prepared .txt
@cp prepared.txt $@
prepared.txt :
@printf "prepare once\n"
@printf "ready\n" > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all: left.txt right.txt
left.txt right.txt: prepared.txt
@cp prepared.txt $@
prepared.txt:
@printf "prepare once\n"
@printf "ready\n" > $@
MAKEFILE
m -s all
Download: MAK-02-006.mk · MAK-02-006.sh
Copy link MAK-03-001 A filename can hide a command goal A file named check makes an ordinary target appear current. The script creates that file and demonstrates the skipped recipe. Compare with MAK-03-002.
Makefile check :
@printf "checked\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
check:
@printf "checked\n"
MAKEFILE
touch check
m -s check
printf "recipe was skipped\n"
Download: MAK-03-001.mk · MAK-03-001.sh
Copy link MAK-03-002 Declare a command goal phony .PHONY says that check names an action rather than a real output. It runs even when a file named check exists.
Makefile .PHONY : check
check :
@printf "checked\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.PHONY: check
check:
@printf "checked\n"
MAKEFILE
touch check
m -s check
Download: MAK-03-002.mk · MAK-03-002.sh
Copy link MAK-03-003 Provide an aggregate all goal An aggregate phony goal joins several real outputs without creating a file named all. Repeating make all does not rebuild current files.
Makefile .PHONY : all
all : a .txt b .txt
a.txt b.txt :
@printf "%s\n" " $@ "
@printf "ready\n" > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.PHONY: all
all: a.txt b.txt
a.txt b.txt:
@printf "%s\n" "$@"
@printf "ready\n" > $@
MAKEFILE
m -s all
printf "again\n"
m -s all
Download: MAK-03-003.mk · MAK-03-003.sh
Copy link MAK-03-004 Create an output directory first The vertical bar introduces an order-only prerequisite. Build the directory before report.txt without using its timestamp to decide whether the report is stale.
Makefile build/report.txt : input .txt | build
@cp input.txt $@
@printf "report created\n"
build :
@mkdir -p $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_16_1'
7
INPUT_16_1
cat > Makefile <<'MAKEFILE'
build/report.txt: input.txt | build
@cp input.txt $@
@printf "report created\n"
build:
@mkdir -p $@
MAKEFILE
m -s build/report.txt
Download: MAK-03-004.mk · MAK-03-004.sh
Copy link MAK-03-005 Directory changes do not rebuild outputs Adding an unrelated file changes the directory timestamp. Because build is order-only, the existing report remains current.
Makefile build/report.txt : input .txt | build
@cp input.txt $@
@printf "report created\n"
build :
@mkdir -p $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_17_1'
7
INPUT_17_1
cat > Makefile <<'MAKEFILE'
build/report.txt: input.txt | build
@cp input.txt $@
@printf "report created\n"
build:
@mkdir -p $@
MAKEFILE
m -s build/report.txt
printf "unrelated\n" > build/other.txt
printf "again\n"
m -s build/report.txt
Download: MAK-03-005.mk · MAK-03-005.sh
Copy link MAK-03-006 A local clean goal A clean action removes only the named generated output in the fresh demonstration directory. Phony clean never collides with a file of the same name.
Makefile .PHONY : all clean
all : result .txt
result.txt :
@printf "generated\n" > $@
clean :
@rm -f result.txt
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.PHONY: all clean
all: result.txt
result.txt:
@printf "generated\n" > $@
clean:
@rm -f result.txt
MAKEFILE
m -s all
m -s clean
test ! -e result.txt
printf "generated output removed\n"
Download: MAK-03-006.mk · MAK-03-006.sh
Copy link MAK-04-001 Name the current target with $@ $@ expands to the target being built. One rule can report or create the selected output name.
Makefile alpha.txt beta.txt :
@printf "%s\n" " $@ "
@printf "ready\n" > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
alpha.txt beta.txt:
@printf "%s\n" "$@"
@printf "ready\n" > $@
MAKEFILE
m -s beta.txt
Download: MAK-04-001.mk · MAK-04-001.sh
Copy link MAK-04-002 Read the first prerequisite with $< $< is the first prerequisite. Copying $< to $@ keeps this recipe independent of the actual names.
Makefile copy.txt : source .txt
@cp $< $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > source.txt <<'INPUT_20_1'
source record
INPUT_20_1
cat > Makefile <<'MAKEFILE'
copy.txt: source.txt
@cp $< $@
MAKEFILE
m -s copy.txt
cat copy.txt
Download: MAK-04-002.mk · MAK-04-002.sh
Copy link MAK-04-003 List unique prerequisites with $^ $^ contains prerequisite names with duplicates removed. A duplicate dependency still appears only once.
Makefile all : alpha .txt beta .txt alpha .txt
@printf "%s\n" " $^"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > alpha.txt <<'INPUT_21_1'
a
INPUT_21_1
cat > beta.txt <<'INPUT_21_2'
b
INPUT_21_2
cat > Makefile <<'MAKEFILE'
all: alpha.txt beta.txt alpha.txt
@printf "%s\n" "$^"
MAKEFILE
m -s all
Download: MAK-04-003.mk · MAK-04-003.sh
Copy link MAK-04-004 Keep duplicates with $+ $+ retains duplicates and their order. This is useful when repeated arguments matter, such as some linker command lines.
Makefile all : alpha .txt beta .txt alpha .txt
@printf "%s\n" " $+"
Actual output Captured run · exit 0
alpha.txt beta.txt alpha.txt
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > alpha.txt <<'INPUT_22_1'
a
INPUT_22_1
cat > beta.txt <<'INPUT_22_2'
b
INPUT_22_2
cat > Makefile <<'MAKEFILE'
all: alpha.txt beta.txt alpha.txt
@printf "%s\n" "$+"
MAKEFILE
m -s all
Download: MAK-04-004.mk · MAK-04-004.sh
Copy link MAK-04-005 List newer prerequisites with $? $? reports the prerequisites newer than the target. The script assigns fixed past timestamps so only changed.txt qualifies.
Makefile report.txt : old .txt changed .txt
@printf "newer: %s\n" " $? "
@touch $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > old.txt <<'INPUT_23_1'
old
INPUT_23_1
cat > changed.txt <<'INPUT_23_2'
new
INPUT_23_2
cat > report.txt <<'INPUT_23_3'
report
INPUT_23_3
touch -t 202001010101 old.txt
touch -t 202101010101 report.txt
touch -t 202201010101 changed.txt
cat > Makefile <<'MAKEFILE'
report.txt: old.txt changed.txt
@printf "newer: %s\n" "$?"
@touch $@
MAKEFILE
m -s report.txt
Download: MAK-04-005.mk · MAK-04-005.sh
Copy link MAK-04-006 Separate normal and order-only inputs $^ excludes order-only prerequisites; $| contains them. The build directory supplies ordering rather than content.
Makefile build/report.txt : input .txt | build
@printf "normal=%s order-only=%s\n" " $^" " $|"
@cp $< $@
build :
@mkdir -p $@
Actual output Captured run · exit 0
normal=input.txt order-only=build
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_24_1'
7
INPUT_24_1
cat > Makefile <<'MAKEFILE'
build/report.txt: input.txt | build
@printf "normal=%s order-only=%s\n" "$^" "$|"
@cp $< $@
build:
@mkdir -p $@
MAKEFILE
m -s build/report.txt
Download: MAK-04-006.mk · MAK-04-006.sh
Copy link MAK-05-001 Define a reusable variable Use $(NAME) to insert a Make variable. Single quotes in the recipe protect the expanded value as one shell argument.
Makefile GREETING = Hello
all :
@printf "%s\n" " $( GREETING) , reader"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
GREETING = Hello
all:
@printf "%s\n" "$(GREETING), reader"
MAKEFILE
m -s all
Download: MAK-05-001.mk · MAK-05-001.sh
Copy link MAK-05-002 Recursive assignment sees later values = stores an expression that is expanded when used. Changing NAME after defining GREETING changes the eventual greeting.
Makefile NAME = Ada
GREETING = Hello $( NAME)
NAME = Grace
all :
@printf "%s\n" " $( GREETING) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAME = Ada
GREETING = Hello $(NAME)
NAME = Grace
all:
@printf "%s\n" "$(GREETING)"
MAKEFILE
m -s all
Download: MAK-05-002.mk · MAK-05-002.sh
Copy link MAK-05-003 Simple assignment captures the current value := expands its right-hand side immediately. GREETING therefore remembers Ada. Compare with MAK-05-002.
Makefile NAME = Ada
GREETING := Hello $( NAME)
NAME = Grace
all :
@printf "%s\n" " $( GREETING) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAME = Ada
GREETING := Hello $(NAME)
NAME = Grace
all:
@printf "%s\n" "$(GREETING)"
MAKEFILE
m -s all
Download: MAK-05-003.mk · MAK-05-003.sh
Copy link MAK-05-004 Provide a default with ?= ?= assigns a value only when the variable is undefined. A command-line value takes precedence over this default.
Makefile MODE ?= release
all :
@printf "%s\n" " $( MODE) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
MODE ?= release
all:
@printf "%s\n" "$(MODE)"
MAKEFILE
m -s all
m -s all MODE = debug
Download: MAK-05-004.mk · MAK-05-004.sh
Copy link MAK-05-005 Append with += += adds words to an existing variable. This is convenient for assembling option lists or target sets.
Makefile WORDS := small
WORDS += complete
WORDS += examples
all :
@printf "%s\n" " $( WORDS) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
WORDS := small
WORDS += complete
WORDS += examples
all:
@printf "%s\n" "$(WORDS)"
MAKEFILE
m -s all
Download: MAK-05-005.mk · MAK-05-005.sh
Copy link MAK-05-006 Override from the command line A command-line assignment beats an ordinary assignment in the Makefile. Run: make all NAME=Grace.
Makefile NAME = Ada
all :
@printf "Hello %s\n" " $( NAME) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAME = Ada
all:
@printf "Hello %s\n" "$(NAME)"
MAKEFILE
m -s all NAME = Grace
Download: MAK-05-006.mk · MAK-05-006.sh
Copy link MAK-06-001 Inspect the origin of a value $(origin NAME) explains where NAME came from. This helps distinguish a Makefile value from a command-line override.
Makefile NAME = Ada
all :
@printf "%s: %s\n" " $( origin NAME) " " $( NAME) "
Actual output Captured run · exit 0
file: Ada
command line: Grace
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAME = Ada
all:
@printf "%s: %s\n" "$(origin NAME)" "$(NAME)"
MAKEFILE
m -s all
m -s all NAME = Grace
Download: MAK-06-001.mk · MAK-06-001.sh
Copy link MAK-06-002 An empty value is still defined A command-line empty string counts as defined, so ?= does not replace it. This differs from a shell fallback that tests for nonempty text.
Makefile NAME ?= Ada
all :
@printf "[%s]\n" " $( NAME) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAME ?= Ada
all:
@printf "[%s]\n" "$(NAME)"
MAKEFILE
m -s all NAME =
Download: MAK-06-002.mk · MAK-06-002.sh
Copy link MAK-06-003 Export a value to the recipe shell Exported Make variables become shell environment variables. Double dollars pass the dollar sign through Make to the shell.
Makefile export LABEL = published
all :
@printf "%s\n" " $$ LABEL"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
export LABEL = published
all:
@printf "%s\n" "$$LABEL"
MAKEFILE
m -s all
Download: MAK-06-003.mk · MAK-06-003.sh
Copy link MAK-06-004 Use a target-specific value A target-specific assignment applies when that target is built and is inherited by its prerequisites unless overridden.
Makefile debug : MODE = debug
release : MODE = release
debug release :
@printf "%s: %s\n" " $@ " " $( MODE) "
Actual output Captured run · exit 0
debug: debug
release: release
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
debug: MODE = debug
release: MODE = release
debug release:
@printf "%s: %s\n" "$@" "$(MODE)"
MAKEFILE
m -s debug release
Download: MAK-06-004.mk · MAK-06-004.sh
Copy link MAK-06-005 Choose rules at parse time with ifeq ifeq is processed while Make reads the file. It chooses the recipe text before any target is executed.
Makefile MODE ?= release
ifeq ($(MODE),debug)
MESSAGE := extra diagnostics
else
MESSAGE := compact output
endif
all :
@printf "%s\n" " $( MESSAGE) "
Actual output Captured run · exit 0
compact output
extra diagnostics
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
MODE ?= release
ifeq ($(MODE),debug)
MESSAGE := extra diagnostics
else
MESSAGE := compact output
endif
all:
@printf "%s\n" "$(MESSAGE)"
MAKEFILE
m -s all
m -s all MODE = debug
Download: MAK-06-005.mk · MAK-06-005.sh
Copy link MAK-06-006 Deliberately override a command-line value The override directive gives this assignment precedence over a command-line assignment. Use it only for values the Makefile really must control.
Makefile override MODE : = required
all :
@printf "%s\n" " $( MODE) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
override MODE := required
all:
@printf "%s\n" "$(MODE)"
MAKEFILE
m -s all MODE = optional
Download: MAK-06-006.mk · MAK-06-006.sh
Copy link MAK-07-001 Capture the stem with $* % matches a stem shared by the target and prerequisite. $* contains that stem while the recipe runs.
Makefile %.out : %.in
@printf "stem=%s\n" " $* "
@cp $< $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > sample.in <<'INPUT_37_1'
record
INPUT_37_1
cat > Makefile <<'MAKEFILE'
%.out: %.in
@printf "stem=%s\n" "$*"
@cp $< $@
MAKEFILE
m -s sample.out
Download: MAK-07-001.mk · MAK-07-001.sh
Copy link MAK-07-002 Reuse one transformation rule Both files follow the same pattern rule. tr uppercases their complete input. $< and $@ identify the current pair.
Makefile all : alpha .upper beta .upper
%.upper : %.txt
@tr "[:lower:]" "[:upper:]" < $< > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > alpha.txt <<'INPUT_38_1'
ada
INPUT_38_1
cat > beta.txt <<'INPUT_38_2'
grace
INPUT_38_2
cat > Makefile <<'MAKEFILE'
all: alpha.upper beta.upper
%.upper: %.txt
@tr "[:lower:]" "[:upper:]" < $< > $@
MAKEFILE
m -s all
cat alpha.upper beta.upper
Download: MAK-07-002.mk · MAK-07-002.sh
Copy link MAK-07-003 Limit a static pattern to selected targets A static pattern rule applies to the explicit target list before the second colon. It is more selective than a general implicit rule.
Makefile OUTPUTS := alpha.out beta.out
all : $( OUTPUTS )
$(OUTPUTS) : %.out : %.in
@cp $< $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > alpha.in <<'INPUT_39_1'
one
INPUT_39_1
cat > beta.in <<'INPUT_39_2'
two
INPUT_39_2
cat > Makefile <<'MAKEFILE'
OUTPUTS := alpha.out beta.out
all: $(OUTPUTS)
$(OUTPUTS): %.out: %.in
@cp $< $@
MAKEFILE
m -s all
cat alpha.out beta.out
Download: MAK-07-003.mk · MAK-07-003.sh
Copy link MAK-07-004 An explicit recipe wins An explicit recipe for special.out takes precedence over the generic pattern recipe for that target.
Makefile %.out : %.in
@cp $< $@
special.out : special .in
@printf "special handling\n" > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > special.in <<'INPUT_40_1'
ordinary input
INPUT_40_1
cat > Makefile <<'MAKEFILE'
%.out: %.in
@cp $< $@
special.out: special.in
@printf "special handling\n" > $@
MAKEFILE
m -s special.out
cat special.out
Download: MAK-07-004.mk · MAK-07-004.sh
Copy link MAK-07-005 Search for inputs with vpath vpath supplies a search directory for matching prerequisites. $< resolves to the discovered path, so the recipe reads the correct file.
Makefile vpath %.txt data
copy.txt : input .txt
@printf "input=%s\n" " $<"
@cp $< $@
Actual output Captured run · exit 0
input=data/input.txt
found record
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
mkdir -p data
cat > data/input.txt <<'INPUT_41_1'
found record
INPUT_41_1
cat > Makefile <<'MAKEFILE'
vpath %.txt data
copy.txt: input.txt
@printf "input=%s\n" "$<"
@cp $< $@
MAKEFILE
m -s copy.txt
cat copy.txt
Download: MAK-07-005.mk · MAK-07-005.sh
Copy link MAK-07-006 Set a pattern-specific value A pattern-specific assignment supplies a value to all matching targets. Other target patterns can use a different value.
Makefile %.debug : MODE = debug
%.release : MODE = release
app.debug app.release :
@printf "%s: %s\n" " $@ " " $( MODE) "
Actual output Captured run · exit 0
app.debug: debug
app.release: release
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
%.debug: MODE = debug
%.release: MODE = release
app.debug app.release:
@printf "%s: %s\n" "$@" "$(MODE)"
MAKEFILE
m -s app.debug app.release
Download: MAK-07-006.mk · MAK-07-006.sh
Copy link MAK-08-001 Discover existing files with wildcard $(wildcard ...) returns matching existing filenames. The script creates alpha.txt and beta.txt before Make reads the expression.
Makefile FILES := $( wildcard *.txt)
all :
@printf "%s\n" " $( FILES) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > alpha.txt <<'INPUT_43_1'
a
INPUT_43_1
cat > beta.txt <<'INPUT_43_2'
b
INPUT_43_2
cat > Makefile <<'MAKEFILE'
FILES := $(wildcard *.txt)
all:
@printf "%s\n" "$(FILES)"
MAKEFILE
m -s all
Download: MAK-08-001.mk · MAK-08-001.sh
Copy link MAK-08-002 Transform filenames with patsubst patsubst substitutes matching word patterns. Here source names become output names without executing a shell.
Makefile SOURCES := alpha.txt beta.txt
OUTPUTS := $( patsubst %.txt,build/%.out,$( SOURCES))
all :
@printf "%s\n" " $( OUTPUTS) "
Actual output Captured run · exit 0
build/alpha.out build/beta.out
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
SOURCES := alpha.txt beta.txt
OUTPUTS := $(patsubst %.txt,build/%.out,$(SOURCES))
all:
@printf "%s\n" "$(OUTPUTS)"
MAKEFILE
m -s all
Download: MAK-08-002.mk · MAK-08-002.sh
Copy link MAK-08-003 Filter a list by suffix filter keeps matching words; filter-out keeps the remainder. Both operate on whitespace-separated lists.
Makefile FILES := notes.org data.tsv plot.gp
all :
@printf "data: %s\n" " $( filter %.tsv,$( FILES)) "
@printf "other: %s\n" " $( filter-out %.tsv,$( FILES)) "
Actual output Captured run · exit 0
data: data.tsv
other: notes.org plot.gp
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
FILES := notes.org data.tsv plot.gp
all:
@printf "data: %s\n" "$(filter %.tsv,$(FILES))"
@printf "other: %s\n" "$(filter-out %.tsv,$(FILES))"
MAKEFILE
m -s all
Download: MAK-08-003.mk · MAK-08-003.sh
Copy link MAK-08-004 Sort and remove duplicates sort orders words and removes duplicates. It is suitable for a unique dependency list, but not for arguments where repetitions matter.
Makefile FILES := beta.txt alpha.txt beta.txt
all :
@printf "%s\n" " $( sort $( FILES)) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
FILES := beta.txt alpha.txt beta.txt
all:
@printf "%s\n" "$(sort $(FILES))"
MAKEFILE
m -s all
Download: MAK-08-004.mk · MAK-08-004.sh
Copy link MAK-08-005 Build a list with foreach foreach evaluates the expression once for each list item. addprefix supplies the corresponding directory prefixes.
Makefile NAMES := alpha beta
OUTPUTS := $( foreach name,$( NAMES) ,$( addprefix build/,$( name)) .out)
all :
@printf "%s\n" " $( OUTPUTS) "
Actual output Captured run · exit 0
build/alpha.out build/beta.out
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
NAMES := alpha beta
OUTPUTS := $(foreach name,$(NAMES),$(addprefix build/,$(name)).out)
all:
@printf "%s\n" "$(OUTPUTS)"
MAKEFILE
m -s all
Download: MAK-08-005.mk · MAK-08-005.sh
Copy link MAK-08-006 Capture a shell result once $(shell ...) captures command output as a Make value. := evaluates this local command once as the file is read; repeated variable references reuse the value.
Makefile VALUE := $( shell printf "computed" )
all :
@printf "%s / %s\n" " $( VALUE) " " $( VALUE) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
VALUE := $(shell printf "computed")
all:
@printf "%s / %s\n" "$(VALUE)" "$(VALUE)"
MAKEFILE
m -s all
Download: MAK-08-006.mk · MAK-08-006.sh
Copy link MAK-09-001 Escape a shell variable with $$ Make consumes one dollar sign. $$name reaches the shell as $name, while $(name) would refer to a Make variable.
Makefile all :
@name= Ada; printf "%s\n" " $$ name"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
@name=Ada; printf "%s\n" "$$name"
MAKEFILE
m -s all
Download: MAK-09-001.mk · MAK-09-001.sh
Copy link MAK-09-002 Separate recipe lines use separate shells A shell variable set on the first recipe line is absent on the next. ${value-unset} gives the shell a visible default.
Makefile all :
@value= kept
@printf "[%s]\n" " $$ {value-unset}"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
@value=kept
@printf "[%s]\n" "$${value-unset}"
MAKEFILE
m -s all
Download: MAK-09-002.mk · MAK-09-002.sh
Copy link MAK-09-003 Join commands that share state Join commands with && to keep a directory change and its dependent command in the same shell. Each example starts in its own temporary directory.
Makefile all :
@mkdir -p sub
@cd sub && printf "inside sub\n" > result.txt
@cat sub/result.txt
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
@mkdir -p sub
@cd sub && printf "inside sub\n" > result.txt
@cat sub/result.txt
MAKEFILE
m -s all
Download: MAK-09-003.mk · MAK-09-003.sh
Copy link MAK-09-004 Use one shell for a complete recipe .ONESHELL passes all recipe lines to one shell. Explicit -eu flags make an unset variable or a failed command stop the recipe.
Makefile .ONESHELL :
SHELL := /bin/sh
.SHELLFLAGS := -eu -c
all :
@value= kept
printf "%s\n" " $$ value"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.ONESHELL:
SHELL := /bin/sh
.SHELLFLAGS := -eu -c
all:
@value=kept
printf "%s\n" "$$value"
MAKEFILE
m -s all
Download: MAK-09-004.mk · MAK-09-004.sh
Copy link MAK-09-005 Choose a visible recipe prefix .RECIPEPREFIX changes the recipe introducer from a tab to the first character of its value. The > prefix makes indentation visible.
Makefile .RECIPEPREFIX := >
all :
>@printf "visible prefix\n"
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.RECIPEPREFIX := >
all:
>@printf "visible prefix\n"
MAKEFILE
m -s all
Download: MAK-09-005.mk · MAK-09-005.sh
Copy link MAK-09-006 Write a shell loop The backslash continues the logical recipe line. $$item belongs to the shell loop, while Make processes the surrounding recipe first.
Makefile all :
@for item in alpha beta; do \
printf "%s\n" " $$ item" ; \
done
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
@for item in alpha beta; do \
printf "%s\n" "$$item"; \
done
MAKEFILE
m -s all
Download: MAK-09-006.mk · MAK-09-006.sh
Copy link MAK-10-001 Include a small configuration file include reads another makefile at this point. options.mk contains MODE = debug; the main file uses that setting.
Makefile include options.mk
all :
@printf "%s\n" " $( MODE) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > options.mk <<'INPUT_55_1'
MODE = debug
INPUT_55_1
cat > Makefile <<'MAKEFILE'
include options.mk
all:
@printf "%s\n" "$(MODE)"
MAKEFILE
m -s all
Download: MAK-10-001.mk · MAK-10-001.sh
Copy link MAK-10-002 Allow an optional include -include permits a missing optional file. The default remains available when local.mk has not been created.
Makefile -include local.mk
MODE ?= release
all :
@printf "%s\n" " $( MODE) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
-include local.mk
MODE ?= release
all:
@printf "%s\n" "$(MODE)"
MAKEFILE
m -s all
Download: MAK-10-002.mk · MAK-10-002.sh
Copy link MAK-10-003 Generate an included makefile Make can build a missing included file, then restart reading its configuration. The generated file defines MODE. .DEFAULT_GOAL prevents the helper rule becoming the default.
Makefile .DEFAULT_GOAL := all
include options.mk
options.mk :
@printf "writing options\n"
@printf "MODE = generated\n" > $@
all :
@printf "%s\n" " $( MODE) "
Actual output Captured run · exit 0
writing options
generated
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.DEFAULT_GOAL := all
include options.mk
options.mk:
@printf "writing options\n"
@printf "MODE = generated\n" > $@
all:
@printf "%s\n" "$(MODE)"
MAKEFILE
m -s all
Download: MAK-10-003.mk · MAK-10-003.sh
Copy link MAK-10-004 Read several files with -f Repeated -f options read makefiles in order. extra.mk appends a word to the list defined in the displayed base.mk.
Makefile WORDS := base
all :
@printf "%s\n" " $( WORDS) "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > extra.mk <<'INPUT_58_1'
WORDS += extra
INPUT_58_1
cat > Makefile <<'MAKEFILE'
WORDS := base
all:
@printf "%s\n" "$(WORDS)"
MAKEFILE
mv Makefile base.mk
m -s -f base.mk -f extra.mk all
Download: MAK-10-004.mk · MAK-10-004.sh
Copy link MAK-10-005 Delegate with $(MAKE) $(MAKE) selects the same executable and marks a recursive invocation for Make. -C enters the child directory; the helper suppresses directory-announcement lines.
Makefile .PHONY : all
all :
@$( MAKE) -s -C child all
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
mkdir -p child
cat > child/Makefile <<'INPUT_59_1'
all:
@printf "child built\n"
INPUT_59_1
cat > Makefile <<'MAKEFILE'
.PHONY: all
all:
@$(MAKE) -s -C child all
MAKEFILE
m -s all
Download: MAK-10-005.mk · MAK-10-005.sh
Copy link MAK-10-006 Forward a command-line variable Command-line assignments propagate to a recursive Make through its flags. The child Makefile uses a default that the parent invocation overrides.
Makefile .PHONY : all
all :
@$( MAKE) -s -C child all
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
mkdir -p child
cat > child/Makefile <<'INPUT_60_1'
NAME ?= Ada
all:
@printf "child sees %s\n" "$(NAME)"
INPUT_60_1
cat > Makefile <<'MAKEFILE'
.PHONY: all
all:
@$(MAKE) -s -C child all
MAKEFILE
m -s all NAME = Grace
Download: MAK-10-006.mk · MAK-10-006.sh
Copy link MAK-11-001 Build independent outputs concurrently -j2 permits two recipes at once. Each writes its own file; the final cat uses a fixed order, independent of job scheduling.
Makefile all : alpha .txt beta .txt
alpha.txt beta.txt :
@printf "%s\n" " $@ " > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all: alpha.txt beta.txt
alpha.txt beta.txt:
@printf "%s\n" "$@" > $@
MAKEFILE
m -s -j2 all
cat alpha.txt beta.txt
Download: MAK-11-001.mk · MAK-11-001.sh
Copy link MAK-11-002 Dependencies enforce order under -j Both outputs require prepared.txt. Their recipes cannot start before its recipe finishes, even with parallel execution enabled.
Makefile all : alpha .txt beta .txt
alpha.txt beta.txt : prepared .txt
@test -s prepared.txt
@cp prepared.txt $@
prepared.txt :
@printf "prepared\n" > $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all: alpha.txt beta.txt
alpha.txt beta.txt: prepared.txt
@test -s prepared.txt
@cp prepared.txt $@
prepared.txt:
@printf "prepared\n" > $@
MAKEFILE
m -s -j2 all
cat alpha.txt beta.txt
Download: MAK-11-002.mk · MAK-11-002.sh
Copy link MAK-11-003 Group output per target --output-sync=target keeps each recipe output together. The order of the left and right blocks may vary; both complete orders are accepted.
Makefile all : left right
.PHONY : all left right
left right :
@printf "%s begin\n" " $@ "
@printf "%s end\n" " $@ "
Actual output Captured run · exit 0
left begin
left end
right begin
right end
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all: left right
.PHONY: all left right
left right:
@printf "%s begin\n" "$@"
@printf "%s end\n" "$@"
MAKEFILE
m -s -j2 --output-sync= target all
Download: MAK-11-003.mk · MAK-11-003.sh
Copy link MAK-11-004 Use one job for serial output -j1 requests one job at a time. With this prerequisite list, the two command goals finish in left-to-right order.
Makefile .PHONY : all left right
all : left right
left right :
@printf "%s\n" " $@ "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.PHONY: all left right
all: left right
left right:
@printf "%s\n" "$@"
MAKEFILE
m -s -j1 all
Download: MAK-11-004.mk · MAK-11-004.sh
Copy link MAK-11-005 Disable parallel execution for an invocation An empty .NOTPARALLEL target serializes this Make invocation even when -j2 is supplied. Recursive invocations have their own parallel policy.
Makefile .NOTPARALLEL :
.PHONY : all left right
all : left right
left right :
@printf "%s\n" " $@ "
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.NOTPARALLEL:
.PHONY: all left right
all: left right
left right:
@printf "%s\n" "$@"
MAKEFILE
m -s -j2 all
Download: MAK-11-005.mk · MAK-11-005.sh
Copy link MAK-11-006 Share jobs with recursive Make Use $(MAKE) for recursion so the child participates in the jobserver. Avoid adding an independent -j value to every child. Each module writes its own output.
Makefile .PHONY : all alpha beta
all : alpha beta
alpha beta :
@$( MAKE) -s -C $@ all
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
mkdir -p alpha
cat > alpha/Makefile <<'INPUT_66_1'
all:
@printf "alpha\n" > result.txt
INPUT_66_1
mkdir -p beta
cat > beta/Makefile <<'INPUT_66_2'
all:
@printf "beta\n" > result.txt
INPUT_66_2
cat > Makefile <<'MAKEFILE'
.PHONY: all alpha beta
all: alpha beta
alpha beta:
@$(MAKE) -s -C $@ all
MAKEFILE
m -s -j2 all
cat alpha/result.txt beta/result.txt
Download: MAK-11-006.mk · MAK-11-006.sh
Copy link MAK-12-001 Preview an ordinary recipe with -n -n prints this ordinary recipe without running it. The follow-up test confirms that result.txt was not created. Recursive $(MAKE), + recipes and makefile remakes have special dry-run behavior.
Makefile result.txt :
@printf "generated\n" > result.txt
Actual output Captured run · exit 0
printf "generated\n" > result.txt
file remains absent
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
result.txt:
@printf "generated\n" > result.txt
MAKEFILE
m -n result.txt
test ! -e result.txt
printf "file remains absent\n"
Download: MAK-12-001.mk · MAK-12-001.sh
Copy link MAK-12-002 Ask whether an output is current -q runs no ordinary recipe: exit 0 means current, 1 means a rebuild is needed, and 2 means an error. The script distinguishes these statuses rather than treating every failure as stale.
Makefile result.txt : input .txt
@cp $< $@
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_68_1'
record
INPUT_68_1
cat > Makefile <<'MAKEFILE'
result.txt: input.txt
@cp $< $@
MAKEFILE
if m -s -q result.txt; then printf "current\n" ; else status = $? ; test " $status " = 1 ; printf "needs rebuild\n" ; fi
m -s result.txt
if m -s -q result.txt; then printf "current\n" ; else exit 1 ; fi
Download: MAK-12-002.mk · MAK-12-002.sh
Copy link MAK-12-003 Trace why a recipe runs --trace reports the rule and the prerequisite that prompted rebuilding. The script replaces the numeric line location with LINE for a stable transcript.
Makefile copy.txt : source .txt
@cp source.txt copy.txt
Actual output Captured run · exit 0
Makefile:LINE: update target 'copy.txt' due to: source.txt
cp source.txt copy.txt
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > source.txt <<'INPUT_69_1'
record
INPUT_69_1
cat > Makefile <<'MAKEFILE'
copy.txt: source.txt
@cp source.txt copy.txt
MAKEFILE
cp source.txt copy.txt
touch -t 202001010101 copy.txt
touch -t 202101010101 source.txt
m --trace copy.txt | sed -E "s/Makefile:[0-9]+:/Makefile:LINE:/g"
Download: MAK-12-003.mk · MAK-12-003.sh
Copy link MAK-12-004 Print a parse-time diagnostic $(info ...) writes a message as the file is read. This precedes the target recipe and helps inspect an expanded variable.
Makefile MODE := debug
$( info reading MODE =$( MODE ))
all :
@printf "running target\n"
Actual output Captured run · exit 0
reading MODE=debug
running target
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
MODE := debug
$(info reading MODE=$(MODE))
all:
@printf "running target\n"
MAKEFILE
m -s all
Download: MAK-12-004.mk · MAK-12-004.sh
Copy link MAK-12-005 Reject an invalid configuration This is an intentional error. $(error ...) stops reading the file before the recipe can run; Make returns status 2. The captured standard error explains the missing setting.
Makefile ifeq ($(strip $(INPUT)),)
$( error INPUT is required )
endif
all :
@printf "would run\n"
Actual output Captured run · exit 2
Standard error Makefile:2: *** INPUT is required. Stop.
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
ifeq ($(strip $(INPUT)),)
$(error INPUT is required)
endif
all:
@printf "would run\n"
MAKEFILE
m -s all
Download: MAK-12-005.mk · MAK-12-005.sh
Copy link MAK-12-006 Diagnose spaces where a tab is required This is an intentional syntax error: the recipe begins with four spaces. Make reports missing separator and exits 2. Compare with MAK-01-001 or the visible prefix in MAK-09-005.
Makefile all :
printf "bad indentation\n"
Actual output Captured run · exit 2
Standard error Makefile:2: *** missing separator. Stop.
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
all:
printf "bad indentation\n"
MAKEFILE
m -s all
Download: MAK-12-006.mk · MAK-12-006.sh
Copy link MAK-13-001 Delete a target when its recipe fails .DELETE_ON_ERROR removes an output modified by a failed recipe. The script verifies that the incomplete file is gone; this expected failure is caught explicitly.
Makefile .DELETE_ON_ERROR :
report.txt :
@printf "incomplete\n" > $@
@false
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > Makefile <<'MAKEFILE'
.DELETE_ON_ERROR:
report.txt:
@printf "incomplete\n" > $@
@false
MAKEFILE
if m -s report.txt 2 > failure.txt; then exit 1 ; else status = $? ; test " $status " = 2 ; fi
test ! -e report.txt
printf "partial output removed\n"
Download: MAK-13-001.mk · MAK-13-001.sh
Copy link MAK-13-002 Publish an output with an atomic rename Write to a temporary sibling first, then rename it only after success. A simulated failure before mv preserves the previously complete report.
Makefile .PHONY : all broken
all : report .txt
report.txt : input .txt
@cp $< $@ .tmp && mv $@ .tmp $@
broken :
@printf "partial\n" > report.txt.tmp; false
Actual output Captured run · exit 0
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_74_1'
complete record
INPUT_74_1
cat > Makefile <<'MAKEFILE'
.PHONY: all broken
all: report.txt
report.txt: input.txt
@cp $< $@.tmp && mv $@.tmp $@
broken:
@printf "partial\n" > report.txt.tmp; false
MAKEFILE
m -s all
if m -s broken 2 > failure.txt; then exit 1 ; else test " $? " = 2 ; fi
cat report.txt
Download: MAK-13-002.mk · MAK-13-002.sh
Copy link MAK-13-003 Declare outputs produced together &: is a grouped-target rule, available in GNU Make 4.3 and later. One invocation creates both files. Asking for both with -j2 must still run the recipe only once.
Makefile all : summary .txt count .txt
summary.txt count.txt & : input .txt
@printf "one grouped recipe\n"
@cp input.txt summary.txt
@wc -l < input.txt | tr -d " " > count.txt
Actual output Captured run · exit 0
one grouped recipe
Ada
Grace
2
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > input.txt <<'INPUT_75_1'
Ada
Grace
INPUT_75_1
cat > Makefile <<'MAKEFILE'
all: summary.txt count.txt
summary.txt count.txt &: input.txt
@printf "one grouped recipe\n"
@cp input.txt summary.txt
@wc -l < input.txt | tr -d " " > count.txt
MAKEFILE
m -s -j2 all
cat summary.txt count.txt
Download: MAK-13-003.mk · MAK-13-003.sh
Copy link MAK-13-004 Connect data, AWK, gnuplot and HTML A complete dependency graph aggregates three input records, plots the totals and writes a linked HTML report. data.tsv contains books 4, pens 2 and books 3. The full runnable example includes plot.gp. Run: make report.html.
Makefile .DEFAULT_GOAL := report.html
totals.tsv : data .tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg : totals .tsv plot .gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html : chart .svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
Actual output Captured run · exit 0
aggregate data
draw chart
publish report
books 7
pens 2
HTML and SVG ready
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > data.tsv <<'INPUT_76_1'
books 4
pens 2
books 3
INPUT_76_1
cat > plot.gp <<'INPUT_76_2'
set terminal svg size 640,360
set output 'chart.svg'
set style fill solid 0.7
set boxwidth 0.6
plot 'totals.tsv' using 2:xticlabels(1) with boxes title 'Totals'
INPUT_76_2
cat > Makefile <<'MAKEFILE'
.DEFAULT_GOAL := report.html
totals.tsv: data.tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg: totals.tsv plot.gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html: chart.svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
MAKEFILE
m -s report.html
cat totals.tsv
test -s chart.svg
test -s report.html
printf "HTML and SVG ready\n"
Download: MAK-13-004.mk · MAK-13-004.sh
Copy link MAK-13-005 Rebuild only a changed downstream branch Changing plot.gp requires a new chart and HTML report, while totals.tsv remains current. Fixed past timestamps make the dependency test repeatable without sleeping.
Makefile .DEFAULT_GOAL := report.html
totals.tsv : data .tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg : totals .tsv plot .gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html : chart .svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
Actual output Captured run · exit 0
aggregate data
draw chart
publish report
change only plotting settings
draw chart
publish report
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > data.tsv <<'INPUT_77_1'
books 4
pens 2
books 3
INPUT_77_1
cat > plot.gp <<'INPUT_77_2'
set terminal svg size 640,360
set output 'chart.svg'
set style fill solid 0.7
set boxwidth 0.6
plot 'totals.tsv' using 2:xticlabels(1) with boxes title 'Totals'
INPUT_77_2
cat > Makefile <<'MAKEFILE'
.DEFAULT_GOAL := report.html
totals.tsv: data.tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg: totals.tsv plot.gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html: chart.svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
MAKEFILE
m -s report.html
sed "s/size 640,360/size 800,450/" plot.gp > plot.gp.tmp
mv plot.gp.tmp plot.gp
touch -t 202001010101 totals.tsv chart.svg report.html data.tsv
touch -t 202101010101 plot.gp
printf "change only plotting settings\n"
m -s report.html
grep -q 'viewBox="0 0 800 450"' chart.svg
Download: MAK-13-005.mk · MAK-13-005.sh
Copy link MAK-13-006 Stage and package a local website Stage only public HTML and SVG files, then archive that directory. This example performs no network deployment. Keep actual SSH uploads as a separate, explicit action.
Makefile .DEFAULT_GOAL := report.html
totals.tsv : data .tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg : totals .tsv plot .gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html : chart .svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
.PHONY : stage package
stage : report .html
@mkdir -p public
@cp report.html public/index.html
@cp chart.svg public/chart.svg
package : stage
@tar -czf website.tar.gz -C public .
Actual output Captured run · exit 0
aggregate data
draw chart
publish report
public/chart.svg
public/index.html
website.tar.gz ready
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > data.tsv <<'INPUT_78_1'
books 4
pens 2
books 3
INPUT_78_1
cat > plot.gp <<'INPUT_78_2'
set terminal svg size 640,360
set output 'chart.svg'
set style fill solid 0.7
set boxwidth 0.6
plot 'totals.tsv' using 2:xticlabels(1) with boxes title 'Totals'
INPUT_78_2
cat > Makefile <<'MAKEFILE'
.DEFAULT_GOAL := report.html
totals.tsv: data.tsv
@printf "aggregate data\n"
@awk '{sum[$$1]+=$$2} END {for (key in sum) print key, sum[key]}' $< | sort > $@
chart.svg: totals.tsv plot.gp
@printf "draw chart\n"
@gnuplot plot.gp
report.html: chart.svg
@printf "publish report\n"
@printf '<!doctype html><title>Make report</title><h1>Totals</h1><img src="chart.svg" alt="Totals by category">\n' > $@
.PHONY: stage package
stage: report.html
@mkdir -p public
@cp report.html public/index.html
@cp chart.svg public/chart.svg
package: stage
@tar -czf website.tar.gz -C public .
MAKEFILE
m -s package
test -s website.tar.gz
find public -type f | sort
printf "website.tar.gz ready\n"
Download: MAK-13-006.mk · MAK-13-006.sh
Copy link MAK-14-001 Build a PDF with latexmk Make coordinates the project; latexmk runs the TeX passes needed for a stable PDF. The real target is build/main.pdf, with build as an order-only prerequisite. The recipe keeps auxiliary files and compiler diagnostics in build/. .DELETE_ON_ERROR removes a file target modified by a failed recipe. If compilation fails, it prints the saved log and returns failure. The driver checks the actual PDF text. Run: make all. The -norc flag makes this demonstration independent of personal latexmk settings.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
build/main.pdf : main .tex | build
@printf "compile main.tex\n"
@$( LATEXMK) $( FLAGS) -outdir= build $< > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
Actual output Captured run · exit 0
compile main.tex
PDF text verified; second build is current
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_79_1'
\documentclass{article}
\begin{document}
Hello from a Make-built document.
\end{document}
INPUT_79_1
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
build/main.pdf: main.tex | build
@printf "compile main.tex\n"
@$(LATEXMK) $(FLAGS) -outdir=build $< > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
MAKEFILE
m -s all
test -s build/main.pdf
pdftotext build/main.pdf text.txt
grep -q 'Hello from a Make-built document.' text.txt
m -q all
printf "PDF text verified; second build is current\n"
Download: MAK-14-001.mk · MAK-14-001.sh
Copy link MAK-14-002 Rebuild after editing an included chapter A file target needs every relevant input in its prerequisites, including included chapters and custom .cls/.sty files. This small project lists sections/*.tex explicitly with wildcard. The driver changes the chapter text, assigns controlled past timestamps and verifies that the new text reaches the PDF. Wildcard covers existing files in this directory; it does not discover arbitrary nested inputs. See MAK-14-003 for delegation to latexmk.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
CHAPTERS := $( wildcard sections/*.tex)
.PHONY : all
all : build /main .pdf
build/main.pdf : main .tex $( CHAPTERS ) | build
@printf "compile document and chapters\n"
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
Actual output Captured run · exit 0
compile document and chapters
compile document and chapters
edited chapter appears in the PDF
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_80_1'
\documentclass{article}
\begin{document}
\input{sections/intro}
\end{document}
INPUT_80_1
mkdir -p sections
cat > sections/intro.tex <<'INPUT_80_2'
Original chapter text.
INPUT_80_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
CHAPTERS := $(wildcard sections/*.tex)
.PHONY: all
all: build/main.pdf
build/main.pdf: main.tex $(CHAPTERS) | build
@printf "compile document and chapters\n"
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
MAKEFILE
m -s all
m -q all
printf "Revised chapter text.\n" > sections/intro.tex
touch -t 202001010101 main.tex build/main.pdf
touch -t 202101010101 sections/intro.tex
m -s all
pdftotext build/main.pdf text.txt
grep -q 'Revised chapter text.' text.txt
if grep -q 'Original chapter text.' text.txt; then exit 1 ; fi
printf "edited chapter appears in the PDF\n"
Download: MAK-14-002.mk · MAK-14-002.sh
Copy link MAK-14-003 Let latexmk discover inputs; preview a watch command The pdf goal is phony, so Make always asks latexmk to inspect the document. Latexmk tracks inputs and reruns TeX only when needed; the driver verifies an unchanged TeX log on the second invocation, then changes an included chapter. This avoids maintaining a second list of nested TeX inputs. Declare generated figures or tables as pdf prerequisites so Make creates them first. The watch goal uses -pvc for continuous preview. Only make -n watch is exercised here: run make watch interactively and stop it with Ctrl-C. A latexmk watcher does not itself invoke unrelated Make rules when their source data changes.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := pdf
.PHONY : pdf watch
pdf : | build
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
watch : | build
@$( LATEXMK) $( FLAGS) -outdir= build -pvc main.tex
build :
@mkdir -p $@
Actual output Captured run · exit 0
unchanged input: TeX engine skipped
changed input: PDF updated
watch command checked without starting a watcher
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_81_1'
\documentclass{article}
\begin{document}
\input{sections/intro}
\end{document}
INPUT_81_1
mkdir -p sections
cat > sections/intro.tex <<'INPUT_81_2'
First discovered chapter.
INPUT_81_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := pdf
.PHONY: pdf watch
pdf: | build
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
watch: | build
@$(LATEXMK) $(FLAGS) -outdir=build -pvc main.tex
build:
@mkdir -p $@
MAKEFILE
m -s pdf
cp build/main.log first.log
m -s pdf
cmp first.log build/main.log
printf "unchanged input: TeX engine skipped\n"
printf "Changed discovered chapter.\n" > sections/intro.tex
m -s pdf
pdftotext build/main.pdf text.txt
grep -q 'Changed discovered chapter.' text.txt
printf "changed input: PDF updated\n"
m -n watch > watch-command.txt
grep -q -- '-pvc main.tex' watch-command.txt
printf "watch command checked without starting a watcher\n"
Download: MAK-14-003.mk · MAK-14-003.sh
Copy link MAK-14-004 Resolve a classic BibTeX bibliography List the .bib database as a prerequisite and let latexmk choose the BibTeX and TeX passes. The sample cites a fictional book defined in references.bib. The driver checks the .bbl, the final PDF text and the absence of unresolved citations in the final TeX log. When using a local .bst file, add it to the prerequisites too. Run: make all.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
build/main.pdf : main .tex references .bib | build
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
Actual output Captured run · exit 0
BibTeX bibliography resolved and PDF verified
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_82_1'
\documentclass{article}
\begin{document}
This fictional example cites a notebook~\cite{bossert2026}.
\bibliographystyle{plain}
\bibliography{references}
\end{document}
INPUT_82_1
cat > references.bib <<'INPUT_82_2'
@book{bossert2026,
author = {Lukas C. Bossert},
title = {A Small Build Notebook},
year = {2026},
publisher = {Tour Examples}
}
INPUT_82_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
build/main.pdf: main.tex references.bib | build
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
MAKEFILE
m -s all
test -s build/main.bbl
grep -q 'bossert2026' build/main.bbl
pdftotext build/main.pdf text.txt
grep -q 'Small Build Notebook' text.txt
if grep -Eq 'undefined citations|Citation .* undefined' build/main.log; then exit 1 ; fi
m -q all
printf "BibTeX bibliography resolved and PDF verified\n"
Download: MAK-14-004.mk · MAK-14-004.sh
Copy link MAK-14-005 Use biblatex and Biber, then update a citation With biblatex configured for backend=biber, latexmk detects and runs Biber automatically. Do not also run BibTeX on the same auxiliary data. The driver builds a real citation, changes the database title and verifies the revised reference in the PDF. This example needs compatible biblatex and Biber installations, normally supplied together by a TeX distribution. The bibliography entry is fictional.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
build/main.pdf : main .tex references .bib | build
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
Actual output Captured run · exit 0
Biber citation resolved
changed bibliography appears in the PDF
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_83_1'
\documentclass{article}
\usepackage[backend=biber,style=numeric]{biblatex}
\addbibresource{references.bib}
\begin{document}
This fictional example cites a notebook~\cite{bossert2026}.
\printbibliography
\end{document}
INPUT_83_1
cat > references.bib <<'INPUT_83_2'
@book{bossert2026,
author = {Lukas C. Bossert},
title = {A Small Build Notebook},
year = {2026},
publisher = {Tour Examples}
}
INPUT_83_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
build/main.pdf: main.tex references.bib | build
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
MAKEFILE
m -s all
test -s build/main.bcf
test -s build/main.bbl
grep -q 'bossert2026' build/main.bbl
pdftotext build/main.pdf first.txt
grep -q 'Small Build Notebook' first.txt
printf "Biber citation resolved\n"
sed 's/Small Build Notebook/Revised Build Notebook/' references.bib > references.bib.tmp
mv references.bib.tmp references.bib
touch -t 202001010101 main.tex build/main.pdf
touch -t 202101010101 references.bib
m -s all
pdftotext build/main.pdf revised.txt
grep -q 'Revised Build Notebook' revised.txt
if grep -Eq 'undefined citations|Please .*run Biber' build/main.log; then exit 1 ; fi
printf "changed bibliography appears in the PDF\n"
Download: MAK-14-005.mk · MAK-14-005.sh
Copy link MAK-14-006 Build a gnuplot figure before the document Make owns the graph data.tsv -> figures/chart.pdf -> build/main.pdf. gnuplot uses the pdfcairo terminal; graphicx includes that generated PDF without TeX shell escape. Both the data and plotting script are prerequisites of the figure. The driver changes one value and verifies that the figure and document really rebuild. Keep generated figures as file targets so unchanged ones can be reused. Run: make all.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
figures/chart.pdf : data .tsv plot .gp | figures
@printf "generate PDF figure\n"
@gnuplot plot.gp
build/main.pdf : main .tex figures /chart .pdf | build
@printf "compile document with figure\n"
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build figures :
@mkdir -p $@
Actual output Captured run · exit 0
generate PDF figure
compile document with figure
generate PDF figure
compile document with figure
updated figure and document verified
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_84_1'
\documentclass{article}
\usepackage{graphicx}
\begin{document}
\section*{Measured values}
\includegraphics[width=0.8\linewidth]{figures/chart.pdf}
\end{document}
INPUT_84_1
cat > data.tsv <<'INPUT_84_2'
1 2
2 4
3 3
INPUT_84_2
cat > plot.gp <<'INPUT_84_3'
set terminal pdfcairo size 5in,3in
set output 'figures/chart.pdf'
set xlabel 'Sample'
set ylabel 'Value'
set yrange [0:6]
plot 'data.tsv' using 1:2 with linespoints title 'Measurements'
INPUT_84_3
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
figures/chart.pdf: data.tsv plot.gp | figures
@printf "generate PDF figure\n"
@gnuplot plot.gp
build/main.pdf: main.tex figures/chart.pdf | build
@printf "compile document with figure\n"
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build figures:
@mkdir -p $@
MAKEFILE
m -s all
m -q all
cp figures/chart.pdf first-chart.pdf
printf "1 2\n2 5\n3 3\n" > data.tsv
touch -t 202001010101 plot.gp main.tex figures/chart.pdf build/main.pdf
touch -t 202101010101 data.tsv
m -s all
if cmp -s first-chart.pdf figures/chart.pdf; then exit 1 ; fi
pdftotext build/main.pdf text.txt
grep -q 'Measured values' text.txt
grep -q 'Measurements' text.txt
printf "updated figure and document verified\n"
Download: MAK-14-006.mk · MAK-14-006.sh
Copy link MAK-14-007 Generate a LaTeX table from data with AWK A generated table is a normal prerequisite of the PDF. The AWK recipe emits rows for a tabular environment, writes a temporary sibling and renames it on success. $$1 and $$2 survive Make expansion as AWK field references. This fixture accepts only plain alphabetic labels and integer values; arbitrary text needs LaTeX escaping. The driver changes books from 4 to 7 and checks both the table source and PDF text.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
tables/rows.tex : data .tsv | tables
@printf "generate table rows\n"
@awk '{printf "%s & %d \\\\\n", $$1, $$2}' $< > $@ .tmp && mv $@ .tmp $@
build/main.pdf : main .tex tables /rows .tex | build
@printf "compile document with table\n"
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build tables :
@mkdir -p $@
Actual output Captured run · exit 0
generate table rows
compile document with table
generate table rows
compile document with table
edited data appears in the table and PDF
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_85_1'
\documentclass{article}
\begin{document}
\section*{Stock counts}
\begin{tabular}{lr}
Item & Count \\
\hline
\input{tables/rows}
\end{tabular}
\end{document}
INPUT_85_1
cat > data.tsv <<'INPUT_85_2'
books 4
pens 2
INPUT_85_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
tables/rows.tex: data.tsv | tables
@printf "generate table rows\n"
@awk '{printf "%s & %d \\\\\n", $$1, $$2}' $< > $@.tmp && mv $@.tmp $@
build/main.pdf: main.tex tables/rows.tex | build
@printf "compile document with table\n"
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build tables:
@mkdir -p $@
MAKEFILE
m -s all
m -q all
printf "books 7\npens 2\n" > data.tsv
touch -t 202001010101 main.tex tables/rows.tex build/main.pdf
touch -t 202101010101 data.tsv
m -s all
grep -Fq 'books & 7 \\' tables/rows.tex
pdftotext -layout build/main.pdf text.txt
grep -Eq 'books[[:space:]]+7' text.txt
printf "edited data appears in the table and PDF\n"
Download: MAK-14-007.mk · MAK-14-007.sh
Copy link MAK-14-008 Keep draft and final builds in separate directories draft.tex defines DraftVersion and both wrappers input main.tex. The pattern rule uses the stem ($*) to choose a wrapper, a separate output directory ($(@D)) for each variant, and jobname=main for a consistent PDF filename. Auxiliary state is never shared between draft and final. The driver inspects both PDFs, then confirms that make -q all is current. Run: make draft, make final or make all.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all draft final
all : draft final
draft : build /draft /main .pdf
final : build /final /main .pdf
build/%/main.pdf : %.tex main .tex
@mkdir -p $( @D)
@$( LATEXMK) $( FLAGS) -outdir= $( @D) -jobname= main $* .tex > $( @D) /latexmk.log 2 >& 1 || \
{ cat $( @D) /latexmk.log >& 2 ; exit 1 ; }
Actual output Captured run · exit 0
draft and final PDFs have independent auxiliary state
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > draft.tex <<'INPUT_86_1'
\def\DraftVersion{1}
\input{main}
INPUT_86_1
cat > final.tex <<'INPUT_86_2'
\input{main}
INPUT_86_2
cat > main.tex <<'INPUT_86_3'
\documentclass{article}
\begin{document}
\ifdefined\DraftVersion
Draft version: comments are visible.
\else
Final version: ready for readers.
\fi
\end{document}
INPUT_86_3
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all draft final
all: draft final
draft: build/draft/main.pdf
final: build/final/main.pdf
build/%/main.pdf: %.tex main.tex
@mkdir -p $(@D)
@$(LATEXMK) $(FLAGS) -outdir=$(@D) -jobname=main $*.tex > $(@D)/latexmk.log 2>&1 || \
{ cat $(@D)/latexmk.log >&2; exit 1; }
MAKEFILE
m -s all
pdftotext build/draft/main.pdf draft.txt
pdftotext build/final/main.pdf final.txt
grep -q 'Draft version: comments are visible.' draft.txt
grep -q 'Final version: ready for readers.' final.txt
test -s build/draft/main.aux
test -s build/final/main.aux
m -q all
printf "draft and final PDFs have independent auxiliary state\n"
Download: MAK-14-008.mk · MAK-14-008.sh
Copy link MAK-14-009 Track project compiler settings and resolve references Put project settings in a versioned latexmkrc and load it explicitly with -norc -r. List the settings file as a prerequisite so editing it schedules another build. The Perl %O and %S placeholders are latexmk command substitutions. This document contains a section reference; latexmk performs the passes needed to resolve it. The driver checks the final PDF and rejects unresolved-reference warnings.
Makefile LATEXMK ?= latexmk
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all
all : build /main .pdf
build/main.pdf : main .tex latexmkrc | build
@$( LATEXMK) -norc -r latexmkrc -pdf -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
Actual output Captured run · exit 0
project settings loaded; section reference resolved
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_87_1'
\documentclass{article}
\begin{document}
\section{Introduction}\label{sec:intro}
See section~\ref{sec:intro}.
\end{document}
INPUT_87_1
cat > latexmkrc <<'INPUT_87_2'
$pdflatex = 'pdflatex -no-shell-escape -interaction=nonstopmode -halt-on-error %O %S';
$max_repeat = 5;
INPUT_87_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all
all: build/main.pdf
build/main.pdf: main.tex latexmkrc | build
@$(LATEXMK) -norc -r latexmkrc -pdf -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
MAKEFILE
m -s all
pdftotext build/main.pdf text.txt
grep -q 'See section 1.' text.txt
if grep -Eq 'undefined references|Reference .* undefined' build/main.log; then exit 1 ; fi
m -q all
printf "project settings loaded; section reference resolved\n"
Download: MAK-14-009.mk · MAK-14-009.sh
Copy link MAK-14-010 Build independent documents in parallel Each document gets its own directory and log, so make -j2 can build both without colliding auxiliary filenames. The list functions produce build/article/main.pdf and build/notes/main.pdf; the pattern rule chooses article.tex or notes.tex using $*. Shared generated assets must be declared as prerequisites before adding them. The driver verifies both PDFs and reports a fixed result after both jobs finish.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
DOCS := article notes
PDFS := $( addsuffix /main.pdf,$( addprefix build/,$( DOCS)))
.PHONY : all
all : $( PDFS )
build/%/main.pdf : %.tex
@mkdir -p $( @D)
@$( LATEXMK) $( FLAGS) -outdir= $( @D) -jobname= main $< > $( @D) /latexmk.log 2 >& 1 || \
{ cat $( @D) /latexmk.log >& 2 ; exit 1 ; }
Actual output Captured run · exit 0
two independent PDFs built with -j2
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > article.tex <<'INPUT_88_1'
\documentclass{article}
\begin{document}
An independent article.
\end{document}
INPUT_88_1
cat > notes.tex <<'INPUT_88_2'
\documentclass{article}
\begin{document}
Independent working notes.
\end{document}
INPUT_88_2
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
DOCS := article notes
PDFS := $(addsuffix /main.pdf,$(addprefix build/,$(DOCS)))
.PHONY: all
all: $(PDFS)
build/%/main.pdf: %.tex
@mkdir -p $(@D)
@$(LATEXMK) $(FLAGS) -outdir=$(@D) -jobname=main $< > $(@D)/latexmk.log 2>&1 || \
{ cat $(@D)/latexmk.log >&2; exit 1; }
MAKEFILE
m -s -j2 all
pdftotext build/article/main.pdf article.txt
pdftotext build/notes/main.pdf notes.txt
grep -q 'An independent article.' article.txt
grep -q 'Independent working notes.' notes.txt
test -s build/article/main.aux
test -s build/notes/main.aux
m -q all
printf "two independent PDFs built with -j2\n"
Download: MAK-14-010.mk · MAK-14-010.sh
Copy link MAK-14-011 Clean auxiliaries while keeping the PDF latexmk -c removes regenerable auxiliary files while keeping the final PDF. This project puts all generated document files in build/, so distclean can remove that owned directory. Neither goal deletes main.tex or source figures. The driver checks that clean keeps the PDF, distclean removes build/ and a subsequent build works. Do not place authoritative source files in the disposable build directory.
Makefile LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction= nonstopmode -halt-on-error
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
.PHONY : all clean distclean
all : build /main .pdf
build/main.pdf : main .tex | build
@$( LATEXMK) $( FLAGS) -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build :
@mkdir -p $@
clean :
@$( LATEXMK) -norc -c -outdir= build main.tex > cleanup.log 2 >& 1 || \
{ cat cleanup.log >& 2 ; exit 1 ; }
distclean : clean
@rm -rf -- build
Actual output Captured run · exit 0
clean: PDF and source retained
distclean: generated directory removed, source retained
document rebuilt after cleanup
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_89_1'
\documentclass{article}
\begin{document}
Hello from a Make-built document.
\end{document}
INPUT_89_1
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
FLAGS := -norc -pdf -no-shell-escape
FLAGS += -interaction=nonstopmode -halt-on-error
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
.PHONY: all clean distclean
all: build/main.pdf
build/main.pdf: main.tex | build
@$(LATEXMK) $(FLAGS) -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build:
@mkdir -p $@
clean:
@$(LATEXMK) -norc -c -outdir=build main.tex > cleanup.log 2>&1 || \
{ cat cleanup.log >&2; exit 1; }
distclean: clean
@rm -rf -- build
MAKEFILE
m -s all
test -s build/main.aux
m -s clean
test -s build/main.pdf
test ! -e build/main.aux
test -s main.tex
printf "clean: PDF and source retained\n"
m -s distclean
test ! -e build
test -s main.tex
printf "distclean: generated directory removed, source retained\n"
m -s all
test -s build/main.pdf
printf "document rebuilt after cleanup\n"
Download: MAK-14-011.mk · MAK-14-011.sh
Copy link MAK-14-012 Package sources and verify a fresh rebuild Archive the Makefile, document, included chapter and compiler settings explicitly. The package goal first verifies the local PDF build; auxiliary caches and local output directories are excluded from the source archive. The driver unpacks into another fresh directory and builds there with make -C, checking the resulting PDF text. Add real bibliography, class/style and figure inputs to the archive list when needed. This is a source package for collaborators with the documented tools installed, not a claim of byte-identical PDFs across TeX distributions.
Makefile LATEXMK ?= latexmk
.DELETE_ON_ERROR :
.DEFAULT_GOAL := all
SOURCES := Makefile main.tex sections/intro.tex latexmkrc
.PHONY : all package
all : build /main .pdf
build/main.pdf : main .tex sections /intro .tex latexmkrc | build
@$( LATEXMK) -norc -r latexmkrc -pdf -outdir= build main.tex > build/latexmk.log 2 >& 1 || \
{ cat build/latexmk.log >& 2 ; exit 1 ; }
build dist :
@mkdir -p $@
package : dist /paper -source .tar .gz
dist/paper-source.tar.gz : $( SOURCES ) build /main .pdf | dist
@tar -czf $@ $( SOURCES)
Actual output Captured run · exit 0
Makefile
main.tex
sections/intro.tex
latexmkrc
unpacked sources build a verified PDF
Complete runnable example #!/usr/bin/env bash
set -euo pipefail
unset MAKEFLAGS MFLAGS GNUMAKEFLAGS
make_bin = $( command -v gmake || command -v make)
m() { " $make_bin " --no-print-directory -rR " $@ " ; }
work_dir = $( mktemp -d)
if [ " ${ KEEP_WORK :- 0 } " = 1 ] ; then
trap 'printf "Artifacts: %s\n" "$work_dir"' EXIT
else
trap 'rm -rf -- "$work_dir"' EXIT
fi
cd " $work_dir "
export TMPDIR = " $work_dir "
cat > main.tex <<'INPUT_90_1'
\documentclass{article}
\begin{document}
\input{sections/intro}
\end{document}
INPUT_90_1
mkdir -p sections
cat > sections/intro.tex <<'INPUT_90_2'
A document rebuilt from its source package.
INPUT_90_2
cat > latexmkrc <<'INPUT_90_3'
$pdflatex = 'pdflatex -no-shell-escape -interaction=nonstopmode -halt-on-error %O %S';
$max_repeat = 5;
INPUT_90_3
cat > Makefile <<'MAKEFILE'
LATEXMK ?= latexmk
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
SOURCES := Makefile main.tex sections/intro.tex latexmkrc
.PHONY: all package
all: build/main.pdf
build/main.pdf: main.tex sections/intro.tex latexmkrc | build
@$(LATEXMK) -norc -r latexmkrc -pdf -outdir=build main.tex > build/latexmk.log 2>&1 || \
{ cat build/latexmk.log >&2; exit 1; }
build dist:
@mkdir -p $@
package: dist/paper-source.tar.gz
dist/paper-source.tar.gz: $(SOURCES) build/main.pdf | dist
@tar -czf $@ $(SOURCES)
MAKEFILE
m -s package
tar -tzf dist/paper-source.tar.gz
mkdir unpack
tar -xzf dist/paper-source.tar.gz -C unpack
test ! -e unpack/build
m -s -C unpack all
pdftotext unpack/build/main.pdf text.txt
grep -q 'A document rebuilt from its source package.' text.txt
printf "unpacked sources build a verified PDF\n"
Download: MAK-14-012.mk · MAK-14-012.sh
Example and command index