为什么这个makefile目标特定变量没有按预期扩展?

Jef*_*eff 11 makefile gnu-make

我有以下简化的makefile,我正在尝试根据不同的目标设置不同的路径.不幸的是,我没有得到我期望的结果.这是make版本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) - $@
Run Code Online (Sandbox Code Playgroud)

结果如下,没有设置OBJDIR:

echo Compile: /DirUtil.o
Run Code Online (Sandbox Code Playgroud)

如果我取消注释"OBJDIR = wrongdirectory"行,我将得到以下结果,这是令人困惑的,因为我看到变量的两个值,我认为我应该只看到一个:

echo mkdir objdir32 - wrongdirectory -
echo Compile: wrongdirectory/DirUtil.o
Run Code Online (Sandbox Code Playgroud)

我假设变量在我认为应该扩展时没有扩展,但我无法弄清楚如何改变这种行为.

Gav*_*ith 14

从GNU信息手册

make文件的所有部分中的变量和函数在读取时都会扩展,除了配方,使用'='的变量定义的右侧,以及使用'define'指令的变量定义的主体.

特定于目标的变量仅适用于配方.内

$(OBJS): | $(OBJDIR)
Run Code Online (Sandbox Code Playgroud)

$(OBJDIR):
Run Code Online (Sandbox Code Playgroud)

它正在获得全局变量.

因此,当您运行时会发生什么make Debug32,它会将内容OBJS视为先决条件,从而导致上面的第一条规则.$(OBJDIR)已被全局值替换,这与第二个规则中的target-name匹配,该规则也以相同的方式替换.

但是,当我们得到食谱时:

    echo mkdir $(OBJDIR) - $@
Run Code Online (Sandbox Code Playgroud)

$(OBJDIR) 尚未被替换,因此它获取特定于目标的变量值.

一个工作版本

.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 $@
Run Code Online (Sandbox Code Playgroud)

主要变化是$$在这一行中使用:

Debug32: $$(OBJS)
Run Code Online (Sandbox Code Playgroud)

只有一个$,我收到错误信息

make: *** No rule to make target `wrongobjs', needed by `Debug32'.  Stop.
Run Code Online (Sandbox Code Playgroud)

然而,随着$$,我得到

echo OBJS are objdir32/obj.o
OBJS are objdir32/obj.o
echo OBJDIR is objdir32
OBJDIR is objdir32
Run Code Online (Sandbox Code Playgroud)

使用辅助扩展允许在先决条件中访问特定于目标的变量.

另一个变化是我创建OBJS了一个特定于目标的变量(因为它是).为了建立一个规则来构建OBJS它的价值,我不得不使用模式规则:

%/obj.o: | %
Run Code Online (Sandbox Code Playgroud)

为避免每个目标文件都有单独的行,您可以执行以下操作:

OBJ_BASENAMES=obj.o obj2.o obj3.o

$(addprefix %/,$(OBJ_BASENAMES)): | %
    touch $@ # Replace with the proper recipe
Run Code Online (Sandbox Code Playgroud)

包含addprefix宏的行扩展为

%/obj.o %/obj2.o %/obj3.o: | %
Run Code Online (Sandbox Code Playgroud)

然后运行首先make anotherdirectory/obj2.o创建一个名为"anotherdirectory"的目录,并在其中创建一个名为"obj2.o"的文件.

请注意,必须列出所有可能的目录OBJDIRS.无法收集OBJDIR的所有特定于规则的值,因此列出它们是最佳选择.替代方案是% :构建任何目录的规则,该目录能够匹配和构建任何目标,这可能是有风险的.(如果放弃使用特定于目标的变量,还有另一种获取可以构建的目录列表的方法:使用具有可预测名称的变量Debug32_OBJDIR,并使用make函数生成其值列表.)

或者,不需要列出目标文件的通用规则:

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

%.o: $$(SOURCE) | $$(DIR)
    touch $@ # Replace with proper recipe
Run Code Online (Sandbox Code Playgroud)

没有功能可以在每个目标的上下文中读取规则,替换目标特定变量并为每个目标获取新规则.不能使用特定于目标的变量以这种方式编写通用规则.