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

为什么这个makefile目标特定变量没有按预期展开?,makefile,gnu-make,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.

我有以下简化的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=errowdirectory”行,我将得到以下结果,这是令人困惑的,因为我看到了变量的两个值,而我认为我应该只看到一个:

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

我假设变量没有在我认为应该扩展的时候被扩展,但是我不知道如何改变这种行为。

来自GNU信息手册

生成文件所有部分中的变量和函数在 阅读,除了在食谱中,变量的右侧 使用“=”的定义,以及使用 “定义”指令

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

$(OBJS): | $(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: | %
然后运行
makeanotherdirectory/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
没有在每个目标的上下文中读取规则、替换特定于目标的变量并为每个目标获取新规则的功能。通用规则不能使用特定于目标的变量以这种方式编写。

这是一种很好的处理方法

%/obj.o: | %
    touch $@

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

然后,您可以为可能需要目录的任何目标编写

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

这是可行的,但似乎我必须两次指定对象目录,两次指定每个.o文件,包括每个源文件的模式规则。我这样做完全错了吗?为两个或多个目标创建一个简单的通用makefile似乎太难了。我添加了一种覆盖所有对象文件的方法。我希望不列出它们(比如
%.o:$$(basename%).cpp |$$(dir$$@)
)是可能的,但我还没有让它工作。我已经添加了一个更易于使用的版本和关于必须列出目录的注释。不过,马克·加莱克(MarkGaleck)构建目录的方法看起来可能更好。
%/.:
    mkdir -p $@

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