这个 Makefile 下的生成最终二进制文件输出的环节有点问题

2016-05-17 23:21:20 +08:00
 fyyz

以前也写过一点 Makefile ,但是基本上都是有一个文件就写条编译命令,于是 Makefile 里面往往会弄得很乱。 双休日看了点 Makefile 的文档,直接上手用在一个小项目里了,但是会出现一点问题,实在搞不定。

先看下目录结构:

# tree
.
├── bin
├── Makefile
├── objs
└── src
    ├── Httpkit.cpp
    ├── Httpkit.h
    └── main.cpp

然后是 Makefile 。其中我重新定义了 %.o:%.cpp 的规则,这样可以把生成的动态链接库文件放到 objs 目录下。

CXX             = g++
CXXFLAGS        = -std=c++11 -Wall -Werror
CFLAGS          = -lcurl -lcurlpp
SRC_DIR         = ./src
OBJS_DIR        = ./objs
BIN_DIR         = ./bin

vpath %.cpp     $(SRC_DIR)
vpath %.h       $(SRC_DIR)
vpath %.o       $(OBJS_DIR)

%.o:            %.cpp
        $(CXX) $(CXXFLAGS) -c -o $(OBJS_DIR)/$@ $^

.PHONY:         default clean

default:        HttpKit
main.o:         main.cpp
Httpkit.o:      Httpkit.cpp
HttpKit:        main.o Httpkit.o
        $(CXX) $(CXXFLAGS) $(CFLAGS) -o $(BIN_DIR)/$@ $^

clean:
        -rm -rf $(OBJS_DIR)/*
        -rm -rf $(BIN_DIR)/*

执行 make 的结果:

g++ -std=c++11 -Wall -Werror -c -o ./objs/main.o ./src/main.cpp
g++ -std=c++11 -Wall -Werror -c -o ./objs/Httpkit.o ./src/Httpkit.cpp
g++ -std=c++11 -Wall -Werror -lcurl -lcurlpp -o ./bin/HttpKit main.o Httpkit.o  #注意这行
g++: error: main.o: No such file or directory
g++: error: Httpkit.o: No such file or directory
Makefile:21: recipe for target 'HttpKit' failed
make: *** [HttpKit] Error 1

上面我标注释的这行输出,虽然自动推导展开了依赖参数,但是依赖的路径完全没有按照 vpath 进行补全。但是奇怪的是,上面两个生成动态链接库的规则也是这样的写法,却完全正确地补全了!

但是,此时 objs 目录下已经有了两个 .o 文件,再执行 make ,直接只有一行输出:

g++ -std=c++11 -Wall -Werror -lcurl -lcurlpp -o ./bin/HttpKit ./objs/main.o ./objs/Httpkit.o

WTF ?!?!?!

现在我有两个问题: 1 ,究竟为什么第一次 make 无法按照 vpath 变量的值自动展开路径,第二次直接就可以了? 2 ,生成最终二进制文件的命令能否也定义成隐含规则自动推导(确实这个意义不大,但是主要还是学习下 Makefile ,并且最终二进制文件并没有后缀名)?

1659 次点击
所在节点    C
10 条回复
fyyz
2016-05-17 23:33:43 +08:00
还有一个问题,怎么看 Make 的隐含规则?网上可以查到不少隐含规则,但是我仔细看了下,并不是与现在版本的 make 的隐含规则精确匹配的。
clarkok
2016-05-18 11:20:27 +08:00
> HttpKit: main.o Httpkit.o

这行应该改成

> HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o

然后 `make -p` 可以让 make 输出所有的内部命令,所有的隐含规则都在这里面
clarkok
2016-05-18 11:22:00 +08:00
另外,讲道理所有的目标都应该写成相对路径,比如

> HttpKit: ......

应该变成

> $(BIN_DIR)/HttpKit: ......

这样
fyyz
2016-05-18 11:31:17 +08:00
@clarkok 能不能依赖里写清楚文件名之后,就不要在规则里再次写一遍文件名了?
fyyz
2016-05-18 11:32:07 +08:00
@clarkok 我还想知道为什么第一次 make 报错,第二次就奇迹般地把路径补全对了?
arakashic
2016-05-18 11:33:55 +08:00
1. VPATH 只解决依赖文件在哪里找的问题,并不添加依赖的前缀。所以 Httpkit 依赖的是 main.o 和 Httpkit.o ,而不是$(OBJS_DIR)/main.o ,$(OBJS_DIR)/Httpkit.o 。同理 main.o 和 Httpkit.o 依赖的是 main.cpp 和 Httpkit.cpp ,而不是$(SRC_DIR)/main.cpp ,$(SRC_DIR)/Httpkit.cpp 。
2. make 的依赖处理是递归的。
第一次 make 的时候,编译这一步是在找不到./main.o 和./Httpkit.o 的时候执行的。编译的这一步理应生成./main.o 和./Httpkit.o ,而不是$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o 。所以到链接的时候就 fail 了。
第二次 make 的时候,因为有了$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o ,所以这个时候 VPATH 就发挥作用了。
fyyz
2016-05-18 11:38:20 +08:00
@arakashic
那么应该这样写,对吗?
$(OBJS_DIR)/main.o: main.cpp
$(OBJS_DIR)/Httpkit.o: Httpkit.cpp
...
arakashic
2016-05-18 11:47:05 +08:00
嗯,还有$(BIN_DIR)/HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o
fyyz
2016-05-18 11:49:46 +08:00
@arakashic
先谢谢你的解答
还有个问题我不是很懂了,为什么 .cpp 和 .o 都在 vpath 里,但是 .cpp 不需要加 $(SRC_DIR),而 .o 就需要加 $(OBJS_DIR) 了?
arakashic
2016-05-18 12:01:02 +08:00
简单来说,第一次的时候 make 认为到处(包括 VPATH )都找不到 main.o 和 Httpkit.o ,所以就要生成这俩。生成的时候需要 main.cpp 和 Httpkit.cpp ,而这俩正好是 VPATH 里能找到的,于是就正常编译了。 make 认为既然顺利经过了这一步, main.o 和 Httpkit.o 已经存在了,于是就不用找了。然而并没有,你修改的规则生成的是$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o ,而不是 HttpKit 所依赖的 main.o 和 Httpkit.o 。所以就 fail 了。
第二次的时候 make 在 VPATH 找到了 main.o 和 Httpkit.o ,所以就直接用了。

Rule of Thumb : VPATH 是适合用来找源代码的,而不适合找目标文件。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/279391

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX