Why does make behave strangely when rule has multiple targets with the % character?
According to GNU Make Manual
A rule with multiple targets is equivalent to writing many rules, each with one target, and all identical aside from that. The same recipe applies to all the targets, but its effect may vary because you can substitute the actual target name into the recipe using ‘$@’. The rule contributes the same prerequisites to all the targets also.
First Makefile:
%.in %.out:
echo BLANK > $@
Corresponding bash session:
$ ls
Makefile
$ make a.in a.out
echo BLANK > a.in
make: Nothing to be done for 'a.out'.
$ ls
Makefile a.in
$ make a.out
echo BLANK > a.out
$ ls
Makefile a.in a.out
$ make b.in c.out
echo BLANK > b.in
echo BLANK > c.out
$ make d.in d.out
echo BLANK > d.in
make: Nothing to be done for 'd.out'.
$ make e.out e.in
echo BLANK > e.out
make: Nothing to be done for 'e.in'.
$ ls
Makefile a.in a.out b.in c.out d.in e.out
Second Makefile:
%.in:
echo BLANK > $@
%.out:
echo BLANK > $@
Corresponding bash session:
$ ls
Makefile
$ make a.in a.out
echo BLANK > a.in
echo BLANK > a.out
$ ls
Makefile a.in a.out
$ # nice
So, the question:
Why doesn’t the first Makefile create targets like <name>.in <same name>.out
simultaneously? Why isn’t it interpreted similar to the second Makefile?
You aren’t supposed to have multiple implicit targets in a single rule. I don’t remember if this is stated anywhere, but rules like that are certainly bad practice. Here’s what’s going on:
$ cat Makefile
%.in %.out :
echo BLANK > $@
$ make -d a.in a.out
[... irrelevant output skipped ...]
Updating goal targets....
Considering target file 'a.in'.
File 'a.in' does not exist.
Looking for an implicit rule for 'a.in'.
Trying pattern rule with stem 'a'.
Found an implicit rule for 'a.in'.
Finished prerequisites of target file 'a.in'.
Must remake target 'a.in'.
echo BLANK > a.in
Successfully remade target file 'a.in'.
Considering target file 'a.out'.
File 'a.out' was considered already.
make: Nothing to be done for 'a.out'.
Note the lines:
Considering target file 'a.out'.
File 'a.out' was considered already.
Implicit targets don’t “exist” until they get to be tried, and thus the rule doesn’t get “split” into multiple rules. When a.in
is tried and matched successfully all other targets are marked as tried. The vast majority of time this is not what you want. Just write two rules.
Your rules tell make
that a single invocation of the recipe will create both the .in
and .out
targets.
https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html explains this. It says (in the penultimate paragraph):
"Pattern rules may have more than one target; however, every target must contain a % character. Multiple target patterns in pattern rules are always treated as grouped targets (see Multiple Targets in a Rule) regardless of whether they use the : or &: separator. "
If you then follow the link to Multiple Targets it explains that grouped targets (normally using the &:
separator when you have explicit rules) tell make
that a single invocation of the recipe will create all of them, not just one at a time.
So your pattern rule is the equivalent of this:
a.in a.out &:
echo BLANK > $@
… and not the equivalent of this as you intended:
a.in a.out :
echo BLANK > $@
As far as I know, there’s no way to make a pattern rule which works like the latter and creates just one at a time. You just have to have separate rules for %.in
and %.out