Let us suppose we use GNU Make on Linux for the purpose of this question.
As a regular shell script writer, the Makefile scripting seems a little odd to me.
Anyways, that is opinion-based, so straight to my question:
I found a way for me to write Makefiles a little bit more like shell scripts.
That being a line break / continuation character usage: \
Example beginning of my Makefile:
DESTDIR ?=
PREFIX ?= /usr/local/bin
install_path := $(DESTDIR)$(PREFIX)
encrypt_script := encrypt-file-aes256
decrypt_script := decrypt-file-aes256
distrib_name := openssl-encryption
this_file := $(lastword $(MAKEFILE_LIST))
.PHONY: check
check: $(encrypt_script) $(decrypt_script)
#tput bold; tput setaf 3; echo Target: $#; echo; tput sgr0
#if [ -f SHA512SUM ]; then \
( sha512sum --check SHA512SUM && \
( tput bold; tput setaf 2; echo; echo "Ok. You may use 'sudo make install' or '(sudo) make install PREFIX=SomeDir' command now."; tput sgr0 ) || \
( tput bold; tput setaf 1; echo; echo "ERROR: Files hash sum mismatch!"; echo; tput sgr0; exit 1 ) \
) \
else \
$(MAKE) --file=$(this_file) SHA512SUM; \
fi
Have I totally crossed the line, or is it in common concept acceptable?
To avoid closing on opinion-based, please avoid your personal preferences, and quote sources.
Here is just a snippet of the code. You can find the whole Makefile without these changes here.
Breaking a complex recipe action across multiple lines is good and sound practice; but excessively long or complex invocations of shell script are a sign that you are doing it wrong. Maybe break out the logic to a separate script, or rethink your approach.
For example, I would simply refactor this to
install:
sha512sum --check SHA512SUM
: the rest of your exisiting install target here
This will fail if SHA512SUM is missing, or if the actual check fails.
If you want colored human-readable output or animated hamsters when this happens, write a wrapper script.
Related
My Makefile is:
.PHONY: check
check:
ifneq $(shell echo 123), $(shell echo 123)
$(error Not equal)
endif
When I run, I've got the error:
$ make
Makefile:3: *** Not equal. Stop.
But this should happen only when they're different, but they're not. Why?
ifneq cannot be indented. the way you've written it, it's being run via a shell command which means the $(error) is being evaluated first by the make command.
i'm guessing you want the make check to actually run two commands only when make check is invoked, and compare their output. you can do:
.PHONY: check
check:
if [ "`echo 123`" != "`echo 123`" ]; then \
echo "Not equal"; \
exit 1; \
fi
According to GNU Make docs, Conditional Parts cannot be used to control shell commands at the time of execution, since conditionals control what make actually "sees" in the makefile.
So to perform condition during compilation process, shell syntax is preferred, e.g.
SHELL := /bin/bash -e
.PHONY: check
check:
#test "$(shell echo 123)" = "$(shell echo 123)" \
|| { echo Not equal; exit 2; } \
&& { echo Equal; }
Here's what I have so far. I'm not looking for RVM-specific answers – I need to be able to replace rvm with, say, evm. Just wanted to pick something everyone would be familiar with :)
install_rvm:
ifeq ("`which rvm > /dev/null; echo $?`", "0")
#echo "rvm already installed!"
else
#echo "installing rvm..."
install_rvm_cmd
endif
Edit
Sorry for not saying this up-front, but I would like install_rvm_cmd to be run as it would normally – I don't want to hide the install process from the user.
I would do it like this:
.PHONY: install_rvm
install_rvm:
#if which rvm > /dev/null; then \
echo "rvm already installed!"; \
else \
echo "installing rvm..."; \
install_rvm_cmd; \
fi
It would be possible to use ifeq but it would entail performing the which rvm check even if install_rvm is not an actual target in a specific run of make. (I'm assuming the general case in which install_rvm is just one target among many.)
Although the string install_rmv_cmd itself wont' be output by make, its output will be output as usual. If you really want to have install_rmv_cmd out on stdout, you can always add echo install_rmv_cmd just before the command itself. To avoid repetition you could have the else branch be:
echo "installing rvm..."; \
cmd=install_rmv_cmd; \
echo $$cmd; \
$$cmd; \
In addition to Louis's answer, you can use ifdef. For example:
EVM_LOCATION := $(shell which evm)
CASK_LOCATION := $(shell which cask)
install_cask:
ifdef CASK_LOCATION
$(info cask is already installed!)
else
curl -fsSkL https://raw.github.com/cask/cask/master/go | python
endif
install_evm:
ifdef EVM_LOCATION
$(info evm is already installed!)
else
curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash
endif
You could use something like this (untested) which uses a stamp file to avoid doing any work once it has been done.
install_rvm_stamp:
__rvm=$$(command -v rvm);\
if [ -z "$$__rvm" ] || ! "$$__rvm" --version >/dev/null 2>&1; then \
echo "installing rvm..."; \
install_rvm_cmd; \
else \
echo 'rvm already installed!'
fi
touch '$#'
.PHONY: install_rvm
install_rvm: install_rvm_stamp
Alternatively, if you don't mind not "function testing" the binary you could do something like this instead.
rvm_bin := $(or $(realpath /usr/local/bin/rvm),$(realpath /bin/rvm),$(realpath /usr/bin/rvm),do_install_rvm)
install_rvm: $(rvm_bin)
do_install_rvm:
#echo "installing rvm..."
install_rvm_cmd
One final comment, as an augmentation to the make-level ifdef option presented in your answer; if the targets in question are only intended to be run manually (and are not used as prerequisites for any other targets) then the extraneous which calls can be avoided with some extra make-level checking.
ifneq (,$(findstring install_evm,$(MAKECMDGOALS)))
EVM_LOCATION := $(shell which evm)
endif
Wrap that in the following and you can avoid undefined variable warnings about MAKECMDGOALS if --warn-undefined-variables is used.
ifneq (undefined,$(origin MAKECMDGOALS))
....
endif
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
I'm completely stumped on how to do this in a Makefile
Let's say I have a target. Inside the target I have a loop. How do i change a variable to keep track of the iterations?
For example:
COUNTER = 0
target:
(loop){
COUNTER++
echo COUNTER
}
I know that variables in Makefiles are only expanded, and I'm not sure if they can be permanently changed, but there has to be a way to do this, right? :(
Here are some sources that are asking similar questions. It seems like those examples only change the variable temporarily:
How do I perform arithmetic in a makefile?
How to do arithmetic operation in makefile?
Doing simple math in Makefile
Maybe I have to use the eval function somehow?
Maybe I have to append onto a Makefile string a character each time and then use something in the shell to count the characters?
If the variable doesn't have to survive the rule, this should do (I'm assuming bash):
clean:
#n=0 ; \
for x in $(THINGS_TO_BE_DELETED); do \
if [ -f $$x ] ; then \
rm $$x; \
let "n+=1" ; \
fi ; \
done ; \
echo deleted $$n files;
Here is one solution: Write a simple script like this:
#!/bin/bash
count=`cat count.txt`
count=$((count + 1))
echo $count
cat $count > count.txt
Initialize the file by doing
$ echo "0" > count.txt
Then include it as a .PHONY requirement to build whatever you'd like.
This is similar to the accepted answer, but the syntax below should work with a POSIX compliant shell. Quotes should also be used inside of the test.
clean:
#n=0; \
for x in *.a *.b *.c ; do \
if [ -f "$$x" ]; then \
rm "$$x"; \
n=$$((n+1)); \
fi; \
done; \
echo deleted $$n files;
Note: tabs must be used for indentation
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.