makefile

Why can't I use $(@D) in a Makefile prerequisite?


When I compile my C files and output the resulting object files into their own directory, I need to make sure this directory exists. I have seen two approaches for this:

  1. Always create the directory of the current target:
    obj/%.o: src/%.c
        @mkdir -p $(@D)
        $(CC) ...
    
  2. Add the obj/ directory as a prerequisite
    obj/%.o: src/%.c | obj/
        $(CC) ...
    
    obj/:
        mkdir -p $@
    

From 1. I like that it uses $(@D) to avoid repetition and to make it more robust, from 2. I like that mkdir is called only once and I feel like it makes more sense conceptually.

When I try to combine both like this

obj/%.o: src/%.c | $(@D)
    $(CC) ...

obj/:
    mkdir -p $@

then the compiler will complain that the obj/ directory does not exist.


For anyone curious about this specific use case: MadScientist mentioned secondary expansion, so I used the following

.SECONDEXPANSION
obj/%.o: src/%.c: $$(@D)
    $(CC) ...

obj:
    mkdir -p $@

This seems to work, but it has a few problem with trailing /, so I removed them for now.


Solution

  • Because that's how make works; from the manual:

    It’s very important that you recognize the limited scope in which automatic variable values are available: they only have values within the recipe. In particular, you cannot use them anywhere within the target list of a rule; they have no value there and will expand to the empty string. Also, they cannot be accessed directly within the prerequisite list of a rule. A common mistake is attempting to use $@ within the prerequisites list; this will not work. However, there is a special feature of GNU make, secondary expansion (see Secondary Expansion), which will allow automatic variable values to be used in prerequisite lists.

    If you're asking, why does make work like this, it's because make always expands targets and prerequisites when it's parsing the makefile, not when it matches the rules. Otherwise, something like this could not work:

    OBJ = foo
    
    $(OBJ)/%.o : %.c | $(OBJ) ;
    
    OBJ = bar
    
    $(OBJ)/%.o : %.cpp | $(OBJ) ;
    

    Trying to special case SOME variables or functions to expand immediately and some be deferred until later, would be a mess and even harder to explain and understand. And of course, until the rule actually matches we don't know what $(@D) should be, because the % could contain a directory.

    The link given in the manual excerpt above explains how to use Secondary Expansion to provide some form of this behavior.