请解释这个Makefile - 可执行规则似乎缺失

时间:2016-03-03 18:53:11

标签: 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 dependencies automatically
%.d: %.cpp
    @echo building include dependencies for $(*F)
    @$(CXX) -MM $(CPPFLAGS) $< | { sed 's#\($*\)\.o[ :]*#\1.o $@ : #g' ; echo "%.h:;" ; } > $@

-include $(depends)

我了解创建的可执行文件是build_target。因此,如果我有Foo.cppBar.cpp,则创建的可执行文件将为FooBar

但它是如何做到的?我只看到1条规则,它是%.d: %.cpp。所以它说Foo.d文件取决于Foo.cpp。但是实际编译Foo的规则呢?

这个makefile可以工作,所以它实际上并没有遗漏任何东西。但是为什么我看不到规则呢?有一些隐含的规则吗?

编辑 - 我确实进行了调试,并看到了以下内容

No need to remake target `foo.d'.
  Considering target file `foo'.
   File `foo' does not exist.
   Looking for an implicit rule for `foo'.
   Trying pattern rule with stem `foo'.
   Trying implicit prerequisite `foo.o'.
Found an implicit rule for `foo'.      ## WHAT EXACTLY IS THIS?
Considering target file `foo.o'.
 File `foo.o' does not exist.
 Looking for an implicit rule for `foo.o'.
 Trying pattern rule with stem `foo'.
 Trying implicit prerequisite `foo.c'.
 Trying pattern rule with stem `foo'.
 Trying implicit prerequisite `foo.cc'.
 Trying pattern rule with stem `foo'.
 Trying implicit prerequisite `foo.C'.
 Trying pattern rule with stem `foo'.
 Trying implicit prerequisite `foo.cpp'.
 Found an implicit rule for `foo.o'.
  Pruning file `foo.cpp'.
  Pruning file `foo.cpp'.
 Finished prerequisites of target file `foo.o'.
Must remake target `foo.o'.
g++   -I../../include -Wall -std=c++11 -O3  -Wsign-compare -Wno-strict-aliasing -s  -c -o foo.o foo.cpp

那很好。这必须是被调用的规则。但这条规则从何而来?我如何知道存在哪些默认规则?

4 个答案:

答案 0 :(得分:2)

您可能会错过了解Makefile的概念pattern ruleautomatic variables

这部分找到.cpp文件:

build_sources:=$(wildcard *.cpp)

此部分创建具有相同名称的目标(使用上面定义的变量build_sources),但扩展名替换为.d:

depends:=$(build_sources:.cpp=.d)

相同类型的构造定义了构建目标(删除了扩展名的相同文件名):

build_targets:=$(build_sources:.cpp=)

然后将默认目标定义为需要build_targets,即对应于.cpp`的可执行文件

all: $(build_targets)

此规则定义了如何从.d

构建.cpp
#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:;" ; } > $@

gcc -MM生成规则以了解.c.cpp文件所依赖的标头。通常,如果test.cpp包含test1.htest2.h,则输出将为:

test.o: test.cpp test1.h test2.h

.d将包含每个cpp文件的依赖项,并将创建一个规则来构建.o文件和每个cpp的可执行文件。 $@是规则的目标(.d文件),除非我误解,否则将包含一个看起来像这样的规则(由sed表达式编写):

filename.o filename.d : filename.cpp <list of headers>
%.h:;

第一条规则赋予.o文件的依赖关系。它没有配方,这意味着它只是将依赖项添加到任何现有规则。隐式规则将用于构建它们。 第二个是这里,以防你压缩标题。在这种情况下,make将使用此规则,该规则表明根本没有任何事情要做(它有一个空配方)。

然后所有.d文件都包含在makefile的一部分中:

-include $(depends)

最后,链接单个.o文件的implicit rule会启动:

  

链接单个目标文件

     

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

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

depends:=$(foreach file,$(build_sources:.cpp=.d),"obj/$(file)")

要在单独的子目录bin中构建二进制文件,您需要对build_targets执行相同的操作:

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

可以通过对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" ; } > $@

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

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

的内容
foo.o: foo.cpp foo.h bar.h

如果foo.cpp包含自己的头文件foo.h以及bar.h,例如(直接或间接)。

sed regex replacement命令现在只需添加文件名,即在.d之后生成的规则即将被写入(.o文件)以上规则,因此它也被标记为依赖于源,就像目标文件一样。当其中一个来源中包含的内容稍后更改时,这一点非常重要。 sed命令还为所有头文件添加了一条不执行任何操作的规则。

foo.o foo.d: foo.cpp foo.h bar.h
%.h:;

然后使用implicit rules

之一链接生成的目标文件.o
  

链接单个目标文件

     

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

     

[...]

答案 3 :(得分:0)

许多规则内置于中。如果您正在使用GNU Make,您可以使用GNU Make来打印它的内置规则:

make -f /dev/null -p

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