Command working in bash but not in Makefile [duplicate] - shell

I'm trying to do this in a makefile and it fails horribly:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($1,a,"-");print a[1]}')
do you know why? I guess it has to do with escaping, but what and where?

It's the dollar sign, in makefiles you'll have to type $$ to get a single dollar sign:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($$1,a,"-");print a[1]}')

Make is quite lispy when you get down to it. Here's a non-awk version that does the same thing:
space := $() #
M_ARCH := $(firstword $(subst -,$(space),$(shell g++ -dumpmachine)))
all:
$(info $(M_ARCH))

Related

GNU make 4.1: Missing separator when $(if ...) is true in a defined function

I am trying to generate an error in a Makefile when a string is not found in the output of a shell command. The shell command depends on a parameter, therefore the whole thing is in a defined function. Here is a minimalist example:
define check_in_abcdefg
$(eval TMP := $(shell echo abcdefg))
$(if $(findstring $(1),$(TMP)),,$(error $(1) not in $(TMP)))
endef
$(call check_in_abcdefg,def)
all:
#echo Hello, world!
I would like this Makefile to output Hello, world! in this case, but I'd like it to output xyz not in abcdefg if I replace the call line with this one:
$(call check_in_abcdefg,xyz)
The problem is that with the def check I have this output:
Makefile:6: *** missing separator. Stop.
Where line 6 is $(call check_in_abcdefg,def)
Why does the syntax check fail when the $(if ...) condition is true since it's actually empty ?
Note that the echo command in the dummy target all is correctly preceded by a tab, not four spaces. I am running GNU make 4.1.90 built for Windows32, and it seems not to happen for newer version of GNU make. I am looking for any answer that could help me make it work with GNU make 4.1.90
I'm not sure why older make versions choke here, but you can make it work with one big $(eval ) like this:
define check_in_abcdefg
$(eval
TMP := $$(shell echo abcdefg)
ifeq ($$(findstring $$(1),$$(TMP)),)
$$(error $$(1) not in $$(TMP))
endif
)
endef
$(call check_in_abcdefg,def)
all:
#echo Hello, world!
To answer the question about why GNU make 4.1 is throwing this error: that version of GNU make is mishandling the newline. In your example:
define check_in_abcdefg
$(eval TMP := $(shell echo abcdefg))
$(if $(findstring $(1),$(TMP)),,$(error $(1) not in $(TMP)))
endef
$(call check_in_abcdefg,def)
The first line of the defined macro (the eval) expands to the empty string, and so does the second line (the if). So, the call expands to a single newline character.
That version of GNU make is not correctly ignoring this newline character and instead throws an error. You can change your makefile to work in those older versions by removing the newline:
define check_in_abcdefg
$(eval TMP := $(shell echo abcdefg))$(if $(findstring $(1),$(TMP)),,$(error $(1) not in $(TMP)))
endef
$(call check_in_abcdefg,def)

Makefile For Loop Pattern Substitution

In the following makefile for loop, how can I edit the string, that the i variable represents, with a pattern substitution in the middle of the string? In my case, I wish to replace any / character in the string with a _ character.
for i in $(MODULES:%.cpp=%); do \
g++ -c Sources/$$i.cpp -o Build/$$i.o; \
done
For example if MODULES = Directory/File.cpp then the inner line should expand to
g++ -c Sources/Directory/File.cpp -o Build/Directory_File.o
This answer is valid only with GNU make and bash.
Simple bash substitution (${parameter/pattern/string}) in the context of a make recipe (double $):
for i in $(MODULES:%.cpp=%); do \
g++ -c Sources/$$i.cpp -o Build/$${i//\//_}.o; \
done
Warning: this works only if the shell used by make is bash. So, add maybe a:
SHELL := bash
at the beginning of your Makefile.
Explanation:
${i/X/_} expands as the value of variable i in which the first occurrence of X is replaced by _.
${i//X/_} expands as the value of variable i in which all occurrences of X are replaced by _.
In your case X is the / character and it must be escaped (\/): ${i//\//_}.
Note that there is probably a less bash and more make way to do the same. Something like:
SRCS := $(shell find Sources -type f -name *.cpp)
OBJS :=
define OBJ_rule
obj := Build/$$(subst /,_,$$(patsubst Sources/%.cpp,%,$(1))).o
OBJS += $$(obj)
$$(obj): $(1)
g++ -c $$< -o $$#
endef
$(foreach s,$(SRCS),$(eval $(call OBJ_rule,$(s))))
.PHONY: objs
objs: $(OBJS)
Which instantiates one rule per module and should do the same... with the significant advantage that, when you type make objs, only the outdated object files are rebuilt. But it's a bit more tricky.

Gnu make: read recipe output into make variable

Is it possible to take a part of the output of a recipe line and use to to set a make variable? For example:
%.o: %.cc
$(eval __time_$* := $(shell date "+%s.%N"))
$(COMPILE.cc) -o $# $<
#echo `date +%s.%N` - $(__time_$*) | bc | xargs printf "%s compile time %6.3f sec\n" $#
Instead of echo'ing the time I want to capture it in a make variable. However, when I use eval and shell instead of the 3rd line above, similar to the 1st line, make appears to read all lines at once, and then schedule the actual recipe shell calls for later. So the result is that the recipe lines are all evaluated at once, so there is little time difference.
The only way I can see to do what you want is to run the compiler inside a $(shell). One problem with doing this is that Make doesn't seem to see the errors, when the command in $(shell) fails. Other than that, here's what I think you wanted:
%.o: %.cc
$(eval __start_$* := $(shell date "+%s.%N"))
#echo $(shell $(COMPILE.cc) -o $# $<)
$(eval __dur_$* := $(shell echo `date +%s.%N` - $(__time_$*) | bc | xargs printf "%s compile time %6.3f sec\n" $#))
Due to the caveat mentioned above, I recommend using the 'time' command to collect these stats, as #wojtow said. If you have different requirements, consider wrapping compilation with a script that collects your timing data and logs it to a file. Then, process the contents of the file as a post-processing step.
The only way I can see to do what you want is to run the compiler
inside a $(shell). One problem with doing this is that Make doesn't
seem to see the errors, when the command in $(shell) fails.
Droid Coder presented a clever approach. A way to solve the problem of make not seeing errors (thereby not aborting on error) is to pass the exit status via the output of the command, while the original compiler output (if any) is redirected to the standard error stream. The rule then is e. g.:
%.o: %.cc
$(eval __start_$* := $(shell date "+%s.%N"))
exit $(shell $(COMPILE.cc) -o $# $< >&2; echo $$?)
$(eval __dur_$* := $(shell echo `date +%s.%N` - $(__time_$*) | bc …))

Checking the gcc version in a Makefile?

I would like to use some gcc warning switchs that aren't available in older gcc versions (eg. -Wtype-limits).
Is there an easy way to check the gcc version and only add those extra options if a recent gcc is used ?
I wouldn't say its easy, but you can use the shell function of GNU make to execute a shell command like gcc --version and then use the ifeq conditional expression to check the version number and set your CFLAGS variable appropriately.
Here's a quick example makefile:
CC = gcc
GCCVERSION = $(shell gcc --version | grep ^gcc | sed 's/^.* //g')
CFLAGS = -g
ifeq "$(GCCVERSION)" "4.4.3"
CFLAGS += -Wtype-limits
endif
all:
$(CC) $(CFLAGS) prog.c -o prog
Edit: There is no ifgt. However, you can use the shell expr command to do a greater than comparison. Here's an example
CC = gcc
GCCVERSIONGTEQ4 := $(shell expr `gcc -dumpversion | cut -f1 -d.` \>= 4)
CFLAGS = -g
ifeq "$(GCCVERSIONGTEQ4)" "1"
CFLAGS += -Wtype-limits
endif
all:
$(CC) $(CFLAGS) prog.c -o prog
To transform full 3-part gcc version (not only first digit) into numerical format, suitable for comparison (e.g. 40701) use
gcc -dumpfullversion -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$/&00/'
Which addresses the possibility of double-digit numbers in any of the version part, and possibility of missing 3-rd part of the version in output of gcc -dumpversion (which is the case in some earlier gcc versions).
So to test the version in makefile, use something like (note $$ inside last sed command)
GCC_GTEQ_472 := $(shell expr `gcc -dumpfullversion -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/'` \>= 40702)
ifeq "$(GCC_GTEQ_472)" "1"
...
endif
I just encountered this problem where I needed to test the first two digits of gcc and wanted a more readable option than the clever sed hackery above. I used bc to do the comparison since it supports floating point (expr treats non-integers as strings):
GCC_VER_GTE44 := $(shell echo `gcc -dumpversion | cut -f1-2 -d.` \>= 4.4 | bc )
ifeq ($(GCC_VER_GTE44),1)
...
endif
If they release gcc 4.10 after gcc 4.9, then a bit of sed hacking is necessary, but this is still pretty readable:
GCC_VER_GTE44 := $(shell echo `gcc -dumpversion | cut -f1-2 -d.` \>= 4.4 | sed -e 's/\./*100+/g' | bc )
ifeq ($(GCC_VER_GTE44),1)
...
endif
I found this and thought it was really clever. It implements >, >=, <, and <= with fewer shell calls:
GCC_VERSION := $(shell gcc -dumpversion)
VERSION := 7.4.0
ifeq ($(VERSION),$(firstword $(sort $(GCC_VERSION) $(VERSION))))
# stuff that requires GCC_VERSION >= VERSION
endif
This example shows >=. You can implement >, <=, or < using combinations of ifneq and $(lastword).
References:
https://lists.gnu.org/archive/html/help-make/2006-04/msg00065.html
Are you using something like autoconf?
It might be worth invoking a 'dummy' compile via gcc with the flag enabled and if that one fails because the compiler doesn't recognise the flag, you can fall back to the command line that doesn't use the newer warning options.
I've made a ready-to-use IF_GCC macro, based on the answers above:
MY_GCC_VERSION=$(if $(GCC_VERSION),$(GCC_VERSION),$(GCC_DEFAULT_VER))
MY_GCC_TOINT=$(shell echo $(1) | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$//')
MY_IF_GCC=$(if $(shell test $(call MY_GCC_TOINT, $(MY_GCC_VERSION)) -$(1) $(2) || echo 0),$(4),$(3))
GCC_DEFAULT_VER:=$(firstword $(shell cc -V 2>&1 | grep default | sed -r 's/( *)([0-9.]+),(.*)/\2/g'))
Usage: $(call MY_IF_GCC,ge,30305,-fan_option_for_gcc_ge_3.3.5)
As the second argument, you can use any operator of those supported by test(1): eq, gt, lt, ge, le etc.
If cc -V doesn't work for you, replace it with gcc -dumpversion or whatever suitable
Hope that helps.
Following Chris, but using awk
GCC_VER_GTE44 := $(shell expr $$(gcc -dumpversion | awk -F. '{print $$3+100*($$2+100*$$1)}') \>= 40400)
note $ needs to be escaped in Makefile with another $.
I think awk is a better tool for this purpose, as it can both split the the version string into $1 $2 and $3 and then do the comparison with >, <, >= etc. in one command. The whole line is omitted from awk output if the expression is false.
GCC_VERSION_GT_75 = $(shell gcc -dumpfullversion -dumpversion | awk -F. '$$1 > 7 && $$2 > 5')
ifneq ($(GCC_VERSION_GT_75),)
# stuff that requires gcc version > 7.5
endif

Escaping in makefile

I'm trying to do this in a makefile and it fails horribly:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($1,a,"-");print a[1]}')
do you know why? I guess it has to do with escaping, but what and where?
It's the dollar sign, in makefiles you'll have to type $$ to get a single dollar sign:
M_ARCH := $(shell g++ -dumpmachine | awk '{split($$1,a,"-");print a[1]}')
Make is quite lispy when you get down to it. Here's a non-awk version that does the same thing:
space := $() #
M_ARCH := $(firstword $(subst -,$(space),$(shell g++ -dumpmachine)))
all:
$(info $(M_ARCH))

Resources