Makefile appending to variable for previous dependencies - makefile

I have a command foo that accepts a list of parameters. I want to alter the parameters I pass to foo depending on the target I am building. I have tried appending to target-specific variables, but that doesn't quite do what I want...
So, for example take this Makefile (which doesn't work since foo is built by target1):
all: target1 target2
foo:
echo foo $(foo_args)
target1: foo_args += abc
target1: foo
echo Target1
target2: foo_args += def
target2: foo
echo Target2
.PHONY: foo target1 target2
What happens:
> make all
foo abc
target1
target2
> make target1
foo abc
target1
> make target2
foo def
target2
What I want:
> make all
foo abc def
target1
target2
> make target1
foo abc
target1
> make target2
foo def
target2
Makefile syntax can be specific to GNU make. I'd also like to keep the parallelism so that target1 and target2 can be built in parallel.

Scalable variant of John's answer:
define add_target
foo_args_all += $2
foo_args_$(strip $1) := $2
$1: foo; #echo $$#
foo_targets += $1
endef
all:
$(eval $(call add_target, target1, abc))
$(eval $(call add_target, target2, def))
all: $(foo_targets)
foo:; #echo foo $(sort $(foreach g,$(or $(MAKECMDGOALS), all),$(foo_args_$g)))
.PHONY: all $(foo_targets)
Output:
$ make -f sample.gmk
foo abc def
target1
target2
$ make -f sample.gmk all
foo abc def
target1
target2
$ make -f sample.gmk target1
foo abc
target1
$ make -f sample.gmk target2
foo def
target2
$ make -f sample.gmk target2 target1
foo abc def
target2
target1
$ make -f sample.gmk target1 target2
foo abc def
target1
target2

Your example is missing a number of details and there may be a better way of handing things (e.g. why are the targets PHONY? How does foo depend on target1?), a complete example that shows exactly how the files are generated will get you a better answer.
That said, something like the following should work in this case
.PHONY: all foo target1 target2
all: foo_args += abc def
all: target1 target2
target1: foo_args += abc
target1: foo
#echo Target1
target2: foo_args += def
target2: foo
#echo Target2
foo:
#echo foo $(sort $(foo_args))

The problem with making target-specific variables is that they are only available in the scope of that target. In particular, if you were building a target with multiple dependencies, you would only go down one dependency at a time, and foo_args would only reflect the targets on the side of the tree you happen to be invoking foo from.
Another solution might be to use something like the following at the top of your makefile:
foo_args_target1 := abc
foo_args_target2 := def
foo_args_all := abc def
foo_args := $(sort $(foreach goal,$(MAKECMDGOALS),$(foo_args_$(goal))))
$(info foo_args is $(foo_args))
This has the advantage that foo_args is available globaly. This still has a scalability issue though -- if you were to create a new target all2 : target1 target2, then you would have to add a foo_args_all2 := ... into the makefile (you couldn't automatically detect that all2 was dependent on target1 or target2, and update foo_args automatically).

Related

Use % (percent wildcard) in the recipe part of a rule

I'd like to do something like this:
PHONY += bar
bar:
$(MAKE) foo_$#
PHONY += foo_%
foo_%:
cp somedir/%.a someotherdir/
.PHONY: $(PHONY)
But the % isn't evaluated in the recipe (the cp part).
My workaround has been to create a phony target with the same name of the file I want to use, but I would prefer a better solution:
PHONY += bar
bar:
$(MAKE) foo_$#
PHONY += foo_%
foo_%: %.a
#:
PHONY += %.a
%.a:
cp somedir/$# someotherdir/
.PHONY: $(PHONY)
Is there any way to pass the expansion of % to the shell command?

Suppress make warning from not-yet-built include without suppressing error

If I have a makefile
include foo
foo: Makefile
#echo 'bar::' > foo
#echo "\t#echo 'bar'" >> foo
bar::
#echo 'baz'
then when I run make bar, I get
Makefile:1: foo: No such file or directory
bar
baz
I want to suppress this warning. If I have a makefile
-include foo
foo: Makefile
#echo 'bar::' > foo
#echo "\t#echo 'bar'" >> foo
bar::
#echo 'baz'
then I get, as expected,
bar
baz
However, if I have a makefile
-include foo
bar::
#echo 'baz'
and I run make bar, then I get
baz
while I would instead like to get something like
Makefile:1: foo: No such file or directory
make: *** No rule to make target 'foo'. Stop.
or at least
make: *** No rule to make target 'foo'. Stop.
How do I suppress the warning without suppressing the error, so that I get a failure if the file cannot be created / if the target does not exist, but no spew when it is successfully created?
This is a hackish solution: You could split your makefile into two like so:
# Makefile
foo.mak:
#echo running $#
#echo 'bar::' > foo.mak
#echo -e "\t#echo 'bar'" >> foo.mak
bar bar2 bar3: foo.mak
#$(MAKE) --file Makefile.2 $#
and then
#Makefile.2:
include foo.mak
bar::
#echo baz
The problem with this is that it has to invoke a new copy of make for each top-level target. I seem to remember using a match-anything rule instead of a list of explicit targets, but I just tried it, and it seems to be giving me problems, so I listed the targets explicitly.
I have discovered an extremely hacky solution, that involves emitting the error message manually:
-include foo
ifeq (,$(filter-out foo,$(MAKECMDGOALS)))
ifeq (,$(wildcard foo))
$(shell $(MAKE) foo)
ifeq (,$(wildcard foo))
$(error No rule to make target 'foo')
endif
endif
endif
foo: Makefile
#echo 'bar::' > foo
#echo "\t#echo 'bar'" >> foo
bar::
#echo 'baz'
Perhaps an ever-so-slightly-less-hacky solution would be:
ifeq (,$(filter-out foo,$(MAKECMDGOALS)))
ifeq (,$(wildcard foo))
$(shell $(MAKE) foo)
endif
include foo
else
-include foo
endif
foo: Makefile
#echo 'bar::' > foo
#echo "\t#echo 'bar'" >> foo
bar::
#echo 'baz'

Dynamically generating a list of targets

If a file exists, I want to add a target to build. If the file does not exist, I want the target to be skipped.
an example:
FILENAME = f
TARGETS := normal
ifneq($(shell stat test_$(FILENAME).c), "")
TARGETS += test
endif
all: $(TARGETS)
normal:
#echo normal
test:
#echo test
I'm not sure the $(shell stat ...) part even works, but the bigger problem is that make with any file test_f.c in the current folder gives:
Makefile:4: *** multiple target patterns. Stop.
Removing the ifneq ... endif block makes the target normal. How can I only run the target test if test_f.c exists?
What you can do is generate a string variable (let's call it OPTIONAL) such that when 'test_f.c' exists, OPTIONAL=test; otherwise, OPTIONAL=_nothing_. And then add OPTIONAL as a prerequisite of all. e.g.:
FILENAME = f
TARGETS = normal
OPTIONAL = $(if $(wildcard test_f.c), test, )
all: $(TARGETS) $(OPTIONAL)
normal:
#echo normal
test:
#echo test
You can also iterate over targets with for loop
.PHONY: all
RECIPES = one
all: RECIPES += $(if $(wildcard test_f.c), two, )
all:
for RECIPE in ${RECIPES} ; do \
$(MAKE) $${RECIPE} ; \
done
one:
$(warning "One")
two:
$(warning "Two")
> make
for RECIPE in one ; do \
/Applications/Xcode.app/Contents/Developer/usr/bin/make ${RECIPE} ; \
done
makefile:11: "One"
make[1]: `one' is up to date.
> touch test_f.c
> make
for RECIPE in one two ; do \
/Applications/Xcode.app/Contents/Developer/usr/bin/make ${RECIPE} ; \
done
makefile:11: "One"
make[1]: `one' is up to date.
makefile:14: "Two"
make[1]: `two' is up to date.

Makefile. Multidimensional list?

I need to write a pattern rule for the following case:
There are 2 folders: A and B
Running the command python gen.py --a=A/file1.foo --b=file2.bar --c=file3.bar generates B/file1.foo
file1, file2 and file3 are different strings
Is there a way to group those filenames in some kind of a multidimensional array, so that all files are written exactly once (I'll use python syntax):
files = [["a1.foo", "a2.bar", "a3.bar"],
#...200 other groups...
["b1.foo", "b2.bar", "b3.bar"]]
and then the rule looks like this:
$(files): B/{reference 1 elem}: A/{1 elem} {2 elem} {3 elem}
python gen.py --a=A/{1 elem} --b={2 elem} --c={3 elem}
Any ideas how to archive it?
You can use standard make syntax for that:
all :
targets :=
define add_target
B/${1}: A/${1} ${2} ${3}
targets += B/${1}
endef
# Build dependencies.
$(eval $(call add_target,a1.foo,a2.bar,a3.bar))
# ...
$(eval $(call add_target,b1.foo,b2.bar,b3.bar))
# One generic rule for all ${targets}
${targets} : % :
#echo Making $# from $^
all : ${targets}
.PHONY: all
Note that these $(eval $(call add_target,...) are white-space sensitive, do not insert spaces in there.
If you would like make to create the directory for outputs automatically do:
${targets} : % : | B
B :
mkdir $#
Sometimes a little repetition isn't so bad really
targets := B/a1.foo B/b1.foo
.PHONY: all
all: $(targets)
$(targets): B/%: A/%
python gen.py --a=$< --b=$(word 2,$^) --c=$(word 3,$^)
B/a1.foo: a2.bar a3.bar
B/b1.foo: b2.bar b3.bar

Make Target Name From Another

Have problem in dynamically "create" target name with .SECONDEXPANSION:
Small Makefile to reproduce problem:
CONFIGS = test1 test2 test3
.SECONDEXPANSION:
all: $(CONFIGS)
OBJECTS=$$(CFG_NAME)_OBJECTS
$(CONFIGS) : CFG_NAME=$#
$(CONFIGS) : $(OBJECTS)
#echo $(CFG_NAME) $# from $^
$(OBJECTS):
#echo OBJECTS $# from $^
#echo DO IT
It says: "No rule to make target 'test1_OBJECTS'.
How can I solve this problem?
EDIT: CHANGE OF THE ANSWER
Thank you much for the answer. It was the simple variant for my task.
So I try to answer in another way.
CONFIGS = test1 test2 test3
PLATFORMS = x86 ppc arm
#will be test1x86 test2x86 ... test1ppc ... test3arm,
#so it is long way to enumarate all variants
VARIANTS = $(foreach c, $(CONFIGS), $(foreach p, $(PLATFORMS), $(c)$(p)))
#C FILE LIST
CFILES:=$(shell /bin/find -name "*.c")
.SECONDEXPANSION:
all: $(VARIANTS)
#More Comlex Rule
#Want to corresponding objects be in bins/test1x86/
OBJECTS:=$(CFILES:%.c=bins/$$(CFGNAME)%.o)
$(CONFIGS) : CFG_NAME=$#
$(CONFIGS) : $(OBJECTS)
#echo $(CFG_NAME) $# from $^
#More complex prerequisites
#I understand that $$(CFGNAME) will be resolve incorrect.
#For each *.c file in subdir I would have object in corresponding path.
#For example, '1/2/3/test.c' will use for generate
#object file 'bins/test1x86/1/2/3/test.o',
#when I call 'make testx86' or 'make all' (it will build all VARIANTS),
#in 'bins/test1x86/1/2/3/'.
#So what have I do?
$(OBJECTS): bins/$$(CFGNAME)_OBJECTS/%o : %.c
#echo OBJECTS $# from $^
#echo DO IT
So, I would like to avoid recursive make calls. Can you help me?
Thank you.
You have a rule for $(OBJECTS), but that target expands to $(CFG_NAME)_OBJECTS, which is not expanded again (ever), so it can't match anything. Try this instead:
test1_OBJECTS test2_OBJECTS test3_OBJECTS:
#echo OBJECTS $# from $^
#echo DO IT
Or better:
OBJECT_SETS = $(addsuffix _OBJECTS, $(CONFIGS))
$(OBJECT_SETS):
#echo OBJECTS $# from $^
#echo DO IT
(And I'm sure you realize your example doesn't really need SECONDEXPANSION at all.)
EDIT:
That should be a separate question, but I'll try to answer it here. (And please use punctuation in the comments in your makefile; they are very difficult to understand.)
There is more than one solution to your problem. Here is one:
vpath %.c $(dir $(CFILES))
CFILES := $(notdir $(CFILES))
I've gotcha it.
CONFIGS = test1 test2 test3
PLATFORMS = p1 p2
#Will be testp1 test1p2 .. test3p2
VARIANTS = $(foreach c, $(CONFIGS), $(foreach p, $(PLATFORMS), $(c)$(p)))
.SECONDEXPANSION:
#.c files list in all subfolders
cfiles = $(shell /bin/find -name "*.c")
#objects for these .c files for custom VARIANT
objects = $(patsubst %.c,%.o,$(addprefix bins/$1/,$(cfiles)))
#Get .c source for object (e.g. bins/test1p1/tests/main_test.o => tests/main_test.c)
get_src=$(shell echo $1 | sed 's/[^\/]*\/[^\/]*\/\(.*\)/\1.c/')
#Build All Variants
all: $(VARIANTS)
#Build objects. Target list contains all objects for all variants.
#Prerequisites get .c sources from the pattern rule for targets
$(foreach v, $(VARIANTS), $(call objects,$(v))) : %.o : $$(call get_src,$$*)
#echo OBJECTS $# FROM $^
#Variants rule, depends on objects
$(VARIANTS): $(call objects,$$#)
#echo $# from $^
Thank you, Beta. You only have tried. :)
Maybe anyone have style or efficiency suggestions.

Resources