Warning: it is entirely possible that none of my regular blog readers will care about this in the slightest. I’m only writing it in case some other random person out there is dealing with this problem and googles for a solution. In particular, if the next paragraph makes your eyes glaze over, just move on, there’s nothing to see here.

Make has a feature called ‘vpath’. You can use this to tell make to look for your source files in some other directory or directories. We use this at work because, in a few circumstances, source files for one program/library/whatever can be taken from multiple other directories; that may sound like a bad idea to you, and I won’t argue with you, but I also don’t have the energy to eliminate all situations where we do that. And vpath deals with that situation just fine.

Except when it doesn’t. Sometimes, bad things happen for obvious reasons (e.g. identically named files in two different source directories); that’s easy enough to fix. And sometimes, as happened last week, bad things happen for really screwy reasons: like you move a source file, but a mention of the old location occurs in an automatically generated dependency file; that in of itself wouldn’t be a big deal, I know how to write dependency files in such a way as to be able to handle that (yay, gcc -MD -MP), but occasionally make’s vpath machinery can get really fixated on the old location.

And the screwy thing is, when we specify the source files, we specify the directories that they’re located in; it’s just hard to write wildcard rules to handle that without throwing away information and using vpath!

Well, it’s actually not that hard, it turns out. Here’s a sample Makefile using vpath:


all: foo

SOURCES = a/a.x b/b.x

TARGETS = $(notdir $(SOURCES:.x=.y))

DIRS = $(dir $(SOURCES))

vpath %.x $(DIRS)

foo: $(TARGETS)
    cat $? > $@

$(TARGETS): %.y: %.x
    cat $? > $@

clean:
    -rm -f *.y foo

Try it out yourself: just create directories a and b, and files a.x and b.x in the corresponding directories. Notice how all the information is there in SOURCES; but then we strip it away to get the %.y: %.x rule to work. Which is screwy.

Here’s how you can avoid it:


all: foo

SOURCES = a/a.x b/b.x

TARGETS = $(notdir $(SOURCES:.x=.y))

define GEN_Y
$(1): $(2)
    cat $(2) > $(1)
endef

$(foreach source,$(SOURCES),$(eval $(call GEN_Y,$(notdir $(source:.x=.y)),$(source))))

foo: $(TARGETS)
    cat $? > $@

clean:
    -rm -f *.y foo

(Your web browser is probably wrapping the foreach bit over two or three lines, don’t be confused.)

Which is quite simple, as it turns out. (At least once you realize recent GNU Make versions have eval.)

Some warnings:

  • The special variables from pattern rules (@$, @?, @*, etc.) don’t work in the eval version of the rule, so make sure you write the rule directly in terms of its arguments
  • GNU Make 3.80 has bugs involving eval, so if you’re using that version (and it isn’t patched by your OS distro), you may want to upgrade to 3.81.

If you’re still reading by now, I, um, salute you? Glad to meet another member of the club of people-who-think-about-make-more-than-they-should…

Post Revisions:

There are no revisions for this post.