Andreas
Andreas

Reputation: 10037

Avoiding redundancies in makefile rules

Consider the following makefile:

C_COMPILER      = gcc
C_COMPILER_OPTS = -DFOOBAR

OBJECTS=obj/dir1/foo.o obj/dir2/bar.o obj/dir3/test.o

obj/dir1/%.o: src/dir1/%.c
    $(C_COMPILER) -o $@ $(C_COMPILER_OPTS) src/dir1/$*.c

obj/dir2/%.o: src/dir2/%.c
    $(C_COMPILER) -o $@ $(C_COMPILER_OPTS) src/dir2/$*.c

obj/dir3/%.o: src/dir3/%.c
    $(C_COMPILER) -o $@ $(C_COMPILER_OPTS) -DANOTHEROPTION src/dir3/$*.c

libfoobar: $(OBJECTS)
    ar rs [email protected] $(OBJECTS)

obj/dir1/foo.o: src/dir1/foo.c
obj/dir2/bar.o: src/dir2/bar.c
obj/dir3/test.o: src/dir3/test.c

As you can see, the build rules for the files in src/dir1 and src/dir2 are identical. Files in src/dir3, however, are built differently.

So is there a way to reduce the two build rules for src/dir1 and src/dir2 into just one rule, thereby eliminating the redundancy?

Upvotes: 1

Views: 299

Answers (2)

Renaud Pacalet
Renaud Pacalet

Reputation: 28945

I would try something like (GNU make only):

C_COMPILER      := gcc
C_COMPILER_OPTS := -DFOOBAR
DIRS            := dir1 dir2 dir3
TOP             := libfoobar.a

.PHONY: all clean

all: $(TOP)

dir1_C_COMPILER_OPTS := $(C_COMPILER_OPTS)
dir2_C_COMPILER_OPTS := $(C_COMPILER_OPTS)
dir3_C_COMPILER_OPTS := $(C_COMPILER_OPTS) -DANOTHEROPTION

# $(1): dir, $(2): file basename
define COMPILE_rule
OBJECTS += obj/$(1)/$(2).o
obj/$(1)/$(2).o: src/$(1)/$(2).c
    $$(C_COMPILER) -o $$@ $$($(1)_C_COMPILER_OPTS) $$<
endef
$(foreach d,$(DIRS),$(foreach f,$(patsubst src/$(d)/%.c,%,$(wildcard src/$(d)/*.c)),$(eval $(call COMPILE_rule,$(d),$(f)))))

$(TOP): $(OBJECTS)
    ar rs $@ $(OBJECTS)

clean:
    rm -f $(OBJECTS) $(TOP)
  • foreach var,$(VAR) loops over words in $(VAR) and assigns the current word to $(var).
  • call var,$(v1),$(v2) performs an expansion of var where it substitutes $(1) with the current value of $(v1), $(2) with the current value of $(v2)... and $$ with $.
  • eval instantiates the result of the call expansion as a regular rule.

If you do not want to use the foreach-eval-call construct, you can also try this slightly more verbose solution (it separates the dependencies from the recipes):

C_COMPILER      := gcc
C_COMPILER_OPTS := -DFOOBAR
TOP             := libfoobar.a

.PHONY: all clean

all: $(TOP)

OBJ1 := $(patsubst src/dir1/%.c,obj/dir1/%.o,$(wildcard src/dir1/*.c))
OBJ2 := $(patsubst src/dir2/%.c,obj/dir2/%.o,$(wildcard src/dir2/*.c))
OBJ3 := $(patsubst src/dir3/%.c,obj/dir3/%.o,$(wildcard src/dir3/*.c))

obj/dir1/%.o: src/dir1/%.c
obj/dir2/%.o: src/dir2/%.c
obj/dir3/%.o: src/dir3/%.c

$(OBJ3): C_COMPILER_OPTS += -DANOTHEROPTION

$(OBJ1) $(OBJ2) $(OBJ3):
    $(C_COMPILER) -o $@ $(C_COMPILER_OPTS) $(patsubst obj%.o,src%.c,$@)

$(TOP): $(OBJ1) $(OBJ2) $(OBJ3)
    ar rs $@ $^

clean:
    rm -f $(OBJ1) $(OBJ2) $(OBJ3) $(TOP)

Upvotes: 1

Florian Weimer
Florian Weimer

Reputation: 33694

Make variables defined with = are evaluated recursively, until no substitutions remain:

So you could use this:

compile-command = $(C_COMPILER) -o $@ $(C_COMPILER_OPTS) src/dir1/$*.c

obj/dir1/%.o: src/dir1/%.c
    $(compile-command)

(Although I personally consider this make behavior a hack and dislike it—but the alternatives are $(eval …) and scripted generation of makefiles, and those are not ideal, either.)

Upvotes: 0

Related Questions