Make: how to force immediate expansion of a variable? - makefile

Consider the following GNU makefile snippet:
# this code is not under my control
A = aaa
B = bbb
# this code is under my control
A += $B
B = ccc
# Prints "aaa ccc". I would rather "aaa bbb".
$(info $A)
The issue is that since A is initially created as a deferred variable, the value of the RHS of the += is taken as a deferred value. Is there something like A += $(immediate B) that I can use?
The following somewhat gets there:
TMP := $B
A += $TMP
but is not an option since this will be in an include file, and gets used several times (in the context of a non-recursive make system). So TMP would get clobbered each time.
UPDATE
A little more justification. This is a non-recursive build, and variable d holds the name of the directory being processed. An include file is invoked for each subdirectory, setting up some variables. For example:
$d.LIBS += $($d.target_dir)/lib.a
LD_FLAGS += $($d.LIBS)
The problem here is that d is ephemeral, with its value changing as soon as the next directory is processed. LD_FLAGS and, for that matter, $d.LIBS may need to remain as deferred variables but d needs to be evaluated immediately here.

If you don't mind A becoming immediate, then you can just use this:
A := $A $B
If you want A to continue to be deferred, but to expand $B immediately, you can use eval because eval expands its argument:
$(eval A += $B)
This will expand $B first, so you get A += bbb, then evaluate that.
If you want some aspects of the value expanded and others not, just escape the not-to-be-expanded content:
$(eval LD_FLAGS += $$($d.LIBS))
I should say, though, that usually the things you're trying to do are handled more by constructed variable names based on the target name, so something like:
LD_FLAGS += $($#_LDFLAGS)
or whatever.

Related

Why do I need eval to set variable in makefile function?

If I have the following at the top of a gnu makefile:
$(if _,a:=1)
$(info a=$a)
Compilation fails (make-3.81: *** No rule to make target '=1', needed by 'a'. Stop., or *** missing separator. Stop. if I exclude the colon). I know I can get around this by using an eval, but I'm not understanding why it's required. Shouldn't this expand to a:=1, be parsed, and set a to 1?
I'm not understanding why it's required
Well, such is the design.
Evaluation procedure always performs a (recursive) expansion (except for "the initial evaluation" where subsequent expansion can be effectively stopped by =, i.e. "lazy assignment"), but expansion procedure never does evaluation, unless it's explicitly told so (basically, $(eval ...) is such an order to switch from expansion to evaluation).
Consider this:
x := 0
y := 1
# z is set to "y<space><equal><space><dollar>x"
z = y = $x
# [0] [1] [y = $x]
$(info [$x] [$y] [$(value z)])
# error as "y = $x" will not be re-evaluated
#$z
# effectively sets y to "0"
$(eval $z)
# [0] [0] [y = $x]
$(info [$x] [$y] [$(value z)])
From make's point of view $(if cond,x=y) does not differ much from $z --- it's expanded but not evaluated. No matter where it stands. Just think anything of a form $(...) to be "data", not "code".
In your case the if function evaluates as its second argument (a:=1) but this is not evaluated in turn as a make variable assignment. With your version of make it becomes a rule. This is probably due to the way make syntactically parses the makefiles. It does not consider that this if statement can be a variable assignment because (before expansion) it looks like none of the valid make variable assignments. So the if statement gets expanded but it is too late for make to consider the result as a variable assignment...
Instead you can use:
a := $(if _,1)
or:
ifneq (_,)
a := 1
endif
or:
$(if _,$(eval a := 1))
or:
$(eval $(if _,a := 1))
The two last forms work because by definition the result of the expansion of the argument of the eval function is processed as plain make statements.

Combine a variable and string and get the value of the variable formed in a single line

I was writing a script where I came across a situation.
Audio_Repo = "/src/audio_123";
Audio_ImgTag = "aud021882";
Audio_Enable = 1;
.....
Video_Repo = "/src/vid_823";
Video_ImgTag = "video9282";
Video_Enable = 0;
....
#Say proj_var ="Audio"
#it could be either Audio or Video based on some conditional check
....
proj_var = "Audio"
....
PROJECT_REPO= ${!{$proj_var"_Repo"}}
#PROJECT_REPO should hold the value "src/audio_123"
But the above representation throws bad substitution error
I know that I could use a temporary variable as follows
temp= $proj_var"_Repo";
PROJECT_REPO = ${!temp};
But I have many properties and I do not want to use temporary variables for each of them. Instead I want single line substitutions.
One way to do it is to use eval:
#! /bin/bash -p
Audio_Repo="/src/audio_123"
Audio_ImgTag=aud021882
Audio_Enable=1
# ...
Video_Repo=/src/vid_823
Video_ImgTag=video9282
Video_Enable=0
# ....
# Say proj_var="Audio"
# it could be either Audio or Video based on some conditional check
# ....
proj_var="Audio"
# ....
eval "Project_Repo=\${${proj_var}_Repo}"
# Project_Repo should hold the value "src/audio_123"
printf '%s\n' "$Project_Repo"
The code prints /src/audio_123.
eval is dangerous, and should be avoided if possible. See Why should eval be avoided in Bash, and what should I use instead?. In this case the temporary variable, despite the increased verbosity, is a better option.
I replaced PROJECT_REPO with Project_Repo to avoid possible a possible clash with an environment variable. See Correct Bash and shell script variable capitalization.
I've fixed some Bash syntax issues in the code in the question. Spaces around = are errors. Semicolons at the ends of lines are unnecessary.
Shellcheck issues some warnings for the code, but they are all harmless.
Another option is to use a helper function:
# ...
# Set the value of the variable whose name is in the first parameter ($1)
# to the value of the variable whose name is in the second parameter ($2).
function setn { printf -v "$1" '%s' "${!2}" ; }
# ...
setn Project_Repo "${proj_var}_Repo"
Using the setn (a poor name, choose a better one) function avoids both a temporary variable and eval.
Uses arrays, not variable names you need to manipulate.
Repo=0
ImgTag=1
Enable=2
Audio=(/src/audio_123 aud021882 1)
Video=(/src/vid_823 video9282 0)
proj_repo=Audio[$Repo]
project_var=${!proj_repo}

How to filter out a pattern with space in Makefile

I want to filter out a pattern as "-Wl, Bdynamic -lmylib" from a long LDFLAGS list. But filter-out function can only handle space-separated list, is there other method to do this in Makefile?
The only way to do it is by replacing spaces with some other character that you know doesn't appear in the variable value. For example suppose you know that the ^ character never appears in your variable value, then you can do something like this:
# Create a variable containing a space
E :=
S := $E $E
LDFLAGS := $(subst ^,$S,$(subst -Wl^Bdynamic^-lmylib,,$(subst $S,^,$(LDFLAGS))))

Why a stem '%' is not expanded by GNU Make within a 'patsubst' function

I have the following makefile:
A = a
B = b
FOO = A B
BAR = $(FOO:%=$(%))
C = c
FOO += C
all:
echo $(BAR)
I expected make to print a b c, but it doesn't (the stem '%' is not expanded or something). I tried changing the assignment of BAR for:
$(eval BAR = $(FOO:%=$$(%)))
but it only prints a b (as it should be because eval immediately performs the substitution using the current value of FOO).
So what's wrong with the definition of BAR and how can I change it so that make prints a b c (without moving any of the other lines, of course)?
% is a perfectly valid variable name (although should be avoided for obvious reasons), so the substitution expression $(FOO:%=$(%)) simply substitutes each token in FOO with the value of the variable %, which is empty - make must expand $(%) before it can perform the substitution.
BAR = $(foreach v,$(FOO),$($v)) is one way you could solve this.

Make return error 127

Am trying to match specific string after doing subst on a variable ; substitution is ok but while trying to match make returns error 127.
Does anyone know what's going on here ? why make should return error ?
$> make -f strsearch.mk
CODE : BBROY_OF_GREAT_BRITAIN_HAD_A_GREAT_WIFE
MORSE = BBROY OF GREAT BRITAIN HAD A GREAT WIFE
**make: MORSE: Command not found
make: * [search_new] Error 127
Here is the snippet of makefile:
VAR1 := BBROY_OF_GREAT_BRITAIN_HAD_A_GREAT_WIFE
search_new:
#echo CODE : $(VAR1)
MORSE = $(subst _, , $(VAR1))
#echo word count : ($words $(MORSE))
#echo After substitution Britain matches: $(filter %BRITAIN%, $(MORSE))
Your variable declaration is in the wrong place or form:
The form you use is for global assignment and cannot be inside a target block.
Either make it a global variable outside a target block
Or use the shell and eval to set the variable value. See the accepted answer for how to approach this issue: Define make variable at rule execution time
A 3rd option is to define the variable as a sort-of prerequisite which is then assigned at the time the dependencies are evaluated.

Resources