Need to check existence of flag in GNUmakefile - makefile

I am checking for existence of flag that is passed by user to GNUmakefile.
Basically, i am checking whether user has passed -j in my makefile. I have added below if condition. But before that i am trying to display MAKEFLAGS where i can see output is empty for that variable.
ifneq (,$(findstring j,$(MAKEFLAGS)))
....
Am i missing anything here?
Sometimes users may also pass --jobs instead of -j , And also i need to check whether the value passed to -j/--jobs is greater than 2
Is there any easy way in GNUmake for doing so in single if condition ?

The answer to your question depends on what version of GNU make you're using.
If you're using GNU make 4.1 or below, then the answer is "no, it's not possible" from within a makefile (of course you can always write a shell script wrapper around make and check the arguments before invoking make).
If you're using GNU make 4.2 or above, then the answer is "yes, it's quite possible". See this entry from the GNU make NEWS file:
Version 4.2 (22 May 2016)
The amount of parallelism can be determined by querying MAKEFLAGS, even when
the job server is enabled (previously MAKEFLAGS would always contain only
"-j", with no number, when job server was enabled).

This is a tricky question because MAKEFLAGS is a very strange make variable. First of all, with GNU make 4.3, -jN, -j N, --jobs N and --jobs=N are all converted to -jN in MAKEFLAGS, which looks interesting. You could thus try something like:
J := $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))
to get the N value passed on the command line or the empty string if -j and --jobs have not been used. But then, if you try the following you will see that it is not the whole story:
$ cat Makefile
.PHONY: all
J := $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))
ifneq ($(J),4)
all:
#echo MAKEFLAGS=$(MAKEFLAGS)
#echo patsubst...=$(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))
#echo J=$(J)
else
all:
#echo J=4
endif
$ make -j4
MAKEFLAGS= -j4 -l8 --jobserver-auth=3,4
patsubst...=4
J=
Apparently MAKEFLAGS is not set when the Makefile is parsed (and the J make variable is assigned the empty string) but it is when the recipes are executed. So, using MAKEFLAGS with conditionals does not work. But if you can move your tests in a recipe, something like the following could work:
.PHONY: all
all:
j=$(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS))); \
if [ -n "$$j" ] && [ $$j -gt 2 ]; then \
<do something>; \
else \
<do something else>; \
fi
Or:
.PHONY: all
J = $(patsubst -j%,%,$(filter -j%,$(MAKEFLAGS)))
all:
#if [ -n "$(J)" ] && [ $(J) -gt 2 ]; then \
<do something>; \
else \
<do something else>; \
fi
Note the use of the recursively expanded variable assignment (J = ...) instead of simple assignment (J := ...).

Related

Makefile evaluation question - foreach list doesnt evaluate before execution

Here's a snippet from a Makefile which in my environment is recursive and it appears this piece of code has a n issue and I don't understand why foreach doesn't evaluate. The variables TOOLVERSION and TOOLSDIR are assigned values based on running a Ruby script. These provide the include paths for the C_FLAGS. The target test2 produces the right result where every element of C_FLAGS seperated by space appears on a new line which is the desired result. However test1 does not evaluate TOOLVERSION and TOOLSDIR so produces garbage. If I use $(shell getenvval.rb version) for assigning TOOLVERSION and TOOLSDIR the foreach work but the value of running the script is incorrect. I thought this was because something defined in the Makefile environment doesn't get to the shell so I used export but it didn't make a difference.
So the question comes down to why does the following foreach loop not work:
#$(foreach flag, $(C_FLAGS), `echo $(flag) >> $(FILE_C1_LIST)`)
while this works:
#echo $(C_FLAGS) >> $(FILE_C2_LIST)
Appreciate any help in understanding the evaluation.
Snippet from Makefile:
export
TOOLVERSION:= `getenvval.rb version`
TOOLSDIR:= `getenvval.rb directory`
FILE_C1_LIST := test_c1.f
FILE_C2_LIST := test_c2.f
C_FILES =\
./a.c \
./b.c
C_FLAGS := \
-I$(TOOLSDIR)/$(TOOLVERSION)/tools/include \
-I./aa/include \
-I./bb/editline \
-g -DDEBUG -DPLISIM -DINCLUDE_EDITLINE -DSYS_UNIX
$(FILE_C1_LIST): $(C_FILES)
rm -f $(FILE_C1_LIST)
touch $(FILE_C1_LIST)
#(echo $(C_FLAGS) )
#$(foreach flag, $(C_FLAGS), `echo $(flag) >> $(FILE_C1_LIST)`)
#$(foreach file, $(C_FILES), `echo $(file) >> $(FILE_C1_LIST)` )
$(FILE_C2_LIST): $(C_FILES)
#rm -f $(FILE_C2_LIST)
#touch $(FILE_C2_LIST)
#echo $(C_FLAGS) >> $(FILE_C2_LIST)
#$(foreach file, $(C_FILES), `echo $(file) >> $(FILE_C2_LIST)` )
test1: $(FILE_C1_LIST)
test2: $(FILE_C2_LIST)
Your expectations are odd. You appear to try to use shell syntax in constructs which are not going to be evaluated by a shell; and even if they were, the expressions would not be syntactically valid.
Without delving too much into the details, try this instead.
printf '%s\n' $(C_FLAGS) >> $(FILE_C1_LIST)
In some more detail, your loop would create the text
`echo one >>file` `echo two >>file` `echo three >>file`
where the backticks imply that the output from echo should be the text of a command which you want the shell to execute; but of course, because of the redirection, there is no output to standard output from any of these commands. (The superficial problem of having all the commands on a single line could be worked around with a semicolon after each.)
As a further aside, there is no need to rm or touch a file you are going to overwrite. Just write it. Your recipes can be substantially simplified.
$(FILE_C1_LIST): $(C_FILES)
printf '%s\n' $(C_FLAGS) $(C_FILES) >$#
$(FILE_C2_LIST): $(C_FILES)
echo $(C_FLAGS) >$#
printf '%s\n' $(C_FILES) >>$#
The parentheses you had around the first echo would needlessly run that in a separate subshell. I'm guessing you had that purely to see what you were doing; removing the # on your recipes does that much better. You can run make silently witth make -s once you have everything debugged and sorted.

Makefile with variable number of targets

I am attempting to do a data pipeline with a Makefile. I have a big file that I want to split in smaller pieces to process in parallel. The number of subsets and the size of each subset is not known beforehand. For example, this is my file
$ for i in {1..100}; do echo $i >> a.txt; done
The first step in Makefile should compute the ranges,... lets make them fixed for now
ranges.txt: a.txt
or i in 0 25 50 75; do echo $$(($$i+1))'\t'$$(($$i+25)) >> $#; done
Next step should read from ranges.txt, and create a target file for each range in ranges.txt, a_1.txt, a_2.txt, a_3.txt, a_4.txt. Where a_1.txt contains lines 1 through 25, a_2.txt lines 26-50, and so on... Can this be done?
You don't say what version of make you're using, but I'll assume GNU make. There are a few ways of doing things like this; I wrote a set of blog posts about metaprogramming in GNU make (by which I mean having make generate its own rules automatically).
If it were me I'd probably use the constructed include files method for this. So, I would have your rule above for ranges.txt instead create a makefile, perhaps ranges.mk. The makefile would contain a set of targets such as a_1.txt, a_2.txt, etc. and would define target-specific variables defining the start and stop values. Then you can -include the generated ranges.mk and make will rebuild it. One thing you haven't described is when you want to recompute the ranges: does this really depend on the contents of a.txt?
Anyway, something like:
.PHONY: all
all:
ranges.mk: a.txt # really? why?
for i in 0 25 50 75; do \
echo 'a_$$i.txt : RANGE_START := $$(($$i+1))'; \
echo 'a_$$i.txt : RANGE_END := $$(($$i+25))'; \
echo 'TARGETS += a_$$i.txt'; \
done > $#
-include ranges.mk
all: $(TARGETS)
$(TARGETS) : a.txt # seems more likely
process --out $# --in $< --start $(RANGE_START) --end $(RANGE_END)
(or whatever command; you don't give any example).

Recursive make: correct way to insert `$(MAKEFLAGS)`

How can I use $(MAKEFLAGS) (or another way of passing variables defined on the command line to sub-make) in a way that supports invocation from shell with both make VAR=val and make -args?
I need my subprojects configurable, but I hate autotools, so I'm using make variables for this, e.g. invoking from shell:
$ make USE_SSE3=1
and USE_SSE3 needs to apply to all builds in all sub-makefiles.
The manual states that:
if you do ‘make -ks’ then MAKEFLAGS gets the value ‘ks’.
Therefore I'm using -$(MAKEFLAGS) (with a dash prefix) in my Makefile.
However, that expands into invalid arguments when variables with no flags are used. If I run:
$ make FOO=bar
then sub-make gets invalid -FOO=bar. OTOH without the dash prefix variable definitions work, then but make -s, etc. don't.
Is there a syntax/variable/hack that makes passing of arguments and lone variable definitions work with sub-makefiles?
The legacy $(MKFLAGS) doesn't have the weird dash prefix problem, but it doesn't include variable definitions either. I've tried fixing the variable with $(patsubst), but that only made things worse by trimming whitespace.
I need the solution to be compatible with the outdated GNU Make 3.81 shipped with Mac OS X Mavericks.
foo:
$(MAKE) -C subproject -$(MAKEFLAGS)
$ make foo -s # MAKEFLAGS = 's'
$ make foo BAR=baz # MAKEFLAGS = 'BAR=baz'
$ make foo -j8 # MAKEFLAGS = ' --job-server=…'
You shouldn't set MAKEFLAGS at all. Why do you want to? You didn't give any reason to do so.
MAKEFLAGS is intended, really, to be an internal implementation passing arguments from a parent make to a child make. It's not intended, generally, to be modified by a makefile. About the only thing you can usefully do to it is add new flags.
If you just run the recursive make using the $(MAKE) variable rather than hardcoding make, it will Just Work:
recurse:
#$(MAKE) all FOO=bar
or whatever.
Years too late I got your answer if I got it right.
You can construct $(MAKEARGS) manually yourself like:
MAKEARGS := $(strip \
$(foreach v,$(.VARIABLES),\
$(if $(filter command\ line,$(origin $(v))),\
$(v)=$(value $(v)) ,)))
MAKEARGS := assign static
strip cleans leading and trailing whitespaces.
foreach v iterate over all variable names.
origin $(v) check if variable origin is "command line".
$(v)=$(value $(v)) output env assignment string.
Alternatively you can unpick the $(MAKEFLAGS) like:
MAKEARGS := $(wordlist 2,$(words $(MAKEFLAGS)),$(MAKEFLAGS))
MAKEFLAGS := $(firstword $(MAKEFLAGS))
Which can leave you with cleaner code for further recursions IMHO. I say this because I sometimes need to keep apart arguments and flags in certain cases. Especially as you get caught in debugging a recursion djungle.
But for any specific case one should consult the manual about recursive options processing.
Changing the $(MAKEFLAGS) can lead to unwanted malfunction.
Another useful information for the willing user could be that the $(MAKEFLAGS) variable is basically the whole argument list passed to make, not only the flag characters. So $(info MAKEFLAGS = $(MAKEFLAGS)) can give you something like:
MAKEFLAGS = rRw -- VAR=val
Cheers
To check if -B is present in make flags i do :
BB_CLOBBER := $(shell echo $(MAKEFLAGS) | grep wB)
ifeq (,$(BB_CLOBBER))
# also force clobber make if these files are missing
BB_CLOBBER := $(shell (test -e $(bb_gen)/minimal/.config && test -e $(bb_gen)/full/.config) || echo "B")
endif
bb_prepare:
ifneq (,$(BB_CLOBBER))
#rm -rf $(bb_gen)/full
...

stop on error when target of makefile rule is a foreach function

I have a makefile that defines several rules where the target is a foreach function.
$(foreach var,$(list), $($(var)_stuff) $($(var)_more_stuff)):
#echo Building $# from $^...
$(CC) $(FLAGS) ...
Is there any way to get make to quit when encountering an error without going through the entire list.
One workaround is to "manually" invoke exit on failure.
For example, assume we have a directory called scripts with a number of shell scripts (with filenames that end with .sh) that we want to execute.
Then a variable declaration like this:
LIST_OF_SCRIPTS ?= $(wildcard scripts/*.sh)
will give us a list of those scripts, and a target like this:
run-all-scripts
#$(foreach scriptfile,$(LIST_OF_SCRIPTS),$(scriptfile);)
will run all of those scripts, but as you note, the foreach loop will keep going whether or not one of the scripts returns an error code. Adding a || exit to the command will force the subcommand to exit on error, which Make will then treat as a failure.
E.g.,
run-all-scripts
#$(foreach scriptfile,$(LIST_OF_SCRIPTS),$(scriptfile) || exit;)
will do what you want (I believe).
Specifically, using your pseudo-code example, I think you want something like this:
$(foreach var,$(list), $($(var)_stuff) $($(var)_more_stuff)):
#echo Building $# from $^...
($(CC) $(FLAGS) ...) || exit
(where all I've changed is wrapping the (CC) $(FLAGS) ... bit in parens and appending || exit to make it fail on error).
The foreach is completely evaluated and substituted before any of the rules are executed. So the behaviour of this should be identical to as if you had hardcoded the rule without using the foreach. In other words, it's not directly relevant to the problem.
There are only a few possible explanations for what you're seeing, mostly described in the manual here:
You are running Make with -k or --keep-going
You are running Make with -i or --ignore-errors
Your targets is defined as prerequisites of the special .IGNORE target
Your recipe starts with a -
Your recipe isn't actually returning a non-zero exit status
Not sure about your example, but maybe problem is in ; - look at Makefile : show and execute:
dirs = $(shell ls)
clean:
$(foreach dir,$(dirs),echo $(dir);)
produce:
$ make clean
echo bin; echo install.sh; echo Makefile; echo README.md; echo utils;
So make check exit code only for last command: echo utils.

ifeq issue I try to understand

is my first question here and first time I manage GNU Make so I want to explain my problem.perhaps you could help me to find a light at the end of this tunnel.
That thing Im trying to do is to check a word into my path and do something after check path
I've got that code on make:
WORD=GNUMAKE; \
FOUND=1; \
echo "$$FOUND"; \
PWD=$(PWD); \
ifeq ($(findstring $$WORD,$$PWD),) \
$(warning list contains "$$WORD") \
endif
but when I run $make I get this error, for me so strange and can't find a solution
could you please help me?
/bin/sh: syntax error at line 1: `ifeq' unexpected
make: *** [all] Error 2
Thank you
Gnu make treats lines joined with \ as a single line. ifeq et. al. need to be on their own line, rather like #ifdef in C (if that's any help to you).
You seem rather confused over what make does.
Make executes a makefile in three distinct phases:
It reads in the Makefile, building a graph in memory, saving macros/expanding macros as necessary.
It looks at what you asked it to make, and decides how to walk the graph.
It walks the graph, expanding the shell recipes before passing the manufactured string to the shell.
You can get make to do your bidding as it reads the makefile
WORD = GNUMAKE
FOUND = 1
$(warning ${FOUND})
ifneq ($(findstring ${WORD},${CURDIR}),)
$(warning list contains "${WORD}")
endif
Or you can get make to do this just as it is making the command to pass to the shell (i.e., before the shell is executed):
.PHONY: target
target:
$(if $(findstring ${WORD},${CURDIR}),$(warning list contains "${WORD}"))echo Shell command here
Or indeed get the shell to do it.
You are messing make commands with shell commands. ifeq is apparently belongs to make but got into shell somehow.
This will find occurance of GNUMAKE word in current path, i.e. it will be one of parent directories. Put this into Makefile and call make.
INPUT := $(shell pwd | tr -s "/" " ")
WORD=GNUMAKE
ifneq ($(filter $(WORD),$(INPUT)),)
$(warning list contains $(WORD))
endif
WORD = GNUMAKE
FOUND = 0
$(warning $$FOUND)
ifneq ($(findstring $$WORD,$(PWD)),)
$(warning list contains $$WORD)
endif
that is exactly what I have been set
hope it helps

Resources