How can I use a pattern rule to add prerequisites like I can to define variables? - makefile

I have the following Makefile:
all: foo/bar/baz
foo/%:
#echo $(VAR)
cp $#.in $#
# This works
foo/bar/%: VAR := Hello world
# This doesn't
foo/bar/%: foo/bar/%.in
foo/bar/baz.in:
touch $#
When I run it, the output is
Hello world
cp foo/bar/baz.in foo/bar/baz
cp: cannot stat ‘foo/bar/baz.in’: No such file or directory
Makefile:4: recipe for target 'foo/bar/baz' failed
make: *** [foo/bar/baz] Error 1
In other words, the pattern-specific variable rule works, but the equivalent syntax to declare an extra prerequisite doesn't. What should I do instead?
The real use case is for copying headers before a build. I wrote
obj/subdir/%.o: CPPFLAGS += -Igen/include
obj/subdir/%.o: | gen/include
gen/include:
# Copy the headers
but the headers don't get copied.

You cannot do this. Pattern rules must define all prerequisite patterns when the rule is created; they cannot be added later.
Writing a pattern rule with no recipe deletes the pattern rule.

Related

Patterns in makefile targets

Makefile:
%_file:
touch $#
foo_%: %_file
bar: foo_bar
Results:
>make foo_bar
make: *** No rule to make target 'foo_bar'. Stop.
>make bar
make: *** No rule to make target 'foo_bar', needed by 'bar'. Stop.
I have intentionally omitted adding .PHONY rules because I know the prerequisites of phony rules are always interpreted as literals. I do not see any reason that make foo_bar or make bar should not run without error. What am I missing?
You have a dependency between foo_% and %_file, but you have defined any rules on how to build foo_%. So when you run:
make foo_bar
Make figures out it first needs to build bar_file, but can't find any rules for building foo_bar. If you were to write instead:
%_file:
touch $#
foo_%: %_file
cp $< $#
bar: foo_bar
Then running make foo_bar would result in:
touch bar_file
cp bar_file foo_bar
rm bar_file
To clarify #larsks answer, the GNU make manual says:
You can cancel a built-in implicit rule by defining a pattern rule with the same target and prerequisites, but no recipe.
So your pattern rule:
foo_%: %_file
does not create a pattern rule, it deletes a (non-existent anyway) pattern rule.
If you want a pattern rule it must have a recipe.

Why does makefile lazy evaluation find a file in a "parent" recipe but not the current one?

This question is a follow-up to What makefile lazy evaluation rule governs this behavior?. I'm still trying to grok some of the rules of gnu make's lazy evaluation.
I want to have a make variable for the content of a directory after that directory has been updated by a recipe.
This Makefile demonstrates that $(A_FILE) is evaluated to find the created file when it's in the "parent" of the recipe that actually creates the file:
A_FILE = $(wildcard subdir/*)
all: a
#echo $(A_FILE)
a:
#mkdir ./subdir
#touch subdir/b
$ rm -rf ./subdir/ && make
subdir/b
$
But the following Makefile has a seemingly trivial change: $(A_FILE) is referenced in the recipe where its containing directory is updated - but now the variable is empty:
A_FILE = $(wildcard subdir/*)
all: a
#echo $(A_FILE)
a:
#mkdir ./subdir
#touch subdir/b
#sleep 1
#echo $(A_FILE)
$ rm -rf ./subdir/ && make
$
I added the sleep to rule out timing issues of the directory being trawled too quickly after it had been updated.
What gives? Why does $(A_FILE) get evaluated against the updated subdir content if it's referenced in the higher-layer recipe but not in the lower-layer recipe where it's actually updated?
GNU make evaluates all lines in the recipe before it starts running any line in the recipe. So, when it is getting ready to run your recipe for the rule a it first expands all the lines, including the last line with $(A_FILE) in it. At that point no parts of the recipe have been run yet so the result is empty.
Then after all the expansion, the shell is invoked to run the lines in the recipe.

why my makefile works with asterisk as wildcard, but do not with percentage?

In my makefile:
default: *.s
echo $(basename $<)
Does echo the basename, but
default: %.s
echo $(basename $<)
outputs:
make: *** No rule to make target '%.s', needed by 'default'. Stop.
I have smf.s file in directory, where the makefile is. So why do not makefile use it as Prerequisite? (only shell-like * does, but % does not), why?
Your first rule:
default: *.s
echo $(basename $<)
works as you expect because * is a wildcard character in GNU Make.
On the other hand, your second rule:
default: %.s
echo $(basename $<)
has %.s as a prerequisite. Since there is no file named %.s Make needs an additional rule to generate this missing file %.s. That is what the error message is about:
make: *** No rule to make target '%.s', needed by 'default'. Stop.
You may think that % is a wildcard character. Actually, it behaves as such in pattern rules. However, your second rule isn't a pattern rule. The following is an excerpt from the documentation:
A pattern rule looks like an ordinary rule, except that its target contains the character ‘%’ (exactly one of them).
Your second rule's target – i.e., default – does not contain the character %. Therefore, it can't be qualified as a pattern rule, so the % in the prerequisite, %.s, is literally the % character.

Wildcard in implicit rule's prerequisites

BUILT_DIR = /tmp/obj
SRC = /source/dir
/tmp/obj/%/builtin.o : $(SRC)/%/*.c
gcc $^ -o $#
But you know :
In order for the pattern rule to apply, its target pattern must match the file name under consideration and all of its prerequisites (after pattern substitution) must name files that exist or can be made.
If i execute make /tmp/obj/hfa/builtin.o,make will complain :
make: *** No rule to make target/tmp/obj/hfa/builtin.o'. Stop.`
How can i modify the Makefile to satisfy my requirement?
You can use Secondary Expansion
BUILT_DIR = /tmp/obj
SRC = /source/dir
.SECONDEXPANSION:
/tmp/obj/%/builtin.o : $$(wildcard $(SRC)/%/*.c)
gcc $^ -o $#
The error you see indicates that there are no .c files which match the pattern $(SRC)/hfa/*.c since the % translates into hfa. So make cannot use the rule you've defined.
Make then starts to use the implicit rules for building and it would not match either.
Finally make gives up.
I just confirmed that the same Makefile and I get the same error only when there are no *.c files in the $(SRC)/hfa directory.
Otherwise, I see the gcc command getting executed.
And from your question, it is not quite clear what your requirement is.
The rule configuration that worked for me:
bar-%-foo:
#touch $#
.SECONDEXPANSION:
foo-%-bar: bar-$$*-foo
#echo "#: $#"
#echo "<: $<"
#touch $#
foo-bar: foo-biz-bar foo-baz-bar
.PHONY: foo-bar
And demo:
$:make foo-bar
#: foo-biz-bar
<: bar-biz-foo
#: foo-baz-bar
<: bar-baz-foo
rm bar-biz-foo bar-baz-foo
$:ls | grep foo
foo-baz-bar
foo-biz-bar

How to generate Makefile rule

I want to do generate rules in Makefile by this:
# $(call cc-defs, ccfiles)
define cc-defs
$1.files = $(patsubst %.cc,%.proto,$1)
$1: $1.files
endef
$(foreach ccfile,$(ccfiles), $(eval $(call cc-defs, $(ccfile))))
but failed with error message:
Makefile:19: *** commands commence before first target. Stop.
Instead that, I can do this by:
# $(call cc-defs, ccfiles)
define cc-defs
$1.files = $(patsubst %.cc,%.proto,$1)
endef
$(foreach ccfile,$(ccfiles), $(eval $(call cc-defs, $(ccfile))))
$(foreach ccfile,$(ccfiles), $(eval $(ccfile):$($(ccfile).files)))
How to make the 1st method works?
Which version of make are you using? $(eval) only appeared in 3.80 (and it only properly works in 3.81 IMHO).
To debug makefiles you'll often have to revert to printf debugging. To see what's going on, replace eval with warning. This shows what you are giving to make:
$ make --warn
Makefile:6: warning: undefined variable `ccfiles'
make: *** No targets. Stop.
(Aside: --warn-undefined-variables is always useful. Undefined variables are untidy.)
O.K., so we need to define $ccfiles. Now we get the for loop firing:
$ make --warn ccfiles=1.cc
Makefile:6: 1.c.files = 1.cc
1.cc: 1.c.files
make: *** No targets. Stop.
Fine. You have given make no recipes, nor a default target. You also have missed out on some variable expansion, and have an extra space in the $(for) invocation (naughty!). Try this:
$ cat Makefile
# $(call cc-defs,ccfiles)
define cc-defs
$1.files = $(patsubst %.cc,%.proto,$1)
$1: $$($1.files) ; echo '[$$#]'
endef
$(foreach ccfile,$(ccfiles), $(eval $(call cc-defs,$(ccfile))))
$ make ccfiles=1.cc
make: *** No rule to make target `1.proto', needed by `1.cc'. Stop.
Note that if all you want to do is for all files in a variable to depend on (or be made from) .proto files, you don't need $(eval).
A pattern rule will do (and will work in older versions of GNU Make too):
$(ccfiles): %.cc: %.proto
echo '[$#]'
This does have the side effect of complaining when the ccfiles variable contains any entries not named *.cc (although it still executes the rule in that case).
$ make ccfiles=hello.cc
make: *** No rule to make target `hello.proto', needed by `hello.cc'. Stop.
$ touch hello.proto
$ make ccfiles=hello.cc
[hello.cc]
$ make ccfiles=hello.c
Makefile:1: target `hello.c' doesn't match the target pattern
[hello.c]
If the variable can contain many things but you only want to add this processing to .cc files, simply add a filter:
$(filter %.cc,$(ccfiles)): %.cc: %.proto
echo '[$#]'
This then results in:
$ make ccfiles=hello.cc
[hello.cc]
$ make ccfiles=hello.c
make: *** No targets. Stop.

Resources