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

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)

Related

make file substantial command

I am learning make and ran across a few commands that I do not understand. Can anyone help me understand these commands please?
I have the following few lines from a larger make file.
blank :=
blank +=
type_switcher = $(or\
$(if $(subst TEMP_WEIGHT,,$(firstword $(subst _,$(blank),$(1)))),,$(blank)),\
$(if $(subst TEMP_SPEED,,$(firstword $(subst _,$(blank),$(1)))),,-temp_speed),\
$(if $(subst TEMP_MASSN,,$(firstword $(subst _,$(blank),$(1)))),,-temp_mass -temp_massn),\
$(error No known type defined in command $(2)))
What is $(1) in this code?
Is there a way to print out all the variables fully expanded but without actually running make?

How ifeq from Makefile works?

I have a Makefile with content:
define SOME_FUNC
ifeq (n,y)
$(warning TRUE)
else
$(warning FALSE)
endif
endef
.PHONY: all
all:
$(eval $(call SOME_FUNC))
After executing "make" command I've got following output:
$ make
Makefile:10: TRUE
Makefile:10: FALSE
make: Nothing to be done for 'all'.
I cannot explain why it happens.
From the documentation:
It’s important to realize that the eval argument is expanded twice; first by the eval function, then the results of that expansion are expanded again when they are parsed as makefile syntax. This means you may need to provide extra levels of escaping for “$” characters when using eval.
You need to double the dollars to have those $(warning ...) functions evaluated on interpreting ifeq instead of expanding eval/call:
$ cat Makefile
define SOME_FUNC
ifeq (n,y)
$$(warning TRUE)
else
$$(warning FALSE)
endif
endef
.PHONY: all
all:
$(eval $(call SOME_FUNC))
$ make
Makefile:11: FALSE
make: Nothing to be done for 'all'.

How to synthesize line breaks in GNU Make warnings or errors?

When using the built-in $(error text) and $(warning text) functions of GNU Make, how can I get line breaks into the error/warning output without acrobatics?
By acrobatics I mean funny methods such as these two:
$(warning $(shell /bin/echo -e "something\nfoo\nbar\nbaz"))
$(warning $(shell /bin/bash -c 'echo -e "something\nfoo\nbar\nbaz"'))
which, btw, didn't work for me with GNU Make 3.81 on Ubuntu 10.04.
Rationale: I want to make the error output in conditional parts (ifeq, ifneq) of my GNUmakefile more readable.
The current workaround for me is to use for each line:
$(warning ...)
and finally for the last line:
$(error ...)
Define a line break variable using define/endef and use it as $n like this:
define n
endef
$(warning "something$nfoo$nbar$nbaz")
Note the two blank lines between define and endef

GNU make conditional function $(if ...) inside a user-defined function always evaluates to true

I am trying to write a make function to touch/create an empty file and/or set the permissions, user and group, where possible, or warn if not. However, every conditional check within my function seems to evaluate to true.
The essentials of my Makefile are
INSTALL_USER := fileUser
INSTALL_GROUP := fileGroup
.PHONY: test
test:
$(call touchFile,~/test.ini)
define touchFile
$(eval fileName := $(strip $(1)))
-touch $(fileName)
-chmod -c 664 $(fileName)
$(info filename info $(fileName))
$(info $(shell stat -c "%a %U:%G" $(fileName)))
$(if ifeq "foo" "bar", #echo match is broken, #echo match works)
$(if ifneq "foo" "bar", #echo match works, #echo match is broken)
$(if ifneq ($(shell stat -c %a $(fileName)),664), $(warning Error - $(fileName) does not have expected permissions of 664))
-chgrp -c $(INSTALL_GROUP) $(fileName)
$(if ifneq ($(shell stat -c %G $(fileName)),$(INSTALL_GROUP)), $(warning Error - $(fileName) does not belong to $(INSTALL_GROUP) group))
-chown -c $(INSTALL_USER) $(fileName)
$(if ifneq ($(shell stat -c %U $(fileName)),$(INSTALL_USER)), $(warning Error - $(fileName) does not belong to $(INSTALL_USER) user))
endef
Running make test outputs
filename info ~/test.ini
664 myUserName:myGroup
Makefile:7: Error - ~/test.ini does not have expected permissions of 664
Makefile:7: Error - ~/test.ini does not belong to common group
Makefile:7: Error - ~/test.ini does not belong to netserve user
touch ~/test.ini
chmod -c 664 ~/test.ini
match is broken
match works
chgrp -c fileGroup ~/test.ini
changed group of `/home/myUserName/test.ini' to fileGroup
chown -c fileUser ~/test.ini
chown: changing ownership of `/home/myUserName/test.ini': Operation not permitted
make: [test] Error 1 (ignored)
I've considered/tried the following:
$(if ...) is evaluated at "compile-time", before the function is called with a parameter. But, the hard-coded ifeq "foo" "bar" also gives an invalid result. Additionally, $(info ...) correctly evaluates $(fileName) at "compile-time".
The documentation doesn't actually give examples, so in addition to $(if ifeq...), I also tried $(ifeq ...), which seemed to be ignored.
"Non-functional" if (i.e., the ifeq without the $(if...)) inside a function gives /bin/sh: ifeq: command not found.
Can someone help identify why my conditionals aren't behaving as I expect (or why I'm expecting the wrong thing)?
Caveat: I know there are still bugs to be worked out if the file doesn't exist, but that should be trivial compared to this hurdle.
$(if ...) conditional function evaluates to true when the first argument passed to it is non-empty. In you case the condition is literal text: ifeq "foo" "bar", which is, obviously, non-empty.
ifeq/ifneq conditionals are in fact directives, not functions. They can't be used inside variable definition and in functions.
Back to your example, to test string for equality inside the condition use functions like filter and findstring:
$(if $(filter foo,bar),#echo match is broken,#echo match works)
$(if $(filter-out foo,bar),#echo match works,#echo match is broken)
BTW this could be also turned into an inline form for better readability:
#echo match $(if $(filter foo,bar),is broken,works)
#echo match $(if $(filter-out foo,bar),works,is broken)
I faced this problem and I found you can use the result of "filter" function in the "condition" part of "if" function. Here is an example useful for opening a pdf either in Linux (with "evince"), or in OSX with "open")
uname :=$(shell uname -s)
is_darwin :=$(filter Darwin,$(uname))
viewpdf :=$(if $(is_darwin), open, evince)
You seem to be misunderstanding the way $(if works. From the make info docs:
$(if CONDITION,THEN-PART[,ELSE-PART])'
The `if' function provides support for conditional expansion in a
functional context
The first argument, CONDITION, first has all preceding and
trailing whitespace stripped, then is expanded. If it expands to
any non-empty string, then the condition is considered to be true.
If it expands to an empty string, the condition is considered to
be false.
In all your examples, your condition is something like ifeq SOMETHING OTHERTHING -- which is a non-empty string (from the ifeq irrespective of what the other things are), and so is treated as true.
if you want to check that your OS version is linux or other and if it is Linux then you want to print some message then u can follow this steps:
ifneq ($(TARGETOS), Linux)
target: server
#echo
#echo $(MSG) $(TARGETOS)
else
target: server
#echo $(MSG) $(TARGET)
endif
before I tried with ifeq but it has printed the else part only.
my OS is Linux, ubuntu.
If anyone found this answer should be modified, so they can give better answer for this also.

Defining custom GNU make functions

What is the problem with the dep2 function in the sample code below?
dep1 = $(eval makefile_list_$1 := $(MAKEFILE_LIST))$(eval -include $1.mk)$(eval MAKEFILE_LIST := $(makefile_list_$1))
define dep2
$(eval makefile_list_$1 := $(MAKEFILE_LIST))
$(eval -include $1.mk)
$(eval MAKEFILE_LIST := $(makefile_list_$1))
endef
$(call dep1,test)
$(call dep2,test)
.DEFAULT_TARGET: all
.PHONY: all
all:
#echo $#
GNU make 3.81 and 3.82 produce Makefile:10: *** missing separator. Stop. which points to the dep2 call, dep1 is run without errors. The only difference between the two variants is the newlines in dep2 (and the whole point why I'd like to use define).
You forgot the =:
define dep2 =
EDIT:
Put a semicolon at the end of each line. I've tested this and it works (in GNUMake 3.81).
define dep2
$(eval makefile_list_$1 := $(MAKEFILE_LIST));
$(eval -include $1.mk);
$(eval MAKEFILE_LIST := $(makefile_list_$1));
endef
Why these semicolons are necessary I don't know, but in the documentation define seems to be used for multi-line "variables" only when defining sequences of shell commands to be used in recipes, not Make commands, so maybe the rules are a little different.
I would move the $(eval ...) calls outside of dep2. By doing it this way, there's no need for semicolons in dep2. This means doubling the $ signs of some expansions to avoid expansion being done too early. So:
define dep2
makefile_list_$1 := $$(MAKEFILE_LIST)
-include $1.mk
MAKEFILE_LIST := $$(makefile_list_$1)
endef
$(eval $(call dep2,test))
# Quick checks for testing, to be removed from the final code...
$(info $(makefile_list_test))
$(info $(MAKEFILE_LIST))
.DEFAULT_TARGET: all
.PHONY: all
all:
#echo $#
I've tested the code above and it works with Gnu Make 4.0. I would expect it to work back to Gnu Make 3.8x. The $(eval $(call ...)) pattern is what I always do to execute my custom functions, and I've used it for quite a while now.
You can do as the below line to kill the error:
FOO := $(call dep2, test)
I guess the reason is the early version of gcc (3.8.1/2) can only accept nothing as the return of expression.
eg $(info string) returns nothing, but $(call dep2, test) returns 2 newlines charaters.
There is much that can be improved in what you are doing. For one thing you really want to factor the eval calls to a single call at the top.
Your particular problem, however, stems from not understanding that the multiline recursive string the make's define command uses never includes the lady new line. The most natural convention for writing evalable functions is
define Foo
Line1
Line2
endef
You can look at the string eval is seeing and see what this does via the info command, e.g.
$(info $(call Foo,x) $(call Foo,y)).

Resources