Makefile:生成多目标文件的方法
在网上已经搜到了最简单的方法,不过带着路径的情况下自己没有改成功。先分享原模板。
然后看下我的文件目录结构
test/
├── bin
├── Makefile
└── src
├── a.cpp
└── b.cpp
然后我失败的模仿(总是只生成一个目标文件)
CXX = g++
CXX_FLAGS = -Wall -g
SRC = $(wildcard ./src/*.cpp)
NOTDIR_SRC = $(notdir $(SRC))
OBJS = $(patsubst %.cpp, ./bin/%.o, $(NOTDIR_SRC))
# 多目标
TARGET_LIST = $(patsubst %.cpp, %, $(NOTDIR_SRC))
$(TARGET_LIST):$(OBJS)
@echo $@
$(CXX) -o ./bin/$@ ./bin/$@.o
@echo $(TARGET_LIST)
./bin/%.o:./src/%.cpp
$(CXX) -c $(CFLAGS) $< -o $@
.PHONY:clean
clean:
-rm -f ./bin/*
上面的Makefile看上去应该是有两个目标文件,然后应该编译两个.cpp文件生成两个可执行文件才对,可是咋总生成一个目标文件呢,目标文件队列中的第二个直接被忽略了。。。。 幸好,一口Linux群里有大佬帮忙解答了:
Makefile只有一个总目标,然后由多个可执行文件组成,在把总目标隐式申明,就行了,你写一个总目标
原来,Makefile虽然支持多目标,但是人家支持的方式是总目标必须只有一个,然后总目标由多个可执行文件组成就好了。于是,修改如下:
CXX = g++
CXX_FLAGS = -Wall -g
SRC = $(wildcard ./src/*.cpp)
NOTDIR_SRC = $(notdir $(SRC))
OBJS = $(patsubst %.cpp, ./bin/%.o, $(NOTDIR_SRC))
.PHONY:all
# 多目标
TARGET_LIST = $(patsubst %.cpp, %, $(NOTDIR_SRC))
all:$(TARGET_LIST)
$(TARGET_LIST):$(OBJS)
@echo $@
$(CXX) -o ./bin/$@ ./bin/$@.o
@echo $(TARGET_LIST)
./bin/%.o:./src/%.cpp
$(CXX) -c $(CFLAGS) $< -o $@
.PHONY:clean
clean:
-rm -f ./bin/*
一切正常。
顺带,记录下大佬的博客.
此外,还有其他人也回答了
Makefile是执行第一个目标文件,然后根据第一个目标文件的依赖往下找。Makefile只彻底执行完第一个目标文件(包括找依赖)之后就以为活干完了不干活了。
后续
第二天,再次读《跟我一起写Makefile》的静态模式这一块,发现之前对静态模式理解有误,静态模式的语法是这样的:
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
还举了个例子:
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
我理解成了targets为foo、bar,target-pattern为foo.o、bar.o, prereq-patterns为foo.c、bar.c。我的错误理解是目标文件、一次依赖文件、二次依赖文件,真是犯傻了,如果一次依赖文件是.o二次依赖文件是.c这种何必引入这种模式呢,直接依赖.c又不是不可以。。。。人家真实用途target-pattern是用来对目标文件进行说明的,比如后缀都是.o等,但是如果没有什么共同特点的话这个说明就直接用%就好了嘛,表示目标文件们没有共同点(个人理解)
如此以来,我们写出正确的静态模式的Makefile:
CXX = g++
CXX_FLAGS = -Wall -g
SRC = $(wildcard ./src/*.cpp)
NOTDIR_SRC = $(notdir $(SRC))
TARGETS = $(patsubst %.cpp, %, $(NOTDIR_SRC))
all:$(TARGETS)
#静态模式的正确理解:<目标文件> <目标文件的模式> <目标的依赖模式>
$(TARGETS) : % : ./src/%.cpp
$(CXX) -c $(CFLAGS) $< -o ./bin/$@
.PHONY:clean
clean:
-rm -f ./bin/*
变量定义知识补充
1. =(递归展开方式)
简单的使用 = 号,在 = 左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值
- 优点:把变量的真实值推到后面来定义
- 缺点:递归定义会让make陷入无限的变量展开过程中去
2. :=(简单方式)
这个是推荐使用的方式,其实这种方法的使用效果与C语言相同(变量定义了,接下来使用它的时候其值就是刚才定义的值,如果后面这个变量又被重新定义了那么只有重新定义之后的部分使用它的新值)。
- 前面的变量不能使用后面的变量,只能使用前面已定义好了的变量
3. ?=
如果变量没有被定义过,那么此刻定义变量;如果变量先前被定义过,那么这条语将什么也不做
Makefile思想的感悟
- 其实人家的思想就是根据第一个目标文件去找依赖,执行命令而已
- 不要乱用,如果出了想不明白的错误,就开始按照总目标文件及依赖文件逐步向后捋,过程中注意关注依赖文件真的能被识别吗,千万别乱用%以为能找的到
Makefile知识加油站
.PHONY:clean
被称之伪目标,原来它真正的用途是避免由于名称相同带来的清理工作不生效问题
假如自己的工程目录也有一个名字叫
clean
的文件,而Makefile中伪目标clean
通常并没有依赖文件,所以它认为既然已经有目标文件了,那我就不用干活了,于是本该清理的操作没有被执行。还有就是Makefile的习惯用法中有个all
目标,其实也可以放到伪目标里面,如.PHONY all clean
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具