Accessing Makefile variable in compile time shell - shell

I have a makefile, 1.mk with following content:
#!make
aa := x
bb := y
cc := z
export
include abc.mk
all:
#echo $(chk1)
And another makefile, abc.mk with following content:
#!make
chk1 := $(shell set -o posix; set | awk -F "=" 'BEGIN{ORS=" "}1 $$1~/[a-zA-Z_][a-zA-Z0-9_]*/ {print $$1}')
chk2 := $(shell env &> pqr)
export
When I check, none has the makefile-variables:
> make all -f 1.mk | grep aa
> grep 'aa' pqr
Thus I can say, compile time shell does not have makefile variables. I want a way to access all makefile variables in compile-time shell.
Constraint: I do not know variable names beforehand to write code in following manner:
chk2 := $(shell export aa=$(aa); env &> pqr)

As far as I understand the way make exports make variables, it does so only in recipes, not in the shells invoked with the shell function. You can test this with, for instance:
$ cat Makefile
aa := x
export
chk := $(shell echo $$aa)
.PHONY: all
all:
#printf 'in shell function: aa = $(chk)\n'
#printf 'in recipe: aa = %s\n' "$$aa"
$ make
in shell function: aa =
in recipe: aa = x

Related

Leading whitespaces in makefile variable

I need to declare a makefile variable with a leading white space, and I used the following code, but it doesn't work
SPACE :=
SPACE +=
VIU_DIAG_SW_VERSION :=$(SPACE)AJ
Try:
NULL :=
VIU_DIAG_SW_VERSION := $(NULL) AJ
Demo:
$ cat Makefile
NULL :=
VIU_DIAG_SW_VERSION := $(NULL) AJ
.PHONY: all
all:
#echo "X$(VIU_DIAG_SW_VERSION)X"
$ make
X AJX
And if you want a variable containing just one space:
SPACE := $(NULL) $(NULL)

Makefile: How to expand a variable in a shell function correctly?

I have a reoccurring piece of code in my makefile, which I want to put in a function. A simplified version of my code looks as follows:
IDS=4 5
MY_FUNC = $(shell echo "max=2; \
counter=1;\
while [ \$$counter -le \$$max ]; do\
id=$$(echo $(IDS) | cut -d" " -f \$${counter}); \
counter=\$$((counter+1)); \
done");
.PHONY: all
all:
#$(call MY_FUNC)
Unfortunately, cut interprets $counter literally and consequently throws the error message cut: invalid field value ‘$counter’.
I do not understand why this is the case, since the command \$$command accesses the value stored inside this variable. Do you may know how to properly call pass counter to cut?
Here is a fixed form of your Makefile.
IDS=4 5
MY_FUNC = $(shell echo 'max=2; \
counter=1; \
while [ $$counter -le $$max ]; do\
id=$$(echo $(IDS) | cut -d" " -f $${counter}); \
counter=$$((counter+1)); \
echo "debug: id: $$id"; \
done');
.PHONY: all
all:
#$(call MY_FUNC)
I have added a echo "debug: id: $$id"; command to help with debugging and prove that the script is behaving as intended. Here is the output:
$ make
debug: id: 4
debug: id: 5
Here are the important points worth noting in the fixed script:
The outermost delimiter for the argument to the outermost echo statement is single-quote (not double-quotes) in order to prevent the $counter, $max, etc. from expanding to empty strings when the echo statement is being executed by the $(shell echo ...) call from Makefile. This also allows proper nesting of the double-quotes used in cut -d" " within the outer single-quotes.
Now that we are using single-quotes as the outer delimiters, the $ symbols within (escaped as $$ in Makefile) need not be escaped with \ anymore.
If however you want to stick with double-quotes as the outermost delimiter, then the alternative solution with minimal changes to your code looks like this:
IDS=4 5
MY_FUNC = $(shell echo "max=2; \
counter=1;\
while [ \$$counter -le \$$max ]; do\
id=\$$(echo $(IDS) | cut -d\" \" -f \$${counter}); \
counter=\$$((counter+1)); \
echo \"debug: id: \$$id\"; \
done");
.PHONY: all
all:
#$(call MY_FUNC)
Once again the output is:
$ make
debug: id: 4
debug: id: 5
Here are the important points to note in this alternative solution:
All $ symbols for the shell (escaped as $$ in Makefile) need to be escaped carefully. They should all occur as \$$ in Makefile. In your code, this was missing for $$(echo. This has been fixed to \$$(echo.
Further all double-quotes within the outer double-quotes need to be carefully escaped as \", so cut -d" " should be written as cut -d\" \".
Susam Pal's answer explains why your use of double quotes was wrong. Use one or the other of the two proposed solutions.
And then, there are a few other aspects you could consider:
You are using the $(shell...) make function in a recipe which does not realy make sense: recipes are already shell scripts. And you do not need the call function neither. A simpler Makefile could be:
IDS = 4 5
define MY_FUNC
max=2; \
counter=1; \
while [ $$counter -le $$max ]; do \
id=$$(echo $(IDS) | cut -d" " -f $${counter}); \
counter=$$((counter+1)); \
done
endef
.PHONY: all
all:
#$(MY_FUNC)
As your recipe has no side effect it is not very useful. But I guess you know it already and this was just an example.
Hard-wiring the number of items in IDS (max=2) in your recipe is not optimal. If you are using GNU make you could use its words function:
max=$(words $(IDS)); \
There are much simpler ways to achieve what you want with the shell. Assuming you just want to print the id values:
for id in $(IDS); do; \
echo $$id; \
done
is easier. But I guess you know it already and this was just an example.
If you want to use the call function you could pass it a parameter (the current id) and iterate with the foreach make function instead of using a shell loop:
IDS = 4 5
define MY_FUNC
echo $(1)
endef
.PHONY: all
all:
$(foreach id,$(IDS),#$(call MY_FUNC,$(id)))
Note the empty last line of MY_FUNC. It is needed to obtain a true multi-line recipe. Alternate solution with a single-line recipe:
IDS = 4 5
MY_FUNC = echo $(1)
.PHONY: all
all:
#$(foreach id,$(IDS),$(call MY_FUNC,$(id));)
GNU make offers many handy functions and has many very useful features. In your case (and assuming you just want to print each id on standard output) you could use patsubst to create a list of phony targets, one per word in IDS and write a static pattern rule for all of them:
IDS = 4 5
ALLS = $(patsubst %,all-%,$(IDS))
.PHONY: all $(ALLS)
all: $(ALLS)
$(ALLS): all-%:
#echo $*
An advantage of this last solution is that your ids are distributed to as many independent rules (the all-X) and their recipes can be run in parallel by make if you allow it to do so (make -j) while with single rule solutions they necessarily run sequentially.

In Makefile: read version from other text file and put the result into a variable in the Makefile

I have an file named "Version.h" with the content of:
#define APP_VERSION_MAJOR 5
#define APP_VERSION_MINOR 6
#define APP_VERSION_PATCH 0
I have a Makefile, and I want to assign a variable in the Makefile, according to the "Version.h" file, in this case:
MY_APP_VERSION = 5.6.0
I managed to find the line with the following command:
#echo "The result is: $$(grep "#define APP_VERSION_MINOR " Version.h)"
Output:
The result is: #define APP_VERSION_MINOR 6
So, how can I put the version in a variable in the Makefile?
Thanks!
To set a make variable you can do something like this:
getnum = $(shell sed -n 's/.*$1 *\([0-9*]\)/\1/p' Version.h)
MY_APP_VERSION := $(call getnum,MAJOR).$(call getnum,MINOR).$(call getnum,PATCH)
This does invoke sed 3 times though.
Try putting this is your makefile:
all:
#echo The result is: \
$$(grep '#define APP_VERSION_MAJOR' Version.h | cut -d' ' -f5).\
$$(grep '#define APP_VERSION_MINOR' Version.h | cut -d' ' -f5).\
$$(grep '#define APP_VERSION_PATCH' Version.h | cut -d' ' -f5)
Then a simple make gives you
$ make
The result is: 5.6.0
Try something like:
APP_VERSION_MAJOR := $(shell awk '// { if ($$2 = 'APP_VERSION_MAJOR) { print $$3 } }' < Version.h)
The := inhibits repeated expansion of the shell command, and the awk command extracts the the value of the assignment.
With more recent GNU make versions, this should work as well (assuming Version.h doesn't use tabs):
$(foreach i, \
$(shell sed 's/#define \([^ ]*\) *\([^ ]*\)/\1:=\2/' < Version.h), \
$(eval $i))
It has the advantage that it will translate many preprocessor defines in one go, no matter what the specifics are, but it can easily go very wrong if Version.h contains unexpected text.
If you manage to read in the file into a variable (my version of make fails to read with $(file < version.h) but yours may work) then you can use the GNU make table toolkit. It was designed exactly for this purpose:
include gmtt.mk
tbl := 3 $(strip $(file < version.h)) # make a table with 3 columns from the file
versions := $(strip $(call select,3,$(tbl),t)) # select 3rd column from table, t(rue) as where-clause
MY_APP_VERSION := $(subst $(space),.,$(versions))
The file must obey the format that you displayed above so that it can be interpreted as a table with three columns. It has the additional benefit that you don't have to care for which shell (e.g. on Windows) is available.

Why does AWK not work correctly in a makefile? [duplicate]

This question already has answers here:
Escaping in makefile
(2 answers)
Closed 7 years ago.
NOTICE: Escaping not problem, sample from shell is only sample, in Makefile $$.
GNU Makefile man says why it's doesn't work:
Note that expansion using ‘%’ in pattern rules occurs after any
variable or function expansions, which take place when the makefile is
read.
--Orig. question:
In pure shell, the next script works correctly:
echo "test2.cpp src2/test2.cpp src1/test1.cpp src1/test.cpp" | \
awk 'BEGIN{RS=" "}{if(NR == 1) f=$0; else if(match($0, f)) print $0;}'
Filter is first: test1.cpp
And it returns: src1/test1.cpp
But in Makefile it does not work correctly (error compared to awk):
OBJ_DIR:=obj
SOURCES:=$(wildcard */*.cpp *.cpp)
OBJECTS_LOCAL:= $(addprefix $(OBJ_DIR)/, $(notdir $(SOURCES:.cpp=.o)))
LOCAL_PATHS_HEADERS:=$(sort $(dir $(wildcard *.h */*.h)))
TARGET:=libcommon.a
all:$(TARGET)
$(TARGET): $(OBJECTS_LOCAL)
ar -rcs $# $^
$(OBJECTS_LOCAL): $(OBJ_DIR)/%.o : $(shell echo %.cpp $(SOURCES) | awk 'BEGIN{RS=" "}{if(NR == 1) f=$$0; else if($$0 ~ f) print $$0;}' )
#mkdir -p $(OBJ_DIR)
#$(CC) -c $< -o $# $(addprefix -I,$(LOCAL_PATHS_HEADERS))
So I take simple in Makefile and check value f, and found some strange length of f
...%.cpp $(SOURCES) | awk '{print ("file1.cpp" ~ $$1)"."$$1"."length($$1)}' )
awk return fail in compared;
print returns "0.file1.cpp.5" to fail with length, because it has forgotten .cppvalue of %, info bellow. I attempted to correct it:
...%.cpp $(SOURCES) | awk 'BEGIN{RS=" "}{if(NR == 1) f=$$0".cpp"; print ("file1.cpp.cpp" ~ f)"."("file1.cpp" ~ f)"."f"."length(f)}' )
but awk return fail in all compared; print returns "0.0.file1.cpp.cpp.9".
I check awk in manual mode, like this:
...%.cpp $(SOURCES) : $(shell echo %.cpp $(SOURCES) | awk '{print "src/"$$1}' )
It works fine, but it isn't variant, because it will kill automatic mode.
--Add
Information about lost length from parameter % to AWK
...%.cppppppp $(SOURCES) | awk '{print ("file1.cpp" ~ $$1)"."$$1"."length($$1)}' )
print returns "0.test2.cppppppp.10"
--Upd, some problem
Above, I was printing return value from $<
But file redirect show that value % does not work in prerequisites(file redirect: "0.%.cpp.5").
Can I use any automatic variable with value in prerequisites?
Almost invariably, when a question is asked about awk in a Makefile, the solution is to properly escape the $ symbols. It's not entirely clear what your question is, but there are some substantial misunderstandings that need to be resolved. In particular, the following "works", but hardly for the reasons you think:
echo "test2.cpp src2/test2.cpp src1/test1.cpp src1/test.cpp" | \
awk 'BEGIN{RS=" "}{if(NR == 1) f=$$0; else if(match($$0, f)) print $$0;}'
You almost certainly do not want $$ in any of the cases they appear here. awk is generally looking for single dollar signs, and when they appear in a Makefile, they are doubled because Make parses the $$ and invokes awk with a single $. In the quoted sample, $$0 on the first record is equivalent to $test2.cpp, but the variable test2.cpp is uninitialized and so has value 0, so on the first pass f is set to the value of $0 (the string "test2.cpp").
In short, if you are invoking awk from the shell, use single $. In the Makefile, use $$ and awk will only see $.

Create comma-separated lists in GNU Make

I have a Makefile with a set of booleans which must be used to control the flags for an external application. The problem is that the flag must be passed as a comma-separated string.
Something like this (non-working pseudo code):
WITH_LIST = ""
WITHOUT_LIST = ""
ifeq ($(BOOL_A),y)
# Append A to list "WITH_LIST"
else
# Append A to list "WITHOUT_LIST"
endif
ifeq ($(BOOL_B),y)
# Append B to list "WITH_LIST"
else
# Append B to list "WITHOUT_LIST"
endif
ifeq ($(BOOL_C),y)
# Append C to list "WITH_LIST"
else
# Append C to list "WITHOUT_LIST"
endif
Now assuming BOOL_A == y, BOOL_B == n and BOOL_C == y, I need to run the following command:
./app --with=A,C --with-out=B
How can I generate these string using Gnu Make?
First you create the two white-space separated lists, either using your method, or thiton's.
Then you use the little trick from the end of section 6.2 of the GNU make manual to create a variable holding a single space, and one holding a comma. You can then use these in $(subst ...) to change the two lists to comma-separated.
PARTS := A B C
BOOL_A := y
BOOL_B := n
BOOL_C := y
WITH_LIST := $(foreach part, $(PARTS), $(if $(filter y, $(BOOL_$(part))), $(part)))
WITHOUT_LIST := $(filter-out $(WITH_LIST), $(PARTS))
null :=
space := $(null) #
comma := ,
WITH_LIST := $(subst $(space),$(comma),$(strip $(WITH_LIST)))
WITHOUT_LIST := $(subst $(space),$(comma),$(strip $(WITHOUT_LIST)))
all:
./app --with=$(WITH_LIST) --with-out=$(WITHOUT_LIST)
A construct like
OPTIONS+=$(if $(filter y,$(BOOL_A)),--with=A,--with-out=A)
should work.
Edit: Sorry, overlooked the necessary collation.
PARTS=A B C
YESSES=$(foreach i,$(PARTS),$(if $(filter y,$(BOOL_$(i))),$(i)))
all:
echo with=$(shell echo $(YESSES) | tr ' ' ',')
The idea is to check for each possible part X whether it's set to yes and insert it into a list if it is yes. This list is whitespace-separated and hard to comma-separate with make, but easy to do this in shell.
Or just use sed: ugly (and untested) but straightforward
WITH_LIST = $(shell echo A$(BOOL_A) B$(BOOL_B) C$(BOOL_C) | sed -e 's/[ABC][^yABC]*//g' -e 's/y//g' -e 's/ /,/g')
WITHOUT_LIST = $(shell echo A$(BOOL_A) B$(BOOL_B) C$(BOOL_C) | sed -e 's/[ABC]y[^ABC]*//g' -e 's/[^ABC ]//g' -e 's/ /,/g')

Resources