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

14

我有以下简化版的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) - $@

没有设置OBJDIR的情况下,结果如下:

echo Compile: /DirUtil.o

如果我取消注释 "OBJDIR = wrongdirectory" 这一行,我会得到以下结果,这些结果让我感到困惑,因为我看到了变量的两个值,而我认为只应该看到一个值:

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

我认为变量没有被扩展,但是我不知道如何改变这种行为。

2个回答

17

来自GNU info手册

在读取时,除了配方、使用'='定义的变量右侧和使用'define'命令定义的变量主体之外,makefile所有部分中的变量和函数都会被展开。

目标特定变量仅适用于配方内。在配方之外,它不起作用。

$(OBJS): | $(OBJDIR)

$(OBJDIR):

它正在获取全局变量。

因此,当您运行 make Debug32 时,它将OBJS内容视为先决条件,这将导致第一条规则。 $(OBJDIR)已经被替换为全局值,并且这与第二条规则中的目标名称匹配,该目标名称也已以同样的方式被替换。

但是,当我们到达配方部分时:

    echo mkdir $(OBJDIR) - $@

$(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 $@

主要的变化是在这行中使用了$$

Debug32: $$(OBJS)

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

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

但是,使用$$,我得到了

echo OBJS are objdir32/obj.o
OBJS are objdir32/obj.o
echo OBJDIR is objdir32
OBJDIR is objdir32
使用二次展开技术可以在前提条件中访问目标特定变量。
另一个更改是将OBJS变量指定为特定目标变量(因为它就是)。为了建立规则来构建无论OBJS的值如何,我必须使用模式规则。
%/obj.o: | %
为了避免每个目标文件都有单独的行,您可以改为执行以下操作:
OBJ_BASENAMES=obj.o obj2.o obj3.o

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

addprefix 宏所在的那一行扩展为:

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

运行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

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


这个可以工作,但是似乎我必须两次指定对象目录和每个.o文件两次,包括每个源文件的模式规则。我做错了吗?对于两个或更多目标创建一个简单通用的makefile似乎太困难了。 - Jeff
我已经添加了一种覆盖所有目标文件的方法。我希望可以不用列出它们所有的名称(类似于%.o: $$(basename %).cpp | $$(dir $$@)),但我还没有让它正常工作。 - Gavin Smith
我已经添加了一个更易于使用的版本,并注明了必须列出目录的注意事项。Mark Galeck构建目录的方法似乎更好。 - Gavin Smith

4
一种处理的好方法。
%/obj.o: | %
    touch $@

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

is:

%/.:
    mkdir -p $@

.SECONDEXPANSION:

稍后,您可以直接编写任何需要目录的目标。
target: prerequisites | $$(@D)/.
    normal recipe for target

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接