diff --git a/content/blog/2024/make-pattern-rules/Makefile b/content/blog/2024/make-pattern-rules/Makefile new file mode 100644 index 0000000..ba4db9d --- /dev/null +++ b/content/blog/2024/make-pattern-rules/Makefile @@ -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) diff --git a/content/blog/2024/make-pattern-rules/index.md b/content/blog/2024/make-pattern-rules/index.md new file mode 100644 index 0000000..f41465b --- /dev/null +++ b/content/blog/2024/make-pattern-rules/index.md @@ -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