Reputation: 10037
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
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
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