how to read from stdin into a makefile variable - makefile

I want to write a Makefile to install go program and other assets, so I want a installdir. I want to check if GOBIN, GOPATH is set, if not, want user to enter a installdir.
I wrote the Makefile as following, but the makefile variable installdir is empty. echo output nothing.
installdir:=$(shell echo $(GOPATH) | cut -d':' -f1)
all: *.go
#GO111MODULE=on GOPATH=$(GOPATH) go build -o trpc main.go
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
Please help!

This code:
install:
ifeq ($(installdir),)
installdir=$(shell echo $(GOBIN) | cut -d':' -f1)
endif
ifeq ($(installdir),)
installdir=$(shell bash -c 'read -s -p "Please input installdir: " tmpdir; echo $$tmpdir')
endif
echo $(installdir)
simply cannot work and represents a fundamental misunderstanding of how make operates.
Make works in two distinct stages: first, it reads and parses all the makefiles, included makefiles, etc. Second, it determines which targets are out of date and runs the recipes to update those targets. Recipes will start a shell and pass the recipe text to that shell. When the shell exits make determines whether it worked or not by looking at the exit code. All make variables and functions in the entire recipe are expanded first, the the shell is invoked on the results. Further, every logical line in the recipe is started in a different shell.
So in your makefile, the ifeq options (which are makefile constructs) are parsed during the first stage, as the makefile is read in. The recipe lines are not run until the second stage, so changes to the installdir variable in a recipe cannot impact the ifeq lines. Further, changes to installdir in a recipe cannot even be seen by make because they happen in a shell, then the shell exits and those changes are lost.
You'll have to write this entire thing in shell syntax and put all of it into a recipe, something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || read -s -p "Please input installdir: " installdir; \
echo $$installdir
(untested). You have to use shell constructs, not make constructs. You should virtually never use the $(shell ...) make function inside a recipe: a recipe is already running in a shell. And you have to use backslash/newline pairs to ensure make considers the entire recipe one logical line, else variables set on one line will not be set on the next line.
Finally, I should point out that this (reading input during make) is just generally a bad idea. For example, if you run make install with the -j option, only one recipe can have control of stdin and make will choose more-or-less randomly which it is.
Generally instead you want to have the user pass the value on the command line, with something like:
$ make installdir=my/dir
so your check in the makefile should instead be something like this:
install:
installdir='$(installdir)'; \
[ -n "$$installdir" ] || installdir=$$(echo $(GOBIN) | cut -d':' -f1); \
[ -n "$$installdir" ] || { echo "Please add installdir=... on the command line"; exit 1; }; \
echo $$installdir

Related

Cannot convert bash script with if egrep on Makefile

I would like to convert and execute
if egrep -r 'my_pattern' ./template_builder
then exit 1
elif egrep -r 'my_second_pattern' ./template_builder
then exit 1
fi
in a Makefile, without success for now.
To build this:
cd /tmp;
mkdir template_builder;
echo "not_pattern" >> ./template_builder/test.txt
# Do the command at the top, nothing happens
echo "my_pattern" >> ./template_builder/test.txt
# Do the command at the top, terminal stops
touch Makefile
In a Makefile, I thought this would work :
check:
if egrep -r 'my_pattern' ./template_builder
then exit 1
elif egrep -r 'my_second_pattern' ./template_builder
then exit 1
fi
make check
if egrep -r 'my_pattern' ./template_builder
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [template] Error 2
How can I fix this?
Your attempt was not far from working!
Add backslashes at the end of every line, and ;s as explicit command separators (and of course use real tabs instead of the 8-space indents below):
check:
if egrep -r 'my_pattern' ./template_builder; \
then exit 1; \
elif egrep -r 'my_second_pattern' ./template_builder; \
then exit 1; \
fi
If I understand you correctly, if the directory template_builder located in /tmp does not contain a file matching the string 'my_pattern' or 'my_second_pattern', you want to exit from make with an error code.
You can achieve this with this rule in Makefile:
check:
egrep -r -v 'my_pattern' /tmp/template_builder || egrep -r -v 'my_second_pattern' /tmp/template_builder
Explanation: the first egrep is going to return an error in the case he finds a match. Due to the presence of the || operator, the second egrep will be invoked. The result of this second command will be the result that make will see. If it returns an error, the execution of make is aborted, which seems to be the behaviour you are expecting.
Caution: I edited my answer. The right boolean operator is || and not &&.
As others have already noted, make runs each separate line in a recipe in a new shell subprocess. (For the record, it uses sh out of the box, not Bash.) The trivial fix is to add a backslash to escape the newline at the end of each line which should be executed in the same shell as the next one. (You need to add a semicolon as well in some places, like before then and else and fi.) But you really want to refactor to use the facilities and idioms of make.
The default logic of make is to terminate the recipe if any line fails. So, your code can be reduced to simply
check: template_builder
! egrep -r 'my_pattern' $<
! egrep -r 'my_second_pattern' $<
The explicit exit 1 is not necessary here (negating a zero exit code produces exactly that); but if you wanted to force a particular exit code, you could do that with
egrep -r 'my_pattern' $< && exit 123 || true
Modern POSIX prefers grep -E over legacy egrep; of course, with these simple patterns, you can use just grep, or even grep -F (née fgrep).
Moreover, if you want to search for both patterns in the same set of files, it's much more efficient to search for them both at once.
check: template_builder
! egrep -e 'my_pattern' -e 'my_second_pattern' -r $<
... or combine them into a single regex my_(second_)?pattern (which requires egrep / grep -E).
Notice also how I factored out the dependency into $< and made it explicit; but you probably want to make this recipe .PHONY anyway, so that it gets executed even if nothing has changed.
(You can't directly copy/paste this code, because Stack Overflow stupidly renders the literal tabs in the markdown source as spaces.)

Makefile: Reuse environment variables

In my Makefile deploy target I create environment variables and I want to reuse those in the following lines:
SHELL=/bin/sh
deploy:
export $(shell sh this-script-generate-key-values.sh | xargs)
echo ${VAR1} #there is no variable here
echo ${VAR2} #there is no variable here
Where:
this-script-generate-key-values.sh generates this output:
VAR1="somevalue"
VAR2="somevalue"
Why the variables are not set in subsequent lines? How can I make it work?
Notes:
This line works: sh this-script-generate-key-values.sh | xargs
The shell must be /bin/sh (no bash)
All lines in a Makefile recipe run in a separate shell. You need to run the lines in a single shell. Also you need to escape the dollar sign ($) so that variable substitution is not done by make but by the shell.
SHELL=/bin/sh
deploy:
export $$(this-script-generate-key-values.sh | xargs) ;\
echo $${VAR1} ;\
echo $${VAR2}
Just to expand on my comment -- you could output to a file, and use the file to generate your output as so:
vars.txt:
this-script-generate-key-values.sh > $#
deploy : vars.txt
echo VAR1=$$(sed -n 's|VAR1=\(.*\)|\1|p' vars.txt)
echo VAR2=$$(sed -n 's|VAR2=\(.*\)|\1|p' vars.txt)
note: you may have to generate dependencies for vars.txt or declare it .PHONY, otherwise, this will not run on every invocation of make.
If the .ONESHELL special target appears anywhere in the makefile then all recipe lines for each target will be provided to a single invocation of the shell. Newlines between recipe lines will be preserved.
.ONESHELL:
deploy:
export $$(this-script-generate-key-values.sh)
echo $${VAR1}
echo $${VAR2}

run before and after each target in a makefile

I would like to run a command before and after each target in a makefile.
so something like this
pre:
#echo pre
#echo running | mailx -s "Start {target}" user#foo.com
post:
#echo post
#echo post | mailx -s "Finish {target}" user#foo.com
j:
long_running_command && echo $# > $#
k: j
long_running_command2 && echo $# > $#
I would like to run pre and post for j and k. Ideally, I would like to get an email for each task that starts and stops.
One way to do it is to modify all the recipes in your makefile to invoke some command. You can put it into a variable so it doesn't look too gross:
START = mailto --subject 'Started target $#' me#my.host
END = mailto --subject 'Finished target $#' me#my.host
j:
$(START)
long_running_command && echo $# > $#
$(END)
k: j
$(START)
long_running_command2 && echo $# > $#
$(END)
The nice thing about this is you can pick and choose which targets you want it for; maybe some of them don't need it. The disadvantage is if the command fails you won't get any "end" email at all.
If you really want to do it for every single target, then you can write a shell script that mimics the shell's behavior but also sends mail, while running the command.
$ cat mailshell
#!/bin/sh
# get rid of the -c flag
shift
mailto --subject "started $*" me#my.host
/bin/sh -c "$#"
r=$?
mailto --subject "ended $* with exit code $r" me#my.host
exit $r
(note this is totally untested but you get the idea I hope). Then in your makefile, set SHELL to that shell:
SHELL := mailshell
j:
long_running_command && echo $# > $#
k: j
long_running_command2 && echo $# > $#
I guess you could still pick and choose by setting SHELL as a target-specific variable only for those targets you wanted to use the shell.
One downside of this is that if you have recipes that have multiple lines you'll get an email for each line individually. You can work around this by enabling .ONESHELL: to force the entire recipe to be passed to a single shell. However, I believe that this may require your mailshell tool to be more sophisticated.
If you only have one command per recipe, you can do this by changing the configuration for the shell the commands are run in.
Have the config file run the pre commands directly and trap EXIT to run the after commands in.
For example:
$ cat Makefile
SHELL := BASH_ENV=/dev/fd/3 3<<<'echo before; trap "echo after" EXIT' /bin/bash
default:
echo default
other:
echo first
echo second
$ make default
echo default
before
default
after
However this may not be what you want if a recipe runs several commands, as the before and after code will run each time.
$ make other
echo first
before
first
after
echo second
before
second
after
And I don't know of a way (outside of recursive Makefiles) to have different recipes use different shells, so this won't work if you only want to set before/after for several recipes.

makefile: using for and ifneq

I have an objective in my makefile named "cambios" that makes a cvs commit on each file of the project (by separate) and shows the last revision.
Now, I have an auxiliar shellscript that do that, but I'd like to know how I can do it in the makefile. I've created the objective cambios2 that do the same without the auxiliar shellscript, but it has some syntax problems.
makefile:
(...)
TODO= makefile cambiosaux.sh lib/libreria.cc include/libreria.h src/principal.cc
(...)
cambios:
#./cambiosaux.sh "$(TODO)"
cambios2:
#for dir in $(TODO); do \
A = $(cvs commit -m "Incorporando cambios automáticamente." $$dir) \
ifneq ($(A),)
echo $dir ; \
echo "Última revisión:"$(echo $(A) | sed 's/.*new revision: //' | sed 's/;.*//') ; \
endif ; \
done
cambiosaux.sh :
for dir in $1
do
A=$(cvs commit -m "Incorporando cambios automáticamente." $dir)
if [ "$A" != "" ]; then
echo $dir
echo "Última revisión:"$(echo $A | sed 's/.*new revision: //' | sed 's/;.*//')
fi
done
There are some syntax problems in the objective cambios2, but I'm really new on doing makefiles and I really don't know how to solve that problems.
Thanks!
You forgot to escape dollars that are parts of Bash command command substitution, and Make tries to perform variable expansion: $(cvs commit ...), $(echo $(A) ...).
Also you can't assign a Make variable inside a recipe. A = $(cvs commit ...) is illegal, it won't be treated neither as Make assignment nor as Bash. Try to run make with --warn-undefined-variables, I guess it will say lots of interesting details.
Finally ifneq conditional is part of Make language, and it gets interpreted at the very early stage of reading Makefile. Thus you must not indent ifneq and endif with tabs. How Make reads a Makefile chapter gives a good explanation.
To conclude, I would recommend you to leave a separate sh as is and just invoke it from your Makefile. It is not good practice to mix code in two different languages.
Okay, I found the way it works:
CVS: $(TODO)
#for dir in $?; do \
echo $$dir ; \
echo "Última revisión:" $$(cvs commit -m "Incorporando cambios automáticamente." $$dir | grep "new revision" | sed 's/.*new revision: //' | sed 's/;.*//') ; \
done
cambios: CVS

Getting the name of the makefile from the makefile

How to get the name of the makefile in the makefile?
Thanks.
Note:
I would need that because I would like my makefile to call itself, but the makefile is not called Makefile, so I'd like to write something like this:
target:
($MAKE) -f ($MAKEFILENAME) other_target
location = $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
WHERE_ART_THOU := $(location)
$(warning $(WHERE_ART_THOU))
I also believe this is GNU make-specific, but I'm not too sure.
(Should you have any questions, refer to amazingly written GNU make manual. But remember, that, just like Makefile, this manual should be read completely before putting the concepts into practice).
I couldn't figure out how it is done easily. As far as I understand, you'll have to do some manual job.
Later I will describe how it could be done and show scripts that introduce current_makefile variable. But I would like to stress an important concept at the first place.
You should understand that if we had some kind of variable current_makefile, that expands to the current makefile name, then it will have to change during the process of reading makefiles. That means that it should be used withinin "immediate" expansion context -- i.e. within commands that are executed during reading the makefile. Most commands, however, are executed after makefiles are read. Therefore, some commands will print the correct value smoothly, while in certain places, where "deferred" expansion is used, it will always expand to the root makefile name.
If you would want to use this variable within rule text, for example, you'll have to do tricks, because rule text always has deferred expansion. So, if your have the rule
rule:
echo In makefile $(current_makefile):
echo Making target $#
it will always print the name of the root makefile. Instead, to force immediate expansion, you will have to create another variable with makefile-specific name (i.e. names of such variables should be different in each makefile):
this_makefile_unique_name := $(current_makefile)
rule:
echo In makefile $(current_makefile):
echo Making target $#
or use eval:.
define make_rule
rule:
echo In makefile $(1):
echo Making target $$#
$(eval $(call make_rule,$(current_makefile)))
If you want to use the name of current makefile for debug purpose only, consider special debugging functions, like warning or info:.
$(warning We're in makefile $(current_makefile))
These functions use "immediate" expansion and will print the correct value.
How to define such a $(current_makefile)?
You have to manually maintain stack of makefile inclusions. When you include a makefile, its name is placed to the top of the stack; when you return from included makefile to the outer one, the topmost name is popped out of stack. This is achieved by inserting special calls to the beginning and the end of makefile:
# Beginning of makefile
$(eval $(makefile_names_push))
#... makefile text
$(warning $(current_makefile))
#...
$(eval $(makefile_names_pop))
#End of file
Now define the functions at the beginning of your root makefile.
lastword=$(word $(words $(1)),$(1))
define makefile_names_push
current_makefile := $$(CURDIR)/$$(call lastword,$$(MAKEFILE_LIST))
makefile_stack :=$$(makefile_stack) $$(current_makefile)
endef
define makefile_names_pop
makefile_stack := $$(filter-out $$(current_makefile),$$(makefile_stack))
current_makefile := $$(call lastword,$$(makefile_stack))
endef
If you're sure your make is new enough (version 3.81+), replace lastword call with builtin function:.
#inctead of $$(call lastword,$$(MAKEFILE_LIST))
$$(lastword $$(MAKEFILE_LIST))
Is it useful?
Totally useless. An only use that might be useful here is to make 100 makefiles that are symlinks to one makefile, the rules in these makefiles depending on their names. But it can be achieved within one makefile and foreach-eval technique described in the manual. So my post was a complete waste of time, though I had some fun :-)
This returns the name of the first Makefile called, i.e. the one at the bottom of the call stack:
MAKEFILE_JUSTNAME := $(firstword $(MAKEFILE_LIST))
MAKEFILE_COMPLETE := $(CURDIR)/$(MAKEFILE_JUSTNAME)
When used in non-cross-recursive situations (e.g. for makedepend), it is just the name of the current makefile.
I wanted to do something similar (for echoing the contents of the Makefile) for when I use Make for managing simple repetitive tasks. I came across this page and found it was exactly what I was after and really useful for my limited understanding of make.
My result after reading this page:
# Makefile - 'make' and 'make help' now echo the makefile.
help:
cat $(lastword $(MAKEFILE_LIST))
start:
sudo -u www /path/to/webapp/myhttpd restart
stop:
sudo kill `cat /path/to/webapp/data/httpd.pid`
A quick excursion to Google suggests this site has the answer.
G'day,
If you make a copy of your original makefile, say makefile_test, and then enter the command:
make -np -f makefile_test 2>&1 | tee output
That will evaluate the makefile and your make environment but not execute any of the commands. Looking through the output file for references to makefile_test will show you what is set in make's environment and where that value is being set.
N.B. This can generate a lot of info! And don't add the -d (debug) switch which will generate tons of additional output about make's decision process but minimal additional info about make's env.
HTH
The solutions here addresses 1) POSIX make with 2) Invoked, non included, makefile in 3) A Unix alike platform.
What the OP asked for:
target:
#pid=$$$$; \
while test `ps -ocomm= $$pid` != make; do \
pid=`ps -oppid= $$pid`; \
done; \
MAKEFILENAME=`ps -oargs= $$pid|sed 's/^.* -f *\([^ ]*\).*$$/\1/'`; \
test -z "$$MAKEFILENAME" -a -f Makefile && MAKEFILENAME=Makefile; \
test -z "$$MAKEFILENAME" -a -f makefile && MAKEFILENAME=makefile; \
export MAKEFILENAME; \
$(MAKE) -e -f $$MAKEFILENAME other_target
The targets depends on the makefile, kind of bloated:
TARGET1_MAKEFILENAME = target1_preamble
all: target1 target2...
target1: $(TARGET1_MAKEFILENAME) other_dependencies...
#test $(TARGET1_MAKEFILENAME) == target1_preamble && exit 0; \
built_instructions_for_target1;
target1_preamble:
#pid=$$$$; \
while test `ps -ocomm= $$pid` != make; do \
pid=`ps -oppid= $$pid`; \
done; \
MAKEFILENAME=`ps -oargs= $$pid|sed 's/^.* -f *\([^ ]*\).*$$/\1/'`; \
test -z "$$MAKEFILENAME" -a -f Makefile && MAKEFILENAME=Makefile; \
test -z "$$MAKEFILENAME" -a -f makefile && MAKEFILENAME=makefile; \
export MAKEFILENAME; \
$(MAKE) -e -f $$MAKEFILENAME target1;
Can be a bit simplified if make is invoked only for all targets.
MAKEFILENAME = invoked_makefile_placeholder
all: target1 target2...
target1: $(MAKEFILENAME) other_dependencies...
#test $(MAKEFILENAME) == invoked_makefile_placeholder && exit 0; \
built_instructions_for_target1;
invoked_makefile_placeholder:
#pid=$$$$; \
while test `ps -ocomm= $$pid` != make; do \
pid=`ps -oppid= $$pid`; \
done; \
MAKEFILENAME=`ps -oargs= $$pid|sed 's/^.* -f *\([^ ]*\).*$$/\1/'`; \
test -z "$$MAKEFILENAME" -a -f Makefile && MAKEFILENAME=Makefile; \
test -z "$$MAKEFILENAME" -a -f makefile && MAKEFILENAME=makefile; \
export MAKEFILENAME; \
$(MAKE) -e -f $$MAKEFILENAME
With the previous approach is trivial to implement a solution for included makefiles based in grep and a unique pattern contained in the makefile.
I never answer when I feel the question got a proper solution.

Resources