GNU make: generic rule for compiling from different paths

Viewed 54

I have a makefile like this:

default: exe

%.obj: %.src
        # dummy compiler
        @echo link $< to $@
        cat $< > $@

exe: main.obj foo.obj bar.obj
        # dummy linker
        @echo link $^ to $@
        cat $^ > $@

main.obj: main.src

foo.obj: /some/dir/1/foo.src

bar.obj: /some/dir/2/bar.src

Make can't compile foo.obj and bar.obj (and 20+ other objects), because it does not use the "%.obj: %.src" rule. The directories of foo.obj and foo.src resp. bar.obj and bar.src do not match, so the rule does not match.

Is there a way to specify a rule that ignores the directory part of the filenames?

Update:

To make that Makefile work, I copied the compiler lines from the pattern rule to the foo.obj and bar.obj rules (and the 20+ other object rules). That's anything but clean and maintainable. Essentially, I need a pattern rule that ignores the source and object directories when comparing.

4 Answers

I know of no such features (excepted macros, you could arrange for them to generate the makefile fragments you are writing manually). But GNU Make has a vpath feature which allows to specify a path where to look for files of a given extension and which interact with the automatic variables to make something close to what you want.

Your Makefile can be then written as:

default: exe

vpath %.src some/dir/1:some/dir/2

%.obj: %.src
    # dummy compiler
    @echo compiling $< to $@
    cat $< > $@

exe: main.obj foo.obj bar.obj
    # dummy linker
    @echo linking $^ to $@
    cat $^ > $@

Note that this is not exactly what you want: if there is a bar.src in both some/dir/1 and some/dir/2, the one from some/dir/1 will be used.

You can make a variable of all .src files in all subdirectories with $(shell find . -name '*.src'). Here's an example:

OBJS := $(shell find . -name '*.src')

default: exe

%.obj: %.src
        @echo link $< to $@
        cat $< > $@

exe: $(OBJS)
        @echo link $^ to $@
        cat $^ > $@

The macro way of doing this would be as follows:

define mymacro
$1: $2
    @echo compiling $$< to $$@
    cat $$< > $$@
endef

$(eval $(call mymacro,foo.obj,/some/dir/1/foo.src))
$(eval $(call mymacro,bar.obj,/some/dir/2/bar.src))

But be aware that macros make the makefile less maintainable (as it's harder to read, and introduces some sharp sticks inexperienced users might trip on). It also works on GNU make, but may fail on some other make systems.

I have written a build system which uses gmtt and which relies on explicit source elicitation. At the core is a beefed up version of wildcard named wildcard-rec which supports recursive descent akin to ** in other extended wildcard matchers (like git) - maybe it fits your taste and use case:

include gmtt.mk

default: exe

SOURCE_LOCATOR := $(call wildcard-rec,main.src some/dir/**/3/*.src some/**/1/foo.src some/dir/2/bar.src)

$(info Compiling these sources: $(SOURCE_LOCATOR))

OBJECTS := $(addsuffix .obj,$(basename $(notdir $(SOURCE_LOCATOR))))

$(info Building these objects: $(OBJECTS))

# Generate the source prerequisite for each object
TGT-PREREQ := $(join $(addsuffix :,$(OBJECTS)),$(SOURCE_LOCATOR))
# Introduce them as targets via eval:
$(foreach tp,$(TGT-PREREQ),$(info $(tp))$(eval $(tp)))

$(OBJECTS):
    @echo compile $< to $@


exe: $(OBJECTS)
    @echo link $^ to $@

Test:

Compiling these sources: main.src some/dir/foo/3/a.src some/dir/foo/3/b.src some/dir/bar/1/foo.src some/dir/2/bar.src
Building these objects: main.obj a.obj b.obj foo.obj bar.obj
main.obj:main.src
a.obj:some/dir/foo/3/a.src
b.obj:some/dir/foo/3/b.src
foo.obj:some/dir/bar/1/foo.src
bar.obj:some/dir/2/bar.src
compile main.src to main.obj
compile some/dir/foo/3/a.src to a.obj
compile some/dir/foo/3/b.src to b.obj
compile some/dir/bar/1/foo.src to foo.obj
compile some/dir/2/bar.src to bar.obj
link main.obj a.obj b.obj foo.obj bar.obj to exe
Related