Makefile default rule pattern - makefile

Just trying to write a conditional Makefile using this skeleton:
TARGET = test
ifeq ($(FOO),y)
$(TARGET):
#echo This is test
$(TARGET)-a:
#echo This is test-a
$(TARGET)-b:
#echo This is test-b
else
$(info FOO is disabled)
endif
When the FOO condition is true, the set of rules based on the TARGET variable (composed of one $(TARGET) and a set of $(TARGET)-substring) work as expected:
$ make test
This is test
$ make test-a
This is test-a
When the FOO condition is false, I want to define a default rule for all my targets, just to report on the screen FOO variable is disabled. I don't know the proper way to do that. Tried some options:
Option1, using the skeleton example, the string "FOO is disabled" is always printed, but it generates an error:
$ make test-a
FOO is disabled
make: *** No rule to make target 'test-a'. Stop.
$ make test
FOO is disabled
make: *** No rule to make target 'test'. Stop.
Option 2, if try to modify the false rule in this way:
else
$(TARGET)-%:
$(info FOO is disabled)
endif
Then all $(TARGET)-substring targets work as expected:
$ make test-a
FOO is disabled
make: 'test-a' is up to date.
$ make test-b
FOO is disabled
make: 'test-b' is up to date.
But this rule fails when making $(TARGET):
$ make test
make: *** No rule to make target 'test'. Stop.
Option 3, if try to remove the hyphen on the false rule defined in option2:
else
$(TARGET)%:
$(info FOO is disabled)
endif
then making $(TARGET) executes a default rule for compiling a test.o object file:
$ make test
FOO is disabled
cc test.o -o test
cc: error: test.o: No such file or directory
cc: fatal error: no input files
compilation terminated.
make: *** [<builtin>: test] Error 1
And I am becoming a little bit crazy trying so satisfy this default rule. Please some help with this would be very useful. Tnks!

There are several ways to solve this, but the simplest is probably just to add another rule:
else
$(TARGET):
#echo FOO is disabled
$(TARGET)%:
#echo FOO is disabled
endif
(I changed $(info ... to #echo ... because the latter will run only when Make execute the rule, while the former will run if the conditional defines those rules, even if the target is something else.)
EDIT: Yes, it's possible to solve this with only one rule, there is more than one way, but no perfect way.
Here is one way:
TARGET = tes
...
else
$(TARGET)%:
#echo FOO is disabled
endif
Note that the last character of test has been removed. The good news is that this rule will apply to test, test-a and test-b; the bad news is that it will also apply to tesw.

As #MadScientist says, .DEFAULT rule fixes the issue when you have a single Makefile. So this would be the final Makefile:
TARGET = test
ifeq ($(FOO),y)
$(TARGET):
#echo This is test
$(TARGET)-a:
#echo This is test-a
$(TARGET)-b:
#echo This is test-b
endif
.DEFAULT:
#echo This is the default rule
Thanks a lot for your help!

Related

How not to get "get make: *** No rule to make target" for goal parameters

I have the following target:
.PHONY: echo
echo:
#echo "parameters:" $(filter-out $#,$(MAKECMDGOALS))
When I execute it, I get not just the target I specified, but also an error when Make tries to run make hello:
$ make echo hello world
parameters: hello world
make: *** No rule to make target `hello'. Stop.
How do I fix or suppress the error in the last line?
The error occurs because Make doesn't know what to do about targets like hello and world. You want it to do nothing, but it doesn't know that.
So add a "match anything" rule that does nothing:
%:
#:

Allow Makefile both append and override target

I have base Makefile for all my services, in some cases I want to use my default "test" target, in other cases I want to override\add to it. These are the files I have so far (and obviously its not working as expect..).
MakefileBase
test:
./.../run-tests.sh
Makefile
BASE_FILE := /path/to/MakefileBase
include ${BASE_FILE}
test:
#$(MAKE) -f $(BASE_FILE) test # un/comment this line in order to run the default tests.
# echo "custom test"
When I run the test with the first line commented out I get the following
Makefile:10: warning: overriding commands for target `test'
/.../MakefileBase:63: warning: ignoring old commands for target `test'
echo "no tests"
no tests
except of the warning it works as expected, the problem is when I try to use the parent function then I get the following errors:
Makefile:9: warning: overriding commands for target `test'
/.../MakefileBase:63: warning: ignoring old commands for target `test'
make[1]: test: No such file or directory
make[1]: *** No rule to make target `test'. Stop.
make: *** [test] Error 2
Actually, both answers so far are wrong or incomplete:
exit 0 in a rule will just exit the current shell (which runs only the exit 0 command, so it is a no-op in this case). So this won't override.
It's not true that you cannot override a command without warning. If it is not necessary that both targets have the same name, you can do:
MakefileBase
.PHONY: test-base
test-base:
echo base
%: %-base # handles cases where you don't want to override
Makefile1
include MakefileBase
.PHONY: test
test:
echo override
Makefile
include MakefileBase
.PHONY: test
test: test-base
echo append
As with double colon rules, the effects of each targets (on each other) have to be considered, especially if you move away from .PHONY (for example, files considered up-to-date because the other rule just updated them).
BTW, I don't see the problem with your approach (aside from the warning). For me it worked fine.
This is what double-colon rules are for:
test::
./.../run-tests.sh
and:
BASE_FILE := /path/to/MakefileBase
include ${BASE_FILE}
test::
#$(MAKE) -f $(BASE_FILE) test
This will "add to" an existing target. There is no way to override a target with a different recipe without incurring a warning.
If you want to do that the only way is to use variables to hold the recipe then override the variable value. For example:
test_recipe = ./.../run-tests.sh
test:
$(test_recipe)
and:
BASE_FILE := /path/to/MakefileBase
include ${BASE_FILE}
test_recipe = #$(MAKE) -f $(BASE_FILE) test
Hacky, but you can get add, and a limited form of override that can never be deeper than one override. Both use double colon rules.
add: use double colons on both rules
override: use double colons on both rules, appending command exit 0 to the last rule
# "addcmd" echoes "BA", "overridecmd" echoes "B"
addcmd ::
echo "A"
addcmd ::
echo "B"
overridecmd ::
echo "A"
overridecmd ::
echo "B"
exit 0

.ONESHELL target (a 'phony' target) has no effect on makefile

After establishing that prerequisites to .PHONY are made target-like.
And looking at the docs, where the following special targets seem to follow the same syntax rules:
'.EXPORT_ALL_VARIABLES'
Simply by being mentioned as a target...
...
...
...
'.ONESHELL'
If '.ONESHELL' is mentioned as a target...
I tried to following makefile:
all:
#foo=bar
#echo "foo=$${foo}"
.PHONY: all
.PHONY: .ONESHELL
By running it, and got:
foo=
Which definitely is not a result from "oneshell" execution.
So, are some special variables more special than others, regarding their syntax rules?
.ONESHELL should be provided as the target not as the prerequisite as you have specified in your question. If you specify .ONESHELL: all you should get the expected output of foo=bar. That is what I get when running make on the following makefile.
.ONESHELL: all
.PHONY: all
all:
#foo=bar
#echo "foo=$${foo}"

.EXPORT_ALL_VARIABLES works only when made 'phony'

The docs provides:
'.EXPORT_ALL_VARIABLES'
Simply by being mentioned as a target, this tells 'make' to export
all variables to child processes by default. *Note Communicating
Variables to a Sub-'make': Variables/Recursion.
However, the following makefiles show that only by making .EXPORT_ALL_VARIABLES a phony target, then and only then, will it have the desired effect on the makefile, i.e. to export ALL variables.
Makefile(version 1) is:
ifeq "$(MAKELEVEL)" "0"
foo=bar
.DEFAULT:;
all: .EXPORT_ALL_VARIABLES
#$(MAKE)
else
all:
#echo 'foo is: $(foo)'
endif
Running, we get:
make[1]: Entering directory '/home/myname'
foo is:
make[1]: Leaving directory '/home/myname'
Makefile(version 2) is:
ifeq "$(MAKELEVEL)" "0"
foo=bar
.DEFAULT:;
all: .EXPORT_ALL_VARIABLES
#$(MAKE)
# This line is added in THIS version.
.PHONY: .EXPORT_ALL_VARIABLES
else
all:
#echo 'foo is: $(foo)'
endif
Running, we get:
make[1]: Entering directory '/home/myname'
foo is: bar
make[1]: Leaving directory '/home/myname'
Now, the only difference between these 2 versions of makefile, is that in the 2nd version, the .EXPORT_ALL_VARIABLES was made phony.
Why is the 'phoniness' needed in order to work?
Simply by being mentioned as a target,
Why is the 'phoniness' needed in order to work?
It's not. You didn't declare .EXPORT_ALL_VARIABLES as a target, you declared it as a prerequisite:
all: .EXPORT_ALL_VARIABLES
That's a prerequisite, not a target. If you declare it as a target:
.EXPORT_ALL_VARIABLES:
then it will work and you won't have to declare it phony.
A more accurate question would be, why does declaring .EXPORT_ALL_VARIABLES as phony work even though it's not declared as a target? It happens because things that are marked phony are assumed to be targets even if they're not explicitly mentioned as such. That may or may not be a bug, depending on how you interpret the intent of .PHONY.
Your questions recently seem to follow a pattern: read the documentation, then write a makefile that does something similar to but not the same as what the documentation says, observe it doesn't work as described, then ask why not.

How can I set long options within a makefile

I am writing a makefile for distribution among students. To ease up their hacking experience, I would like make to warn about uninitialised variables.
I know there is the option --warn-undefined-variables to do just this, and of course, I can add an alias รก la alias make="make --warn-undefined-variables" to my .bashrc. But I would like to set this option within the makefile so students will automatically profit from those warnings too, when they start to extend the makefile.
The logical way to do so would be the MAKEFLAGS variable. However, while it works for short options, I cannot get it to work with --warn-undefined-variables as described in Can make warn me, when I use unset variables?
Makefile:
MAKEFLAGS=--warn-undefined-variables
$(info MAKEFLAGS: $(MAKEFLAGS))
$(info ${BAR})
Call:
$ make
MAKEFLAGS: --warn-undefined-variables
make: *** No targets. Stop.
$ make --warn-undefined-variables
MAKEFLAGS: --warn-undefined-variables
Makefile:3: warning: undefined variable 'BAR'
make: *** No targets. Stop.
When I change the MAKEFLAGS to -d the console is flooded with debug information, so I know MAKEFLAGS is set correctly. Any suggestions?
I have GNU make 4.0 here and I cannot for the life of me get make to honor MAKEFLAGS= --warn-undefined-variables with a straightforward Makefile. However, if I make the Makefile invoke itself, then MAKEFLAGS= --warn-undefined-variables works in the child invocation!
MAKEFLAGS= --warn-undefined-variables
$(info MAKEFLAGS: $(MAKEFLAGS))
$(info $(BAR))
# This prevents a warning if we invoke make without a target...
MAKECMDGOALS?=
all:
ifndef RECURSED
$(MAKE) RECURSED=1 $(MAKECMDGOALS)
else
echo $(FOO)
endif
If I just run make, I get:
MAKEFLAGS: --warn-undefined-variables
make RECURSED=1
make[1]: Entering directory '/tmp/t1'
MAKEFLAGS: --warn-undefined-variables
Makefile:3: warning: undefined variable 'BAR'
Makefile:12: warning: undefined variable 'FOO'
echo
make[1]: Leaving directory '/tmp/t1'
Either I'm borking on something... or there's a bug in make. I'm inclined to think the latter.

Resources