Search code examples
makefilegnu

Makefile with two targets and separate build folders


I am trying to use one Makefile with two similar targets and two separate build folders. The only difference between the targets is the addition of a CFLAG define.

Here is a snippet of what I have, however I can't get the build folder to evaluate to something different depending on the target. I used foo and bar to represent the different targets.

...
foo_BUILD_DIR := build_foo/
bar_BUILD_DIR := build_bar/

C_SRCS := main.c

CFLAGS := -std=c99 -Os -Wall
foo_CFLAGS := $(CFLAGS) -DBLE=1

C_OBJS := $(addprefix $(BUILD_DIR),$(subst .c,.o,$(C_SRCS)))

$(BUILD_DIR)%.o: %.c
    @mkdir -p $(@D)
    $(CC) $(CFLAGS) $^ -o $@

foo:$(OBJS)
    $(CC) $(OBJS) $(LDFLAGS) -o $@ 

bar:$(OBJS)
    $(CC) $(OBJS) $(LDFLAGS) -o $@ 

Solution

  • You have three problems to solve here.

    The first is building object files in the build directories. First we write a pattern rule to build foo objects:

    foo_BUILD_DIR := build_foo # Don't put the trailing slash in the dir name, it's a pain.
    
    CFLAGS := -std=c99 -Os -Wall
    foo_CFLAGS := $(CFLAGS) -DBLE=1
    
    $(foo_BUILD_DIR)/%.o: %.c
        @mkdir -p $(@D)
        $(CC) $(foo_CFLAGS) $^ -o $@
    

    Once that's working perfectly, we write another rule for the bar objects:

    $(bar_BUILD_DIR)/%.o: %.c
        @mkdir -p $(@D)
        $(CC) $(CFLAGS) $^ -o $@
    

    The second problem is tidying up what we've written so far. That foo_CFLAGS variable is now the only thing making these two recipes different, so let's get rid of it with a target-specific variable assignment:

    $(foo_BUILD_DIR)/%.o: CFLAGS += -DBLE=1
    
    $(foo_BUILD_DIR)/%.o: %.c
        @mkdir -p $(@D)
        $(CC) $(CFLAGS) $^ -o $@
    

    Now we can combine the rules into one pattern rule with two targets:

    $(foo_BUILD_DIR)/%.o: CFLAGS += -DBLE=1
    
    $(foo_BUILD_DIR)/%.o $(bar_BUILD_DIR)/%.o: %.c
        @mkdir -p $(@D)
        $(CC) $(CFLAGS) $^ -o $@
    

    The third problem is getting the foo and bar rules to require the right objects. Obviously this rule:

    foo:$(OBJS)
        ...
    

    won't work, we need something specific to foo:

    foo: $(addprefix $(foo_BUILD_DIR)/, $(OBJS))
        ...
    

    This works, but it requires us to write a foo rule that specifies $(foo_BUILD_DIR), a bar rule that specifies $(bar_BUILD_DIR), and so on. Is there a lazier way? All we need is to take the target (e.g. foo) and get it into the prerequisite list. As you know, the automatic variable $@ contains the target, but it isn't available in the prerequisite list, because the prerequisite list is expanded before a value is assigned to that variable-- unless we use Secondary Expansion. This is an advanced technique, but it lets us do a second expansion in the later phase (escaping our variables with an extra $ to protect them from the first expansion):

    .SECONDEXPANSION:
    
    foo: $(addprefix $$($$@_BUILD_DIR)/, $(OBJS))
        ...
    

    And once that's working, we can add another target, or as many as we want:

    foo bar: $(addprefix $$($$@_BUILD_DIR)/, $(OBJS))
        ...
    

    There are one or two more refinements possible, but this is enough to start with.