Can I make a makefile abort outside of a rule? - makefile

At the top of my Makefile, before any of the rules, I have the following:
ifeq ($(ENVIRONMENT),LOCAL)
TARGET := local_target
else
TARGET := hello
endif
If the ENVIRONMENT environment variable is not set, or is set to a value other than LOCAL, instead of setting TARGET to hello, I want the makefile to halt and exit with -1.
Can I do that?? When I change TARGET := hello to exit -1, I get the following error:
Makefile:4: *** missing separator. Stop.
How can I make this work??

exit is a shell command, so you could use the shell assignment operator (i.e.: !=) inside the Makefile to call exit:
TARGET != exit -1
This would be actually equivalent to:
TARGET := $(shell exit -1)
Note again that this calls a shell that in turns runs the shell's exit built-in, i.e.: it does not exit make. The typical approach for exiting while processing a makefile is to call GNU Make's error built-in function instead:
$(error Error-message-here)
Putting everything together:
ifeq ($(ENVIRONMENT),LOCAL)
TARGET := local_target
else # indent with spaces, not a tab
$(error ENVIRONMENT not set to LOCAL)
endif

Related

Makefile if statement causing some weird behavior

In my makefile, the user supplies an argument called EXEC (make target EXEC=something). I want this to happen:
if EXEC equals "server"
make the variable NOT equal to "client"
if EXEC equals "client"
make the variable NOT equal to "server"
I tried doing this:
ifeq ($(EXEC),server)
NOT := client
endif
ifeq ($(EXEC),client)
NOT := server
endif
I run this by saying make -f build.mk EXEC=server
the output is:
NOT := client
make[2]: NOT: No such file or directory
Why is this error happening?
It seems you've indented the variable assignment with a TAB character. That means that line is considered part of the recipe for the previous target.
Since you haven't provided the entire makefile, or at least the section of the makefile before/after this, we can't say more than that.
However, in general in a makefile you should never indent any lines with TAB characters unless they are intended to be a part of a recipe.

GNU Makefile treating each recipe line as sub-shell command without continuation character

I am trying to get the target_compile to work.
copy_shared_object:
cp shared_object.so ${CURRENT_DIR}
PROJECT_SIM_OPTS += -LDFLAGS -L${CURRENT_DIR},-lm -load
target_compile: copy_shared_object actual_compile_with_sim_opts
.
.
.
actual_compile_with_sim_opts:
.
.
.
I am getting the Error despite the fact that I have not added ;\ on the first line starting with cp
make: PROJECT_SIM_OPTS: Command not found
makefile:282: recipe for target 'copy_shared_object' failed
make: *** [copy_shared_object] Error 127
What you likely want is something like:
${CURRENT_DIR}/shared_object.so: shared_object.so
cp $^ $#
target_compile: PROJECT_SIM_OPTS += -LDFLAGS -L${CURRENT_DIR},-lm -load
target_compile: copy_shared_object actual_compile_with_sim_opts
#echo PROJECT_SIM_OPTS=${PROJECT_SIM_OPTS} ...
To explain a few things (and to reiterate #Beta's remarks): The variable ${CURRENT_DIR} is a makefile variable. It can come from either the environment or makefile. make will substitute the value for the variable name at its first phase (before it runs any rules). Therefore its value cannot be changed when running a rule. Makefile variables have a single $, and require braces around them if they're multi-character tokens.
${PROJECT_SIM_OPTS} is a target-specific makefile variable. It's still a makefile variable, so it cannot change its value when the make is executing the rules. That being said, its value is specific to the target_compile rule, and any rule that is being run as a result of that rule.
For shell variables, it's possible to set a value within a recipe, however, the scope of that value is that recipe line itself. In order to use shell variables you need to do $$shellvarname (with two $'s, as make expands $$ to $ before invoking the shell) That being said, each line of a recipe is run in a subshell, and any variable values will not be visible in other subshells. So, for example, if you have:
target: prereq
#shellVar="value"; echo "recipe1: shellVar is $$shellVar"
#echo "recipe2: shellVar is $$shellVar"
it will output:
recipe1: shellVar is value
recipe2: shellVar is
as recipe1's subshell does not communicate with recipe2's subshell, and therefore recipe2 is not aware of recipe1's value for the variable.

Using ifeq to test the variables

I tried to write this dynamic target to check the variable before running the actual target:
.PHONY: check-env-%
check-env-%:
ifeq ($(${*}),)
$(error not found ${*})
endif
so that I can use it like:
build: check-env-VERSION
But looks like it cannot compare it and even when I supply the required variable, it errors: Makefile:16: *** not found VERSION. Stop.
I believe I'm using the ifeq correctly but not sure why it cannot compare it?
From the docs: "Conditionals control what make actually “sees” in the makefile, so they cannot be used to control recipes at the time of execution." So your access to $* always yields an empty string at the time of makefile analysis, leaving your $(error) as recipe instruction.
Vroomfondel is right. What you can do instead is this:
check-env-%:
test $($*) || (echo $* not found; exit 1;)
...
test shall stop when there is no variable defined.

Require an environment variable to be set in a Makefile target

I'm trying to require that an environment variable be set in a Makefile when running a specific target. I'm using the technique from the answer to this question, where you setup another target that will guarantee an environment variable to be set.
Mine looks like this:
require-%:
# if [ "${${*}}" = "" ]; then \
$(error You must pass the $* environment variable); \
fi
With that target setup, this is expected:
$ make require-FOO
Makefile:3: *** You must pass the FOO environment variable. Stop.
However, when testing, I can never get it to not error:
$ make require-FOO FOO=something
Makefile:3: *** You must pass the FOO environment variable. Stop.
$ make require-FOO FOO=true
Makefile:3: *** You must pass the FOO environment variable. Stop.
$ make require-FOO FOO='a string'
Makefile:3: *** You must pass the FOO environment variable. Stop.
Even when I comment out the if block in the target:
require-%:
# # if [ "${${*}}" = "" ]; then \
# $(error You must pass the $* environment variable); \
# fi
I still get an error when running it:
$ make require-FOO FOO=something
Makefile:3: *** You must pass the FOO environment variable. Stop.
What's am I doing wrong? How can I get this to work?
You modified the solution presented in that linked answer without understanding the difference.
The linked answer uses a shell echo and a shell exit to do the message output and exiting.
Your modification uses the make $(error) function.
The difference is that the shell commands only execute when the shell logic says they should but the make function executes before make runs the shell commands at all (and always expands/executes). (Even in shell comments because those are shell comments.)
If you want this asserted at shell time then you need to use shell constructs to test and exit. Like the original answer.
If you want this asserted at recipe expansion time then you need to use make constructs to test and exit. Like this (untested):
require-%:
#: $(if ${${*}},,$(error You must pass the $* environment variable))
#echo 'Had the variable (in make).'

Get exit code 1 on Makefile if statement

I'm trying to get the exit code on the ifdef statement if the statement is not true, but I tried by using exit 1 and $(call exit 1)
when using the first on the following code I get "Makefile:11: * missing separator. Stop."
...
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
exit 1
endif
...
By using $(call exit 1) I get no error but the makefile still keeps executing.
What I'm trying to accomplish is to exit the Makefile on the else with the error code 1
Thanks
As geekosaur says you can't put a shell command like exit 1 as a makefile operation. Makefiles are not shell scripts, although they can contain shell scripts. Shell commands can only appear within a target recipe, and nowhere else.
If you have a sufficiently new version of GNU make you can use the $(error ...) function, like this:
ifdef PACKAGE
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
Also note that ifdef will be true if the variable is defined, even if it's defined to be the empty string. You may prefer:
ifneq ($(PACKAGE),)
PACKAGEDIR = $(HOME)/$(PACKAGE)
else
$(error You must define the PACKAGE variable)
endif
to ensure the variable is set to a non-empty value.
And, it's possible your version of GNU make is too old to support the $(error ...) function, although it's been around for a long time now.
You can't simply have bare code sticking somewhere in a Makefile; code (such as exit 1) is always associated with a build rule of some kind.
In this case you want the $(error) function. It may not be sufficient to drop it in the else, though, for the same reason that exit 1 itself won't work there; you may need to rephrase the whole thing as
PACKAGEDIR := $(if $(flavor PACKAGE),undefined,$(error PACKAGE must be defined!),$(HOME)/$(PACKAGE))

Resources