一、Makefile命令规则
假设有一个Test.cpp文件,代码如下:
1 2 3 4 5 6 7 8
| #include <iostream>
using namespace std;
int main() { count << "===Test===" << endl; return 0; }
|
我们可以使用gcc或g++来 预处理
、汇编
、编译
、链接
,也可以利用Makefile来执行这些命令。
在Makefile中填入配置:
1 2 3
| # 生成预处理文件 Test.i:Test.cpp g++ -E Test.cpp -o Test.i
|
Test.i 是目标,Test.cpp 是依赖,整体的意思是:Test.i是依赖于Test.cpp生成的。
左右两边用冒号隔开,也就是Makefile命令规范:
下一行是 g++ 命令,Test.i的生成是根据这条 g++ 命令生成的。g++前面必须含有分割符,而且必须是tab分割,不能是空格,否则在执行Makefile时不被识别。
执行该Makefile文件:
![image-20230717161647472](/../../../../images/image-20230717161647472.png)
Makefile中可能由多个目标组成,make命令会找到Makefile中第一个目标执行。也可以指定一个目标执行:
现在Makefile中只有一个目标,所以可以执行:
二、Makefile实现 预处理
、汇编
、编译
、链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 生成预处理文件 Test.i:Test.cpp g++ -E Test.cpp -o Test.i
# 生成汇编文件 Test.s:Test.i g++ -S Test.i -o Test.s # 生成目标文件 Test.o:Test.s g++ -c Test.s -o Test.o # 生成链接文件(可执行文件) Test:Test.o g++ Test.o -o Test。
|
定义四个目标,分别是:Test.i
、Test.s
、Test.o
、Test
。
分别执行这四个目标:
1 2 3 4
| make Test.i make Test.s make Test.o make Test
|
可以完成 预处理
、汇编
、编译
、链接
。
但是,完成这写指令,还可以一步完成,我们的目标是执行 make 命令完成所有的步骤。
只需要改变一下Makefile中的目标顺序即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 生成链接文件(可执行文件) Test:Test.o g++ Test.o -o Test # 生成目标文件 Test.o:Test.s g++ -c Test.s -o Test.o # 生成汇编文件 Test.s:Test.i g++ -S Test.i -o Test.s
# 生成预处理文件 Test.i:Test.cpp g++ -E Test.cpp -o Test.i
|
输入make命令一次性执行完所有的目标:
![image-20230717161803969](/../../../../images/image-20230717161803969.png)
注意:顺序要正确,必须形成逐步依赖关系。
三、makefile编译多个文件
Student.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef _STUDENT_H_ #define _STUDENT_H_
#include <string>
using namespace std;
class Student {
private: string name; int age;
public: void setName(string name); string getName(); void setAge(int age); int getAge(); };
#endif
|
Student.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include "Student.h"
void Student::setName(string name) { this->name = name; } string Student::getName() { return name; } void Student::setAge(int age) { this->age = age; } int Student::getAge() { return age; }
|
Test.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream> #include "Student.h"
using namespace std;
int main() {
Student* stu = new Student(); stu->setName("zhangsan"); stu->setAge(13); cout << "姓名:" << stu->getName() << ", 年龄:" << stu->getAge() << endl; return 0; }
|
Makefile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| # 生成链接文件(可执行文件) Test:Test.o Student.o g++ Test.o Student.o -o Test # 生成目标文件Test.o Test.o:Test.s g++ -c Test.s -o Test.o
# 生成目标文件Student.o Student.o:Student.s g++ -c Student.s -o Student.o # 生成汇编文件Test.s Test.s:Test.i g++ -S Test.i -o Test.s
# 生成汇编文件Test.s Student.s:Student.i g++ -S Student.i -o Student.s # 生成预处理文件Test.i Test.i:Test.cpp g++ -E Test.cpp -o Test.i
# 生成预处理文件Student.i Student.i:Student.cpp g++ -E Student.cpp -o Student.i
|
执行make命令之后,生成 预处理文件
、汇编文件
、目标文件
、可执行文件
。
![img](/../../../../images/webp-168958193485010.webp)
四、makefile多文件管理
在编译期间,在同一个目录下生成多个文件,文件多了有点乱,这时,就需要分类管理,将不同格式的文件放入不同的文件夹。
修改Makefile配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# 生成链接文件(可执行文件) Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o g++ ${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o -o ${BIN_DIR}/Test # 生成目标文件Test.o ${OBJ_DIR}/Test.o:${INFO_DIR}/Test.s g++ -c ${INFO_DIR}/Test.s -o ${OBJ_DIR}/Test.o
# 生成目标文件Student.o ${OBJ_DIR}/Student.o:${INFO_DIR}/Student.s g++ -c ${INFO_DIR}/Student.s -o ${OBJ_DIR}/Student.o # 生成汇编文件Test.s ${INFO_DIR}/Test.s:${INFO_DIR}/Test.i g++ -S ${INFO_DIR}/Test.i -o ${INFO_DIR}/Test.s
# 生成汇编文件Test.s ${INFO_DIR}/Student.s:${INFO_DIR}/Student.i g++ -S ${INFO_DIR}/Student.i -o ${INFO_DIR}/Student.s # 生成预处理文件Test.i ${INFO_DIR}/Test.i:${SRC_DIR}/Test.cpp g++ -E ${SRC_DIR}/Test.cpp -o ${INFO_DIR}/Test.i
# 生成预处理文件Student.i ${INFO_DIR}/Student.i:${SRC_DIR}/Student.cpp g++ -E ${SRC_DIR}/Student.cpp -o ${INFO_DIR}/Student.i
|
目录结构如下:
![img](/../../../../images/webp-168958196836513.webp)
1 2 3 4 5 6
| bin、include、info、obj、src文件夹需要手动创建; bin文件夹存放可执行文件; include文件夹存放头文件; info文件夹存放预处理文件和汇编文件; obj文件夹存放目标文件; src文件夹存放c或cpp文件;
|
五、clean命令
在Makefile中添加:
1 2
| clean: del bin obj info
|
执行clean命令,可以删除 bin、obj、info文件夹中所有的文件。
六、Makefile通配符
1 2 3 4 5 6 7
| %.o:表示一个xx.o文件 $@:表示目标文件 $<:表示第一个依赖文件 $^:所有不重复的依赖文件,以空格分开 $*:不包含扩展名的target文件名称 $+:所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件 $?:所有时间戳比target文件晚的依赖文件,并以空格分开
|
由于 -o 后面输出的必然是目标文件,所以 -o 后面的输出文件可以替换为 $@:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# 生成链接文件(可执行文件) ${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o g++ ${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o -o $@ # 生成目标文件Test.o ${OBJ_DIR}/Test.o:${INFO_DIR}/Test.s g++ -c ${INFO_DIR}/Test.s -o $@
# 生成目标文件Student.o ${OBJ_DIR}/Student.o:${INFO_DIR}/Student.s g++ -c ${INFO_DIR}/Student.s -o $@ # 生成汇编文件Test.s ${INFO_DIR}/Test.s:${INFO_DIR}/Test.i g++ -S ${INFO_DIR}/Test.i -o $@
# 生成汇编文件Test.s ${INFO_DIR}/Student.s:${INFO_DIR}/Student.i g++ -S ${INFO_DIR}/Student.i -o $@ # 生成预处理文件Test.i ${INFO_DIR}/Test.i:${SRC_DIR}/Test.cpp g++ -E ${SRC_DIR}/Test.cpp -o $@
# 生成预处理文件Student.i ${INFO_DIR}/Student.i:${SRC_DIR}/Student.cpp g++ -E ${SRC_DIR}/Student.cpp -o $@ clean: del bin obj info
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #$<、$^ 和 % 结合使用,可以最大程度上简化配置: # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# 生成链接文件(可执行文件) ${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o g++ $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件 ${OBJ_DIR}/%.o:${INFO_DIR}/%.s g++ -c $< -o $@ # 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.s:${INFO_DIR}/%.i g++ -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.i:${SRC_DIR}/%.cpp g++ -E $< -o $@ clean: del bin obj info
|
以上配置显然已经简单了很多。
另外,头文件已经放在include目录,cpp文件已经放在了src目录,所以需要指定头文件位置,由两种方法可以指定:
【方法一】在cpp中修改头文件位置
将
改成
1
| #include "../include/Student.h"
|
【方法二】g++命令后面指定头文件路径
加入 -I $(INC_DIR)
导入头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# -I 指定头文件 CXXFLAGS=-I $(INC_DIR)
# 生成链接文件(可执行文件) ${BIN_DIR}/Test:${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o g++ $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件 ${OBJ_DIR}/%.o:${INFO_DIR}/%.s g++ $(CXXFLAGS) -c $< -o $@ # 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.s:${INFO_DIR}/%.i g++ $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.i:${SRC_DIR}/%.cpp g++ $(CXXFLAGS) -E $< -o $@ clean: del bin obj info
|
g++出现了多次,也可以使用变量代替,并且新增TAGERT和OBJS变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# -I 指定头文件 CXXFLAGS=-I $(INC_DIR)
# CC 指定编译器 gcc 、 g++ CC=g++
#最终生成的目标, TAGERT=Test
# 目标文件 OBJS=${OBJ_DIR}/Test.o ${OBJ_DIR}/Student.o
# 生成链接文件(可执行文件) ${BIN_DIR}/${TAGERT}:${OBJS} ${CC} $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件 ${OBJ_DIR}/%.o:${INFO_DIR}/%.s ${CC} $(CXXFLAGS) -c $< -o $@ # 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.s:${INFO_DIR}/%.i ${CC} $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.i:${SRC_DIR}/%.cpp ${CC} $(CXXFLAGS) -E $< -o $@ clean: del bin obj info
|
以上配置还存在一定的弊端,OBJS变量指定了目标文件,此时的目标文件是需要指定具体目标文件的。
七、wildcard函数查找
$(wildcard pattern)
:pattern定义了文件名的格式,wildcard取出其中存在的文件。
1 2
| # wildcard查找当前目录下所有cpp文件 SRCS=$(wildcard $(SRC_DIR)/*.cpp)
|
此时 SRCS 就是 src目录下所有cpp格式的文件。
八、patsubst函数替换
$(patsubst pattern,replacement,$(var))
:从var中将符合patern格式的内容,替换为replacement。
1 2 3 4 5 6
| # wildcard查找当前目录下所有cpp文件 SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# notdir 去除掉绝对路径,只保留名字 # patsubst 把字串 $(notdir $(SRCS)) 符合模式[%.cpp]的单词替换成[%.o] OBJS=$(patsubst %.cpp,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
|
patsubst 可以解决指定明确目标文件的弊端,改进弊端之后的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| # 定义变量INFO_DIR,指定预处理文件和汇编文件的存放目录 INFO_DIR = ./info
# 定义变量INC_DIR,指定头文件的存放目录 INC_DIR = ./include
# 定义变量SRC_DIR,指定c/c++文件的存放目录 SRC_DIR = ./src
# 定义变量BIN_DIR,指定可执行文件的存放目录 BIN_DIR = ./bin
# 定义变量OBJ_DIR,指定可执行文件的存放目录 OBJ_DIR = ./obj
# -I 指定头文件 CXXFLAGS=-I $(INC_DIR)
# CC 指定编译器 gcc 、 g++ CC=g++
#最终生成的目标, TAGERT=Test
# wildcard查找当前目录下所有cpp文件 SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# notdir 去除掉绝对路径,只保留名字 # patsubst 把字串 $(notdir $(SRCS)) 符合模式[%.cpp]的单词替换成[%.o] OBJS=$(patsubst %.cpp,$(OBJ_DIR)/%.o,$(notdir $(SRCS)))
# wildcard *.cpp 当前目录下所有c文件 SRCS=$(wildcard $(SRC_DIR)/*.cpp)
# 生成链接文件(可执行文件) ${BIN_DIR}/${TAGERT}:${OBJS} ${CC} $(CXXFLAGS) $^ -o $@
# 生成所有目标文件 %:任意字符通配符 $<:表示第一个依赖文件 ${OBJ_DIR}/%.o:${INFO_DIR}/%.s ${CC} $(CXXFLAGS) -c $< -o $@ # 生成所有汇编文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.s:${INFO_DIR}/%.i ${CC} $(CXXFLAGS) -S $< -o $@
# 生成所有预处理文件 %:任意字符通配符 $<:表示第一个依赖文件 ${INFO_DIR}/%.i:${SRC_DIR}/%.cpp ${CC} $(CXXFLAGS) -E $< -o $@ clean: del bin obj info
|
九、Makefile的变量
Makefile的变量分为两种:及时变量
、延时变量
1 2 3 4
| 【1】 `:=`:即时变量,该变量的值即刻确定,在定义时就被确定了; 【2】`=`:延时变量,该变量的值,在使用时才确定; 【3】`?=`:延时变量,第一次定义才起效,如果前面被定义过了就忽略这句; 【4】`+=`:附加,它是即时变量还是延时变量取决于前面的定义;
|
十、函数遍历
$(foreach val,list,text)
:对于list(通常用空格隔开)里的每一个变量执行text操作
1 2 3 4 5 6 7 8 9
| # 定义一个list LIST=a b c
# 用f代表A中的各个变量,执行第三个参数的操作。 # foreach 遍历关键字,f:LIST中的每个变量, RESULT=$(foreach f,$(LIST),$(f).result)
all: @echo RESULT=$(RESULT)
|
输出结果:
1
| RESULT=a.result b.result c.result
|
十一、filter函数过滤
$(filter pattern...,text)
:在text里面取出符合pattern格式的值
$(filter-out pattern...,text)
:在text里面取出不符合pattern格式的值
1 2 3 4 5 6 7 8 9 10 11 12
| # 定义变量C C= a b c d/
# 在变量C中取出符合%/的值 D=$(filter %/,$(C))
# 在变量C中取出不符合%/的值 E=$(filter-out %/,$(C))
all: @echo D=$(D) @echo E=$(E)
|
输出结果:
十二、CXXFLAGS补充
上面已经定义了 CXXFLAGS 变量:
我们还可以对它进行补充,比如:
1 2
| # -I 指定头文件 CXXFLAGS=-I $(INC_DIR) -Wall -O2 -std=c++11 -frtti -fexceptions
|
可以指定的FLAG有:
1 2 3 4 5 6 7 8 9
| 【1】-Werror:会把所有警告当成错误 【2】-I: 该选项用于指定编译程序时依赖的头文件路径 【3】-On: 这是一个优化选项,如果在编译时指定该选项,则编译器会根据n的值(n取0到3之间) 对代码进行不同程度的优化,其中-O0 表示不优化,n的值越大,优化程度越高 【4】-L: 库文件依赖选项,该选项用于指定编译的源程序依赖的库文件路径,库文件可以是静态链接库,也可以是动态链接库 【5】-Wall: 允许发出gcc能提供的所有有用的警告,也可以用-W(warning)来标记指定的警告 【6】-std=c++11:C++11标准 【7】-frtti 和 -fexceptions:关闭 exceptions、rtti 可以减小程序的占用的空间和提升程序的运行效率, 关闭后可能带来一些不兼容问题,使用 typeid 运算符必须开启 RTTI
|
原文:NDK<第五篇>:Makefile(windows环境) - 简书 (jianshu.com)