I am trying to get this rule to work. Its totally blowing up my whole Makefile.
It should validate that the value for version the user passed exists in the PYVERSION variable.
The actual behavior is mixed because I've tried so many different things. I'm no GNU expert so I've tried ${version} and $(version) and $version and the same things for PYVERSION. The current version I've posted here always goes to the else block, not matter the version input.
PYVERSIONS := "3.5 3.6 3.7 3.8"
.PHONY: venv
venv:
if test $(findstring ${version}, $(PYVERSIONS)); then
/Library/Frameworks/Python.framework/Versions/${version}/bin/python3 -m venv venv
else
$(error Bad python version given (${version}) project only supports ${PYVERSIONS})
fi;
calling it like make venv version=3.5 should successfully execute the if block and create the venv.
calling it like make venv version=2.7 should raise the error message in the else block.
I was trying to follow this post
Update
With help from #Beta, this is the final solution I got to work.
PYVERSIONS := "3.6 3.7 3.8"
PYPATH := /Library/Frameworks/Python.framework/Versions
.PHONY: venv
venv:
#if test $(findstring ${version}, $(PYVERSIONS)) ; \
then \
echo "Creating virtual environment for python ${version}"; \
$(PYPATH)/${version}/bin/python3 -m venv venv; \
else \
echo "Unsupported python version (${version}). Project supports $(PYVERSIONS)"; \
fi
Actually disagree with #Beta here. Since the answer is known before you even start executing the recipe, $(error …) is the clear way to go.
Now, make stores the entire recipe as a single recursively expanded variable. When make decides to build venv (for instance, you type make venv), it will expand the entire recipe, before passing each resulting line one-by-one to fresh instances of the shell.
Thing is, when make expands your recipe,
it always expands $(error Bad python version given …), and make stops even before calling the shell.
How do we get around this?
PYVERSIONS := 3.6 3.7 3.8
PYPATH := /Library/Frameworks/Python.framework/Versions
pyversion = $(or $(filter ${version},${PYVERSION}),$(error $$version [${version}] must be exactly one of ${PYVERSIONS}))
.PHONY: venv
venv:
echo "Creating virtual environment for python ${version}"
$(PYPATH)/${pyversion}/bin/python3 -m venv venv
So,
You type make venv
Make expands the recipe
Make expands pyversion
Make expands $(filter ${version},3.6 3.7 3.8)
If you have not set version on the command-line or environment to one of the three blessed strings,
make stops there and then with a helpful message NICE
OTOH if it has been set appropritely, make is happy and starts executing the recipe
Other nice characteristics:
No shell syntax
The exit code of python3 is returned to make (a big hole in your original recipe imho).
Things always look better in make rather than the shell.
You are mixing shell syntax with Make syntax.
If you want to handle this with a shell conditional, you must put the whole thing on one line in the recipe (since each line executes in its own subshell):
venv:
if test $(findstring ${version}, $(PYVERSIONS)) ; then echo $(version)/bin/python3 venv; else echo bad version $(version); fi
or you can wrap the line by means of backslashes:
venv:
if test $(findstring ${version}, $(PYVERSIONS)) ; \
then \
echo $(version)/bin/python3 venv; \
else \
echo bad version $(version); \
fi
(Note that there is only one TAB, in front of if.)
If you want to use a Make conditional within the rule:
venv:
ifeq ($(findstring ${version}, $(PYVERSIONS)),)
echo bad version $(version);
else
echo echo $(version)/bin/python3 venv;
endif
If you want to use the Maker error command, you have a problem. Make will evaluate the conditional before executing any rule, so if you use error, then if no valid version number is given, Make will throw the error even if venv was not the target. It is possible to use error, but it's a pain, so you must first decide whether the effect is worth the price.
Related
So I have a really nice make macro that creates me a pretty message during compilation that is done in such a way that the destination of the message can be implemented externally.
It uses the $(shell ) make function something like this.
send_msg = $(shell $do_send_msg "$1")
Where do_send_msg can be expanded to be echo, wall, email, etc.
I use this lots in the makefiles.
I'd like to be able to use the same macro in recipes as the result of the recipe may change what is needed to be sent, dependent on some test made to the result of an external program.
However variables in recipes are expanded by make entirely before the lines in the recipe are called one at a time.
So if I write (e.g.)
if test_prog; then \
$(call send_msg,PASS);\
else \
$(call send_msg,FAIL);\
fi
Effectively this becomes (not strictly true syntax, but close enough):
(bash -c 'echo "PASS"') &
(bash -c 'echo "FAIL"') &
(bash -c 'if test_prog; then \
\
else \
\
fi)'
This of course will not work, it will run test_prog and, separately, regardless of the exit status both send_msg lines will expand and be executed.
So it will print both PASS and FAIL as the two subshells are run independently and in parallel by make.
I don't want to 'cheat' and use another variable in the $(call ) function or even worse a global that, ok, would allow an if some_var ... in the send_msg implementation but would reduce it's flexability as the implementation would have to understand that variable in all cases.
Another way would be to just have two different send_msg macros, one with and one without the $(shell ) function. Simple, but not elegant.
Right now I am using a 'hack' and calling the same makefile with a variable. If that variable is set then it sends it's contents otherwise does nothing. This works fine but to me it seems clunky and wrong, there must be a better way.....
e.g.
if test_prog; then \
MESSAGE=PASS $(MAKE) message;\
else \
MESSAGE=FAIL $(MAKE) message;\
fi
Where the Makefile says (and this is an abreviated version to convey the idea)
ifneq ($(MESSAGE),)
message:
$(call send_msg,$(MESSAGE))
else:
message:
endif
Question:
How would I make (make) detect if the macro is being expanded inside a Makefile recipe or inside the Makefile body and effectively keep or remove the $(shell ) call that wraps how the work is done?
e.g. (if make_or_shell existed)
ifdef some_test
$(make_or_shell send_msg,"Message from Make")
endif
goal:
$(make_or_shell send_msg,"Message from Recipe")
I have a few software projects which are distributed as RPMs. They are versioned using semantic versioning to which we affix a release number. Using the regular conventions, this is MAJOR.MINOR.PATCH-REL_NUM. Though beyond the scope of this article, the release numbers are stored in git. The release target in the makefile looks something like this:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
While debugging, I eventually discovered that, although the call to eval was the third step in the recipe, it was actually being evaluated first! This is why the RPM always had a release number one less than the number I was watching get pushed to the remote.
I have done much googling on this and I haven't found any hits that explain the order of evaluation with regard to eval when used in recipes. Perhaps it isn't even with respect to eval but functions in general. Furthermore, I haven't found verbiage on this in the GNU manuals for make either (if it's there, kindly point out what chapter). I've worked around the problem so it's not a bother, I'm just wondering, is this expected and if so, why?
The missing bit, that no one above is getting, is simple: when make is going to run a recipe it expands all lines of the recipe first, before it starts the first line. So:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
# Although the third step, this was re-ordered to step 1
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
when make decides to run the release target it first expands all the lines in the recipe, which means the eval is expanded, then it runs the resulting lines. That's why you're getting the behavior you're seeing.
I don't really see why you need to use eval here at all; why not just use:
release:
$(MAKE) clean
$(BLD_ROOT)/tools/incr_rel_num
$(MAKE) rpm RPM_RELEASE_NUM="$$(cat $(BLD_ROOT)/path/to/rel_num.txt))"
(BTW, you should never use bare make inside your makefiles; you should always use $(MAKE) (or ${MAKE}, same thing).
The $(eval ...) function
generates a fragment of make-sytax which becomes part of the parsed makefile.
The makefile is parsed entirely before any recipes are executed and when recipes
are executed all make-statements, make-expressions and make-variables have been
evaluated away.
So it does not make sense to consider an $(eval ...) call as being one
of the lines of a recipe. It might generate values that are used in the make-expansion
of the recipe, but if so then this happens when the makefile is parsed, before the recipe is run.
Thus in your example, the line:
$(eval RELEASE_NUMBER=$(shell $(BLD_ROOT)/path/to/rel_num.txt))
which I assume should really be:
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
is evaluated when the makefile is parsed, and let's say it results in the
make-variable RELEASE_NUMBER acquiring the value 1.0, because, when the
makefile is parsed, the file $(BLD_ROOT)/path/to/rel_num.txt) contains
1.0. In that case your recipe:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
$(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt))
make rpm RPM_RELEASE_NUM=$(RELEASE_NUMBER)
will resolve to the like of:
release:
make clean
some_build_dir/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=1.0
You will observe when make runs the recipe that it prints no line that
is "the expansion of" $(eval RELEASE_NUMBER=$(shell cat $(BLD_ROOT)/path/to/rel_num.txt)),
because there is no such thing in the recipe. It doesn't matter that:
some_build_dir/tools/incr_rel_num
is presumably a command that writes, say, 1.1 or 2.0 in the file some_build_dir/path/to/rel_num.txt.
That action simply has no effect on the recipe. Nothing that executed in the recipe
can change the recipe.
$(eval ...) has no business in your recipe. What you want to achieve is simply:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
RELEASE_NUMBER=$$(cat $(BLD_ROOT)/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$$RELEASE_NUMBER
where $$ is what you do in a makefile to escape $ and, in this case,
leave it for the shell when the recipe is executed.
This recipe expands to 3 shell commands executed in sequence:
$ make clean
$ some_build_dir/tools/incr_rel_num
$ RELEASE_NUMBER=$(cat some_build_dir/path/to/rel_num.txt) && \
make rpm RPM_RELEASE_NUM=$RELEASE_NUMBER
and might as well be simplified further to:
release:
make clean
$(BLD_ROOT)/tools/incr_rel_num
make rpm RPM_RELEASE_NUM=$$(cat $(BLD_ROOT)/path/to/rel_num.txt)
You are correct, there are multiple levels of evaluation. The content on what is inside eval is evaluated a first time before that the function is actually called. If you want the content of eval to be evaluated at the time eval is called, you have to escape the $ sign by putting it twice, like this :
$(eval RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
To view what is really inside eval at the time it's called you can use the same syntax with info instead of eval :
$(info RELEASE_NUMBER=$$(shell $(BLD_ROOT)/path/to/rel_num.txt))
Now I'm not sure about the part which is evaluated too soon so the $ symbols that I doubled may not be the good one(s), but using the info function will help you to find the correct command.
I have a makefile which calls itself, in order to obtain a license for the compiler before compiling anything, and release the license even when compilation fails, like this:
.PHONY main_target
main_target:
#license_grab &
#sleep 2
-#$(MAKE) real_target
#license_release
This works great if the makefile is named "makefile". But if I make a copy of the makefile to experiment with something, and invoke it with make -f makefile_copy, then the wrong makefile gets used in the recursive call. How do I prevent this without hard-coding the makefile name in the makefile itself?
Edit: Unfortunately I'm stuck using GNU Make version 3.79.1, so I cannot use MAKEFILE_LIST, which was apparently introduced in version 3.80. Therefore none of the answers in this question will work for me.
You can use the MAKEFILE_LIST variable:
THIS_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
.PHONY main_target
main_target:
#license_grab &
#sleep 2
-#$(MAKE) -f $(THIS_MAKEFILE) real_target
#license_release
You can set the MAKE variable outside the makefile, to include the makefile name (unless of course, it gets overridden). Something like this (for bash):
MAKE="make -f makefile_copy" make -e -f makefile_copy
or this (in pretty much any shell):
make MAKE="make -f makefile_copy" -f makefile_copy
Since i am not so experienced with the building process / makefiles on linux i ran in follow problem:
the Setup:
i have an makefile A, which needs some enviroment variables set before running, this is done by running . ./set_A_vars.sh (set_A_vars.sh contains many export lines) before running make -f A
now i need to make project A within a makefile B.
i tried the following setup for makefile B:
all: debug release
A_debug:
. ./set_A_vars.sh && make -f A DEBUG=1
A_release:
. ./set_A_vars.sh && make -f A DEBUG=0
debug: A_debug some_B_stuff_debug
release: A_release some_B_stuff_debug
however i get lots of errors, which sound like the enviroment variables in set_A_vars.sh have not been set for make -f A ... in B.
How can i call makefile A from makefile B with the enviroment variables in set_A_vars.sh set in makefile B ??
Any help appreciated.
Your makefile looks good with these provisos:
When you call make from a makefile, please use the macro invocation ${MAKE} rather than plain make. (This ensures parallel make works, and also means it still works even if your make has another name (GNUmake say).)
If your targets do not correspond to actual files, then mark them with .PHONY (see below).
Does some_B_stuff_debug require A to be built first? Then you must tell make this.
some_B_stuff_debug: A_debug
some_B_stuff_debug: A_release
This is clearly wrong. One way is to enforce the ordering via the shell.
Try something like this:
.PHONY: debug
debug:
. ./set_A_vars.sh && ${MAKE} -f A DEBUG=1
${MAKE} some_B_stuff_debug
.PHONY: release
release:
. ./set_A_vars.sh && ${MAKE} -f A DEBUG=0
${MAKE} some_B_stuff_debug
.PHONY: some_B_stuff_debug
∶
Your makefiles should work. I suggest you try the following:
Try running set_A_vars.sh from the command line.
Verify that the variables you wanted set are set.
make -f MakefileA, to verify that MakefileA really does work nicely with these variables set.
Try a rule in MakefileB that will test one of the variables, say FOO:
test_var:
#echo FOO is $(FOO)
This should work if you have just run set_vars.sh. If it doesn't, then there are a couple of things that could be wrong...
Now clear the variables (including FOO) and try this rule in MakefileB:
set_vars_and_test_them:
./set_A_vars.sh && echo FOO is $(FOO)
Now put it together:
A_debug:
./set_A_vars.sh && make -f MakefileA DEBUG=1
(I recommend against calling a makefile "A".)
I need to execute some make rules conditionally, only if the Python installed is greater than a certain version (say 2.5).
I thought I could do something like executing:
python -c 'import sys; print int(sys.version_info >= (2,5))'
and then using the output ('1' if ok, '0' otherwise) in a ifeq make statement.
In a simple bash shell script it's just:
MY_VAR=`python -c 'import sys; print int(sys.version_info >= (2,5))'`
but that doesn't work in a Makefile.
Any suggestions? I could use any other sensible workaround to achieve this.
Use the Make shell builtin like in MY_VAR=$(shell echo whatever)
me#Zack:~$make
MY_VAR IS whatever
me#Zack:~$ cat Makefile
MY_VAR := $(shell echo whatever)
all:
#echo MY_VAR IS $(MY_VAR)
Beware of recipes like this
target:
MY_ID=$(GENERATE_ID);
echo $MY_ID;
It does two things wrong. The first line in the recipe is executed in a separate shell instance from the second line. The variable is lost in the meantime. Second thing wrong is that the $ is not escaped.
target:
MY_ID=$(GENERATE_ID); \
echo $$MY_ID;
Both problems have been fixed and the variable is useable. The backslash combines both lines to run in one single shell, hence the setting of the variable and the reading of the variable afterwords, works.
I realize the original post said how to get the results of a shell command into a MAKE variable, and this answer shows how to get it into a shell variable. But other readers may benefit.
One final improvement, if the consumer expects an "environment variable" to be set, then you have to export it.
my_shell_script
echo $MY_ID
would need this in the makefile
target:
export MY_ID=$(GENERATE_ID); \
./my_shell_script;
Hope that helps someone. In general, one should avoid doing any real work outside of recipes, because if someone use the makefile with '--dry-run' option, to only SEE what it will do, it won't have any undesirable side effects. Every $(shell) call is evaluated at compile time and some real work could accidentally be done. Better to leave the real work, like generating ids, to the inside of the recipes when possible.
Wrapping the assignment in an eval is working for me.
# dependency on .PHONY prevents Make from
# thinking there's `nothing to be done`
set_opts: .PHONY
$(eval DOCKER_OPTS = -v $(shell mktemp -d -p /scratch):/output)
With GNU Make, you can use shell and eval to store, run, and assign output from arbitrary command line invocations. The difference between the example below and those which use := is the := assignment happens once (when it is encountered) and for all. Recursively expanded variables set with = are a bit more "lazy"; references to other variables remain until the variable itself is referenced, and the subsequent recursive expansion takes place each time the variable is referenced, which is desirable for making "consistent, callable, snippets". See the manual on setting variables for more info.
# Generate a random number.
# This is not run initially.
GENERATE_ID = $(shell od -vAn -N2 -tu2 < /dev/urandom)
# Generate a random number, and assign it to MY_ID
# This is not run initially.
SET_ID = $(eval MY_ID=$(GENERATE_ID))
# You can use .PHONY to tell make that we aren't building a target output file
.PHONY: mytarget
mytarget:
# This is empty when we begin
#echo $(MY_ID)
# This recursively expands SET_ID, which calls the shell command and sets MY_ID
$(SET_ID)
# This will now be a random number
#echo $(MY_ID)
# Recursively expand SET_ID again, which calls the shell command (again) and sets MY_ID (again)
$(SET_ID)
# This will now be a different random number
#echo $(MY_ID)
Here's a bit more complicated example with piping and variable assignment inside recipe:
getpodname:
# Getting pod name
#eval $$(minikube docker-env) ;\
$(eval PODNAME=$(shell sh -c "kubectl get pods | grep profile-posts-api | grep Running" | awk '{print $$1}'))
echo $(PODNAME)
I'm writing an answer to increase visibility to the actual syntax that solves the problem. Unfortunately, what someone might see as trivial can become a very significant headache to someone looking for a simple answer to a reasonable question.
Put the following into the file "Makefile".
MY_VAR := $(shell python -c 'import sys; print int(sys.version_info >= (2,5))')
all:
#echo MY_VAR IS $(MY_VAR)
The behavior you would like to see is the following (assuming you have recent python installed).
make
MY_VAR IS 1
If you copy and paste the above text into the Makefile, will you get this? Probably not. You will probably get an error like what is reported here:
makefile:4: *** missing separator. Stop
Why: Because although I personally used a genuine tab, Stack Overflow (attempting to be helpful) converts my tab into a number of spaces. You, frustrated internet citizen, now copy this, thinking that you now have the same text that I used. The make command, now reads the spaces and finds that the "all" command is incorrectly formatted. So copy the above text, paste it, and then convert the whitespace before "#echo" to a tab, and this example should, at last, hopefully, work for you.
In the below example, I have stored the Makefile folder path to LOCAL_PKG_DIR and then use LOCAL_PKG_DIR variable in targets.
Makefile:
LOCAL_PKG_DIR := $(shell eval pwd)
.PHONY: print
print:
#echo $(LOCAL_PKG_DIR)
Terminal output:
$ make print
/home/amrit/folder
From the make manual
The shell assignment operator ‘!=’ can be used to execute a shell script and set a >variable to its output. This operator first evaluates the right-hand side, then passes >that result to the shell for execution. If the result of the execution ends in a >newline, that one newline is removed; all other newlines are replaced by spaces. The >resulting string is then placed into the named recursively-expanded variable. For >example:
hash != printf '\043'
file_list != find . -name '*.c'
source