I need a makefile with a wildcard rule that is only used for targets in the root of my codebase.
Let's say I have the following rules in my Makefile:
%.md:
@echo "Running rule '%.md' for target '$@'."
md/%:
@echo "Running rule 'md/%' for target '$@'."
When I run make ex1.md md/ex2.md unknown/ex3.md
, I get this result:
Running rule '%.md' for target 'ex1.md'.
Running rule '%.md' for target 'md/ex2.md'.
Running rule '%.md' for target 'unknown/ex3.md'.
The expected result is:
Running rule '%.md' for target 'ex1.md'.
Running rule 'md/%' for target 'md/ex2.md'.
make: *** No rule to make target 'unknown/ex3.md'. Stop.
To clarify, %.md
should only run for md
files in the same directory as the Makefile
, and nowhere else.
For completeness, I am aware that the order of the rules in the Makefile matter, and flipping the order of the two rules above would "work" for md/ex2.md
. This is not enough to solve the problem, as I also want make unknown/ex3.md
to fail rather than running %.md
.
Is there a way to do that with GNU Make?
The closest documentation pages I found about it are these:
Updates:
make
was designed to do.$(@D)
(automatic variable)[https://www.gnu.org/software/make/manual/make.html#Automatic-Variables], like this:md/%:
@echo "Running rule 'md/%' for target '$@'."
%.md:
@if [ "$(@D)" != '.' ]; then echo "Rule '%.md' is only meant for files on the current dir."; exit 1; fi
@echo "Running rule '%.md' for target '$@'."
The result is this:
$ make ex1.md md/ex2.md unknown/ex3.md
Running rule '%.md' for target 'ex1.md'.
Running rule 'md/%' for target 'md/ex2.md'.
Rule '%.md' is only meant for files on the current dir.
make: *** [Makefile:127: unknown/ex3.md] Error 1
If recursive make is acceptable you could try:
$ cat Makefile
%.md:
ifeq ($(MODE),)
$(MAKE) MODE=$(@D) $@
else ifeq ($(MODE),.)
echo "Running rule '%.md' for target '$@'."
endif
md/%:
echo "Running rule 'md/%' for target '$@'."
$ make -s ex1.md md/ex2.md unknown/ex3.md
Running rule '%.md' for target 'ex1.md'.
Running rule 'md/%' for target 'md/ex2.md'.
make[1]: *** No rule to make target 'unknown/ex3.md'. Stop.
make: *** [Makefile:3: unknown/ex3.md] Error 2
The idea is to use conditionals to separate the recipes for %.md
files:
%.md
calls make again with the mode set to the directory part (.
or other)..
mode: the recipe for %.md
is that for files in the root directory.%.md
. If another rule applies (e.g. md/%
for md/ex2.md
), it is chosen, else make stops with an error (unknown/ex3.md
).Note that the top make will fail and stop on the first encountered error, so:
make -s unknown/ex3.md ex1.md md/ex2.md
make[1]: *** No rule to make target 'unknown/ex3.md'. Stop.
make: *** [Makefile:3: unknown/ex3.md] Error 2
If you prefer to continue building the other valid targets (and if you are OK with a 0 exit status) just add -
on front of the recipe:
-$(MAKE) MODE=$(@D) $@