最近要搞 linux 下 c++ 开发,花了几天时间学习了一下 makefile. 总结了一点自己平时会用的比较多的东西。首先是多目录下嵌套调用 makefile. 我觉得这个很重要。很多时候自己写测试代码或者做一些预研的时候都要用到。
我这个演示例子采用了一个平时我用的比较多的目录结构,相信大家应该也常会用到与此类似的目录结构。今天写这篇文章希望能对正在查相关资料的同行有所帮助。我保证这篇文章里的所有代码都经过充分测试。
首先贴目录结构图:
这个目录里面,源文件的目录有两个:分别是 my_str_utils 和 xtests. my_str_utils 目录里面主要是写了一些函数的声明和实现,头文件都放在 my_str_utils/headers 里面,对应的源文件都放在 my_str_utils/source 里面,。而 xtests 里面放了个 main.cpp 用来测试 my_str_utils 中写的函数。我把目标文件单独放在 xdirObjs 中,这样目录会整洁很多。最后的可执行程序我单独放在了 xdirExecs 中。
在每个有源文件的目录里都写了一个 Makefile, 用于编译当前目录下的 Makefile. 在 xdirObjs 目录里写了一个 Makefile 用于链接目标文件并生成可执行文件。最后在工程的根目录里写了一个 Makefile 用于发出 make 指令,启动编译过程。
下面是各个目录中的 Makefile 文件。我就不用太多的文字来描述了。但注释会非常详细。
首先是项目根目录下的 Makefile 的代码:
.PHONY: all clean
# 可执行文件名, 这个根据具体程序会有变动。
EXEC_FINAL := tests.exe
# 子目录可以有多个, 以空格分开即可,这个会根据具体程序有所变动。
# 注意 xdirObjs 目录要和 DIR_OBJS 中定义的一致。
SUBDIRS := my_str_utils/source \
xtests \
xdirObjs
# 当前目录
DIR_TOP := $(shell pwd)
# 定义变量(有的书上也叫宏),指定 object 文件的文件夹和可执行文件的文件夹。
DIR_OBJS := $(DIR_TOP)/xdirObjs
DIR_EXEC := $(DIR_TOP)/xdirExecs
# 我这里由需要在 DIR_OBJS 目录中放一个 Makefile 文件,所以 DIR_OBJS 目录已经事
# 先创建好,这里就不再创建了。
DIRS := $(DIR_EXEC)
# 定义变量
CC = g++
# 定义命令起了个别名。
RM = rm
MKDIR = mkdir
MAKE = make
MAKECLEAN = make clean
# -Wall 表示输出警告信息。
CFLAGS = -Wall
# 使在这个目录里面定义的变量可以在被调用的下级 Makefile 中使用。
export EXEC_FINAL DIR_TOP DIR_OBJS DIR_EXEC CC RM MKDIR MAKE MAKECLEAN CFLAGS
# 给伪目标 all 添加两个依赖。这两个依赖在后面也被当作目标。
all: $(DIRS) $(SUBDIRS)
# 创建文件夹
$(DIRS):
$(MKDIR) -p $@
# 关键在于这部分
# 对每个 source 目录(或子项目)进行编译。
# 这种写法,后面一定要跟一个动作,这里是 ECHO, 也可以定义成其他名字。
# ECHO 可以什么都不做,但一定要存在。
# 这里有个隐含规则,由于 SUBDIRS 定义了多个文件,如果用了后面的 ECHO, 就会遍历每个
# 目录并对每个目录执行一次 $(MAKE) -C $@
# 如果不加 ECHO, 就会把 SUBDIRS 中的整个字符串当成一个路径,只执行一次。(非常神奇)
$(SUBDIRS): ECHO
# 注意这个 -C 一定需要大写。这句代码的意思据说与 cd $(SUBDIRS) && $(MAKE) 一样, 没有亲自测。
$(MAKE) -C $@
ECHO:
# 执行清理任务。
clean:
$(RM) -rf $(DIR_OBJS)/*.o
$(RM) -rf $(DIR_EXEC)/$(EXEC_FINAL)
下面是 source 目录下的 Makefile:
# 需要使用 -I 选项指明本地头文件的目录
# 头文件路径根据项目目录结构不同,会有所变化
INC := -I../headers
# 定义变量 SRCS, 用于指代所有的 .cpp 文件。
SRCS = $(wildcard *.cpp)
# 根据 .cpp 推导出 .o
OBJS = $(SRCS:.cpp=.o)
# 给 OBJS 加路径前缀,使其变成完整路径的形式
OBJS := $(addprefix $(DIR_OBJS)/,$(OBJS))
.PHONY: taskslocal
# 此处必须加一个伪目标,因为 make 只能把没有匹配符的目标作为构造目标。
# 也就是说 Makefile 中至少要有一个没有匹配符的目标。否则会报没有 target 的错误。
taskslocal: $(OBJS)
# 用规则对 .o 文件个 .cpp 文件进行匹配。
$(DIR_OBJS)/%.o: %.cpp
$(CC) -g -c $(CFLAGS) $(INC) -o $@ $<
下面是 xtests 目录中的 Makefile:
# 需要使用 -I 选项指明本地头文件的目录
# 头文件路径根据项目目录结构不同,会有所变化
INC := -I../my_str_utils/headers
# 定义变量 SRCS, 用于指代所有的 .cpp 文件。
SRCS = $(wildcard *.cpp)
# 根据 .cpp 推导出 .o
OBJS = $(SRCS:.cpp=.o)
# 给 OBJS 加路径前缀,使其变成完整路径的形式
OBJS := $(addprefix $(DIR_OBJS)/,$(OBJS))
.PHONY: taskslocal
# 此处必须加一个伪目标,因为 make 只能把没有匹配符的目标作为构造目标。
# 也就是说 Makefile 中至少要有一个没有匹配符的目标。否则会报没有 target 的错误。
taskslocal: $(OBJS)
# 用规则对 .o 文件个 .cpp 文件进行匹配。
$(DIR_OBJS)/%.o: %.cpp
$(CC) -g -c $(CFLAGS) $(INC) -o $@ $<
最后是 xdirObjs 目录中的 Makefile:
# 定义变量,指明目标文件为当前目录下所有的 .o 文件。
OBJS_G = $(wildcard *.o)
# 链接目标文件,生成可执行文件。
$(DIR_EXEC)/$(EXEC_FINAL):$(OBJS_G)
$(CC) -g -o $@ $^
最后把编译成功时的打印信息也贴一下:
[karl@localhost ~/xcpps/005_utils]$ make clean
rm -rf /home/karl/xcpps/005_utils/xdirObjs/*.o
rm -rf /home/karl/xcpps/005_utils/xdirExecs/tests.exe
[karl@localhost ~/xcpps/005_utils]$ make
# 注意这个 -C 一定需要大写。
make -C my_str_utils/source
make[1]: Entering directory `/home/karl/xcpps/005_utils/my_str_utils/source'
# -c 表示只编译,不链接。
g++ -g -c -Wall -I../headers -o /home/karl/xcpps/005_utils/xdirObjs/my_str_utils.o my_str_utils.cpp
make[1]: Leaving directory `/home/karl/xcpps/005_utils/my_str_utils/source'
# 注意这个 -C 一定需要大写。
make -C xtests
make[1]: Entering directory `/home/karl/xcpps/005_utils/xtests'
# -c 表示只编译,不链接。
g++ -g -c -Wall -I../my_str_utils/headers -o /home/karl/xcpps/005_utils/xdirObjs/main.o main.cpp
make[1]: Leaving directory `/home/karl/xcpps/005_utils/xtests'
# 注意这个 -C 一定需要大写。
make -C xdirObjs
make[1]: Entering directory `/home/karl/xcpps/005_utils/xdirObjs'
g++ -g -o /home/karl/xcpps/005_utils/xdirExecs/tests.exe my_str_utils.o main.o
make[1]: Leaving directory `/home/karl/xcpps/005_utils/xdirObjs'
[karl@localhost ~/xcpps/005_utils]$
最后,我是在 vscode 里搭建的环境。理论上,对 Makefile 不会有任何影响。但我还是把 vscode 里面的项目配置贴出来,以免有网友需要看 vscode 项目设置。
c_cpp_properties.json:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++14",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
tasks.json:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "make",
"args": [],
"group":{
"kind": "build",
"isDefault": true
}
}
]
}
launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
// "program": "enter program name, for example ${workspaceFolder}/a.out",
"program": "${workspaceFolder}/xdirExecs/tests.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
写完了,希望对大家有所帮助。