How to include path prefix in GNU Make pattern rule - makefile

Consider the following:
%.foo: %.bar
echo $< > $#
Assuming we have one file 1.bar, the command executed is simply echo 1.bar > 1.foo. However, when % contains a path, rather than just a file name, it start becoming finicky. My problem is that I want to prepend another path to %.bar, the pattern becomes completely mangled. I.e., when %.bar is nice/path/1.bar, this becomes impossible:
%.foo: /project/something/%.bar
echo $< > $#
This will run, but it executes echo nice/path//project/something/1.bar > 1.foo in stead of echo /project/something/nice/path1.bar > 1.foo
The reason for this is in how make does its pattern rules. From the docs:
When the target pattern does not contain a slash (and it usually does not), directory names in the file names are removed from the file name before it is compared with the target prefix and suffix. [...] The directories are ignored only for the purpose of finding an implicit rule to use, not in the application of that rule. Thus, ‘e%t’ matches the file name src/eat, with ‘src/a’ as the stem. When prerequisites are turned into file names, the directories from the stem are added at the front, while the rest of the stem is substituted for the ‘%’. The stem ‘src/a’ with a prerequisite pattern ‘c%r’ gives the file name src/car
Is there any way I can turn this off for a specific rule?

You may like to read up on How Patterns Match:
When the target pattern does not contain a slash (and it usually does not), directory names in the file names are removed from the file name before it is compared with the target prefix and suffix. After the comparison of the file name to the target pattern, the directory names, along with the slash that ends them, are added on to the prerequisite file names generated from the pattern rule’s prerequisite patterns and the file name. The directories are ignored only for the purpose of finding an implicit rule to use, not in the application of that rule. Thus, ‘e%t’ matches the file name src/eat, with ‘src/a’ as the stem. When prerequisites are turned into file names, the directories from the stem are added at the front, while the rest of the stem is substituted for the ‘%’. The stem ‘src/a’ with a prerequisite pattern ‘c%r’ gives the file name src/car.
The above explains why nice/path/ is prepended to prerequisite /project/something/1.bar.
One fix would be to use full file names in rules, e.g.:
${CURDIR}/nice/path/%.foo: /project/something/%.bar

Related

Why does make select pattern rule if only a substring matches?

I am writing a makefile that is supposed to create multiple executables from the code for different architectures. The idea is that the suffix of each executable indicates the architecture for which it was built. This is a simplified excerpt:
TARGET_NAME = foo
TARGET_AMD64 = $(TARGET_NAME)_amd64
TARGET_ARMHF = $(TARGET_NAME)_armhf
.PHONY:
all: $(TARGET_AMD64) $(TARGET_ARMHF) ;
$(TARGET_NAME)_%: lib/mylib.a
#echo "$# built."
lib/mylib.a: bin/mylib/src/mac/foo_ct.o
#echo "$# built."
When typing make I receive a circular dependency:
make: Circular bin/mylib/src/mac/foo_ct.o <- lib/mylib.a dependency dropped.
bin/mylib/src/mac/foo_ct.o built.
lib/mylib.a built.
foo_amd64 built.
foo_armhf built.
Apparently, make is looking for a rule that matches bin/mylib/src/mac/foo_ct.o. For reasons beyond my understanding, make takes $(TARGET_NAME)_% (the second rule).
Why is that the case? Only the filename foo_ct.o would match the pattern rule $(TARGET_NAME)_%, but why is the preceding path bin/mylib/src/mac/ ignored?
This is actually the defined behaviour:
When the target pattern does not contain a slash (and it usually does not), directory names in the file names are removed from the file name before it is compared with the target prefix and suffix. After the comparison of the file name to the target pattern, the directory names, along with the slash that ends them, are added on to the prerequisite file names generated from the pattern rule’s prerequisite patterns and the file name. The directories are ignored [...]
You will therefore have to specify your target with its absolute path:
TARGET_NAME = $(CURDIR)/foo
Note however, that $(CURDIR) is a GNU make extension.
Alternatively you could also specify a more specific rule for `bin/mylib/src/mac/foo_ct.o` to break the cycle:
TARGET_NAME = foo
TARGET_AMD64 = $(TARGET_NAME)_amd64
TARGET_ARMHF = $(TARGET_NAME)_armhf
.PHONY:
all: $(TARGET_AMD64) $(TARGET_ARMHF;
$(TARGET_NAME)_%: lib/mylib.a
#echo "$# built."
lib/mylib.a: bin/mylib/src/mac/foo_ct.o
#echo "$# built."
bin/mylib/src/mac/foo_ct.o:
true

What's the meaning of targets in a Makefile that start with a ., and how is it different/similar to suffix rules?

In a Makefile, I saw this:
all: .made
# ... some other code
.made: $(Program) bashbug
#echo "$(Program) last made for a $(Machine) running $(OS)" >.made
Is the .made rule a suffix rule as per: https://www.gnu.org/software/make/manual/make.html#Suffix-Rules?
Also, are all rules with targets that have a . in front suffix rules?
Otherwise, what is the significance of targets that start with .? It seems like there's a significance as per this in https://www.gnu.org/software/make/manual/make.html#How-Make-Works:
By default, make starts with the first target (not targets whose names
start with ‘.’).
But its significance is not mentioned.
Also, if it is a suffix rule, how come .made can be used as a prerequisite in the all rule? (It's not mentioned that the targets of suffix rules can be used as prerequisites in other rules.)
P.S.: This question is related to but different from what is the meaning for . (dot) before the target in makefile. This question asks explicitly for the difference between a target with . and a target of a suffix rule.
Is the .made rule a suffix rule
No, because .made is not a "known suffix".
Also, are all rules with targets that have a . in front suffix rules?
No, only those where the word after the dot is a "known suffix":
Your first two questions are answered by https://www.gnu.org/software/make/manual/make.html#Suffix-Rules:
Suffix rule definitions are recognized by comparing each rule’s target against a defined list of known suffixes. When make sees a rule whose target is a known suffix, this rule is considered a single-suffix rule. When make sees a rule whose target is two known suffixes concatenated, this rule is taken as a double-suffix rule.
In your example .made is an actual filename. Your Makefile has a rule for it that creates/updates the file:
#vvvv
.made: $(Program) bashbug
#echo "$(Program) last made for a $(Machine) running $(OS)" >.made
# ^^^^^
All pretty normal; it would work the same way with any other name.
The only significance of the leading dot is that it makes the file "hidden" by convention, i.e. ls won't show it (without specifying -a), plain * won't match it, etc.

Makefile: reuse value of % of a pattern rule inside the recipe

In a Makefile I have:
images/schematic.pdf: images/schematic.svg
inkscape -D -z --file=$^ --export-pdf=$# --export-latex
sed -i "s:schematic:images/schematic:g" $#_tex
What this rule does is:
Use inkscape to convert to a latex-ready .pdf PDF file + its corresponding .pdf_tex text file (see this answer: https://tex.stackexchange.com/a/2107/104581)
Modify the mentioned .pdf_tex file so that it will not break latex compiling from a "higher" directory (namely . when the .pdf_tex file is in ./images)
My problem:
I have many rules in this form i. e. where only schematic is changed. I would like to use a pattern-rule that replaces schematic by %. And to use % in the recipe (in the sed command).
However the rule:
images/%.pdf: images/%.svg
inkscape -D -z --file=$^ --export-pdf=$# --export-latex
sed -i "s:%:images/%:g" $#_tex
does not works: % is interpreted literally in the recipe.
I also tried to replace % in the recipe by $% but this variable seems to be empty.
Unsatisfactory solution:
Add a line in the recipe to create a (make) variable that will hold the result of notdir(removeprefix($<)) (using this question or a call to bash because there is no removeprefix in GNU Make).
You want $*.
From https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html :
$*:
The stem with which an implicit rule matches (see How Patterns Match).
If the target is dir/a.foo.b and the target pattern is a.%.b
then the stem is dir/foo. The stem is useful for constructing names of
related files.
In a static pattern rule, the stem is part of the file name that matched the ‘%’ in the target pattern.
In an explicit rule, there is no stem; so ‘$*’ cannot be determined in that way. Instead, if the target name ends with a
recognized suffix (see Old-Fashioned Suffix Rules), ‘$*’ is set to the
target name minus the suffix. For example, if the target name is
‘foo.c’, then ‘$*’ is set to ‘foo’, since ‘.c’ is a suffix. GNU make
does this bizarre thing only for compatibility with other
implementations of make. You should generally avoid using ‘$*’ except
in implicit rules or static pattern rules.
If the target name in an explicit rule does not end with a recognized suffix, ‘$*’ is set to the empty string for that rule.
$% is an automatic variable, but it's for archive (.a library) members, and almost never useful.

GNU make - how to set an implicit pattern as a prerequisite

I have this implicit rule:
%.so: %.so.5
qnx_ln $< $#
I realized that for another target, I have to make all .so files the prerequisite for that target.
I tried this:
makegen: $(TEAM_ROOT)HMI_FORGF/src/src.pro module_dirs %.so
...
But I got the output
*** No rule to make target '%.so', needed by 'makegen'. Stop.
% prerequisite patterns can only be used in static and implicit pattern rules, where they match the respective % part of the target; when used in a regular rule % is a literal character.
You'll need to specify the dependencies literally, unless there is some correspondence between certain source filenames and the .so filenames that you can leverage, presumably you're already doing either of these to link the .so files in the first place.
As pointed out previously, no you can't do that because this is not how prerequisite patterns work. Maybe you gave the following a thought and rejected it but I suspect you might find the following a close-enough fit:
%.so.target: %.so.5
echo $< >> $(BUILD)/so.targets
SO_TARGETS=$(basename $(shell cat $(BUILD)/so.targets))
makegen: $(TEAM_ROOT)HMI_FORGF/src/src.pro module_dirs $(SO_TARGETS)
Maybe you are looking for a rule to match on every existing *.so file?
makegen: $(TEAM_ROOT)HMI_FORGF/src/src.pro module_dirs $(wildcard *.so)
...
However, if there are patterns which could generate *.so files which have not yet generated those files, they will (obviously) not be matched by the wildcard, which simply examines existing files. If that's what you actually want to accomplish, you'll probably want to enumerate the actual files, one way or another.

Fake dynamic files in Makefile

I want to run pocketlint on all **/*.js files.
.PHONY: lint_js2
LINT_JS = $(wildcard static/js/*.js static/js/**/*.js)
LINT_JS_TARGETS = $(addprefix lint__,$(LINT_JS))
#.PHONY: $(LINT_JS_TARGETS)
lint_js2: $(LINT_JS_TARGETS)
echo $<
lint__%: %
pocketlint $<
However, I get this error:
make: *** No rule to make target `lint__static/js/ad_list.js', needed by `lint_js2'. Stop.
Why lint__static/js/ad_list.js is not captured by lint__%?
If I uncomment second .PHONY, it echoes lint__static/js/ad_list.js, but does not invoke pocketlint static/js/ad_list.js. Why?
If my approach is wrong, what would be the right approach? Since tasks are independent, I would appreciate if make -j50 would do what I am expecting.
Thanks!
It's not clear what is intended: does pocketlint write a file named lint__static/js/ad_list.js, or is that really a phony filename? Anyway...
Reread the second paragraph of How Patterns Match:
When the target pattern does not contain a slash (and it usually does not), directory names in the file names are removed from the file name before it is compared with the target prefix and suffix. After the comparison of the file name to the target pattern, the directory names, along with the slash that ends them, are added on to the prerequisite file names generated from the pattern rule's prerequisite patterns and the file name.
In short, % generally matches just a filename, not a pathname with slashes in it. So lint__static/js/ad_list.js is not captured because actually it is only ad_list.js that is being matched against lint__%.
If you can arrange it so that the output files from pocketlint are static/js/lint__ad_list.js etc, then this could be made to work:
LINT_JS_TARGETS = $(foreach f,$(LINT_JS),$(dir $f)lint__$(notdir $f))
lint__%: %
pocketlint $<
Alternatively you can make % match pathnames by having the target pattern be a pathname (containing a slash):
LINT_JS_TARGETS = $(addprefix linted/,$(LINT_JS))
linted/%: %
pocketlint $<
This time % = static/js/ad_list.js does match the pattern rule.
In either case, you're going to have to have pocketlint produce output (if indeed it produces output) named differently than lint__static/*.
Implicit rule search is suppressed for phony targets (see Phony Targets, paragraph 5). So the rule involving pocketlint is never considered when lint__static/js/ad_list.js is phony.
It's not obvious why the result is Nothing to be done for (phony) lint__static/js/ad_list.js rather than No rule to make target lint__static/js/ad_list.js, but I wouldn't lose sleep over it!

Resources