一、Makefile简述
一)Makefile是什么
Makefile文件描述了Linux系统下C/C++工程的编译规则,用来自动化编译C/C++项目。只需要一个make命令,整个工程就开始自动编译,不需要手动执行GCC命令。
Windows下的集成开发工具(IDE)已经内置了Makefile,或者说自动生成Makefile,不用手动编写
二)Makefile解决了什么问题
1、编译时,需要链接库
把要链接的库文件放在 Makefile 中,制定相应的规则和对应的链接顺序。这样只需要执行 make 命令,工程就会自动编译
2、编译大的工程会话费很大的时间
Makefile 支持多线程并发操作,会极大的缩短我们的编译时间,并且当我们修改了源文件之后,编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译,也极大的解决了我们耗费时间的问题。
三)Makefile文件中的规则
1、规则组成部分
分成两部分:依赖的关系和执行的命令
- 依赖的关系
- 执行的命令
结构如下
targets : prerequisites command #或者 targets : prerequisites; command command
结构说明
- targets(目标):规则的目标,可以是Object File(一般称为它为中间文件),也可以是可执行文件,还可以是标签
- 目标放在":" 的前面,名字可以是字母和下换线组成;可以定义多个目标
- prerequisites(依赖):依赖文件,要生成targets需要的文件或者目标。可以是多个,也可以没有
- command(命令):make需要执行的命令(任意的shell命令),可以有多重命令,每一条命令占一行
注意:我们的目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab
键
2、使用Makefile的方式、
需要编写好Makefile文件,然后在shell中执行make命令,程序就会自动执行,得到最终的结果。
3、Makefile内容编写的规则
⼀个规则是由⽬标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。
其依赖树中包括三个规则,make 会检查所有三个规则当中的⽬标(⽂件)与先决条件(⽂件)之间的时间先后关系,从⽽来决定是否要重新创建规则中的⽬标。
1)显示规则
显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令
2)隐晦规则
make 命名有自动推导的功能,所以隐晦的规则可以比较粗糙地简略地书写 Makefile,这是由 make 命令所支持的
3)变量的定义
定义变量一般都是字符串,当Makefile被执行时,其中的变量都会都扩展到相应的引用位置上
4)文件指示
三个部分
- 一个Makefile引用另外一个Makefile
- 根据情况指定Makefile中的有效部分
- 定义一个多行的命令
5)注释
只有行注释,其注释用 “#”。若需要使用#,使用 \ 转义(如 \#)
四)Makefile的工作流程
Makefile可以使用的文件名称:GNUmakefile 、 makefile 、 Makefile,这也是执行make时,找文件的顺序。
推荐使用Makefile作为文件名,规范的写法
1、make执行时的默认规则
make执行的是第一规则:Makefile中出现的第一个依赖关系。此规则的第一目标称为“最终目标”或“终极目标”
2、make是否顺利执行的影响因素
下面两个条件必须同时满足
- 是否制定了正确的依赖规则
- 当前目录下是不是存在需要的依赖文件
3、更新规则
只有修改过的源文件 或者 不存在的目标文件会进行重建
优点:那些没有改变的文件件不用重新编译,这样大大地节省了时间,提高了编程的效率。
五)清理工作目录的过程文件
在使用的时候会产生中间文件会让整个文件看起来很乱,所以在编写 Makefile 文件的时候会在末尾加上这样的规则语句
.PHONY:clean clean: rm -rf *.o test
shell 中执行 "make clean" 命令,编译时的中间文件和生成的最终目标文件都会被清除,方便我们下次的使用
二、Makefile的编写
一)Makefile通配符的使用
通配符 | 使用说明 |
---|---|
* | 匹配0个或者是任意个字符 |
? | 匹配任意一个字符 |
[] | 我们可以指定匹配的字符放在 "[]" 中 |
"%.o" 把需要的所有的 ".o" 文件组合成为一个列表,从列表中挨个取出的每一个文件,"%" 表示取出来文件的文件名(不包含后缀),然后找到文件中和 "%"名称相同的 ".c" 文件,然后执行下面的命令,直到列表中的文件全部被取出来为止。
这个属于 Makefile 中静态模规则:规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。跟我们的多规则目标的意思相近,但是又不相同。
二)Make变量的定义和使用
1、变量的定义
变量的使用可以提高makefile的可维护性。⼀个变量的定义很简单,就是⼀个名字(变量名)后⾯跟上⼀个等号,然后在等号的后⾯放这个变量所期望的值。
对于变量的引⽤,则需要采⽤$(变量名)或者${变量名}这种模式。
定义变量的语法
变量的名称=值列表
定义变量的规则
变量的名称可以由大小写字母、阿拉伯数字和下划线构成。等号左右的空白符没有明确的要求,因为在执行 make 的时候多余的空白符会被自动的删除。至于值列表,既可以是零项,又可以是一项或者是多项
2、变量的基本赋值
Makefile 的变量的四种基本赋值方式: 简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。 递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。 条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。 追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。
3、自动变量
对于每一个规则,目标和先决条件的名字会在规则中多次出现,每一次出现都是一种麻烦,更麻烦的是,若改变了目标和依赖的名,那得在命令中全部跟着改。所以这就需要我们用到Makefile的自动变量。
最常用的自动变量
- $@ ⽤于表示⼀个规则中的⽬标。当我们的⼀个规则中有多个⽬标时,$@所指的是其中任何造成命令被运⾏的⽬标。
- $^ 则表示的是规则中的所有先择条件。
- $< 表示的是规则中的第⼀个先决条件。
1)测试
.PHONY:all all:first second third @echo "\$$@ = $@" @echo "\$$^ = $^" @echo "\$$< = $<" first second third:
执行make
]# make $@ = all $^ = first second third $< = first
需要注意的是,在 Makefile 中‘$’具有特殊的意思,因此,如果想采⽤ echo 输出‘$’,则必需⽤两个连着的‘$’。还有就是,$@对于 Shell 也有特殊的意思,我们需要在“$$@”之前再加⼀个脱字符‘\’。
4、特殊变量
1)MAKE变量
它表示的是make 命令名是什么。当我们需要在 Makefile 中调⽤另⼀个 Makefile 时需要⽤到这个变量,采⽤这种⽅式,有利于写⼀个容易移植的 Makefile。
Makefile
.PHONY:clean all: @echo "MAKE = $(MAKE)"
2)MAKECMDGOALS变量
表示当前用户所输入的make目标是什么
.PHONY: all clean all clean: @echo "\$$@ = $@" @echo "MAKECMDGOALS = $(MAKECMDGOALS)"
执行返回的结果
]# make $@ = all MAKECMDGOALS = ]# make all $@ = all MAKECMDGOALS = all ]# make clean $@ = clean MAKECMDGOALS = clean ]# make all clean $@ = all MAKECMDGOALS = all clean $@ = clean MAKECMDGOALS = all clean
5、扩展变量
1)递归扩展变量
使⽤等号进⾏变量定义和赋值,对于这种只⽤⼀个“=”符号定义的变量,我们称之为递归扩展变量(recursively expanded variable)
2)简单扩展变量
简单扩展变量(simply expanded variables),是⽤“:=”操作符来定义。对于这种变量,make只对其进行一次扫描赫尔替换
3)条件扩展变量
条件赋值符“?=”,条件赋值的意思是当变量以前没有定义时,就定义它并且将左边的值赋值给它,如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能
.PHONY: all foo = x foo ?= y bar ?= y all: @echo "foo = $(foo), bar = $(bar)"
执行make
]# make foo = x, bar = y
4)使用“+=” 变量进行赋值
.PHONY: all objects = main.o foo.o bar.o utils.o objects += another.o all: @echo $(objects)
执行make返回
]# make main.o foo.o bar.o utils.o another.o
6、override
在设计Makefile时,不希望在Makefile中定义的变量被覆盖,就可以使用override
1)未使用override前
编写Makefile ]# cat Makefile .PHONY: all foo = x all: @echo "foo = $(foo)" 执行make(结果:变量的值被覆盖了) ]# make foo=1217 foo = 1217
2)使用override
编写Makefile ]# cat Makefile .PHONY: all override foo = x all: @echo "foo = $(foo)" 执行make(结果变量的值没有发生改变) ]# make foo=1217 foo = x
三)Makefile中命令前@
通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译XXX模块...... 当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出: echo 正在编译XXX模块...... 正在编译XXX模块...... 如果make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。 而make参数“-s”或“--slient”则是全面禁止命令的显示。
四)假目标
使用假目标,假目标最从常用清净就是避免所定义的目标和的已经存在文件是从重名的情况,假⽬标可以采⽤.PHONY 关键字来定义,需要注意的是其必须是⼤写字⺟
foo.c
#include <stdio.h> void foo() { printf("this is foo() !\n"); }
main.c
extern void foo(); int main() { foo(); return 0; }
Makefil.PHONY: cleansimple: main.o foo.o
gcc -o simple main.o foo.o main.o: main.c 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
采⽤.PHONY 关键字声明⼀个⽬标后,make 并不会将其当作⼀个⽂件来处理,⽽只是当作⼀个概念上的⽬标。对于假⽬标,我们可以想像的是由于并不与⽂件关联,所以每⼀次 make 这个假⽬标时,其所在的规则中的命令都会被执⾏。
执行make
]# make gcc -o main.o -c main.c gcc -o foo.o -c foo.c gcc -o simple main.o foo.o ]# ls foo.c foo.o main.c main.o Makefile simple
五)模式
每个目标都写一个不同的规则来描述,若项目比较多相当的费时,人为写也容易出错。于是“模式”应运而生。
模式中使用%作为通配符,采⽤了模式以后,不论有多少个源⽂件要编译,我们都是应⽤同⼀个模式规则的,很显然,这⼤⼤的简化了我们的⼯作
.PHONY: clean CC = gcc RM = rm EXE = simple OBJS = main.o foo.o $(EXE): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -o $@ -c $^ clean: $(RM) $(EXE) $(OBJS)
六)函数
https://www.cnblogs.com/paul-617/p/15501875.html
指明每一个需要被编译的程序,对于一个源程序文件比较多的项目 ,若每增加或是删除一个文件都得更新Makefile,其工作量不可小觑
.PHONY: clean CC = gcc RM = rm EXE = simple SRCS = $(wildcard *.c) OBJS = $(patsubst %.c,%.o,$(SRCS)) $(EXE): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -o $@ -c $^ clean: $(RM) $(EXE) $(OBJS)
1、addprefix 函数
用来再给字符串中每个子串前加上一个前缀,格式为$(addprefix prefix, names...)
.PHONY:all without_dir= foo.c bar.c main.c with_dir:=$(addprefix objs/, $(without_dir)) all: @echo $(with_dir)
2、filter函数
filter 函数⽤于从⼀个字符串中,根据模式得到满⾜模式的字符串,其形式是:$(filter pattern..., text)
.PHONY: all sources = foo.py bar.go baz.sh ugh.js sources := $(filter %.py %.sh, $(sources)) all: @echo $(sources)
make执行结果
]# make foo.py baz.sh
3、filter-out函数
filter-out 函数⽤于从⼀个字符串中根据模式滤除⼀部分字符串,其形式是:$(filter-out pattern..., text)
]# cat Makefile .PHONY: all sources = foo.py bar.go baz.sh ugh.js sources := $(filter %.py %.sh, $(sources)) all: @echo $(sources)
make执行结果
]# make foo.py baz.sh
4、patsubst函数
patsubst 函数是⽤来进⾏字符串替换的,其形式是:$(patsubst pattern, replacement, text)
]# cat Makefile .PHONY:all mixed=foo.c bar.c main.o objects:=$(patsubst %.c, %.o, $(mixed)) all: @echo $(objects)
make执行结果
]# make foo.o bar.o main.o
5、strip函数
strip 函数⽤于去除变量中的多余的空格,其形式是:$(strip string)
]# cat Makefile .PHONY:all original=foo.c bar.c stripped:=$(strip $(original)) all: @echo "original = $(original)" @echo "stripped = $(stripped)"
make执行结果
]# make original = foo.c bar.c stripped = foo.c bar.c
6、wildcard函数
wildcard 是通配符函数,通过它可以得到我们所需的⽂件,这个函数类似我们在 Windows 或Linux 命令⾏中的“*”。其形式是:$(wildcard pattern)
环境
]# ls bar.c foo.c main.c Makefile
Makefile内容
]# cat Makefile .PHONY:all SRC=$(wildcard *.c) all: @echo "SRC = $(SRC)"
make执行的结果
]# make SRC = foo.c main.c bar.c
三、Makefile延伸
一)创建目录
编译项目前创建目录(为默认执行的target),下次执行all前,先执行clean 清理下环境
.PHONY:all MKDIR=mkdir DIRS=objs exes RM=rm RMFLAGS=-fr all:$(DIRS) ${DIRS}: $(MKDIR) $@ clean: $(RM) $(RMFLAGS) $(DIRS)
二)将文件放入目录
为了将目标文件或是执行程序分别放入所创建的objs和exes目录中,需要使用到Makefile中的addprefix
需要提前准备好.c相应的文件
.PHONY:all clean MKDIR=mkdir RM=rm RMFLAGS=-fr CC=gcc DIR_OBJS=objs DIR_EXE=exes DIRS=$(DIR_OBJS) $(DIR_EXE) EXE=test EXE:=$(addprefix $(DIR_EXE)/, $(EXE)) SRCS=$(wildcard *.c) OBJS=$(SRCS:.c=.o) OBJS:=$(addprefix $(DIR_OBJS)/, $(OBJS)) all:$(DIRS) $(EXE) $(DIRS): $(MKDIR) $@ $(EXE):$(OBJS) $(CC) -o $@ $^ $(DIR_OBJS)/%.o:%.c $(CC) -o $@ -c $^ clean: $(RM) $(RMFLAGS) $(DIRS)
make执行的结果
]# make mkdir objs mkdir exes gcc -o objs/foo.o -c foo.c gcc -o objs/main.o -c main.c gcc -o objs/bar.o -c bar.c gcc -o exes/test objs/foo.o objs/main.o objs/bar.o
三)条件语句
make条件语法进行分析,条件语句形式:ifdef、ifeq、ifndef和inneq四种。
如果⾮得⽤条件语法,那得使⽤ Shell 所提供的条件语法而不是 Makefile 的。Makefile 中的条件语法有三种形式,如下所示。
其中的 conditional-directive 可以是 ifdef、ifeq、ifndef 和 ifneq 中的任意⼀个。
conditional-directive text-if-true endif
或者
conditional-directive text-if-true else text-if-false endif
或者
conditional-directive text-if-one-is-true else conditional-directive text-if-true else text-if-false endif
举例
]# cat Makefile .PHONY: all sharp = square desk = square table = circle ifeq ($(sharp), $(desk)) result1 = "desk == sharp" endif ifneq "$(table)" 'square' result2 = "table != square" endif all: @echo $(result1) @echo $(result2)
make运行结果
]# make desk == sharp table != square