Conditional in makefile that redefines variable? - bash

I have a variable in my makefile I would like to either unset or redefine, so that targets a, b and c use the correct value of MY_VARIABLE (or none at all) within their respective makefiles.
If I run make foo with the following:
export MY_VARIABLE := 'false'
foo: prep_foo dist
prep_foo:
$(shell export MY_VARIABLE='true')
echo ${MY_VARIABLE}
dist: a b c
a:
make -C a/src
b:
make -C b/src
c:
make -C c/src
I get this output:
export MY_VARIABLE='true'
echo 'false'
false
...
If, instead, I run make foo with the following makefile:
export MY_VARIABLE := 'false'
foo: prep_foo dist
prep_foo:
$(shell unset MY_VARIABLE)
echo ${MY_VARIABLE}
dist: a b c
a:
make -C a/src
b:
make -C b/src
c:
make -C c/src
I get the following output:
make: unset: No such file or directory
echo 'false'
false
...
How can I unset or redefine MY_VARIABLE when specifying a target (like foo, in this case)?
EDIT
Here is the situation I would like to avoid:
dist: a b c
foo: a_foo b_foo c_foo
a:
make -C a/src
...
a_foo
make -C a_foo/src
I just want the a target to use a different value for my particular variable, so that compilation is handled differently in that target's makefile.
Also, it doesn't look like I can export or unset variables within a target. For example:
dist: a b c
foo: a_foo b_foo c_foo
a:
make -C a/src
...
a_foo:
export MY_VARIABLE='true'; make -C a/src
If I try to do so, I get something similar to the following error on the export MY_VARIABLE='true' line (and similarly if I try to use unset):
Makefile:16: *** unterminated variable reference. Stop.
Does this help clarify what I'm trying to do?
EDIT 2
I tried a target which touch-es a file and tries to run the child target's makefile (which checks for the file's existence):
foo: prep_foo
prep_foo:
touch a/src/.foo
make -C a/src
When I try to run this via make foo, I get the following error:
Makefile:14: *** commands commence before first target. Stop.
If I remove the make statement from prep_foo, then I can touch the file without getting the error message, but I cannot trigger making the a target, so this doesn't seem to help.
The following yields the same commands commence before first target error message:
foo: prep_foo a
prep_foo:
touch a/src/.foo
Is there an example of using touch to communicate state to child targets?

The common way to communicate between targets in a Makefile is through files. Just touch a file in one target and check it in the other. Can this help you solve your problem?

Don't forget that every single shell execution in make is done in a new process. Trying to fiddle with the environment variables of recipes like this does not really make much sense -- the environment you have modified in any given line is gone after that shell dies, before the next line is executed.
You may be able to do the job like this:
a:
unset MY_VARIABLE; $MAKE -C a/src
or
a:
$MAKE MY_VARIABLE=foo -C a/src

Related

How to exit a makefile without running other tasks, with a successful exit-code?

I've got a makefile that has a 'contingency step' to bootstrap the environment if the task is run without prior, critical setup:
bootstrap: ensure_opam_switch_once build
.ONESHELL:
$(FRONTEND_OPAM_SWITCH):
#echo ''
#echo "$(bold)[make bootstrap]$(sgr0) An opam-switch doesn't exist for frontend/, creating one ..."
OPAMYES=true $(MAKE) init
opam exec --sw="${FRONTEND_DIR}" -- $(MAKE) bootstrap
#EXIT_CODE=$$?
#exit $$EXIT_CODE
ensure_opam_switch_once: | $(FRONTEND_OPAM_SWITCH)
init:
# this does critical first-time setup
build:
# ... everything else happens here
The issue is that several shell-variables need to be set for the rest of the makefile — and I'm not guaranteed to know those variables. (opam sets up some stuff with eval; think rbenv, nvm, pyenv, etc.)
My plan/hack here is to re-execute the make-task after setup, with those shell-variables configured in the environment make is invoked from. (In the above example, that's the opam exec line.)
However, my issue at the moment is that, if the sub-make exits with success, then my #exit $$EXIT_CODE doesn't result in Make exiting! It continues to run and attempt to execute other dependents, which obviously fails because the outer Make invocation doesn't have any of the necessary shell-environment.
Is there a way to stop Make with a successful exit-code?
You could use conditionals to hide or not parts of your Makefile. Example with a PASS make variable that defines what part of the Makefile shall be used:
$ cat Makefile
.PHONY: all
all:
ifeq ($(PASS),)
#echo 'PASS = $(PASS), VAR = $(VAR)'
export VAR=VAL; $(MAKE) PASS=1
else
#echo 'PASS = $(PASS), VAR = $(VAR)'
endif
$ make
PASS = , VAR =
export VAR=VAL; make PASS=1
make[1]: Entering directory 'foo'
PASS = 1, VAR = VAL
make[1]: Leaving directory 'foo'

Export variable for a target in POSIX Make

In GNU Make you can export a variable for a target:
foo: export X=42
foo:
echo $$X
# Call several more commands that use $X.
Is there a way to do this in portable POSIX Make? So far, I've found two ways. The first is to basically merge all commands into one:
foo:
export X=42; \
echo $$X; \
# Call several more commands that use $X.
This is bad because now everything is bundled together. The second is to call $(MAKE):
foo:
$(MAKE) foo_ X=42
foo_:
echo $$X
# Call several more commands that use $X.
But this has an extra call to make again. Is there a better way?
The simplest solution is probably to set the variable on the command line by invoking make with:
make X=42
This way:
The make X variable is defined and set to 42, even if it is set to another value in the Makefile.
The shell environment variable X is defined and set to 42 for all recipes.
If you cannot use this (for instance because it is make that computes the value) the recursive make solution is probably the best option:
ifeq ($(X),)
X := <some-make-magic>
all:
$(MAKE) X=$(X) all
else
all:
<recipe-that-uses-X-environment-variable>
endif

makefile: Is it possible to let rule call rule process inherit variable values?

I want to call rule inside another rule. So I tried to write the following test makefile
var = 11
a:
echo $(var)
$(eval var=22)
echo $(var)
$(MAKE) b
b:
echo $(var)
The problem is that when I run make a, the $(MAKE) b will still output 11, but I wish it should be 22. So my problem is : Is there a way to inherit variable values across different rules?
If you're in the same file, you should avoid this altogether -- stop thinking of make as some "advanced scripting tool". It is not. It's a tool for building files doing the minimum necessary work while respecting all dependencies and therefore, your job is to exactly state these dependencies using prerequisites.
Look at this example and what it does:
var = 11
printvar:
echo $(var)
setvar:
$(eval var=22)
a: printvar setvar b
b:
echo $(var)
.PHONY: a b printvar setvar
Note that none of these rules create an actual file. Normally, a rule should create its target, if it isn't, it must be listed as a prerequisite of the special target .PHONY to let make know this. This should be an exception -- the primary use of make is that it can decide whether it has to apply a rule by comparing the timestamps of the prerequisites with that of the target. A rule's recipe is only executed if there's a prerequisite that is newer than the target. With a .PHONY rule, the recipe has to be executed each and every time.
When talking about recursive make, the question would make some more sense. One easy way to pass a variable from a parent make process to a child is to export it to the environment. In your example, the following would do:
var ?= 11 # only set var if it doesn't have a value yet
export var # export var to the environment, so it's available to child make
a:
echo $(var)
$(eval var=22)
echo $(var)
$(MAKE) b
b:
echo $(var)
.PHONY: a b
Of course, this only makes sense in practice when you have different Makefiles, so not just call $(MAKE) b, but e.g. $(MAKE) -C [some subdir] b. IMHO, recursive make should be avoided as it's very hard to get the dependencies correct with recursive make. But anyways, this would work.
When you invoke make again from the recipe of a, you really launch a new make process that will parse again your makefile and thus, its first line, that assigns value 11 to variable var. Next, this second make invocation builds target b and echoes 11...
If you want to pass a variable value to a sub-make invocation you can do it on the command line with the make VAR=VALUE ... syntax. In your example, you could, for instance:
$ cat Makefile
var = 11
a:
echo $(var)
$(eval var=22)
echo $(var)
$(MAKE) var=$(var) b
b:
echo $(var)
$ make a
echo 11
11
echo 22
22
make var=22 b
make[1]: Entering directory 'foo'
echo 22
22
make[1]: Leaving directory 'foo'
It works because variables that are assigned on the command line, by default, override the definitions found in the makefile (see the make manual).
This command line assignment may look strange because it seems to assign the value of variable var to itself but it does not. It assigns the current value of variable var of the first (top) make invocation to variable var of the sub-make invocation.
Yes. IMHO you should revert the order: ‘a’ depends on ‘b’
var = 11
b:var =22
b: a
echo b:$(var)
a:
echo: a:$(var)

Make runs target with variable name regardless of whether file exists

I'm using GNU make to work with some data. When I try to run a target with a variable name, make will run that target, regardless of whether the target file already exists. Consider the following Makefile:
include config.mk
.PHONY : all
all : $(PG_DB).db
$(PG_DB).db :
createdb $(PG_DB) -U $(PG_USER)
touch $#
where config.mk contains:
MAKEFLAGS += --warn-undefined-variables
SHELL := bash
.SHELLFLAGS := -eu -o pipefail
.DEFAULT_GOAL := all
.DELETE_ON_ERROR:
.SUFFIXES:
PG_USER="foo"
PG_DB="foo"
When I run make, make creates the Postgres database, and then touches the file foo.db. However, when I run make again, the output is:
createdb "foo" -U foo
createdb: database creation failed: ERROR: database "foo" already exists
make : *** ["foo".db] Error 1
This shouldn't have happened! I would expect make, in this situation, to check the prerequisites for the phony target all, see that foo.db already exists, and exit immediately without doing anything.
Strangely, this is exactly what happens when I get rid of the variables in the target names:
include config.mk
.PHONY : all
all : foo.db
foo.db :
createdb $(PG_DB) -U $(PG_USER)
touch $#
When I run make with this modified Makefile, I get:
make: Nothing to be done for `all`.
Which is exactly what I expect.
What's going on here?
The problem does not come from the variable, it comes from the quotation mark in the variable value. make does not remove the quotation mark before checking the dependency. So it is checking for the file "foo".db with the quotation mark included. While the command touch "foo".db gets interpreted by the shell that removes the quotation marks. So for make, the file will never be there and you will always have the same problem. When you put the dependency explicitly, you put it as foo.db that does not include the quotation mark. It makes all the difference.
in config.mk, turn
PG_DB="foo"
into
PG_DB=foo
it should work. I guess createdb is a normal shell command that will not care about the quotation marks. Otherwise you will need to add it before calling the command.

multiple makefiles in one directory

I have a makefile in a directory of mine which builds scripts with certain environment variables set. What if I want to create another makefile in the same directory with different environment variables set? How should I name the two make files? Does makefile.1 and makefile.2 work? How do I call them?
You can give sensible names to the files like makefile.win and makefile.nix and use them:
make -f makefile.win
make -f makefile.nix
or have a Makefile that contains:
win:
make -f makefile.win
nix:
make -f makefile.nix
and use make win or make nix
You can name makefile whatever you want. I usually name it like somename.mk. To use it later you need to tell make what makefile you want. Use -f option for this:
make -f somename.mk
Actually you can have two set of environment variables in the same make file. for example
COMPILER = gcc
CCFLAGS1 = -g
CCFLAGS2 = -Wall
a: main.c
${COMPILER} ${CCFLAGS1} main.c
b: test.c
${COMPILER} ${CCFLAGS2} test.c
then you can just say make a or make b. Depending on what you want.
Also it is possible with -f flag to call which makefile you want to call.
You can do something like this rather than using multiple makefiles for the same purpose. You can pass the environment or set a flag to the same makefile. For eg:
ifeq ($(ENV),ENV1)
ENV_VAR = THIS
else
ENV_VAR = THAT
endif
default : test
.PHONY : test
test:
#echo $(ENV_VAR)
Then you can simply run the make command with arguments
make ENV=ENV1
I have two makefiles in the same directory. Many of the recipes have identical names and here are two solutions:
1. Prefix in make
proja_hello:
#echo "hello A"
projb_hello:
#echo "hello N"
2. Keep two separate files
Project A has makefile. Type make hello.
Project B has a separate make file called projb.mk. Type bmake hello.
This works since I've added alias bmake ='make -f projb.mk to my .bashrc. Note! This command can be called anywhere but only works where projb.mk exists.
Note! You lose autocompletion of make with the alias and typing make -f projb.mk hello is not better than typing make projb_hello.

Resources