C++ 请解释此Makefile-似乎缺少可执行规则

C++ 请解释此Makefile-似乎缺少可执行规则,c++,makefile,C++,Makefile,我有一个我不懂的makefile build_sources:=$(wildcard *.cpp) depends:=$(build_sources:.cpp=.d) build_targets:=$(build_sources:.cpp=) .PHONY: all all: $(build_targets) .PHONY: clean clean: rm -f $(build_targets) *.{a,o,d} #build the list of header file de

我有一个我不懂的makefile

build_sources:=$(wildcard *.cpp)
depends:=$(build_sources:.cpp=.d)
build_targets:=$(build_sources:.cpp=)

.PHONY: all
all: $(build_targets)

.PHONY: clean
clean:
    rm -f $(build_targets) *.{a,o,d}

#build the list of header file dependencies automatically
%.d: %.cpp
    @echo building include dependencies for $(*F)
    @$(CXX) -MM $(CPPFLAGS) $< | { sed 's#\($*\)\.o[ :]*#\1.o $@ : #g' ; echo "%.h:;" ; } > $@

-include $(depends)

太好了。这一定是被呼叫的规则。但这条规则从何而来?如何知道存在哪些默认规则?

您的make包包含大量默认规则,此Makefile依赖于这些规则。尝试使用-d(调试信息)运行make。我相信这将向您展示所有正在发挥作用的内容。

理解此
Makefile
可能会错过的是和的概念

此部分查找.cpp文件:

build_sources:=$(wildcard *.cpp)
这一部分创建具有相同名称的目标(使用上面定义的变量
build\u sources
),但扩展名替换为.d:

depends:=$(build_sources:.cpp=.d)
相同类型的构造定义了生成目标(相同的文件名,扩展名已删除):

然后,默认目标被定义为需要
构建\u目标
,即对应于.cpp的可执行文件`

all: $(build_targets)
此规则定义如何从
.cpp
生成
.d

#build the list of header file dependencies automatically
%.d: %.cpp
    @echo building include dependencies for $(*F)
    @$(CXX) -MM $(CPPFLAGS) $< | { sed 's#\($*\)\.o[ :]*#\1.o $@ : #g' ; echo "%.h:;" ; } > $@
.d将包含每个cpp文件的依赖项,并将创建一个规则来构建
.o
文件和每个cpp的可执行文件
$@
是规则(
.d
文件)的目标,除非我弄错了,否则它将包含如下规则(由sed表达式编写):

最后,链接单个
.o
文件的方法开始生效:

链接单个对象文件

n是通过C编译器运行链接器(通常称为ld)从n.o自动生成的。使用的精确配方是“$(CC)$(LDFLAGS)n.o$(LOADLIBES)$(LDLIBS)”

编辑:要在子目录
obj
中构建对象,必须修改每个文件名:

depends:=$(foreach file,$(build_sources:.cpp=.d),"obj/$(file)")
要在单独的子目录
bin
中生成二进制文件,您需要对生成\u目标执行相同的操作:

build_targets:=$(foreach file,$(build_sources:.cpp=), "bin/$(file)")
然后您需要编写规则来构建它,因为默认规则不再起作用(
.o
不在同一目录中)。您需要添加如下规则:

bin/foo: obj/foo.o
    $(CC) $(LDFLAGS) obj/foo.o $(LOADLIBES) $(LDLIBS) -o bin/foo
make -f /dev/null -p
这可以通过正确修改long shell命令来完成:

    @$(CXX) -MM $(CPPFLAGS) $< | { sed 's#\($*\)\.o[ :]*#\1.o $@ : #g' ; echo "%.h:;" ; obj=`echo $< | sed 's/.cpp/.o/'` ; bin=`echo $< | sed 's/.cpp//'` ; echo "$bin: $obj" ; echo -e "\t\$(CC) \$(LDFLAGS) $obj \$(LOADLIBES) \$(LDLIBS) -o $bin" ; } > $@
$(CXX)-MM$(CPPFLAGS)$<{sed's#\($*\)\.o[:]*.\1.o$@:\'g';echo“%.h:;“obj='echo$>$@

生成文件本身不完整。它会自动创建文件
.d
,这些文件是最后包含在makefile中的片段。这些文件是使用
%.d
-规则创建的

因此,请查看生成的
.d
文件,以查看每个对象文件的规则。规则生成脚本是用sed编写的,有点难读,但实际上非常简单。首先,使用
-MM
标志在
.cpp
文件上调用编译器,它将输出如下内容

foo.o: foo.cpp foo.h bar.h
例如,如果
foo.cpp
包括其自己的头文件
foo.h
以及
bar.h
,则(直接或间接)

sed
regex replacement命令现在只需将生成的规则即将写入的文件名(即
.d
文件)添加到上述规则中的
.o
之后,因此它也被标记为取决于源,就像对象文件一样。当其中一个源中的include稍后更改时,这一点很重要。sed
sed
命令还为所有头文件添加了一条不执行任何操作的规则

foo.o foo.d: foo.cpp foo.h bar.h
%.h:;
生成的对象文件
.o
然后使用以下选项之一链接:

链接单个对象文件 n由n.o自动生成 通过C编译器运行链接器(通常称为
ld
)。精确的 使用的配方是
$(CC)$(LDFLAGS)n.o$(LOADLIBES)$(LDLIBS)

[……]

许多规则都是为了制定的。如果您使用的是GNU Make,您可以让它打印其内置规则,如下所示:

bin/foo: obj/foo.o
    $(CC) $(LDFLAGS) obj/foo.o $(LOADLIBES) $(LDLIBS) -o bin/foo
make -f /dev/null -p

您可以使用
-r
使其忽略内置规则;如果您对Makefile执行此操作,您会发现它会抱怨它不知道如何创建目标。

对。但是构建
.d
的规则是不够的,因为它生成
Foo.d
,而不是实际生成的
Foo
通过C编译器。使用的精确公式是“$(CC)$(LDFLAGS)n.o$(LOADLIBES)$(LDLIBS)”。对于只有一个源文件的简单程序,此规则是正确的。“是这样吗?我正在更新更多详细信息。事实上,.d将包含在makefile中,并包含构建文件的规则,现在就完成了!很抱歉出现了问题:-)作为后续问题-如何更改生成文件以使输出在单独的生成目录中生成?几乎正确-sed命令使
$@
的扩展依赖于与对象文件相同的内容(在本例中,写入的行是
foo.o foo.d:foo.cpp foo.h bar.h
)因此,如果任何相关源发生更改,依赖项文件将重新生成。@TobySpeight感谢您的解释。我编辑了我的答案。
foo.o: foo.cpp foo.h bar.h
foo.o foo.d: foo.cpp foo.h bar.h
%.h:;
make -f /dev/null -p