Search code examples
cmakefilegnu-maketargetrecipe

Target dependent source files in gnu make


My project is a firmware that has a common logic part which is device independent and a device dependent part. Now I want one (phoney) target to build device A and one (phoney) target to build device B. Both targets shall ideally produce the same named binary. Obviously both targets depend on the common device independent code and their own individual sources. target_A: $(COMMON_OBJ) $(A_OBJ)

I tried to set target dependent variables but the fact that they are only evaluated in the recipe makes it impossible to create a dependency list based on a shared Variable between the targets.

This doesn't work:

target_A:
    DEV_SRC = foo_a.c bar_a.c
target_A: $(COMMON_OBJ) $(DEV_SRC:.c=.o)

The situation I have now is this: I need to write the same recipe for each target and that is what I like to avoid. Can this be combined into one single target/recipe?

target_A: $(COMMON_OBJ) $(A_OBJ)
    <build recipe>
target_B: $(COMMON_OBJ) $(B_OBJ)
    <build recipe>

For a bit more background: I like to make the Makefile simple enough so that fellow engineers can for example add another target, define it's sources and maybe add the target or variable to an existing list. All this I consider doable for every programmer but to write recipes and extend the Makefiles logic should be avoided to minimize the error potential especially for people with no experience with make.


Solution

  • With the help of secondary expansion, I would make it as easy as:

    $ cat Makefile
    TARGETS := target_A target_B
    
    target_A_SRC := target_A.c
    target_B_SRC := target_B.c
    
    COMMON_OBJ := common.o
    
    $(foreach target,$(TARGETS),$(eval $(target)_OBJ := $(addsuffix .o, $(basename $($(target)_SRC)))))
    
    .SECONDEXPANSION:
    
    .PHONY: all
    all: $(TARGETS)
    
    .PHONY: clean
    clean:
            -rm -f $(TARGETS) $(foreach target,$(TARGETS),$($(target)_OBJ)) $(COMMON_OBJ)
    
    $(TARGETS): $(COMMON_OBJ) $$($$@_OBJ)
            $(LINK.o) $(OUTPUT_OPTION) $^
    
    

    In order to add new target it would be enough to add new target into $(TARGETS) and define *_SRC variable accordingly. All the rest (build and clean) would be handled by already existing recipes.

    Sample output:

    $ make
    cc    -c -o common.o common.c
    cc    -c -o target_A.o target_A.c
    cc   -o target_A common.o target_A.o
    cc    -c -o target_B.o target_B.c
    cc   -o target_B common.o target_B.o
    
    $ make clean
    rm -f target_A target_B target_A.o target_B.o common.o