blog: Make Pattern Rules
This commit is contained in:
parent
f6955df4f9
commit
cc98666b2d
2 changed files with 194 additions and 0 deletions
67
content/blog/2024/make-pattern-rules/Makefile
Normal file
67
content/blog/2024/make-pattern-rules/Makefile
Normal file
|
@ -0,0 +1,67 @@
|
|||
# A Makefile that explores pattern and grouped rules. This Makefile expects the
|
||||
# GNU version of Make.
|
||||
|
||||
# Relevant docs:
|
||||
# https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
|
||||
# https://www.gnu.org/software/make/manual/html_node/Pattern-Match.html
|
||||
# https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html
|
||||
# https://www.gnu.org/software/make/manual/html_node/Multiple-Targets.html
|
||||
|
||||
|
||||
NAMES=zip zap
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(NAMES:%=%.x) $(NAMES:%=%.y)
|
||||
|
||||
|
||||
# Here's a simple pattern rule that generates a .txt file from a name. In this
|
||||
# rule % represents the name (the 'stem' in Makefile lingo) in the targets and
|
||||
# prerequisites, and $* represents the stem in the recipe.
|
||||
|
||||
%.txt:
|
||||
@echo "=> Making $*.txt"
|
||||
python -c 'print("$*!".title())' > "$@"
|
||||
|
||||
|
||||
# Here are a couple explicit rules that generate a single file given some input
|
||||
# files. Note the input files (prerequisites) will be generated with the pattern
|
||||
# rule above.
|
||||
|
||||
zip.a : florp.txt
|
||||
@echo "=> Making $@ with $<"
|
||||
cat "$<" > "$@"
|
||||
|
||||
zap.a : bloop.txt
|
||||
@echo "=> Making $@ with $<"
|
||||
cat "$<" > "$@"
|
||||
|
||||
|
||||
# This is a Grouped rule, marked with &:. It indicates that the two target files
|
||||
# are produced together in one invocation of the rule, rather than two separate
|
||||
# files produced by the same rule.
|
||||
|
||||
zip.x zip.y &: zip.a
|
||||
@echo "=> Making XY files from $< with explicit rule"
|
||||
echo "[X]" > zip.x
|
||||
cat "$<" >> zip.x
|
||||
echo "[Y]" > zip.y
|
||||
cat "$<" >> zip.y
|
||||
|
||||
|
||||
# Pattern rules with multiple targets are always Grouped, so you don't need the
|
||||
# &: separator.
|
||||
|
||||
%.x %.y : %.a
|
||||
@echo "=> Making XY files from $< with pattern rule"
|
||||
echo "[X]" > $*.x
|
||||
cat "$<" >> $*.x
|
||||
echo "[Y]" > $*.y
|
||||
cat "$<" >> $*.y
|
||||
|
||||
|
||||
clean:
|
||||
rm -f bloop.txt florp.txt
|
||||
rm -f $(NAMES:%=%.a)
|
||||
rm -f $(NAMES:%=%.x)
|
||||
rm -f $(NAMES:%=%.y)
|
127
content/blog/2024/make-pattern-rules/index.md
Normal file
127
content/blog/2024/make-pattern-rules/index.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
title: "Makefile Pattern Rules"
|
||||
date: 2024-12-04T14:26:57-08:00
|
||||
publishDate: 2024-12-05
|
||||
resources:
|
||||
- name: makefile
|
||||
src: Makefile
|
||||
tags:
|
||||
- TIL
|
||||
- Make
|
||||
- Software
|
||||
---
|
||||
|
||||
I recently found myself hacking on a Makefile (the GNU kind, not the [BSD
|
||||
kind][bsdmake]) that made heavy use of pattern rules and grouped rules. These
|
||||
are concepts I haven't spent a lot of time with so I wrote myself a small test
|
||||
Makefile to explore them in a little more detail.
|
||||
|
||||
|
||||
## Pattern Rules
|
||||
|
||||
[Pattern rules][patterns] drive most of the usage of `make` as a tool for
|
||||
building software. `make` includes a bunch of pattern rules for many of the
|
||||
kinds of source files you're likely to encounter. For example, it has implicit
|
||||
rules for building `.o` files out of `.c` files.
|
||||
|
||||
Here's a simple pattern rule that generates text files:
|
||||
|
||||
```make
|
||||
%.txt:
|
||||
@echo "=> Making $*.txt"
|
||||
python -c 'print("$*!".title())' > $@
|
||||
```
|
||||
|
||||
The `%` is tells `make` that this is a patten rule. It's a placeholder for a
|
||||
string of non-whitespace characters, which `make` calls a _stem_. You can
|
||||
reference the stem in the body of the rule with the [automatic variable][auto]
|
||||
`$*`.
|
||||
|
||||
This particular rule creates a text file with a particular stem by echoing a
|
||||
string to a file with this little Python snippet.
|
||||
|
||||
Here's a slightly more complex pattern rule:
|
||||
|
||||
```make
|
||||
%.x : %.a
|
||||
@echo "=> Making X file from $< with pattern rule"
|
||||
echo "[X]" > $*.x
|
||||
cat "$<" >> $*.x
|
||||
```
|
||||
|
||||
The major difference with this rule is that it has a prerequisite (the part
|
||||
after the colon) that also has a `%`. So, this rule defines how to build a `.x`
|
||||
file from a `.a` file with the same stem.
|
||||
|
||||
Later on, if I write a rule like this:
|
||||
|
||||
```make
|
||||
zip.a : florp.txt
|
||||
@echo "=> Making $@ with $<"
|
||||
cat $< > $@
|
||||
```
|
||||
|
||||
`make` will understand that it first needs to generate `florp.txt` with the
|
||||
pattern rule for `%.txt`. Then it can execute this rule to build `zip.a`.
|
||||
|
||||
|
||||
## Grouped Rules
|
||||
|
||||
Grouped rules are another feature of `make` that lets you specify [more than one
|
||||
output][multiples] for a given rule. If you're writing C family languages, this
|
||||
is useful for generating a header and source file pair, and making sure that
|
||||
they get updated together.
|
||||
|
||||
```make
|
||||
zip.x zip.y &: zip.a
|
||||
@echo "=> Making XY files from $< with explicit rule"
|
||||
echo "[X]" > zip.x
|
||||
cat "$<" >> zip.x
|
||||
echo "[Y]" > zip.y
|
||||
cat "$<" >> zip.y
|
||||
```
|
||||
|
||||
Generally when you write a rule with more than one output, `make` understands
|
||||
that each of the ouput files is built separately with the same rule. However, a
|
||||
rule with a `&:` separator indicates that the outputs are built from a single
|
||||
invocation of the rule. `make` will rebuild the rule if any of the outputs is
|
||||
out-of-date.
|
||||
|
||||
|
||||
## Altogether Now
|
||||
|
||||
You can combine pattern rules and group rules into a single rule too. This rule
|
||||
creates a pattern for building a pair of `.x` and `.y` files from a `.a` file
|
||||
with a particular stem.
|
||||
|
||||
```make
|
||||
%.x %.y : %.a
|
||||
@echo "=> Making XY files from $< with pattern rule"
|
||||
echo "[X]" > $*.x
|
||||
cat "$<" >> $*.x
|
||||
echo "[Y]" > $*.y
|
||||
cat "$<" >> $*.y
|
||||
```
|
||||
|
||||
These rules are always treated as a group. It doesn't matter if you use `:` or
|
||||
`&:`.
|
||||
|
||||
|
||||
## Rule Precedence
|
||||
|
||||
The way `make` decides which rule to use to produce a file are a little subtle,
|
||||
especially when you combine pattern rules and explicit rules. In general an
|
||||
explicit rule should take precedence over a pattern. In my Makefile, the rule
|
||||
that explicitly builds `zip.x` and `zip.y` will win over the pattern for `%.x`
|
||||
and `%.y`. When multiple pattern rules match a target, the rules are [more
|
||||
complex][matching].
|
||||
|
||||
---
|
||||
|
||||
Download the full [Makefile]({{< page/resource-ref makefile >}}).
|
||||
|
||||
[bsdmake]: {{< ref "blog/2024/bsd-make" >}}
|
||||
[auto]: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
|
||||
[patterns]: https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html
|
||||
[multiples]: https://www.gnu.org/software/make/manual/html_node/Multiple-Targets.html
|
||||
[matching]: https://www.gnu.org/software/make/manual/html_node/Pattern-Match.html
|
Loading…
Add table
Add a link
Reference in a new issue