automate rules in makefile from two variables at the same time - makefile

I have some search patterns that fits poorly as part of a file name for the result. I therefore split the regular expression and the corresponding file name part into two different variables like below. How can I automate the following so that I do not have to manually list files for ALL and also do not have to manually enter the rules running grep?
SEARCH_RE = test a/b/c a.*b
SEARCH_FILE = test abc ab
ALL = result.test result.abc result.ab
all: $(ALL)
result.test:
grep test inputfile > result.test
result.abc:
grep a/b/c inputfile > result.abc
result.ab
grep a.*b inputfile > result.ab

I recommend
ALL = $(addprefix result.,$(SEARCH_FILE))
As for writing the rules, the kind of lookup I think you want can be done in Make, but really shouldn't be-- it would be a horrible kludge. I'd suggest doing it this way:
result.test: TARG = test
result.abc: TARG = a/b/c
result.ab: TARG = a.*b
$(ALL):
grep $(TARG) inputfile > $#

I don't know how to create the rules, but the ALL target is easy enough:
ALL = $(patsubst %,result.%,$(SEARCH_FILE))

Related

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.

Replacing text with sed in shell

Like a number of other questers on here, I'm attempting to use sed to replace text inside of a file. This is in the context of a sh script I'm using to run an analysis code (the executable: "turns2d_over") a number of times with a slight modification to the inputs (contained in the file "steady") after each run. There is a specific line that I'm trying to replace that reads as follows where X.X is a number:
ALFA = X.X EXPLANATION
However...instead of replacing ALFA = X.X with ALFA = Y.Y (a different number), sed either does nothing or, in the case I've put in below (which is a copy of my current sh script), it deletes ALL text in the entire file...which is rather unhelpful.
TOT_VALS=9
ALPHAS[1]=-2.0
ALPHAS[2]=0.0
ALPHAS[1]=2.2
ALPHAS[4]=4.5
ALPHAS[5]=6.3
ALPHAS[6]=8.3
ALPHAS[7]=10.2
ALPHAS[8]=12.2
ALPHAS[9]=14.4
cd ./naca0008
for VAL in $(seq 1 $TOT_VALS); do
sed -n -i 's/ALFA = .*/ALFA = '${ALPHAS[$VAL]}'/g' steady
./turns2d_over<steady
mv 'fort.1' 'grid_in' # rename input grid to save from bulk move
mv 'fort.8' 'fort.q' # rename soln & grid outputs
mv 'fort.9' 'fort.g'
mkdir ./'alfa='${ALPHAS[$VAL]}/; mv 'fort.'* *'.dat' $_ # move files
cp 'steady' $_
mv 'grid_in' 'fort.1' # ready input grid for next run
done
So what's going on?
You either need to remove the -n or add a p at the end of your sed command. The -n means sed won't automatically print the pattern space (and the p will print out the current match).
If you want sed to leave the rest of the file (steady) alone, then remove the -n.
sed -i 's/ALFA = .*/ALFA = '${ALPHAS[$VAL]}'/g' steady
If you want sed to delete the rest of the file (steady) and only print the matching ALFA line, add p like this:
sed -n -i 's/ALFA = .*/ALFA = '${ALPHAS[$VAL]}'/gp' steady

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')

Shell script to manipulate if loop

I am trying to modify a if loop in a large code base.My need is as follows.
The code may contain as follows.This is just a random combination example of an if condition.I need to modify if else if conditions alone.
if((A==B)&&(C==D)&&((E==F)||(G==H))||(I)&&(J!=K))
should be modified as
if((string.Compare(A,B)==0)&&(string.Compare(C,D)==0)&&((string.Compare(E,F)==0)||(string.Compare(G,H)==0))||(I)&&(string.Compare(J,K)!=0))
I tried with Java but utterly failed.I believe this is possible with sed or awk.Any help?
You could do it basically with any language that supports regular expressions replacement.
Here's a 3 lines working C# example:
string text = "if((A==B)&&(C==D)&&((E==F)||(G==H))||(I)&&(J!=K))";
string pattern = #"\((?:(\w)((?:=|!)=)(\w))\)";
var replaced = Regex.Replace(text, pattern, m => string.Format("(string.Compare({0},{1}){2}0)", m.Groups[1].Value, m.Groups[3].Value, m.Groups[2].Value));
Console.WriteLine(replaced);
And the result:
if((string.Compare(A,B)==0)&&(string.Compare(C,D)==0)&&((string.Compare(E,F)==0)||(string.Compare(G,H)==0))||(I)&&(string.Compare(J,K)!=0))
sed -r 's/([[:alpha:]])([=!]=)([[:alpha:]])/string.Compare(\1,\3) \2 0/g'
The spaces around \2 aren't strictly necessary.
Basically you don't need to use awk or sed to construct the if statement. It is enough to write:
if [ A == B -a C == D -a ( E == F -o G == H ) -o I -a J != K ]; then
your code here
fi
The == operator compares strings. If you wanted to compare numbers use -eq operator. See this chapter of bash manual (scroll down to test command), and the description of usage of primary conditional expressions

Resources