Can I persuade Make to give me a better error message when prerequisites are given through a variable with secondary expansion? - gnu-make

Observe:
OBJECT_FILES=
.PHONY: all
all: project.exe
.SECONDEXPANSION:
%.exe:: $$(OBJECT_FILES);
project.exe: OBJECT_FILES += module.o
Assume module.o doesn't exist on disk (because I forgot to create it, or whatever).
Debate about why I don't just use project.exe: module.o aside (this is an MCVE! I do more things with $OBJECT_FILES elsewhere, in reality) how can I get a better diagnostic?
$ make all
make: *** No rule to make target `project.exe', needed by `all'. Stop.
I'd prefer:
$ make all
make: *** No rule to make target `module.o', needed by `project.exe'. Stop.
Can Make be co-erced to do this without fundamentally changing my design?
Or, at least, why is it doing this?
$ make -v | head -n 2
GNU Make 3.82
Built for x86_64-redhat-linux-gnu

The message make would have to display could become rather complicated. The reason for that is because of the implicit pattern rule, make is actually trying one after the other every rule whose target match project.exe and there could be an indefinite number. Consider for example the following:
OBJECT_FILES=
.PHONY: all
all: project.exe
.SECONDEXPANSION:
%.exe:: $$(OBJECT_FILES);
%.exe:: does-not-exist-on-unix;
%.exe:: does-not-exist-on-amiga;
%.exe:: does-not-exist-except-perhaps-by-redmond;
project.exe: OBJECT_FILES += does-not-exist
Using make with the -dr options, this is what we get:
Considering target file 'project.exe'.
File 'project.exe' does not exist.
Looking for an implicit rule for 'project.exe'.
Trying pattern rule with stem 'project'.
Trying implicit prerequisite 'does-not-exist'.
Trying pattern rule with stem 'project'.
Trying rule prerequisite 'does-not-exist-on-unix'.
Trying pattern rule with stem 'project'.
Trying rule prerequisite 'does-not-exist-on-amiga'.
Trying pattern rule with stem 'project'.
Trying rule prerequisite 'does-not-exist-except-perhaps-by-redmond'.
No implicit rule found for 'project.exe'.
The line Trying implicit prerequisite 'does-not-exist' is the "best" way make tells you the prerequisite of the first pattern rule (the one with the secondary expansion of OBJECT_FILES) was attempted.
Also, if any of rules has its prerequisites found, then you won't get any error message: make simply allows implicit pattern rules to fail "silently", and it's actually happening a lot if you don't disable built-in rules. For example, built-in rules can produce an object file (.o target) out of several types of source files, like .cpp (C++), .c (C), .p (Pascal), .f (Fortran)... So depending on which source file is actually there, the right rule will be picked up, and the other rules will be silently "dropped". If you've got both a .c and a .cpp file, I don't know what happens!
If you can rewrite your makefile so that you don't have a pattern rule anymore, e.g. like this:
OBJECT_FILES=
EXE_FILES := project.exe project2.exe
.PHONY: all
all: $(EXE_FILES)
.SECONDEXPANSION:
$(EXE_FILES):: $$(OBJECT_FILES);
project.exe: OBJECT_FILES += does-not-exist
project2.exe: OBJECT_FILES += does-not-exist-either
Then make will output the messages you want (albeit not in the expected order):
$ make -k all
make: *** No rule to make target 'does-not-exist-either', needed by 'project2.exe'.
make: Target 'project2.exe' not remade because of errors.
make: *** No rule to make target 'does-not-exist', needed by 'project.exe'.
make: Target 'all' not remade because of errors.

Related

Why can't gnu make figure out this sequence of rules?

I have this Makefile:
%: %.x
cp $< $#
build/%: src/%
cp $< $#
And a directory structure that looks like this:
Makefile
build/
src/
hello.x
Why does make behave as follows:
$ make build/hello
make: *** No rule to make target 'build/hello'. Stop.
Why can't it see that
it can translate src/hello.x into src/hello using the first rule, and
copy src/hello into build/hello using the second rule?
According to GNU make manual, that is:
If you do not mark the match-anything rule as terminal, then it is non-terminal. A non-terminal match-anything rule cannot apply to a file name that indicates a specific type of data. A file name indicates a specific type of data if some non-match-anything implicit rule target matches it.
Your first rule is a non-terminal match-anything rule, so it cannot apply to the target src/hello which indicates a specific type of data. Debug log with make build/hello -d also shows the process:
......
Considering target file `build/hello'.
Looking for an implicit rule for `build/hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `build/hello,v'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `build/RCS/hello,v'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `build/RCS/hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `build/s.hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `build/SCCS/s.hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/hello'.
Looking for a rule with intermediate file `src/hello'.
Avoiding implicit rule recursion.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/hello,v'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/RCS/hello,v'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/RCS/hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/s.hello'.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/SCCS/s.hello'.
No implicit rule found for `build/hello'.
Finished prerequisites of target file `build/hello'.
No need to remake target `build/hello'.
make: Nothing to be done for `build/hello'.
You should mark your first rule the match-anything rule as terminal by defining it with a double colon.
When a rule is terminal, it does not apply unless its prerequisites actually exist. Prerequisites that could be made with other implicit rules are not good enough. In other words, no further chaining is allowed beyond a terminal rule.
Change your makefile to:
%:: %.x
cp $< $#
build/%: src/%
cp $< $#
Test with make build/hello:
cp src/hello.x src/hello
cp src/hello build/hello
rm src/hello
The debug log below shows how it works:
......
Looking for a rule with intermediate file `src/hello'.
Avoiding implicit rule recursion.
Trying pattern rule with stem `hello'.
Trying implicit prerequisite `src/hello.x'.
Found an implicit rule for `build/hello'.
Considering target file `src/hello.x'.
Finished prerequisites of target file `src/hello.x'.
No need to remake target `src/hello.x'.
Considering target file `src/hello'.
File `src/hello' does not exist.
Pruning file `src/hello.x'.
Finished prerequisites of target file `src/hello'.
Must remake target `src/hello'.
cp src/hello.x src/hello
Putting child 0x08a51438 (src/hello) PID 30908 on the chain.
Live child 0x08a51438 (src/hello) PID 30908
Reaping winning child 0x08a51438 PID 30908
Removing child 0x08a51438 PID 30908 from chain.
Successfully remade target file `src/hello'.
Finished prerequisites of target file `build/hello'.
Must remake target `build/hello'.
cp src/hello build/hello
Putting child 0x08a51438 (build/hello) PID 30909 on the chain.
Live child 0x08a51438 (build/hello) PID 30909
Reaping winning child 0x08a51438 PID 30909
Removing child 0x08a51438 PID 30909 from chain.
Successfully remade target file `build/hello'.
Removing intermediate files...
rm src/hello

What are the braces in this makefile rule for?

I am reading a makefile for a Qt-created project that has the following:
{backend}.cpp{release\}.obj::
$(CXX) -c $(CXXFLAGS) $(INCPATH) -Forelease\ #<<
$<
<<
(above code is using \t for recipe and is as written in makefile)
Both the rule and the recipe confuse me.
I'll start with {backend} in the rule. Obviously the same confusion for {release} as well. I assume this is a reference to a particular sub-directory named backend. I guess that ..\backend\release\bar.obj would be found as a legitimate target? But what part of make says this is legitimate syntax and what exactly happens here?
FWIW: This is in a section commented as: ##### implicit rules.
Version: GNU Make 4.2.1 Built for x86_64-unknown-cygwin
Bonus points:
Explain the use of #<< and << in the recipe... (Yes, I'm lacking in bash shell finesse...). Is this referencing the first prerequisite with $< and silently redirecting it? Why isn't it $$<?
Thanks.
That is an NMAKE batch-mode rule
https://learn.microsoft.com/en-us/cpp/build/batch-mode-rules?view=vs-2017
The equivalent GNU Make rule would be something like
backend/%.obj: release/%.cpp:
With the difference that, as the name suggests, these rules will invoke their recipes only once for all valid targets and expect the rule to create all of the targets in a single pass with the $< macro.
The << syntax is NMAKE's inline file feature
https://learn.microsoft.com/en-us/cpp/build/inline-files-in-a-makefile?view=vs-2017
This expands and captures everything between the angle brackets and saves it to a file, in this case a temporary file as no filename is specified after the brackets. The file is then passed to the compiler as a response file on the first line of the recipe through the # option.

How to write a makefile executing make one directory level up

Can I write a wrapper makefile that will cd one level up and execute there make with all the command options I have given the wrapper?
In more detail:
Directory project contains a real Makefile with some different targets.
Directory project/resources contains the wrapper Makefile which should call Makefile in project.
When I am in my shell in directory project/resources, I execute
make TARGET
and the Makefile there just cds one directory up and calls
make TARGET
in the directory project.
Is this possible? And how?
You could use a very simple Makefile for all your sub-directories:
%:
$(MAKE) -C .. $#
% is a last resort match-anything pattern rule that will match any target... for which there is no implicit rule (GNU make has an incredibly large number of implicit rules). So, if none of your targets are covered by an implicit rule, this should work. Else you will have to tell make not to use the implicit rules it knows. This can be done (with GNU make) by calling make with the -r option:
cd project/resources
make -r <anything>
will call make in project for target <anything>. The main drawback is that the -r flag is passed to the sub-make and so the implicit rules will not apply neither in project, which can be a problem. If it is you can obtain the same effect by adding an empty .SUFFIXES target to theMakefile in project/resources:
.SUFFIXES:
%:
$(MAKE) -C .. $#
With my version of GNU make (3.82) it works like a charm and the sub-make has all the default implicit rules.
Yes, you can have a makefile which works for "any" target.
The GNU make manual discusses this in the Overriding Part of Another Makefile section:
Sometimes it is useful to have a makefile that is mostly just like another makefile. You can often use the ‘include’ directive to include one in the other, and add more targets or variable definitions. However, it is invalid for two makefiles to give different recipes for the same target. But there is another way.
In the containing makefile (the one that wants to include the other), you can use a match-anything pattern rule to say that to remake any target that cannot be made from the information in the containing makefile, make should look in another makefile. See Pattern Rules, for more information on pattern rules.
For example, if you have a makefile called Makefile that says how to make the target ‘foo’ (and other targets), you can write a makefile called GNUmakefile that contains:
foo:
frobnicate > foo
%: force
#$(MAKE) -f Makefile $#
force: ;
If you say ‘make foo’, make will find GNUmakefile, read it, and see that to make foo, it needs to run the recipe ‘frobnicate > foo’. If you say ‘make bar’, make will find no way to make bar in GNUmakefile, so it will use the recipe from the pattern rule: ‘make -f Makefile bar’. If Makefile provides a rule for updating bar, make will apply the rule. And likewise for any other target that GNUmakefile does not say how to make.
The way this works is that the pattern rule has a pattern of just ‘%’, so it matches any target whatever. The rule specifies a prerequisite force, to guarantee that the recipe will be run even if the target file already exists. We give the force target an empty recipe to prevent make from searching for an implicit rule to build it—otherwise it would apply the same match-anything rule to force itself and create a prerequisite loop!
One option: use a wrapper file to execute the commands to do that. Just be sure your target make files don't include the child directory that has the wrapper, or else you can create an endless loop. For example,
clean:
pushd .. && make clean && popd
Using the comment of user Renaud Pacalet and the answer to a different question the following one-liner is as close as I could get. The whole Makefile reads:
IGNORE := $(shell $(MAKE) -C .. $(MAKECMDGOALS))
This solutions comes with a few caveats:
Command line option -B does not get passed through to the subsequent make call.
The output of the subsequently called make process (in the project directory) is not printed to stdout.
The wrapper make process reports for any given target at the end :
make: *** No rule to make target TARGET. Stop.

How does GNU make's "file" function work?

I am thinking I may need to use the file function in GNU make, and just can not follow the example they give. I have looked online, but don't see any post with more explanation. Here is the example they give:
program: $(OBJECTS)
$(file >$#.in,$^)
$(CMD) $(CMDFLAGS) #$#.in
#rm $#.in
I think I know what it is doing at a high level as it is explained in the manual.
$#.in
is a list of all the target files
$^
is a list of the source files
I am not sure how #$#.in is used on the third line or what there is an # sign at the beginning. What does that mean please? What does it supposed to do?
The key to the operation of that recipe is given in the prose immediately preceding it in the manual:
Many commands use the convention that an argument prefixed with an # specifies a file containing more arguments. Then you might write your recipe in this way:
program: $(OBJECTS)
$(file >$#.in,$^)
$(CMD) $(CMDFLAGS) #$#.in
#rm $#.in
$# is the target file (there is only one of those in any given recipe)
$#.in is the target file with .in added to the end of the name.
$^ is the "list" of the all the prerequisites of the target.
#$#.in is the name of the target with .in at the end and # at the start.
So the $(file ...) call in that recipe writes the list of prerequisites of the target into a file called program.in in "overwrite" mode and then passes that file name to the $(CMD) command using the #filename convention that was mentioned.

Passing rules to make recursively

It's probably trivial to do this but I can't see how.
I want to have a parent Makefile to decide which Makefile to call recursively based on the value of a variable passed in the command line.
I.e., I want to be able to call my main Makefile with:
make some_rule TARGET=a
or
make some_rule TARGET=b
and have my main Makefile decide based on the value of TARGET which makefile to invoke to run make some_rule. (For example, decide whether to call sub_directory_a/Makefile or sub_directory_b/Makefile to execute rule some_rule.)
Note: I have many different rules, so I do not want my main Makefile to list all the possible rules and for each of them call recursively the correct Makefile. I am hoping my main Makefile can only be a few lines long and not have to be updated whenever I create new rules.
You could do what you describe with
default_target:
%:
$(MAKE) -C some_directory_$(TARGET) $#
The %: rule is a pattern rule in which the pattern matches all rules (called a match-anything rule by the GNU make manual); $# is the current target. Note that the default_target: rule doesn't have a recipe, so calling make without a target will use the recipe of the match-anything rule (the only one that applies and has a recipe) to try to build default_target.
The caveat of this approach is that targets cannot be declared phony. If you want to have phony targets, you'll have to specify the recipe for those targets again, for example
PHONY_TARGETS = all clean distclean
.PHONY: $(PHONY_TARGETS)
$(PHONY_TARGETS):
$(MAKE) -C some_directory_$(TARGET) $#
%:
$(MAKE) -C some_directory_$(TARGET) $#
Unfortunately, I do not know a trick to declare all targets phony, which is what you'd really want to do.
Note that you can use ifeq etc. with the variables you set at the command line if you want to allow more fancy values for TARGET than parts of directory names, such as
%:
ifeq ($(TARGET),gibson)
echo 'Planet $# was successfully hacked.'
else
$(MAKE) -C some_directory_$(TARGET) $#
endif
Also note that a more common way to set common variables for many Makefiles is to put them into a file, often common.mk, and include it from the other Makefiles:
include ../common.mk # to include common.mk from some_directory_a/Makefile
But you'll have to decide yourself which approach is a better fit for your project.

Resources