Reputation: 1335
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
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
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