以前也写过一点 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 ,并且最终二进制文件并没有后缀名)?
感谢 @clarkok @arakashic,还有 @Maykiller 同学,经过大家的指导,现在已经可以全部自动化推导来实现了。
CXX = g++ -std=c++11
CXXFLAGS = -Wall -Werror
CFLAGS = -lcurl -lcurlpp
SRC_DIR = ./src
OBJS_DIR = ./objs
BIN_DIR = ./bin
vpath %.cpp $(SRC_DIR)
vpath %.h $(SRC_DIR)
.PHONY: default clean
$(OBJS_DIR)/%.o: %.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $^
$(BIN_DIR)/HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o
$(CXX) $(CXXFLAGS) $(CFLAGS) -o $@ $^
default: $(BIN_DIR)/HttpKit
clean:
@rm -rf $(OBJS_DIR)/*
@rm -rf $(BIN_DIR)/*
1
fyyz OP 还有一个问题,怎么看 Make 的隐含规则?网上可以查到不少隐含规则,但是我仔细看了下,并不是与现在版本的 make 的隐含规则精确匹配的。
|
2
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 输出所有的内部命令,所有的隐含规则都在这里面 |
3
clarkok 2016-05-18 11:22:00 +08:00
另外,讲道理所有的目标都应该写成相对路径,比如
> HttpKit: ...... 应该变成 > $(BIN_DIR)/HttpKit: ...... 这样 |
6
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 就发挥作用了。 |
7
fyyz OP |
8
arakashic 2016-05-18 11:47:05 +08:00
嗯,还有$(BIN_DIR)/HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o
|
9
fyyz OP @arakashic
先谢谢你的解答 还有个问题我不是很懂了,为什么 .cpp 和 .o 都在 vpath 里,但是 .cpp 不需要加 $(SRC_DIR),而 .o 就需要加 $(OBJS_DIR) 了? |
10
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 是适合用来找源代码的,而不适合找目标文件。 |