Gnu make: Target in macro from subst - makefile

I want to generate a list of equivalent targets:
TARGETLIST = a b c d e f
define RULE
$(subst X,$(1),file_X.txt) $(subst X,$(1),otherfile_X.txt) &: # some dependency
# some commands
endef
$(foreach _t, $(TARGETLIST), $(eval $(call RULE, $(_t))))
This does not do what I want: If I want to build file_b.txt for example the recipe is not found. How do I fix this?

Your problem comes from the double expansion that takes place in your macro when calling foreach-eval-call. You need to double the $ of your $(subst...: $$(subst.... But why not simply letting call do your substitutions?
TARGETLIST = a b c d e f
define RULE
file_$(1).txt otherfile_$(1).txt &: # some dependency
# some commands
endef
$(foreach _t,$(TARGETLIST),$(eval $(call RULE,$(_t))))
call automatically replaces the $(1) occurrences in the RULE macro by the current $(_t) of the foreach loop. This is what call is made for.

Related

Makefile: embed 'if' condition in 'foreach' loop

I am trying to embed if condition foreach loop in makefile.
COMP1 := 1
COMP2 := KKKKKKKKKKKKKKKKKK
define dir_rule_template
$(info $(1))
endef
$(foreach compdir,hello hai how are you, \
$(ifdef COMP1 , $(eval $(call dir_rule_template,$(COMP1)/$(compdir))), $(eval $(call dir_rule_template,$(COMP2)/$(compdir)))))
Output:-
testuser#system-linux-test:~/debug_test/new_make_client$ make -f test.mk
1/hello
KKKKKKKKKKKKKKKKKK/hello
1/hai
KKKKKKKKKKKKKKKKKK/hai
1/how
KKKKKKKKKKKKKKKKKK/how
1/are
KKKKKKKKKKKKKKKKKK/are
1/you
KKKKKKKKKKKKKKKKKK/you
make: *** No targets. Stop.
Here i am expecting only :-
1/hello
1/hai
1/how
1/are
1/you
Am I doing something wrong here? if COMP1 is defined, I don't want to go to latter eval statement. I thought the latter one should be treated as else part. Please advice!
The conditional ifdef is not a function, so it can't be easily combined with the foreach function.
But you can put it in the template:
define template
ifdef COMP1
$$(info $(COMP1)/$(1))
else
$$(info $(COMP2)/$(1))
endif
endef
$(foreach compdir,hello hai how are you, \
$(eval $(call template,$(compdir))))
EDIT: It still isn't clear exactly what you are trying to do, but if you can use the if function as #MadScientist suggests, then a cleaner solution is possible:
$(foreach compdir,hello hai how are you,\
$(info $(if $(COMP1),$(COMP1),$(COMP2))/$(compdir)))

Capture two folders in prerequisite for Makefile's target

For my Makefile, my prerequisite is of the form
$(Root)a/b/c/d/File.source
and I'd like to target
a/c.zip
knowing that there are multiple folders for a and c, that b is fixed, that d can vary, but that the source file is always called File.source.
Taking inspiration from this answer, I've tried
As:= $(notdir $(shell find $(Root) -mindepth 1 -maxdepth 1 -type d))
define RULE
$1/%.zip: $1/b/%/*/File.source
echo "Test"
endef
$(foreach a, $(As), $(eval $(call RULE, $(a))))
But it does not seem to work.
I think this could be relatively simple, given the fact how patterns match. Having a rule
%.zip: b/%.foo
will cause make a/c.zip to search for a/b/c.foo file. With this in mind we can write a quite simple Makefile:
$ cat Makefile
# Extract directories that contain File.source files
sourcedirs := $(dir $(wildcard $(ROOT)*/b/*/*/File.source))
# Generate archive names: drop the /b/ part and add .zip extension
archives := $(patsubst %/,%.zip,$(subst /b/,/,$(dir $(sourcedirs:/=))))
%.zip: b/%/*/File.source
echo Making $# from $<
all: $(archives)
The output:
$ find . -name File.source
./a/b/c/d/File.source
./foo/b/bar/baz/File.source
./foo/b/cfg/d/File.source
$ make -s
Making foo/bar.zip from foo/b/bar/baz/File.source
Making foo/cfg.zip from foo/b/cfg/d/File.source
Making a/c.zip from a/b/c/d/File.source
Let's first find all File.source files that match the $(Root)*/b/*/*/File.source pattern:
FILES := $(shell find $(Root)*/b/* -maxdepth 2 -mindepth 2 -type f -name File.source)
Then, let's create a macro for each of them (I over-exaggerated the number of intermediate variables for easier understanding, you can simplify a bit, if you wish):
# $(1): File.source path
define MY_RULE
$(1)-dirs := $$(subst /, ,$(1))
$(1)-Roota := $$(word 1,$$($(1)-dirs))
$(1)-a := $$(patsubst $$(Root)%,%,$$($(1)-Roota))
$(1)-c := $$(word 3,$$($(1)-dirs))
$(1)-target := $$($(1)-a)/$$($(1)-c).zip
$$($(1)-target): $(1)
#echo "Test for $$< (target $$#)"
endef
And finally, let's call the macro on each File.source file:
$(foreach f,$(FILES),$(eval $(call MY_RULE,$(f))))
All in all, plus a test phony default goal for easier testing:
FILES := $(shell find $(Root)*/b/* -maxdepth 2 -mindepth 2 -type f -name File.source)
.PHONY: test
.DEFAULTGOAL := test
# $(1): File.source path
define MY_RULE
$(1)-dirs := $$(subst /, ,$(1))
$(1)-Roota := $$(word 1,$$($(1)-dirs))
$(1)-a := $$(patsubst $$(Root)%,%,$$($(1)-Roota))
$(1)-c := $$(word 3,$$($(1)-dirs))
$(1)-target := $$($(1)-a)/$$($(1)-c).zip
$$($(1)-target): $(1)
#echo "Test for $$< (target $$#)"
test: $$($(1)-target)
endef
$(foreach f,$(FILES),$(eval $(call MY_RULE,$(f))))
Note: there is a possibility that you have more than one File.source file with the same corresponding target, because of the d part of your pattern. If this happens GNU make will issue warnings and use only the last encountered rule for each such target. There are ways to raise an error, if you prefer, but as all this is already quite complicated, let's leave it as an exercise on make conditionals.

Makefile expanding variable inside define

define func1
include $(shell pwd)/test/$(strip $1)/component.mk
$(info :::::::${NAME} ::::::::::::::: )
endef
INCLUDES := a b c
$(foreach dir, $(INCLUDES), $(eval $(call func1, $(dir)) ))
all : $(objs)
Contents of each makefile:
cat test/a/component.mk
NAME := AA
cat test/b/component.mk
NAME := BB
cat test/c/component.mk
NAME := CC
Output is
::::::: :::::::::::::::
:::::::AA :::::::::::::::
:::::::BB :::::::::::::::
It looks like first time NAME is empty.
Let's look at the expansion of $(foreach dir, ${INCLUDES}, $(eval $(call func1, ${dir}) )) in painful detail.
${INCLUDES} is expanded, giving $(foreach dir,a b c,$(eval $(call func1,${dir})))
Next dir is set to a
$(call func1,a) is expanded
1 is set to a
func1 is expanded:
include $(shell pwd)/test/$(strip $1)/component.mk
$(info :::::::${NAME} ::::::::::::::: )
$(shell pwd) becomes HERE, say (N.B. Use ${CURDIR} instead)
$(strip $1) becomes $(strip a) becomes a
${NAME} expands to nothing
$(info ::::::: ::::::::::::::: ) expands to nothing
As a side effect ::::::: ::::::::::::::: appears on stdout
$(eval $(call func1,a)) expands to $(eval include HERE/test/a/component.mk), expands to nothing
As a side effect, the include is processed by make
Presumably HERE/test/a/component.mk exists and contains valid make syntax,
and the variable NAME gets a value.
1 is set to b. Lather, rinse, repeat.
Tip
To get a hint of problems in code like this, always run make with --warn:
$ make --warn -Rr
Makefile:8: warning: undefined variable 'NAME'
::::::: :::::::::::::::
⋮
Fix
To get some insight, replace the $(eval stuff) with $(error [stuff])
$ make
::::::: :::::::::::::::
Makefile:8: *** [ include /cygdrive/c/Users/somewhere/a/component.mk
]. Stop.
Here we see the $(info …) has disappeared even before it has got to the eval.
The naive fix is pretty horrible.
define func1
include $(shell pwd)/test/$(strip $1)/component.mk
$$(info :::::::$${NAME} ::::::::::::::: )
endef
Running this with the $(error …) in place gives
$ make
Makefile:8: *** [ include /cygdrive/c/Users/somewhere/a/component.mk
$(info :::::::${NAME} ::::::::::::::: )]. Stop.
That stuff between the [ and ] is valid make syntax.
Tidied up it looks like:
include /cygdrive/c/Users/somewhere/a/component.mk
$(info :::::::${NAME} ::::::::::::::: )
Job done. There are cleaner ways, but you need to understand the pain first!

Missing separate error from makefile

With this Makefile, I'm not sure why I'm thrown a missing separator error.
define foo
$(eval a := $(1))
$(eval b := $(1))
endef
$(call foo,hello)
$(info $(a))
$(info $(b))
all: ;
If I however replaces the first eval with this,
$(eval a := $(1)) \
then the error goes away. eval expands to nothing, and it's happy with only one eval inside the define. But I'm not sure why it's complaining in this case, nor why the trailing back slash solves it.
define foo
$(eval a := $(1))
$(eval b := $(1))
endef
bar:=$(call foo,hello)
$(info $(a))
$(info $(b))
$(info bar=[$(bar)])
all: ;
Running this makefile outputs:
$ make -f Makefile.sample
hello
hello
bar=[
]
make: 'all' is up to date.
So $(foo) function outputs new line character. It should either output nothing or value should be captured in variable or trapped with $(eval) or $(strip).
$(eval a := $(1)) \ results in no new line output from $(foo) that is why it fixes the problem.
Alternatives to adding backslashes to your $(foo) are:
#1:
define fooBody
$(eval a := $(1))
$(eval b := $(1))
endef
foo = $(strip $(call fooBody,$1,$2))
#2
define fooBody
$(eval a := $(1))
$(eval b := $(1))
endef
foo = $(eval $(call fooBody,$1,$2))
#3
$(strip $(call foo,hello))
#4
$(eval $(call foo,hello))
#5
.:=$(call foo,hello)
My personal choice is #1.
The call expansion results in a single newline (because, as you say, both the eval's expand to the empty string. When make tries to parse that, it doesn't understand it and throws this error.
The important thing to understand is that make will break the input into logical lines before it tries to run expansion. So after the expansion is complete, make expects to see a single line of output and it doesn't understand newlines existing in that single line.
Probably this could be handled better, if you wanted to file a bug at https://savannah.gnu.org/bugs/?func=additem&group=make
ETA Actually I checked and this has already been fixed in GNU make 4.2:
$ make-4.1
Makefile:5: *** missing separator. Stop.
$ make-4.2
make: *** No targets. Stop.

Using GNU-make functions to check if variables are defined

I'm writing a makefile that requires some enviroment variables to be defined. I am trying to use something like this to acheive this:
define check-var-defined
ifndef $(1)
$(error $(1) is not defined)
endif
endef
$(call check-var-defined,VAR1)
$(call check-var-defined,VAR2)
$(call check-var-defined,VAR3)
rule1:
#stuff
When I run make with no args I get this:
$ make
Makefile:7: *** VAR1 is not defined. Stop.
But when I run it with VAR1 specified I get the same error.
$ make VAR1=hello
Makefile:7: *** VAR1 is not defined. Stop.
Any ideas why this doesn't work? What can I do to make this work? Thanks in advance.
(Note that I need to check that the variables are actually defined when the makefile is run, as I need to include another makefle further down and the variables need to be set correctly by the time I do this).
The $(call ...) function does not evaluate the results of the function as if it were makefile code, so you can't things like ifdef there.
What happens is that the contents of check-var-defined are expanded and since it doesn't recognize the ifdef operation, it just proceeds to expand the $(error ...) function every time.
If you want to use ifdef you have to use $(eval ...) with $(call ...) which will evaluate the result as if it were a makefile.
Simpler is to use the $(if ...) function, like this:
check-var-defined = $(if $(1),,$(error $(1) is not defined))
Note that this will fail if the variable is empty, which is not quite the same thing as being undefined; it could have been defined to be empty (as VAR1=). But that's the way ifdef works, too, confusingly.
the macro in 1st answer is great but doesn't actually report the name of the 'empty' variable. here is a slight improvement with example/test:
# -*- mode: makefile -*-
check-var-defined = $(if $(strip $($1)),,$(error "$1" is not defined))
my_def1:=hello
my_def3:=bye
$(call check-var-defined,my_def1)
$(call check-var-defined,my_def2)
$(call check-var-defined,my_def3)
and the result:
Makefile:10: * "my_def2" is not defined. Stop.
defined = $(strip $(filter-out undefined,$(flavor $1)))
ensure-defined = \
$(eval .ensure-defined :=) \
$(foreach V,$(sort $1), \
$(if $(call defined,$V),,$(eval .ensure-defined += $V)) \
) \
$(if $(strip ${.ensure-defined}), \
$(foreach V,${.ensure-defined}, \
$(info NOT DEFINED: $$$V) \
) \
$(error Required variables not defined) \
)
ifFOO = $(if $(call defined,FOO), \
$(info FOO is defined: '${FOO}'), \
$(info FOO not defined) \
)
$(ifFOO)
FOO := foo
$(ifFOO)
$(call ensure-defined,FOO BAR)
all: ; #:
OUTPUT:
$ make -f foo.mk
FOO not defined
FOO is defined: 'foo'
NOT DEFINED: $BAR
foo.mk:25: *** Required variables not defined. Stop.

Resources