user1476176
user1476176

Reputation: 1095

GNU Make Corresponding Lists

I have a list of products:

Which depends on a different list of sources:

The dependence is one-to-one (the nth element of the first list has the nth element of the second list as its source), and the recipe is the same in all cases. There is, for all intents and purposes, no structure to the lists - it's not possible to write a simple expression which allows the nth element of the list to be generated from n. The solution that I know will work is

1 : z
   [recipe]

2 : y 
   [identical recipe]

3 : x
   [identical recipe]

4 : w
   [identical recipe]

...but I don't like this because it makes it easier to make a mistake when modifying the lists or the recipe. I would prefer to take advantage of the correspondence pattern and begin with

SRCLIST = z y x w
DSTLIST = 1 2 3 4

And then somehow have a general rule like

DSTLIST_n : SRCLIST_n
    [recipe]

Is there any way of doing something like this?

Upvotes: 1

Views: 401

Answers (3)

bobbogo
bobbogo

Reputation: 15493

Nice problem. You didn't mention which version of make you are using, but .SECONDEXPANSION often works well for these sorts of source lookup tables.

A sketch:

srcs := z x y w
targets := 1 2 3 4

.SECONDEXPANSION:

pairs := $(join ${targets},$(addprefix :,${srcs}))
lookup-src = $(patsubst $1:%,%,$(filter $1:%,${pairs}))

${targets}: $$(call lookup-src,$$@)
    echo '[$^] -> [$@]'

Upvotes: 1

Etan Reisner
Etan Reisner

Reputation: 80992

This is a bit ugly but I believe it should work. (There are probably slightly better ways but this was the first thing I came up with.)

SRCLIST = z y x w
DSTLIST = 1 2 3 4
# Create a list of : the length of SRCLIST
MIDLIST = $(foreach s,$(SRCLIST),:)

$(info SRCLIST:$(SRCLIST))
$(info DSTLIST:$(DSTLIST))
$(info MIDLIST:$(MIDLIST))

# Join the three lists together (in two passes since $(join) only takes two lists)
JOINLIST = $(join $(join $(DSTLIST),$(MIDLIST)),$(SRCLIST))
$(info joined:$(JOINLIST))

# eval each of the created strings to create the prerequisite entries
$(foreach r,$(JOINLIST),$(eval $r))

# Set the rules to build all the targets.
$(DSTLIST):
    echo '$@ for $^'

$ touch z y x w
$ make
SRCLIST:z y x w
DSTLIST:1 2 3 4
MIDLIST:: : : :
joined:1:z 2:y 3:x 4:w
echo '1 for z'
1 for z
echo '2 for y'
2 for y
echo '3 for x'
3 for x
echo '4 for w'
4 for w

I should note that this will not deal with spaces in any of the entries at all well (but that's generally true of make so nothing specific to this solution).

You could also always just create a Canned Recipe and then just stick that in each explicitly written out target as in your original idea.

Upvotes: 1

user1476176
user1476176

Reputation: 1095

Inspired by Etan, here is what I found worked:

SRCLIST = z y x w
DSTLIST = 1 2 3 4

# Make a list of ":" for combining
SEPARATOR = $(foreach s,$(SRCLIST),:)

# Define a parameterized rule which accepts the dst:src info as an argument
define dst-src

$1
   [rule]

endef

# Define the list of dependencies
DST_SRC_RELNS = $(join $(join $(DSTCLIST),$(SEPARATOR)),$(SRCLIST))
# ^ DST_SRC_RELNS evaluates to z:1 y:2 x:3 w:4

# Print a preview of the rules the makefile generates itself
$(info $(foreach r,$(DST_SRC_RELNS),$(call dst-src,$r)))

# Generate the rules
$(foreach r,$(DST_SRC_RELNS),$(eval $(call dst-src,$r)))

I think that you could get away with not defining the parameterized rule dst-src by actually writing the rule out inside the $(eval ...), but I didn't like this for two reasons:

  • you need to define a newline macro for the result to be something that make will recognize as a rule
  • adding more text within the $(foreach ...) makes it even harder for a human reader to figure out what's really going on

Upvotes: 1

Related Questions