Search code examples
makefilegnu

Makefile: order of adding prefix affects how a list of targets is built from a common dependency


Motivation:

I have a C project in which multiple .o files are to be generated from a common file. This main file uses preprocessor directives to conditionally include other .h files as needed, depending on target-specific variables defined in the makefile.

I've written this rule below, but depending on the order in which I apply my variable references I get different outcomes.

One small(ish) change, two different outputs

Consider two versions of code from my Makefile. In version A we have the following snippets:

MAIN_OBJ:= $(MAIN_1) $(MAIN_2) $(MAIN_3) $(MAIN_4)

... omitted non-relevant rules (including an all: rule)

$(OBJECT_DIR)/$(MAIN_1): MFLAG = $(METHOD_1_FLAG)
    
$(OBJECT_DIR)/$(MAIN_2): MFLAG = $(METHOD_2_FLAG)
    
$(OBJECT_DIR)/$(MAIN_3): MFLAG = $(METHOD_3_FLAG)
    
$(OBJECT_DIR)/$(MAIN_4): MFLAG = $(METHOD_4_FLAG)

$(OBJECT_DIR)/$(MAIN_OBJ): $(SOURCE_DIR)/$(DEPENDENT_MAIN)
    $(CC) -DUSE_$(MFLAG) $(CFLAGS) -o $@ $<

This only successfully builds the first target, $(OBJECT_DIR)/$(MAIN_1). The remaining three never get compiled and make stops there.

Now in version B we redefine MAIN_OBJ so that the directory prefix is included within the target list itself:

MAIN_OBJ:= $(MAIN_1) $(MAIN_2) $(MAIN_3) $(MAIN_4)
MAIN_OBJ:= $(addprefix $(OBJECT_DIR)/,$(MAIN_OBJ)

... omitted non-relevant rules (again)

$(OBJECT_DIR)/$(MAIN_1): MFLAG = $(METHOD_1_FLAG)
    
$(OBJECT_DIR)/$(MAIN_2): MFLAG = $(METHOD_2_FLAG)
    
$(OBJECT_DIR)/$(MAIN_3): MFLAG = $(METHOD_3_FLAG)
    
$(OBJECT_DIR)/$(MAIN_4): MFLAG = $(METHOD_4_FLAG)

$(MAIN_OBJ): $(SOURCE_DIR)/$(DEPENDENT_MAIN)
    $(CC) -DUSE_$(MFLAG) $(CFLAGS) -o $@ $<

This solution works, and compiles all 4 .o files, each with the proper $(MFLAG) value.

What's happening here?

This is probably a dumb question, but why does Version A only compile one .o file? I recognize version B is a generally better way to write rules.

Let me provide one more example that will perhaps illustrate my confusion.

Say we want to write a much more common type of rule: compiling targets from a list with a pattern rule for finding dependencies.

Doing something similar to Version A wouldn't result in a single .o being successfully generated:

MY_FILES:= $(wildcard $(SOURCE_DIR)/*.c))
MY_OBJ:= $(patsubst $(SOURCE_DIR)/%.c, %.o, $(MY_FILES))

...

$(OBJECT_DIR)/$(MY_OBJ): $(OBJECT_DIR)/%.o: $(SOURCE_DIR)/%.c
    $(CC) $(CFLAGS) -o $@ $<

Clearly the above is a bad idea, and you should write something like this instead:

MY_FILES:= $(wildcard $(SOURCE_DIR)/*.c))
MY_OBJ:= $(patsubst $(SOURCE_DIR)/%.c, $(OBJECT_DIR)/%.o, $(MY_FILES))

...

$(MY_OBJ): $(OBJECT_DIR)/%.o: $(SOURCE_DIR)/%.c
    $(CC) $(CFLAGS) -o $@ $<

But my question is this:

Why in this case does adding the directory prefix in the rule itself result in nothing being built, while in version A of my makefile the first target was successfully made?


Solution

  • "Version A" fails because make is just expanding things like you asked it to. A variable reference like this:

    $(OBJECT_DIR)/$(MAIN_OBJ): ...
    

    says "expand the variable OBJECT_DIR, then add a "/", then expand the variable MAIN_OBJ". So you get:

    $(OBJECT_DIR)/$(MAIN_1) $(MAIN_2) $(MAIN_3) $(MAIN_4): ...
    

    So, only the first one is actually prefixed by the OBJECT_DIR value, not all of them (since you didn't show what the values were for all these variables I didn't complete the expansion).

    Secondly, make always builds just the first target that it finds in the makefile (unless you override that with the command line or .DEFAULT). You don't say what the "non-relevant rules" are that you omitted, but unless one of them was an all target or similar that depends on all the MAIN_* targets, make will only build the first one which is the behavior you saw.

    ETA Prepending to all words is trivial using various methods; see the GNU make manual.

    One option:

    $(addprefix $(OBJECT_DIR)/,$(MAIN_OBJ)): ...
    

    Another option:

    $(MAIN_OBJ:%=$(OBJECT_DIR)/%): ...
    

    Another option:

    $(patsubst %,$(OBJECT_DIR)/%,$(MAIN_OBJ)): ...