How to use pattern-dependent variables in dependencies in make pattern rules - makefile

I'd like to define a GNU make pattern rule with the dependencies in a pattern-dependent variable. What I'd like is something like this:
%.exe : $(%_EXE_SOURCES) $(%_EXE_RESOURCES)
$(CSC_V)$(CSC) $(CSCFLAGS) $($*_EXE_CSCFLAGS) -target:exe \
-out:$# $($*_EXE_SOURCES) $($*_EXE_RESOURCES)
And to later define something like
FOO_EXE_SOURCES = src/Foo.cs
all: Foo.exe
The rule presented works to build; in the body of the rule the $($*_EXE_SOURCES) variable is expanded to $(FOO_EXE_SOURCES), which expands to src/Foo.cs. The dependencies don't expand properly, however; changing src/Foo.cs does not cause Foo.exe to be rebuilt.
I suspect that this can't actually be done in make, but perhaps someone has a work-alike make fragment?

You could use "secondary expansion". Something like this should accomplish what you are looking for:
Foo_EXE_SOURCES := foo.cs bar.cs baz.cs
all: Foo.exe
.SECONDEXPANSION:
%.exe: $$($$*_EXE_SOURCES)
$(CSC_V)$(CSC) $(CSCFLAGS) $($*_EXE_CSCFLAGS) -target:exe \
-out:$# $($*_EXE_SOURCES) $($*_EXE_RESOURCES)
Enabling secondary expansion allows the use of automatic variables (i.e. $* in this case) in the prerequesites list, which is something that would otherwise not work.

Related

Matching a dependency using `subst` function in Makefile

I have a rule for the target
data/processed/21.12.2021/experiment6/written_piv21122021.005.exp6.mp4
in my makefile such that it has a dependency
data/raw/21.12.2021/experiment6/piv21122021.005.exp6.mov
Using subst functions, I'm trying to create the dependency by pattern matching as
%/written_*.mp4: \
$(subst processed,raw, $$*)/*.mov \
<do something>
However, the above rule can't find the *.mov dependency. i have tried many versions of $(subst processed,raw, $$*)/*.mov to match the dependency but didn't work.
How to do this? What is the correct syntax?
First, you can't do this:
%/written_*.mp4:
You can't combine a pattern % with a wildcard *. You have to realize that make works in two very discrete steps: first, all the makefiles are parsed and an internal representation of all the targets and prerequisites are constructed into a graph. Then, make walks that graph figuring out what needs to be built and how to build it, and running recipes.
Make variables, functions, and wildcards in targets and prerequisites are expanded when makefiles are parsed (in the first step). Automatic variables like $* are not set until a recipe is invoked (the second step), and patterns like % are not matched/expanded until make tries to decide how to build something (again in the second step).
So, a rule like:
%/written_*.mp4: $(subst processed,raw, $$*)/*.mov
can't work because the wildcard will expand to files matching the literal filename %/written_*.mp4 which clearly has no matches since you won't have a directory named %. In any event you can't use wildcards in targets because when make is parsing the makefile those targets won't exist (since that's what you want make to build) so the wildcards won't match anything. Also, $$* is the literal string $* and there is no processed string in that so the subst function will do nothing. And, even if /*.mov did match something it would put ALL the files matching that wildcard as prerequisites of every target so they'd all get rebuilt whenever any one changed.
And finally, you definitely should not use a backslash after your prerequisites: this just turns your recipe into prerequisites.
Your problem is very difficult to solve because your target and prerequisite differ in multiple distinct places and make doesn't support multiple % matching. You can get most of the way there with this:
data/processed/%.mp4: data/raw/%.mov
<do something>
However this is not quite right because the % in the target is written_... while in the prerequisite it's just ... and this is not possible to represent in make.
If you can rework the filenames so that instead of written_piv21122021.005.exp6.mp4 you can use piv21122021.005.exp6.written.mp4 (or even better you don't need the written_ prefix at all) then you can easily do this. If not you'll need to get very fancy to make this work.
Here is an imperfect kludge.
Delegate the work to another makefile I'll call adjunct.mk. In the main makefile:
data/processed/%.mp4:
#$(MAKE) -f adjunct.mk $#
And in adjunct.mk this ugly transformation:
.SECONDEXPANSION:
$(MAKECMDGOALS): $$(patsubst data/processed/%,data/raw/%,$$(subst written_,,$$(patsubst %.mp4,%.mov,$$#)))
...do whatever with $< and $#...
This incurs the usual cost of recursive Make: it blinds the higher-level Make to the dependency relations being tracked by the lower one. So if your Make must build that mov file, or you try to build the mp4 when the mov does not exist, then this solution will require some more careful pipefitting before it will work correctly.

Makefile: Match-Anything Pattern Rule

I have a main Makefile that calls Makefiles placed in subfolders. For testing purposes I would like to add Match-Anything rule at the end of main Makefile.
This rule would be:
%:
make -e -C subdir $#
Are there any contrarguments for such a rule?
I think you meant "con", as in "pro or con", not "cont". "Con" is short for the Latin "contra".
For your question, the downside of adding a new "match anything" rule is that any file that doesn't exist, will try to be created using this rule. For example suppose you run include foo.mk and foo.mk doesn't exist... make will attempt to build foo.mk by running your rule. Basically it can be confusing. It's possible there could be some performance impact; if you run make -d and examine it you should see if your match-anything rule is being used at all during a normal build. I'm not sure if there are any other serious downsides.
By the way you should always only use the make variable $(MAKE) when invoking a sub-make in a recipe; never use the raw make command.

How can I add a directory to the search path of GNU Make?

I have a makefile that looks something like this:
include anotherFile.mk
all:
someStuff
The file anotherFile.mk is like this:
include yetAnotherFile.mk
export SOME_VAR = 93
The problem is that anotherFile.mk and yetAnotherFile.mk are in a different directory from my Makefile. So my makefile can't just be changed to this:
include $(OTHER_PROJECT_PATH)/anotherFile.mk
all:
someStuff
The problem with this approach is that the include statement in anotherFile.mk will fail because it will be searching in the current directory.
A partial solution that I found is to pass the --include-dir=$OTHER_PROJECT_PATH flag to the invocation of make, but that's a bit user-unfriendly.
So my question is: Is there something I can put inside my makefile that will add to the directories that make searches for when executing an include? Something like MAKE_INCLUDE_DIRS += $(OTHER_PROJECT_PATH)
Surprisingly there doesn't seem to be a good answer to that question. Forcing .INCLUDE_DIR doesn't help and there doesn't seem to be any way around invoking make with --include-dir=$OTHER_PROJECT_PATH.
It is however possible to put the appropriate recursive make invocation inside the makefile but, in order to get it to work for all reasonable cases it quickly becomes too complicated to be worth it. In summary it requires:
a top level condition to check if the OTHER_PROJECT_PATH is in .INCLUDE_DIR
the appropriate target with the recipe invoking make recursively
possibly additional targets if there are multiple command goals
the real make file enclosed in the else part of the conditional
You Makefile would look like this:
OTHER_PROJECT_PATH := other
ifeq (,$(filter $(OTHER_PROJECT_PATH), $(.INCLUDE_DIRS)))
# this is the mechanism to add the include dir in a recursive make
$(or $(firstword $(MAKECMDGOALS)),all):
$(MAKE) -I$(OTHER_PROJECT_PATH) $(MAKECMDGOALS)
# add empty targets for additional goals if needed
ifneq (,$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)))
$(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)):
endif
else
# this is where the real makefile starts
all more:
echo $#: $< $^
include a.mak
endif
It still does not seem possible from a makefile, but if you have a script that sets up environment variables, you can use MAKEFLAGS (e.g. export MAKEFLAGS=I/your/path ordentlich on Linux, or SET on Windows)

Function in prerequisite

A have a make target foo/%.bar. It matches files like:
foo/x/y/z.bar
foo/a.bar
Now, I want a prerequisite prereq.o which must reside in the same folder than the .bar file. Thus, for foo/x/y/z.bar the prerequisite should be foo/x/y/prereq.o, for foo/a.bar it should be foo/prereq.o.
How to achieve this?
I tried using the dir function like this:
foo/%.bar : foo/$(dir %)prereq.o
However, this does not work because functions are evaluated before the patterns are expanded. So how does it work?
You have to use .SECONDEXPANSION:
.SECONDEXPANSION:
foo/%.bar : foo/$$(dir %)prereq.o
#echo $# $<
The $$ in $$(dir...) is necessary so that it is not evaluated until the second expansion occurs.

Variable for '%' in multiple files matching

I would like to make a complete text document from several sources (since one of the file source change, I want the doc to change).
I have to pass it through a translator I develop. I would like to pass the language as argument, to make it cleaner.
Yesterday, late at night, I dreamed of a makefile like this...
#makefile
# ...
my_complete_doc.%.html: my_trans_exe header.%.html $(wildcard source/*.%.html)
$< --language $(variable_for_%) > $#
(?) Does it replace % by all the languages which have their own header.language.html files. And does the file my_completed_doc.language.html get changed as soon as one of the source/*.language.html get changed?
(?) How to get the % replaced in several prerequisites, possibly into the wildcard and necessarily in the recipe?
First, the easy problem: you wish to use the '%' variable in the recipe. The answer is to use the '$*' automatic variable:
my_complete_doc.%.html: my_trans_exe ...
$< --language $* > $#
Then the easy question: yes, the header.%.html prerequisite is correct. When you try to build my_complete_doc.dutch.html then Make will evaluate it as header.dutch.html, when you try to build my_complete_doc.french.html, Make will evaluate it as header.french.html.
Now the tricky problem: the prerequisite $(wildcard source/*.%.html). Ordinarily, Make expands $(wildcard ...) statements before executing any rule, or deciding which targets to build. So it searches for any files such as source/foo.%.html or source/bar.%.html (that is, files whose names contain the character '%'), finds none, and evaluates the statement as an empty string. But Make will defer this evaluation until it has chosen the rule, if you use SECONDEXPANSION:
.SECONDEXPANSION:
my_complete_doc.%.html: my_trans_exe header.%.html $$(wildcard source/*.%.html)
$< --language $* > $#
(Note the '$$'. In the first -- ordinary -- expansion, Make reduces "$$(...)" to "$(...)", and in the second -- when '%' has a value -- it expands "$(...)".) Now if you modify any file such as source/foo.german.html, Make will consider the file my_complete_doc.german.html out of date and in need of rebuilding.

Resources