Defining custom GNU make functions - makefile

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)).

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)

Using eval with wildcard in a Makefile

Borne out of morbid curiosity and seeing CMake's ExternalProject, I've tried to hack up a cute little attempt at an automatic git-dependency manager for a C++ project, however I can't quite make Make dance the way I want it to.
# shortname, git address, configure, make (install), make clean
DEPENDENCIES:=\
catch,https://github.com/philsquared/Catch.git,true,true,true
, := ,
hit_subtree = git subtree $1 --prefix deps/$2 $2 master --squash
define get_or_update
$(if $(wildcard deps/$1/*),
git fetch $1 master && $(call hit_subtree,pull,$1),
$(if $(shell git ls-remote catch),
true,
git remote add -f $1 $2) && $(call hit_subtree,add,$1)
)
endef
update_cxx_flags = $$(if $$(wildcard deps/$1/include/*),$$(eval CXXFLAGS += -Ideps/$1/include),)
update_ld_flags = $$(if $$(wildcard deps/$1/lib/*),$$(eval LDFLAGS += -Ideps/$1/lib),)
define update_flags
$(eval $(call update_cxx_flags,$1))
export CXXFLAGS
$(eval $(call update_ld_flags,$1))
export LDFLAGS
endef
build_project = cd deps/$1 && $4 && $2 && $3
define git_dependency
$(call get_or_update,$1,$2)
$(call build_project,$1,$3,$4,$5)
$(call update_flags,$1)
endef
caller = $(call git_dependency,$(word 1,$1),$(word 2,$1),$(word 3,$1),$(word 4,$1),$(word 5,$1))
git_dependencies:
$(foreach dep,$(DEPENDENCIES),$(call caller,$(subst $(,), ,$(dep))))
#echo ${CXXFLAGS}
#echo ${LDFLAGS}
The problem lies in the update_flags function: specifically, update_flags tries to modify CXXFLAGS and LDFLAGS to account for new include/lib dirs however it seems that $(eval ...) isn't doing what I want it do. On the first run (i.e. when the directory is first being cloned) the $(wildcard ...) function sees no sub-directories of deps/$1 however if I invoke make a second time it then works fine. To me, this suggests that $(eval ...) isn't actually evaluating update_cxx_flags and instead the function is being non-lazily evaluated. What am I doing wrong?
Here is your SSCCE:
all:
touch foobar
echo $(wildcard foobar)
This 'does not work', as you observe, first time, but second time, it works. Why? Because, GNU Make first evaluates the whole recipe, before executing any lines of it. Then, after the recipe is evaluated (translated into the shell language), only then it is executed.
OK, you wanted to do it with $$, it still won't work, the double $ won't make it defer to the recipe execution, it will just evaluate twice during the processing of eval:
all:
touch foobar
$(eval $$(info $$(wildcard foobar)))
On the chat, I told you what is happening, but you are assuming some "caching".
You are a very knowledgeable person in certain areas, but you must remember when you learn something new, to start from the beginning and follow simple examples and manual. I am giving you simple examples, analyze them with the help of the manual and do not spin your own theories.
Mark's answer led me to google to work out why $(eval $$(wildcard foobar)) wouldn't behave as intended -- after all, at the very least GNU make promises to evaluate the argument as though it was 'typed' into your makefile.
It turns out that $(wildcard ...) is a little too smart for its own good: it caches directories and only updates the cache if a file is generated via a makefile rule. In this instance, the file is generated by dropping to shell and using git which violates the assumption that files are generated via makefile rules. Thus, the check in update_cxx_flags is incorrect (as well as update_ld_flags). Instead, it should be modified as so:
update_cxx_flags = $$(if `ls deps/$1/include/* 2>/dev/null`,$$(eval CXXFLAGS += -Ideps/$1/include),)
where the /dev/null clobber is so that an error message doesn't appear when the file doesn't exist. This makes the makefile behave as expected, which is what I wanted!

Makefile with multiple rules sharing same recipe with patternrules?

I want to remove the duplication of recipe in a makefile like the following
SHELL := /bin/bash
a_% : a1_% a2_%
cat $^ > $#
b_% : b1_% b2_% %_b3
cat $^ > $#
However the following does not work. I guess the trick in this SO question does not work with pattern rules.
SHELL := /bin/bash
a_% : a1_% a2_%
b_% : b1_% b2_% %_b3
a_% b_%:
cat $^ > $#
Any suggestions ? ( In my original makefile, recipe duplication is occurring in 4 targets, and each of those take 3 substitutions, so I can't unroll the targets)
--EDIT--
I realized that one way to solve this was the following.
CMD1 = cat $^ > $#
a_% : a1_% a2_%
$(CMD1)
b_% : b1_% b2_% %_b3
$(CMD1)
I believe this does what you asked for:
SHELL := /bin/bash
define STUFF
$(1)_%: $(1)1_% $(1)2_% $(2)
cat $$^ > $$#
endef
$(eval $(call STUFF,a))
$(eval $(call STUFF,b,%_b3))
How this works:
The general form of the rule is defined as STUFF. (You'd obviously want a better name in your own Makefile.) Note the doubling of dollar signs in $$^ and $$#. This protects them from evaluation when $(call ...) is executed. $(1) and $(2) will be replaced by $(call ...) with positional arguments.
$(call STUFF,a) "calls" STUFF with $(1) set to the string a and $(2) set to the empty string. The return value is:
a_%: a1_% a2_%
cat $^ > $#
Note how one $ was stripped from the remaining variables.
$(eval ...) evaluates the return value obtained in the previous step as if that string had been put in the Makefile. So it creates the rule.
Steps 2 and 3 also happen for the b files. It is similar to what happens for the a files except that this time $(2) is set to the string %_b3.
This is essentially the method I've used in the past to avoid duplication of rules for cases where the rules were rather complex. For the specific case you show in your question, I'd use the shared command variable you mention in your question.

Get include tree of GNU makefiles

In an existing GNU Make build system, I'd like to see a tree of the makefile includes. How may I do this? Like Do you know tool building tree of include files in project\file? but for GNU Make rather than C and C++.
A related but slightly different capability I'd be interested in: being able to put $(info ...) statements in a makefile and have it print out a backtrace of includes for that info statement.
If you just want a list of included makefiles, a $(info Included files: ${MAKEFILE_LIST}) at the bottom of the main makefile fits the bill.
However, if you do want a tree, then you'll have to replace all include <file> with $(call include,<file>). The include function would be something like:
space :=
space +=
${space} := ${space}
stack := $(firstword ${MAKEFILE_LIST})
define includefunc
stack := $(subst ${ },|,$(strip ${MAKEFILE_LIST} $1)) ${stack}
$(info INCLUDES $(firstword ${stack}))
include $1
stack := $(wordlist 2,$(words ${stack}),${stack})
MAKEFILE_LIST := $(subst |, ,${stack})
endef
include = $(eval $(value includefunc))
$(call include,1.mak)
$(call include,_1/1.mak)
$(call include,folder/Makefile)
.PHONY: all
all: ; echo $# success

gmake: eval inside a recipe in function

Is there a way to do such thing in a makefile for gmake:
GOALS := g1
define fun_one
#echo "blabla" #this causes an error - maybe can't be recognized as a recipe
endef
define fun_two
$(1):
$(eval $(call fun_one,$(1)))
endef
$(forech goal, $(GOALS), $(eval $(call fun_two,$(goal))))
all: ${GOALS}
As far as I understand, I can't define a part of a recipe outside a function that defines a rule, am I write?
There's no need for the first eval; it just tells Make to enact that line while it's still parsing the definition of fun_one. Eliminate the eval and the makefile will work:
define fun_two
$(1):
$(call fun_one,$(1))
endef

Resources