Jeff
Jeff

Reputation: 1335

Why does this makefile target specific variable not expand as expected?

I have the following simplified makefile and I'm trying to set different paths based on different targets. Unfortunately, I'm not getting the results that I expect. This is with make version 3.81.

.SECONDEXPANSION:
all:  Debug32

# Object directory set by target
Debug32:  OBJDIR = objdir32
#OBJDIR = wrongdirectory

# ObjDir is empty here. :(
OBJS = $(addprefix $(OBJDIR)/,DirUtil.o)

$(OBJDIR)/%.o : %.cpp
            echo Compile: $@

Debug32: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
            echo mkdir $(OBJDIR) - $@

The results are as follows with no setting of OBJDIR:

echo Compile: /DirUtil.o

If I uncomment the "OBJDIR = wrongdirectory" line, I'll get the following results, which are confusing since I see both values of the variable where I think I should only see one:

echo mkdir objdir32 - wrongdirectory -
echo Compile: wrongdirectory/DirUtil.o

I'm assuming that the variables are not being expanded when I think they should, but I can't figure out how to alter this behavior.

Upvotes: 14

Views: 9799

Answers (2)

Gavin Smith
Gavin Smith

Reputation: 3154

From the GNU info manual

Variables and functions in all parts of a makefile are expanded when read, except for in recipes, the right-hand sides of variable definitions using '=', and the bodies of variable definitions using the 'define' directive.

The target-specific variable only applies within recipes. Within

$(OBJS): | $(OBJDIR)

and

$(OBJDIR):

it is getting the global variable.

So working through what happens when you run make Debug32, it sees the contents of OBJS as a prerequisite, which leads to the first rule above. $(OBJDIR) has already been substituted with the global value, and this matches the target-name in the second rule which has also been substituted the same way.

However, when we get to the recipe:

    echo mkdir $(OBJDIR) - $@

$(OBJDIR) has not been substituted yet, so it gets the target-specific variable value.

A working version

.SECONDEXPANSION:
all:  Debug32

# Object directory set by target
Debug32:  OBJDIR = objdir32
OBJDIR = wrongdirectory

Debug32: OBJS = $(addprefix $(OBJDIR)/,obj.o)
OBJS = wrongobjs

Debug32: $$(OBJS)
    echo OBJS are $(OBJS)
    echo OBJDIR is $(OBJDIR)

%/obj.o: | %
    touch $@

OBJDIRS = objdir32 wrongdirectory anotherdirectory
$(OBJDIRS):
#   mkdir $(OBJDIR)
    mkdir $@

The main change is using $$ in this line:

Debug32: $$(OBJS)

With only a single $, I get the error message

make: *** No rule to make target `wrongobjs', needed by `Debug32'.  Stop.

However, with the $$, I get

echo OBJS are objdir32/obj.o
OBJS are objdir32/obj.o
echo OBJDIR is objdir32
OBJDIR is objdir32

The use of secondary expansion has allowed accessing the target-specific variable in the prerequisites.

The other change is that I made OBJS a target-specific variable (because it is). In order to have a rule to build OBJS whatever its value, I had to use a pattern rule:

%/obj.o: | %

To avoid having a separate line for each object file, you could do the following instead:

OBJ_BASENAMES=obj.o obj2.o obj3.o

$(addprefix %/,$(OBJ_BASENAMES)): | %
    touch $@ # Replace with the proper recipe

The line containing the addprefix macro expands to

%/obj.o %/obj2.o %/obj3.o: | %

Then running make anotherdirectory/obj2.o creates a directory called "anotherdirectory" first, and creates a file called "obj2.o" within it.

Note all possible directories have to be listed in OBJDIRS. There's no way to collect all the rule-specific values of OBJDIR, so listing them is the best choice. The alternative is a % : rule to build any directory, which be capable of matching and building any target, which could be risky. (If you abandon the use of target-specific variables, there is another way of getting a list of directories that could be built: use variables with predictable names like Debug32_OBJDIR instead, and generate a list of their values using make functions.)

Alternatively, a generic rule that doesn't require listing the object files:

SOURCE=$(basename $(notdir $@)).cpp
DIR=$(patsubst %/,%,$(dir $@))

%.o: $$(SOURCE) | $$(DIR)
    touch $@ # Replace with proper recipe

There is no feature to read a rule in the context of every target, substituting in target-specific variables and acquiring a new rule for each target. Generic rules cannot be written in this way using target-specific variables.

Upvotes: 17

Mark Galeck
Mark Galeck

Reputation: 6385

a good way to handle

%/obj.o: | %
    touch $@

OBJDIRS = objdir32 wrongdirectory anotherdirectory
$(OBJDIRS):
    mkdir $@

is:

%/.:
    mkdir -p $@

.SECONDEXPANSION:

then later you can just write, for any target that may need a directory

target: prerequisites | $$(@D)/.
    normal recipe for target

Upvotes: 4

Related Questions