Allow Makefile both append and override target - makefile

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

Related

Makefile: exit on conditional

I want to check that an environment variable is set before executing some code in a Makefile. If it's not set I want to throw an error with a simple error message:
run:
[ -z "$(MY_APP)" ] && echo "MY_APP must be set" && exit 1
echo "MY_APP is set. Yay!"
echo "Let's continue on with the command..."
When MY_APP is not set I get the following error, which is desired:
[ -z "" ] && echo "MY_APP must be set" && exit 1
MY_APP must be set
make: *** [run] Error 1
However, when MY_APP is set I get the following error:
[ -z "EXAMPLE_NAME" ] && echo "MY_APP must be set" && exit 1
make: *** [run] Error 1
Any idea what I'm doing wrong? And is there a better way to do this?
Recall that the && condition require that all conditions must be TRUE to pass. Since the first condition fail, the whole command will return a status of 1 (-> false), effectively stopping the make
You can use the following, so that the test will fail only when MY_APP is missing.
Note that I'm using false instead of exit 1. Also better to use "${MY_APP}", which make it easier to copy/paste from Make to shell prompt/script.
run:
{ [ -z "$(MY_APP)" ] && echo "MY_APP must be set" && false } || true
...
# Or just if-Then-Else
if [ -z "${MY_APP}" ] ; then echo "MY_APP must be set" ; false ; fi
...
You can test environment variables with Makefile conditional syntax, like this:
sometarget:
ifndef MY_APP
#echo "MY_APP environment variable missing"
exit 1
endif
somecommand to_run_if_my_app_is_set
Note that ifndef/ifdef operate on the name of the variable, not the variable itself.
It seems that you are trying to use a Makefile to run commands which are not building targets (the target name run is a giveaway). You already got bitten by one of Makefile and shells caveats. Makefile caveat: exit status is inspected after each line and if not zero abort immediately. Shell caveat: the test command ([) returns a non zero exit status so the entire line returns non zero.
The rule of thumb is: a recipe of a rule should create a filename named like the target of the rule.
Here is a rule (to clarify the terms):
target:
recipe command lines
should create file named target
There are some exceptions to this rule of thumb. Most notably make clean and make install. Both typically do not create files named clean or install. One can argue that make run maybe also be an exception to this rule of thumb.
If your run is as simple as a typical clean then I might agree about making an exception. But usually commands are run with command line arguments. Before long you will want make run accept arguments. And making make accept custom command line arguments is not fun at all.
You tried to manipulate the behaviour using environment variables which is somewhat less problematic than command line arguments. But still problematic enough to make you trip over a caveat.
My suggestion for a fix:
Put complex recipes in a shell script. There you have all the power and flexibility of a shell script without the awkwardness of makefiles. For example as explained here: Basic if else statement in Makefile
In case of a typical run target write a wrapper shell script around the makefile which lets the makefile rebuild the target and then run the target. For exampe as explained here: Passing arguments to "make run"
You can conditionally exit the Makefile using error control function, at least in the GNU version.
This snippet is a helpful condition to put into the head of the Makefile. It exits with a message of help, if make was not called from within the directory of the Makefile.
MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
ifneq (${MAKEFILE_DIR}, $(shell pwd))
INVALID_LOCATION:=`make` must be called from within ${MAKEFILE_DIR} (or with option -C ${MAKEFILE_DIR})
$(error ERROR: $(INVALID_LOCATION))
endif
See: https://www.gnu.org/software/make/manual/html_node/Make-Control-Functions.html
Useful in case your paths are relative to the Makefile and you don't want them to prefix with a base.

Pass file argument through to child make

Conditions:
a makefile is passed to the make: make -f ../makefile hi
the hi target also calls make
I need a way to propagate the value from -f to the sub-make command running inside hi.
bye:
echo 'whatever'
hi:
${MAKE} bye
Assuming there is no default makefile in the current directory
(presumably the reason you are passing -f ../makefile) then
your example ../makefile will work as it is if you invoke
make like:
$ make MAKEFILES=../makefile hi
make bye
make[1]: Entering directory '/home/imk/develop/scrap'
echo 'whatever'
whatever
make[1]: Leaving directory '/home/imk/develop/scrap'
See 3.4 The Variable MAKEFILES
If there is a default makefile in the current directory then this
approach will cause make to read the default makefile after it has
read ../makefile, with untoward results. In that case prefer #Dummy00001's answer.
The only possibility I'm aware of is to use the MAKEFILE_LIST variable:
CURR_MAKEFILE:=$(lastword $(MAKEFILE_LIST))
bye:
echo 'whatever'
hi:
+$(MAKE) -f $(CURR_MAKEFILE) bye
But note, as it is written in the official documentation, that the variable records all parsed makefiles, including the includeed ones. The value of the variable is only reliable when queried at the very top of the makefile, before any includes.

Using Make with force snippet to override other file

I've been trying to get a makefile, a, to include another makefile, b, if the target specified is not found in file a. I'm using this snippet to try and achieve this, but from echos I've put into the file I can see that makefile b is being accessed even when the target is found in a and run.
The snippet I'm using from the link above is:
foo:
frobnicate > foo
%: force
#echo "No target found locally, running default makefile"
#$(MAKE) -f Makefile $#
force: ;
Specifically I'm getting "Nothing to be done" outputs when makefile b is being used, and makefile a is behaving as expected. This is shown below:
$ make all # all target appears in both make files
No target found locally, running default makefile
make[1]: Entering directory `/home/user/currdir' # (b)
make[1]: Nothing to be done for `Makefile'.
make[1]: Leaving directory `/home/user/currdir'
Local all # (a)
Is there a better way to be doing this?
addition: After adding another echo to the % rule, I've found that $# is "Makefile", when it should be the target trying to be built.
I don't really understand your question based on the example you gave; there is no "a" or "b" in that example, just one Makefile.
However, the behavior you're seeing is due to GNU make's re-making makefiles capability. When you create match-anything pattern rules as you've done, you have to consider that every single target or prerequisite that make wants to build will match that rule. That's a large burden.
You can avoid having remade makefiles match by creating explicit rules for them, such as:
Makefile: ;

error using makefile, targets and %

I'm trying to debug the following code:
TESTS=$(shell cat yoursourcefile)
all: $(TESTS)
%: compile_design
compile $#_tb.vhd >> log_file.log
simulate $#
I got this error:
makefile_tb.vhd >> log_file.log
as if makefile is a target
this error disappears when I add a character or more before %:
T%: compile_design
compile $#_tb.vhd >> log_file.log
simulate $#
This works but implies that all my targets starts with "T" which is not always the case.
My questions are:
what's exactly the function of % here ?
How to get rid of this error?
As suggested, I added
makefile: ; $#:
at the end, so I have now:
TESTS=$(shell cat yoursourcefile)
all: $(TESTS)
%: compile_design
compile $#_tb.vhd >> log_file.log
simulate $#
makefile: ; $#:
then when I do:
make all
I get [all] error2 all_tb.vhd >> log_file.log
but all_tb.vhd does not exist !
The %: compile_design rule is a "match-anything" pattern rule. It says "hey make, if you ever want to build any file, with any name, then you can do it by running these commands. Oh and by the way, if you have a file you want to build and it's older than the compile_design file, then you need to rebuild it". Generally you want to avoid match-anything rules, but if your target names truly have no specific pattern, you can't.
When you add the T before it then it tells make that instead of any file, that rule can only build files that begin with T.
The reason make is trying to rebuild the makefile is that GNU make has a special feature that allows it to remake its own makefiles. So after it reads its makefile it will try to re-make it. Normally this has no effect because there's no rule to build a makefile, but here you've added a rule that you've told make can build anything. Adding the T keeps the pattern from matching Makefile because Makefile doesn't begin with T.
The simplest thing for you to do is define an explicit rule for the makefile: make always chooses an explicit rule, if it exists, over an implicit rule like a pattern rule:
Makefile: ; #:
This creates an explicit rule that does nothing (: is the shell built-in command that does nothing).

Generic make rules using define

I'm trying to create a common template to generate rules build a set of test cases and place them in unique target locations but have hit a bit of snag with the define directive. The following is the relevant section of my Makefile:
ROOT=../..
PLATFORM=akyboard_gcc
# include all the test cases for the current platform
# They add to the TEST_CASES variable
TEST_CASES=A B
A_FILES = a1.c a2.c
B_FILES = b1.c b2.c
check: $(TEST_CASES:%=check_%)
define CHECK_template
# build artifact directories
$(1)_BLDDIR=$(ROOT)/build/$(PLATFORM)/$(1)
$(1)_OBJDIR=$$($(1)_BLDDIR)/obj
$(1)_EXEDIR=$$($(1)_BLDDIR)/exe
# prepend src/ to all the files that are part of the test case
$(1)_FILES := $($(1:%=%_FILES):%=src/%)
# add the test runner as one of the files to be compiled
$(1)_FILES += test_runner/$(PLATFORM)/main.c
# construct list of objects generated by sources
$(1)_OBJ = $($(1)_FILES:%.c=$$($(1)_OBJDIR)/%.o)
# This creates a rule such as check_{test_case}:
check_$(1): $$($(1)_OBJ)
#echo 1 $(1)
#echo 2 $$($(1)_FILES)
#echo 3 $$($(1)_OBJ)
#echo 5 $$($(1)_OBJDIR)
#echo 4 $$($(1)_BLDDIR)
#echo 6 $$($(1)_EXEDIR)
$$($(1)_OBJDIR)/%.o: $(ROOT)/%.c
#echo coconut
endef
$(foreach testcase, $(TEST_CASES), \
$(eval $(call CHECK_template,$(testcase))) \
)
Issuing "make check" gives the following error
*** No rule to make target `../../build/akyboard_gcc/A/obj/a1.o', needed by `check_A'. Stop.
If I manually create the target rules below it builds without errors
../../build/akyboard_gcc/A/obj/a1.o:
../../build/akyboard_gcc/A/obj/a2.o:
../../build/akyboard_gcc/B/obj/b1.o:
../../build/akyboard_gcc/B/obj/b2.o:
But changing rules like below causes the build error:
../../build/akyboard_gcc/A/obj/%.o:
Would be grateful for any help.
You can find what wrong with your template by replacing eval with info call.
$(foreach testcase, $(TEST_CASES), $(info $(call CHECK_template,$(testcase))))
And your template is fine. But you probably have a problem with pattern rules.
From GNU make manual:
A pattern rule can be used to build a given file only if there is a
target pattern that matches the file name, and all prerequisites in
that rule either exist or can be built.
Perhaps you don't have required sources in $(ROOT) directory. And make just can't create a rule for object file from pattern.

Resources