why `make` implicit rule doesn't work "across folders"? - makefile

I have two parallel folder hierarchies, and I'm trying to make *.ttl from *.jsonld
JSON/*.jsonld
AssociationEvent/*.jsonld
Turtle/*.ttl
AssociationEvent/*.ttl
Turtle/Makefile is like this:
JSONLD = $(wildcard ../JSON/*/*.jsonld) $(wildcard ../JSON/*.jsonld)
TTL = $(JSONLD:../JSON/%.jsonld=%.ttl)
all: $(TTL) $(warning JSONLD=$(JSONLD)) $(warning TTL=$(TTL))
%.ttl: ../JSON/%.jsonld
jsonld format -q $^ | cat prefixes.ttl - | riot -syntax ttl -formatted ttl > $#
The $(warning) print what is expected, but then an error:
make
Makefile:4: JSONLD=../JSON/AssociationEvent/AssociationEvent-g.jsonld ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
Makefile:4: TTL=AssociationEvent/AssociationEvent-g.ttl Example_9.8.1-MasterData-complying-with-schema.ttl
make: *** No rule to make target 'AssociationEvent/AssociationEvent-g.ttl', needed by 'all'. Stop.
The ttl in the current folder (eg Example_9.8.1-MasterData-complying-with-schema.ttl) are made as expected, but for the ttl in subfolders (eg AssociationEvent/AssociationEvent-g.ttl), make says it doesn't know how to make them. Printing debug info shows this (amongst many other things):
make -p
Example_9.8.1-MasterData-complying-with-schema.ttl: ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
# Implicit rule search has been done.
# Implicit/static pattern stem: 'Example_9.8.1-MasterData-complying-with-schema'
# Last modified 2021-03-23 15:42:42.2814072
# File has been updated.
# Successfully updated.
# automatic
# # := Example_9.8.1-MasterData-complying-with-schema.ttl
# automatic
# % :=
# automatic
# * := Example_9.8.1-MasterData-complying-with-schema
# automatic
# + := ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
# automatic
# | :=
# automatic
# < := ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
# automatic
# ^ := ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
# automatic
# ? := ../JSON/Example_9.8.1-MasterData-complying-with-schema.jsonld
# variable set hash-table stats:
# Load=8/32=25%, Rehash=0, Collisions=1/13=8%
# recipe to execute (from 'Makefile', line 7):
jsonld format -q $^ | cat prefixes.ttl - | riot -syntax ttl -formatted ttl > $#
# Not a target:
AssociationEvent/AssociationEvent-g.ttl:
# Implicit rule search has been done.
# File does not exist.
# File has not been updated.
So it seems to me the implicit rule doesn't fire for filenames across folders. But the wildcard % in the implicit rule should match / like any other char??
%.ttl: ../JSON/%.jsonld
#MadScientist Sorry, your suggestion doesn't work:
JSONLD = $(wildcard ../JSON/*.jsonld) $(wildcard ../JSON/*/*.jsonld)
TTL = $(JSONLD:../JSON/%.jsonld=./%.ttl)
all: $(TTL)
$(warning $(TTL))./%.ttl: ../JSON/%.jsonld
jsonld format -q $^ | cat prefixes.ttl - | riot -syntax ttl -formatted ttl > $#
produces
./AssociationEvent/AssociationEvent-a.ttl ./AssociationEvent/AssociationEvent-c.ttl ...
make: *** No rule to make target 'AssociationEvent/AssociationEvent-a.ttl', needed by 'all'. Stop.

From the manual
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.
In this case, Make wants to build AssociationEvent/AssociationEvent-g.ttl, it sees the target pattern %.ttl, matches that rule with stem AssociationEvent/AssociationEvent-g, but then looks at the prerequisite pattern ../JSON/%.jsonld and (here's where things go wrong) derives the prerequisite AssociationEvent/../JSON/AssociationEvent-g.jsonld. And there's no such file, so Make rejects this rule.
So much for the explanation. The least hideous solution I can think of at the moment involves dropping pattern rules in favor of generated rules:
define template
$(1).ttl: ../JSON/$(1).jsonld
jsonld format -q $$^ ... > $$#
endef
$(foreach X,$(patsubst ../JSON/%.jsonld,%,$(JSONLD)),$(eval $(call template,$(X))))

Related

Make: wildcard to match full path?

Using make for generic purposes (not compiling)
Suppose I have a set of files with full path names, and I would like to do something with that file.
something --file a/b/c/X > $< # (for example)
And I have made a rule:
something-%:
something --file $*
Which matches fine against, say, "something-foo" but does not catch "something-a/b/c/foo". Is there a way to write a wildcard rule for this latter case?
The manual describes 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.
In your case when calling as make something-a/b/c/foo, something-a/b/c/ is treated as directory and removed, so the rest does not match your rule. This can be easily checked:
$ cat Makefile
something-%:
echo something --file $<
f%o:
echo $*
Output:
$ make something-OtherDirectory/src/foo -dr
GNU Make 4.2.1
...
Considering target file 'something-OtherDirectory/src/foo'.
File 'something-OtherDirectory/src/foo' does not exist.
Looking for an implicit rule for 'something-OtherDirectory/src/foo'.
Trying pattern rule with stem 'o'.
Found an implicit rule for 'something-OtherDirectory/src/foo'.
Finished prerequisites of target file 'something-OtherDirectory/src/foo'.
Must remake target 'something-OtherDirectory/src/foo'.
echo something-OtherDirectory/src/o
...
Note that it matched the other pattern rule with the stem of o.
You can make it work your way if your pattern does include a slash. For sake of completeness I would also define a prerequisite if your rule is based on a file and declare target as phony if it does not generate a real output file:
$ cat Makefile
.PHONY: something/%
something/%: %
echo something --file $<
Output:
$ make something/OtherDirectory/src/foo.c
echo something --file OtherDirectory/src/foo.c
something --file OtherDirectory/src/foo.c

Why does my Makefile pattern rule run its recipe multiple times?

As per the gnu make documentation, a pattern rule's "...recipe is executed only once to make all the targets." However, I have the following Makefile
.PHONY: entrypoint
entrypoint: test_1.cpp test_2.cpp
test_%.cpp:
echo $#
And running make produces:
echo test_1.cpp
test_1.cpp
echo test_2.cpp
test_2.cpp
I'm new to make, and I'm probably misunderstanding something, but the documentation seems misleading if clear.
$ make -v
GNU Make 4.0
...
You're misreading the documentation. It means, the recipe is run only one time assuming that all the target patterns in that rule will be created.
Since you have only one target pattern in your rule (test_%.cpp`) make knows that each time it runs that recipe it will create one output file matching that pattern. To create different targets that match that pattern it will run multiple instances of the recipe.
If you had a rule like this:
%.x %.y %.z :
dothings
then make would expect that a single invocation of the recipe dothings would create all the targets matching this pattern (e.g., foo.x, foo.y, and foo.z).
Contrast this with an explicit rule like this:
foo.x foo.y foo.z :
dothings
Make here treats this exactly as if you'd written this:
foo.x :
dothings
foo.y :
dothings
foo.z :
dothings
That is, to build all three of these targets it would run the recipe three times.
There's no way to tell make "please run this recipe one time and it will produce every single target that could possibly match the pattern foo_%.cpp".
The following functions construct a dynamic list of dependencies of your multiple-target where the non-existent files are named last. This is more or less the method named "Another attempt" in the link you gave, except that it doesn't trip over missing files and is able to make a missing file by giving it as target on the command line. What it does not: execute the multitarget recipe if one of the multitargets is out of date relative to the others, but I think this is more of wanted side effect than a problem. The only drawback is the syntactic ugliness as you have to write it into an eval expression which forces you to quote all variables in the recipe which shall be evaluated at execution time.
define newline :=
endef
list2rules = $(firstword $1) $(if $(word 2,$1),: $(word 2,$1)$(newline)$(call list2rules,$(wordlist 2,1000,$1)))
multitarget = $(call list2rules,$(wildcard $1) $(filter-out $(wildcard $1),$1))
.PHONY: all
targets = test1 test2 footest3
#$(info $(call multitarget,$(targets)))
all: somefile
somefile: $(targets)
touch somefile
# here we generate the dependency list on the spot. Only one recipe to update all targets.
$(eval $(call multitarget,\
$(targets)) : ; \
touch $(targets) \
)

Makefile secondary expansion: execution of escaped function?

Consider the following makefile:
.SECONDEXPANSION:
%.result: jobs/% $$(call get-job,$$<)
echo $^
define get-job
$(shell head -n 1 $(1))
$(shell tail -n +2 $(1))
endef
The idea is that each file under jobs/ contains a list of filenames, which should be appended to the prerequisite list.
However, if I want to create xyz.result from an existing file jobs/xyz, I get the following error message:
$ make -n xyz.result
head: cannot open 'xyz.result' for reading: No such file or directory
tail: cannot open 'xyz.result' for reading: No such file or directory
head: cannot open 'xyz.result' for reading: No such file or directory
tail: cannot open 'xyz.result' for reading: No such file or directory
make: *** No rule to make target 'xyz.result'. Stop.
I am aware that $$< isn't set to what I want, as it reflects the prerequisite list of any previous rule.
What I don't understand is the following:
In my understanding, $$< should evaluate to the empty string (as shown in the example in the official doc, under the second sub-heading). However, it seems to be expanded to the value of the target here (xyz.result). Why is that?
It seems that the get-job function is called twice (head and tail both bark twice). I understand that the prerequisite list is expanded twice. But in the first run, the call is still escaped, so this isn't what I expect.
(Maybe the whole approach is flawed, and I shouldn't be (ab)using Makefile for this kind of task in the first place.)
I don't know why $$< expands that way.
You can make your configuration work by using $$* instead, though:
%.result: jobs/% $$(strip $$(call get-job,jobs/$$*))
echo $^
You need to add the strip call so that the newlines embedded in the results of the call will be turned into spaces.

Makefile extract part of filepath as compiler flag

as i am currently working on my makefile i encountered another problem. I am using this rule as part of my building process which transforms .mid files into .s files.
$(MIDAS): $(BLDPATH)/%.s: %.mid
$(shell mkdir -p $(dir $#))
#test $($< | sed "*")
$(MID2AGB) $(MIDFLAGS) -G$($< | sed ".*mus/vcg([0-9]{3})/.*\.mid") $< $#
All .mid input files follow the same format: .mus/vcg[0-9]{3}/..mid, meaning they are stored in different directories following the naming convention vcgXXX where X can be any digit from 0-9. (Maybe my regex is even wrong for this).
When i am calling $(MID2AGB) i want to use a compiler flag -GXXX. However the XXX of this flag has to match the XXX from the input file path.
My makefile code does not work. Any idea how to fix this problem?
There is a crude but effective way to do this using Make's string manipulation tools:
# suppose the source is .mus/vcg456/Z.mid
$(MIDAS): $(BLDPATH)/%.s: %.mid
#echo $* # this gives .mus/vcg456/Z
#echo $(subst /, ,$*) # this gives .mus vcg456 Z
#echo $(word 2,$(subst /, ,$*) # this gives vcg456
#echo $(subst vcg,,$(word 2,$(subst /, ,$*)) # this gives 456

Makefile: Use target filepath in prereq

I have the following project structure:
+-Makefile
+-src/
+-a/
| +-foo.py
+-b/
| +-foo.py
+-c/
| +-foo.py
Each foo.py file is a different file with exactly the same name (ie they have different inodes but are all literally called 'foo.py' - although of course in reality the name is not foo.py, which is just an example).
I wish to create a GNU Makefile rule which, when run, creates the following structure:
+-Makefile
+-src/
+-a/
| +-foo.py
| +-a.zip
+-b/
| +-foo.py
| +-b.zip
+-c/
| +-foo.py
| +-c.zip
This is the closest I have been able to figure out, although of course it fails due to using a target variable in the prereqs which doesn't seem to be allowed:
SRCDIR = src/
PRJ_DIRS = a b c
SRC_FILE = foo.py
# This next rather nasty line turns PRJ_DIRS in to, e.g., src/a/a.zip etc.
ZIP_FILES = $(addprefix $(SRCDIR),$(join $(PRJ_DIRS),$(PRJ_DIRS:%=/%.zip)))'
build: $(ZIP_FILES)
# Next line crashes because we can't use $# in the prereq
$(ZIP_FILES): $(addprefix $(dir $#), $(SRC_FILE))
touch $#
So one way to put the question:
How can I write a rule the uses each corresponding foo.py file as a prereq to build the appropriate .zip file?
Or, a more direct question that might actually be looking at this from the wrong angle:
How can I refer to the specific target that's being built in the prereqs?
Okay, since you consider secondary expansion a kludge, it's $(foreach ...) and $(eval ...) to the rescue again (a very powerful combination).
Replace your rule with the following and you should get what you want.
define rule
$(SRCDIR)$(1)/$(1).zip: $(SRCDIR)$(1)/$(SRC_FILE)
touch $$#
endef
$(foreach dir, $(PRJ_DIRS), $(eval $(call rule,$(dir))))

Resources