views:

704

answers:

6

I find I'm writing a lot of Makefiles that could be cleaned up with the use of n-tuple lists. But I can't find any way to do this properly (and cleanly). So far I've only been able to come up with using $(shell ...) and tr, sed, or otherwise non-Makefile standards.

For example, I'd like to do this:

XYZs = \
    dog.c  pull_tail bark  \
    duck.c chase     quack \
    cow.c  tip       moo

all:
    @- $(foreach X Y Z,$(XYZs), \
        $(CC) $X -o bully/$Y ; \
        ln bully/$Y sounds/$Z ; \
    )

Is there a good way to iterate n-tuple lists in Makefiles? Thanks!

+2  A: 

Makefiles are essentially declarative in nature, so I don't think that make itself provides what you want. However, you seem to be wanting to associate some string values with specific targets, so maybe the Target specific variable values feature of GNU make will be of interest. This is an extract from the manual:

There is one more special feature of target-specific variables: when you define a target-specific variable, that variable value is also in effect for all dependencies of this target (unless those dependencies override it with their own target-specific variable value). So, for example, a statement like this:

prog : CFLAGS = -g

prog : prog.o foo.o bar.o

will set CFLAGS to -g' in the command script for prog', but it will also set CFLAGS to `-g' in the command scripts that create prog.o, foo.o, and bar.o, and any command scripts which create their dependencies.

If you haven't already read it, the GNU make manual is pretty damn good.

Edit: To do what you asked about in your comment:

dog: ANIMAL=dog.c BULLY=pull_tail SOUND=bark

use:

dog: ANIMAL=dog.c 
dog: BULLY=pull_tail 
dog: SOUND=bark
anon
Cool, and it might be half-way there! Is there syntax to do this? dog: ANIMAL=dog.c BULLY=pull_tail SOUND=bark
Dylan
+1, very nice.
j_random_hacker
The problem isn't that make is declarative, it's that it doesn't provide a declarative syntax for this frequently needed pattern!
reinierpost
A: 

You're doing it backwards.

You're trying to treat make like it's a script. It's not, instead its a set of rules on how to create X given Y. Then the make engine figures out what needs to happen to get that result.

For the example given, you really should be using a script for the generation steps. Perhaps calling that from make, but let make handle the CC stuff.

caskey
This is a limitation in make that has no reason to be there in the first place, and can be worked around with $(foreach) anyway.
reinierpost
+1  A: 

None that I know of, but that is because you're trying to force make to work ans an imperative language, when that is not what it is.

In GNU make you'd probably want to do something like:

pull_tail : SOUND=bark
pull_tail : dog.c 
        $(CC) $< -o $^
        ln $@ $(SOUND)

chase : SOUND=quack
chase : duck.c 
        $(CC) $< -o $^
        ln $@ $(SOUND)

...

Or better yet, redefine the default rule for .c files to handle the linking for you, but the strange structure of your names (the program names don't have a lexical relationship to the source names) makes that hard.

If what you want to be able to rebuild this quickly without an lot of hand editing, you probably want to write a script to regenerate the makefile framgment from data and use the include feature of GNU make...

dmckee
+1  A: 

I'd check the GNU Make manual on foreach. here are some random snips that I've used in a different project... the example is incomplete, but maybe it will give you some ideas? I might clean this up later if I've got more time...

REMAKE_PROGS:= dog duck cow

XYZs = \
    dog.c  pull_tail bark  \
    duck.c chase     quack \
    cow.c  tip       moo

$(foreach src, $(XYZs), $(eval $MAKE $(src))

$(REMAKE_PROGS):
        @echo "\n# === $@ linking\n"
        $(call compile,$@,$(OBJS_$@),$(CXX),$(MPICXX),$(LDFLAGS) $(LIBS) $(SYSLIBS) $(OTHER_LIB) $(EXTRA_LD_FLAGS))
        @echo "\n# --- $@ compiled\n"

define compile
  @mkdir -p $(dir $(1))
  $(if ${ANNOUNCE},@echo "\n# +++ ${MAKECMDGOALS} compiling\n" $(eval ANNOUNCE=))
  $(call compiler,$(1),NOMPI,$(3),$(4))
  $(eval MY_FLAGS=$(FLAGS_$(1)) $(5))
  $(if $(filter %xlf %xlf90,$(COMPILER_$(1))),$(eval MY_FLAGS:=$(MY_FLAGS:-D%=-WF,-D%)))
  $(strip $(COMPILER_$(1)) $(2) $(MY_FLAGS) -o $(1) )
endef
Pete
I am using not foreach-call-make, but foreach-eval-call, which is just as ugly. See http://www.gnu.org/software/automake/manual/make/Eval-Function.html#Eval-Function
reinierpost
A: 

You can use default rules for a set of files with the same extension as in for compiling each c file to an o. Of course you are not restricted to any special file extensions. For compiling a set of .c files you could do it like this:

OBJS = foo.o bar.o baz.o
OUT = myprog

all: $(OBJS)
        $(SILENT) echo "LD $@"
        $(SILENT) $(CPP) -o $(OUT) $^ $(LDFLAGS)

%.o: %.c
        $(SILENT) echo "CC $<"
        $(SILENT) $(CC) $(CCOPTS) -o $@ -c $<
bluebrother
The trouble starts when you need to use one "the current $(OBJ) value" within the pattern rule. GNU make doesn't provide declarative syntax for that, and $(foreach) is an ugly (but perfectly functional) workaround.
reinierpost
A: 

Thanks for the hints -- after some hacking I think this is more what I was hoping for:

XYZs = \
    dog.c:pull_tail:bark  \
    duck.c:chase:quack \
    cow.c:tip:moo

all:
    @- $(foreach XYZ,$(XYZs), \
        $(eval X = $(word 1,$(subst :, ,$(XYZ)))) \
        $(eval Y = $(word 2,$(subst :, ,$(XYZ)))) \
        $(eval Z = $(word 3,$(subst :, ,$(XYZ)))) \
        \
        $(CC) $X -o bully/$Y ; \
        ln bully/$Y sounds/$Z ; \
    )

Can anyone do better?

Dylan