开发环境全能管家-make Makefile文件all: echo hello world # 目标 # echo命令是将内容打印在终端上 #执行Makefile文件指令有两种 #1、make 执行默认目标 #2、make 目标名(由字符和下划线组成)-----构建all目标 test: echo jest for test #从上可以知道 #1、一个Makefile中可以定义多个目标 #2、当调用make指令时我们可以告诉它构建的目标是什么如果不告诉则以第一个目标为默认目标 #3、一个规则可以存在多条指令存在依赖关系all:test echo hello world # 目标依赖关系 # echo命令是将内容打印在终端上 #执行Makefile文件指令有两种 #1、make 执行默认目标 #2、make 目标名(由字符和下划线组成)-----构建all目标 test: echo jest for test #从上可以知道 #1、一个Makefile中可以定义多个目标 #2、当调用make指令时我们可以告诉它构建的目标是什么如果不告诉则以第一个目标为默认目标 #3、一个规则可以存在多条指令 #4、如果只想输出hello world我们只需要在echo命令前添加即可执行结果jest for test hello world说明1、在执行test之前会先执行依赖的test依赖目标是先决条件makefile文件执行顺序makeflie的执行最终的目标一定是没有依赖的1、将规则运用于程序编译1.1创建一个simple工程foo.c#include stdio.h void foo(void) { printf(hello world\n); }system.h#ifndef __SYSTEM_H #define __SYSTEM_H void foo(void); #endifmain.c#include system.h int main(void) { foo(); return 0; }Makefile#我们在写makefile时需要把关系依赖图在脑海中构造出来 #如下图 #simple-main.o-main.c # -foo.o-foo.c all:main.o foo.o gcc -o simple main.o foo.o main.o:main.c #用gcc编译main.c生成main.o文件 gcc -o main.o -c main.c foo.o:foo.c gcc -o foo.o -c foo.c clean: rm simple main.o foo.o #1、make使用过文件的时间戳来判断是否需要重新编译如果依赖关系中的文件的时间戳比目标文件的时间戳新那么目标文件就需要重新编译 #2、编写完文件需要自己保存未保存的文件make以旧文件进行编译。2、让Makefile更专业2.1 假目标的用处以2.1为例子我们运用touch clean指令后再执行make clean,make将不再执行因为make把clean当作文件来处理认为clean是最新的不需要去重新执行。对于Makefile目标明和文件名重复是在所难免的对于这种问题就需要假目标来解决##将clean变为假目标 .PHONY:clean all:main.o foo.o gcc -o simple main.o foo.o main.o:main.c #用gcc编译main.c生成main.o文件 gcc -o main.o -c main.c foo.o:foo.c gcc -o foo.o -c foo.c clean: rm -fr simple main.o foo.o #2.1 #从上可以知道 #1、一个Makefile中可以定义多个目标 #2、当调用make指令时我们可以告诉它构建的目标是什么如果不告诉则以第一个目标为默认目标 #3、一个规则可以存在多条指令 #4、如果只想输出hello world我们只需要在echo命令前添加即可 #5、make使用过文件的时间戳来判断是否需要重新编译如果依赖关系中的文件的时间戳比目标文件的时间戳新那么目标文件就需要重新编译 #6、编写完文件需要自己保存未保存的文件make以旧文件进行编译。 #2.2 #1、假目标关键字 .PHONY:定义的假目标名作用是使make将假目标名不当作文件进行处理。只要执行目标该目标的命令一定会被执行2.2运用“变量名”提高可维护性#我们在写makefile时需要把关系依赖图在脑海中构造出来 #如下图 #simple-main.o-main.c # -foo.o-foo.c .PHONY:clean CCgcc RMrm EXE simple OBJS main.o foo.o #引用变量名 $(变量名)或${变量名} $(EXE):$(OBJS) $(CC) -o $(EXE) $(OBJS) main.o:main.c #用gcc编译main.c生成main.o文件 $(CC) -o main.o -c main.c foo.o:foo.c $(CC) -o foo.o -c foo.c clean: #-fr -f强制删除 -r递归删除目录 $(RM) -fr $(EXE) $(OBJS) #1、make使用过文件的时间戳来判断是否需要重新编译如果依赖关系中的文件的时间戳比目标文件的时间戳新那么目标文件就需要重新编译 #2、编写完文件需要自己保存未保存的文件make以旧文件进行编译。1、自动变量名.PHONY:all all:first second third echo \$$ $ echo $$ $ echo $$^ $^ first second third: # 1、$ 一个规则中的目标 # 2、$ 规则中的第一个先决条件 # 3、$^ 表示规则中所有的先决条件运行结果$ all $ first $^ first second thirdsimple运用自动变量名之后.PHONY:clean CCgcc RMrm EXE simple OBJS main.o foo.o #引用变量名 $(变量名)或${变量名} $(EXE):$(OBJS) $(CC) -o $ $^ main.o:main.c #用gcc编译main.c生成main.o文件 $(CC) -o $ -c $^ foo.o:foo.c $(CC) -o $ -c $^ clean: #-fr -f强制删除 -r递归删除目录 $(RM) -fr $(EXE) $(OBJS) #1、make使用过文件的时间戳来判断是否需要重新编译如果依赖关系中的文件的时间戳比目标文件的时间戳新那么目标文件就需要重新编译 #2、编写完文件需要自己保存未保存的文件make以旧文件进行编译。 #3、自动变量名 # 1、$ 一个规则中的目标 # 2、$ 规则中的第一个先决条件 # 3、$^ 表示规则中所有的先决条件 # ... #4、如果想采用echo输出$,必须用两个$$来表示。对于Bash shell,需要在$$前添加脱字符\(引号不包含在内)。2、特殊变量名MAKE和MAKECMDGOALSMAKE变量表示当前处理的Makefile命令是什么。MAKECMDGOALS表示当前构建的目标名当指令不指定目标名时MAKECMDGOALS为空想用echo输出当前MAKE处理的命令是什么 echo MAKE $(MAKE)3、变量的类别与赋值变量的类别由递归扩展变量和简单扩展变量.PHONY:all x foo y $(x) b x later xx : foo yy : $(xx) b xx : later all: echo x $(y), xx $(yy)结果x later b, xx foo b递归扩展变量()会被赋值多次取决于最后一次的赋值简单扩展变量(:)只能被赋值一次条件赋值变量 ? 值 如果变量已有值则不执行若无赋值则执行赋值操作追加赋值变量 值 变量 变量 值 这里的值都是命令的拼接4、变量的值及其来源1自动变量根据规则的上下文自动获得2在运行make时通过命令参数定义 make bar x3变量还可以来自shell环境5、高级变量的引用功能.PHONY:all foo a.c b.c c.c bar: $(foo:.c.o) all: echo bar $(bar)结果bar a.o b.o c.o变量的引用即在赋值的过程中完成文件名的后缀替换操作6、避免变量被覆盖的方法override 变量名 值 使用override修饰变量后该变量的值不会被改变。2.3借助模式精简规则将simpel利用规则进行精简.PHONY:clean CCgcc RMrm EXE simple OBJS main.o foo.o #引用变量名 $(变量名)或${变量名} $(EXE):$(OBJS) $(CC) -o $ $^ # main.o:main.c # #用gcc编译main.c生成main.o文件 # $(CC) -o $ -c $^ # foo.o:foo.c # $(CC) -o $ -c $^ %.o:%.c $(CC) -o $ -c $^ clean: #-fr -f强制删除 -r递归删除目录 $(RM) -fr $(EXE) $(OBJS)2.4通过“函数”增强功能1、abspath函数$(abspath _names)将names中的各路径名转换成绝对路径并将转换后的结果返回.PHONY:all ROOT : $(abspath /usr/../lib) all : echo $(ROOT) #结果 #/lib2、addprefix函数$(addprefix _prefix,_names),为_names每一个名字增加前缀_prefix并将增加了前缀的名字列表返回。3、addsuffix函数$(addsuffix _suffic,names),为_names每一个名字增加后缀_prefix并将增加了后缀的名字列表返回。4.eval函数$(eval _text),使make将再一次解析_text语句.PHONY:all sources foo.c bar.c baz.s ugh.h $(eval sources : $(filter %.c %.s,$ $(sources))) all : echo $(sources) #结果 #foo.c bar.c baz.s5、filter函数$(filter _pattern,_text),从一个名字列表_text中根据模式_pattern得到满足需要名字的列表并返回6、filter-out函数$(filtre-out _pattern,_text),从一个名字列表_text中根据模式_pattert排除7满足需要名字的列表并返回7、notdir函数$(notdir _names),从路径_names中抽取文件名并将文件名返回8、patsubst函数$(patsubst _pattern, _replacement, _text),将名字列表_text中符合_pattern模式的名字替换为_replacement并将替换后的名字返回9、realpath函数$(realpath _names),获取_names所对应的真实路径名10、strip函数$(strip _string),清除名字列表_string中多余的空格11、wildcard函数$(wildcard _pattern),得到当前目录满足_pattern模式的文件或目录名列表。3、提高编译环境的实用性通过makefile自动生成中间件目录并通过目录来管理文件。3.1新建一个complicated项目main.c#include foo.h #include stdio.h void main() { foo(); return ; }food.h#ifndef __FOO_H #define __FOO_H void foo(); #endiffoo.c#include foo.h #include stdio.h void foo() { printf(this is foo()! \r\n); }Mikeflie#假目标 all clean 默认执行all目标 .PHONY: all clean MKDIR mkdir RM rm RMFLAGS -fr CC gcc DIR_OBJS objs DIR_EXES exes DIRS $(DIR_OBJS) $(DIR_EXES) #最终生成的可执行程序名无 .exe适配 Linux/WSL。 EXE complicated #make 内置函数 wildcard自动匹配当前目录下所有 .c 文件。 SRCS $(wildcard *.c) #变量替换把列表里所有 .c 后缀替换成 .o OBJS $(SRCS:.c.o) #addprefix函数$(addprefix _prefix,_names),为_names每一个名字增加前缀_prefix并将增加了前缀的名字列表返回。 #内置函数 addprefix给每一个 .o 文件增加前缀 objs/ #main.o test.o → objs/main.o objs/test.o #递归扩展变量()会被赋值多次取决于最后一次的赋值简单扩展变量(:)只能被赋值一次 OBJS : $(addprefix $(DIR_OBJS)/, $(OBJS)) EXE : $(addprefix $(DIR_EXES)/, $(EXE)) all: $(DIRS) $(EXE) $(DIRS): #3、自动变量名 # 1、$ 一个规则中的目标 # 2、$ 规则中的第一个先决条件 # 3、$^ 表示规则中所有的先决条件 $(MKDIR) $ $(EXE): $(OBJS) $(CC) -o $ $^ $(DIR_OBJS)/%.o: %.c foo.h echo hello $(CC) -o $ -c $ clean : #rm -fr obj exes ,引用自定义变量的格式$(自定义变量) $(RM) $(RMFLAGS) $(DIRS) $(EXE)通过makefile编译工程后生成可执行文件放到exes文件夹随着工程的越来越庞大我们应该如何高效的理解makefile的执行逻辑1、makefile文件执行是从上到下执行的有依赖找依赖对应的目标没依赖执行指令2、我们要在脑海中有自己工程的依赖树图理解文件之间的依赖关系以complicated为例子我们最终想要的是把可执行文件放到 exes目录下把中间生成文件放在objs目录all: $(DIRS) $(EXE)需要先有目录才能将文件放进去先执行目标$(DIRS)生成目录第二步生成可执行文件$(EXE): $(OBJS)$(CC) -o $ $^$(EXE)依赖$(OBJS)自动生成文件依赖关系#假目标 all clean 默认执行all目标 .PHONY: all clean MKDIR mkdir #创建目录 RM rm #删除文件 RMFLAGS -fr #递归删除 CC gcc DIR_OBJS objs DIR_EXES exes DIR_DEPS deps DIRS $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) #最终生成的可执行程序名无 .exe适配 Linux/WSL。 EXE complicated EXE : $(addprefix $(DIR_EXES)/, $(EXE)) #make 内置函数 wildcard自动匹配当前目录下所有 .c 文件。 SRCS $(wildcard *.c) #变量替换把列表里所有 .c 后缀替换成 .o OBJS $(SRCS:.c.o) #addprefix函数$(addprefix _prefix,_names),为_names每一个名字增加前缀_prefix并将增加了前缀的名字列表返回。 #内置函数 addprefix给每一个 .o 文件增加前缀 objs/ #main.o test.o → objs/main.o objs/test.o #递归扩展变量()会被赋值多次取决于最后一次的赋值简单扩展变量(:)只能被赋值一次 OBJS : $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS $(SRCS:.c .dep) EDEP : $(addprefix $(DIR_DEPS)/, $(DEPS)) all: $(DIRS) $(DEPS) $(EXE) $(DIRS): #3、自动变量名 # 1、$ 一个规则中的目标 # 2、$ 规则中的第一个先决条件 # 3、$^ 表示规则中所有的先决条件 $(MKDIR) $ $(EXE): $(OBJS) $(CC) -o $ $^ #引入.h文件这样修改.h文件make也会察觉到是否被修改 $(DIR_OBJS)/%.o: %.c $(CC) -o $ -c $^ #自动生成文件依赖关系 $(DIR_DEPS)/%.dep: %.c echo Creating $ ... #输出 # 每一条指令make都在一个新的shell指令下执行 # 多条make指令如果想在一个shell下执行,可以通过分号;连接起来 # 每条指令执行完后,会自动换行,所以需要在每条指令的末尾添加反斜杠\ set -e; \ # 如果出错就 退出脚本 $(RM) $(RMFLAGS) $.temp ; \ # 删除临时文件 $(CC) -E -MM $^ $.temp ; \ # 生成临时文件 send s,\(.*\)\.o[ :]*,objs/\l.o: ,g $.temp $ ; \ # 替换临时文件中的.o为objs/ $(RM) $(RMFLAGS) $.temp ; \ # 删除临时文件 clean : #rm -fr obj exes ,引用自定义变量的格式$(自定义变量) # $(RM) $(RMFLAGS) $(DIRS) $(EXE) $(RM) $(RMFLAGS) $(DIRS)使用依赖关系文件