1.1 makefile简介
在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过这需要我们投入一些时间去完成一个或者多个称之为Makefile 文件的编写。
所要完成的Makefile 文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。尽管看起来可能是很复杂的事情,但是为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”,一旦提供一个(通常对于一个工程来说会是多个)正确的 Makefile。编译整个工程你所要做的唯一的一件事就是在shell 提示符下输入make命令。整个工程完全自动编译,极大提高了效率。
make是一个命令工具,它解释Makefile 中的指令(应该说是规则)。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile 中可以使用系统shell所提供的任何命令来完成想要的工作。Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。
1.2 编写makefile格式说明
- Makefile文件里使用shell命令时,命令的前面必须是TAB键。
- 输入make默认执行Makefile文件里的第一个命令。
- 在Makefile文件里#号代表注释
- Make命令支持寻找解析:makefile”和“Makefile”这两种默认文件名。
- 如果要指定特定的 Makefile,你可以使用 make 的“-f”和“--file”参数,如:make -f Make.Linux 或 make --file Make.AIX。
- make –v 输出make版本和版权问题
- makefile里使用echo命令进行信息输出,类似于C语言的printf。示例:echo “12345” 或者 echo $(ABC)
- 在shell命令前加上@符号,可以隐藏命令的执行过程! 比如:@echo “12345” ,只会输出12345。
- Make命令的参数选项:
Make命令本身可带有四种参数:标志、宏定义、描述文档名和目标文档名。其标准形式为:
Make [flags] [macro definitions] [targets]
Unix系统下标志位flags选项及其含义为:
-f file 指定file文档为描述文档,假如file参数为"-"符,那么描述文档指向标准输入。假如没有"-f"参数,则系统将默认当前目录下名为makefile或名为Makefile的文档为描述文档。在Linux中, GNU make 工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索 makefile文档。
-i 忽略命令执行返回的出错信息。
-s 沉默模式,在执行之前不输出相应的命令行信息。
-r 禁止使用build-in规则。
-n 非执行模式,输出任何执行命令,但并不执行。
-t 更新目标文档。
-qmake操作将根据目标文档是否已更新返回"0"或非"0"的状态信息。
-p 输出任何宏定义和目标文档描述。
-dDebug模式,输出有关文档和检测时间的周详信息。
Linux下make标志位的常用选项和Unix系统中稍有不同,下面只列出了不同部分:
-c dir 在读取 makefile 之前改变到指定的目录dir。
-I dir 当包含其他 makefile文档时,利用该选项指定搜索目录。
-h help文挡,显示任何的make选项。
-w 在处理 makefile 之前和之后,都显示工作目录。
1.3 makefile变量的定义与使用
注意:变量只能在目标:符号范围之外进行定义赋值。
例如:
变量的定义格式: ABC=
变量的引用方式: $(ABC)
变量的赋值方式:
- 直接赋值:ABC=1234
- 赋值多个值:ABC= 123 567 890
- 在之前的变量基础上增加值:ABC+=789
示例图:
图1-1 变量的定义与使用
变量在目标的前面或者后面定义都可以正常使用,make会先解析整个文件,然后再执行特定的目标。
1 all: 2 echo "a="$(a) 3 a=1234567890 4 a+=8888 |
1.4 echo命令
通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被 make 显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译 XXX 模块...... |
当 make 执行时,会输出“正在编译 XXX 模块......”字串,但不会输出命令,如果没有“@”,那么,make 将输出:
echo 正在编译 XXX 模块...... |
如果 make 执行时,带入 make 参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
而 make 参数“-s”或“--slient”则是全面禁止命令的显示。
-i 表示忽略make中出现的错误!
- 同时输出多个值示例:
@echo "1234""5678""8900" @echo "1234" "5678" "8900" @echo "1234" $(aa) $(bb)........ @echo "1234 =$(ppp)" |
- 输出””号
@echo "\"AAAAAA"\" |
输出结果:
"AAAAAA"
1.5 Makefile的条件判断
1.5.1 ifeq与ifneq
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
all:#注意ifeq后边必须要有一个空格 ifeq (12,13)#相等为真,执行下面代码。不相等就为假,执行else下边代码 @echo "相等!" else @echo "不相等!" endif#条件判断的结束语句 |
我们可以从上面的示例中看到三个关键字:ifeq、else 和 endif。ifeq 的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else 表示条件表达式为假的情况。endif 表示一个条件语句的结束,任何一个条件表达式都应该以 endif 结束。注意ifeq后边必须要有一个空格。其他linux命令需要以TAB键开头。
与ifeq相反
all: ifneq (12,13) @echo "不相等!" else @echo "相等!" endif |
1.5.2 ifdef与ifndef
关键字“ifdef”。语法是:ifdef <variable-name>
如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。
ABC=123 all: ifdef ABC #检测ABC是否定义 @echo "已经定义!" else @echo "没有定义!" Endif |
与ifdef刚好相反
ABC=123 all: ifndef ABC #检测ABC是否定义 @echo "没有定义!" else @echo "已经定义!" endif |
1.6 设置Makefile文件搜索路径
注意:vpath和VPATH 设置的路径只针对Makefile的依赖有效。对linux的命令无效。比如:GCC
gcc 编译依赖的.h文件需要使用-I(大写i) 进行指定!
1.6.1 VPATH
VPATH 特殊变量,可以设置Makefile中所有文件的搜索路径,包括依赖文件和目标文件。
变量“VPATH”的定义中,使用空格或者冒号(:)将多个目录分开。make 搜索的目录顺序,按照变量“VPATH”定义中顺序进行(当前目录永远是第一搜索目录)。
例如:
VPATH = src:../headers |
它指定了两个搜索目录,“src”和“../headers”。
- VPATH示例:
VPATH=src #设置搜索的路径 all:123.o #依赖条件 @gcc 123.o -o app |
注意:gcc编译时需要自己包含.h头文件。 否则会报错。 比如:-I ./include -L 表示指定库路径。
1.6.2 vpath
vpath:关键字
它所实现的功能和上一小节提到的“VPATH”变量很类似,但是
它更为灵活。它可以为不同类型的文件(由文件名区分)指定不同的搜索目录。
vpath %.c ./FILE_1 #设置.c文件的搜索路径
vpath %.h ./FILE_1 #设置.h文件的搜索路径
vpath与VPATH的区别在于VPATH指定全局的搜索路径,而vpath可以针对特定的文件搜索路径。vpath命令主要有三种形式:
vpath pattern path : 符合pattern的文件在path目录搜索。
vpath pattern : 清除pattern指定的文件搜索路径
vpath : 清除所有文件搜索路径。
- vpath示例:
vpath %.c src all:123.o @gcc 123.o -o app |
注意:如果使用makefile的自动推导功能就不能给GCC指定头文件路径,编译就会报错,可将.h和.c放入同一个文件下即可。
1.7 makefile之间嵌套调用与参数传递
1.7.1 Makefile的嵌套调用
第一层的makefile:
ADDR=./addr_1 #存放makefile文件的路径 all: make -C $(ADDR) @echo "调用成功!" |
第二层的Makefile:
all: @echo "下层Makefile调用成功!!" |
1.7.2 Makefile之间传参传递-1
使用export关键字声明!
第一层Makefile:
ADDR=./addr_1 export ABC+=123 456 789 all: make -C $(ADDR) @echo "调用成功!" |
第二层makefile:
all: @echo "下层Makefile调用成功!!" @echo $(ABC) |
1.7.3 Makefile之间传参传递2
直接传递参数。
第一层Makefile:
ADDR=./addr_1 all: make -C $(ADDR) ABC="123456789" @echo "调用成功!" |
第二层makefile:
all: @echo "下层Makefile调用成功!!" @echo $(ABC) |
1.7.4 指定调用下层Makefile命令1
第一层Makefile:
ADDR=./addr_1 all: make -C $(ADDR) ABC="123456789" clean @echo "调用成功!" |
第二层makefile:
all: @echo "下层Makefile调用成功!!" @echo $(ABC) clean: @echo "下层clean调用成功!!" |
1.7.5 指定调用下层Makefile命令2
第一层Makefile:
ADDR=./addr all: make -C $(ADDR) obj1obj2 |
第二层makefile:
all: @echo "all命令调用成功!" obj1: @echo "obj1命令调用成功!" obj2: @echo "obj2命令调用成功!" |
执行结果:
obj1命令调用成功! obj2命令调用成功! |
1.8 Makefile获取shell命令的输出
比如命令:ls pwd
两种形式调用:1. `pwd` 2. $(shell pwd)
- 调用示例1 :
A+= $(shell ls) B+= `ls` all: @echo $(A) @echo $(B) |
- 调用示例2:
ADDR=`pwd`/addr_1 all: @echo $(ADDR) make -C $(ADDR) PWD=`pwd` |
1.9 自动化编译
目的:将三个.c文件,和两个.h文件编译一个可执行文件。
#include "main_1.c" #include "main_2.h" #include "main_2.c" #include "main_3.h" #include "main_3.c" |
1.9.1 Makefile文件编写示例1
NUM就是将要生成的目标文件。
NUM:main_1.o main_2.o main_3.o#依赖项—Makefile就是一层层的查找依赖 gcc -o NUM main_1.o main_2.o main_3.o main_1.o:main_1.c main_2.h main_3.h gcc -c main_1.c main_2.o:main_2.c main_2.h gcc -c main_2.c main_3.o:main_3.c main_3.h gcc -c main_3.c clear: rm *.o -rf |
1.9.2 Makefile文件编写示例2
使用Makefile自动推导的功能
OBJ=main_1.o main_2.o main_3.o #变量赋值,将依赖文件赋值给OBJ变量 NUM: $(OBJ) gcc -o NUM $(OBJ) main_1.o:main_1.c main_2.h main_3.h #生成main_1.o需要依赖下面的文件 main_2.o:main_2.c main_2.h #使用Makefile自动推导功能,省去gcc -c main_3.c main_3.o:main_3.c main_3.h clear: rm $(OBJ) -rf |
1.9.3 Makefile文件编写示例3
Makefile文件自动推导功能
OBJ=main_1.o main_2.o main_3.o #变量赋值,将依赖文件赋值给OBJ变量 NUM: $(OBJ) gcc -o NUM $(OBJ) $(OBJ):main_2.h main_3.h #将依赖文件合在一起,自动推导的命令 clear: rm $(OBJ) -rf |
1.10 Makefile的特殊符号
- include包含其他Makefile。 语法: include <路径>
1.10.1 赋值符号
= | 最基本的赋值语句。 |
:= | 覆盖变量之前的值。比如:ABC=123 ABC:=897 那么ABC最终的值等于897 |
?= | 如果变量定义过,则使用之前的值,本次赋值没有效。 |
+= | 追加变量,每个变量之间自动使用空格隔开。 |
% | 通配符,表示匹配所有文件。 |
- | 忽略命令的错误。比如: -rm 123.c 。如果123.c文件不存在,rm删除就会报错,加上-可以忽略错误。 |
@ | 加在命令前面,隐藏命令的输出 |
: | 依赖规则定义符。 格式:规则:依赖文件 |
1.10.2 自动化编译
$@ | 表示目标文件。 |
$< | 表示依赖文件集合中的第一个依赖文件。 |
$? | 表示更新的依赖文件。 |
$^ | 表示所有的依赖文件,去除重复的依赖文件。 |
$+ | 和$^符号一样 ,没有去重的功能。 |
- 加入自动化编译变量的makefile写法
目的:编译1个.h和2个.c
CC=gcc OBJ=main.o print.o app:$(OBJ) $(CC) -o $@ $(OBJ) %.o:%.c $(CC) -c $< -o $@ 同等于 $(CC) -c $< 不指定生成目标名称,默认使用默认的.c文件命名。 clean: @rm $(OBJ) -fv |
1.11 常用的makefile函数
注意:函数只能在目标之外调用。
1.11.1 字符串替换函数
函数原型格式:$(subst <A>,<C>,<D>)
函数功能:将D中的A全部替换为C
函数返回值:替换后的完整值
示例:
ABC=1111188888 aa=$(subst 1,A,$(ABC)) 注意:subst后面必须有一个空格 all: @echo "变量="$(aa) |
输出结果:
[root@xiaolong 2th]# make 变量=AAAAA88888 |
1.11.2 去掉字符串的前后空字符串
函数原型格式:$(strip <str>)
函数功能:将str字符串的前后空字符去掉。
函数返回值:替换后的完整值
示例:
ABC=" 12345" DATA=$(strip $(ABC)) all: @echo "替换前"=$(ABC) @echo "替换后"=$(DATA) |
输出结果:
[root@xiaolong 2th]# make 替换前= 12345 替换后= 12345 |
1.11.3 字符串查找
函数原型格式:$(findstring <A>,<C>)
函数功能:在C数据包中查找是否有A数据存在
函数返回值:查找成功返回查找到的数据,否则返回值空字符
- 示例1:
ABC=" 12345" all: @echo $(findstring 5,$(ABC)) |
输出结果:
[root@xiaolong 2th]# make 5 |
- 示例2:
ABC=12345哈哈 all: @echo $(findstring 哈,$(ABC)) |
输出结果:
[root@xiaolong 2th]# make 哈 |
- 示例3:
ABC=12345 all: ifeq ($(findstring 5,$(ABC)),5) @echo "查找成功" else @echo "查找失败" endif |
输出结果:
[root@xiaolong 2th]# make 查找成功 |
1.11.4 执行shell命令
格式:$(shell <执行的shell命令>)
示例:
1.11.5 产生错误信息
格式:$(error <想要打印的错误提示>)
功能:执行该函数会立即产生一个错误,终止Makefile的执行。
示例:
ABC="123456789" all: @echo $(error $(ABC)) @echo "hello world" |
输出结果:
[root@xiaolong 2th]# make Makefile:3: *** "123456789"。 停止。 |
1.11.6 产生警告信息
格式:$(warning <输出的警告提示信息>)
功能:执行该函数会产生警告信息,但是不会终止makefile的执行。
示例:
ABC="123456789" all: @echo $(warning $(ABC)) @echo "hello world" |
输出结果:
[root@xiaolong 2th]# make Makefile:3: "123456789" hello world |
1.11.7 判断变量是否是环境变量
语法:$(origin <变量名称>)
如果是环境变量函数返回: environment ,不是环境变量返回undefined。
示例:
1.12 特殊变量
1.12.1 CC变量
CC变量定义了makefile默认编译程序使用的编译器。 如果makefile中不定义CC变量,CC默认表示gcc
示例:
OBJ=main.o print.o CC=arm-linux-gcc app:$(OBJ) $(CC) $(OBJ) -o app |
输出结果:
[root@xiaolong 2th]# make arm-linux-gcc -c -o main.o main.c arm-linux-gcc -c -o print.o print.c arm-linux-gcc main.o print.o -o app |
1.12.2 模式指定变量CFLAGS
CFLAGS变量可以指定目标在编译时加载的参数。
- 示例1:
%.o:CFLAGS=-c |
在生成.o的时候,都会加上-c参数。
在生成.o时就是这样:gcc -c xxx.c
- 当使用“%”作为目标时,指定的变量会对所有类型的目标文件有效。
例如:%:CFLAGS=-c
- 编译多个目录下的文件时makefile编写方式
源码目录结构:
├── include │ └── print.h ├── Makefile └── src ├── main.c └── print.c |
Makefile编写方式1:
VPATH=include:src OBJ=main.o print.o CC=gcc INCLUDE=-I include app:$(OBJ) $(CC) $(OBJ) -o app %.o:CFLAGS = $(INCLUDE) clean: @rm $(OBJ) -f |
编译结果:
[root@xiaolong 2th]# make gcc -I include -c -o main.o src/main.c gcc -I include -c -o print.o src/print.c gcc main.o print.o -o app |
Makefile编写方式2:
VPATH=include:src OBJ=main.o print.o CC=gcc INCLUDE=-I include CFLAGS = $(INCLUDE) #全局指定,后面的所有编译都会加上这个参数 app:$(OBJ) @$(CC) $(OBJ) -o app clean: @rm $(OBJ) -f |
1.12.3 环境变量
- SHELL :环境变量,表示当前所用的shell
- CURDIR :环境变量,表示当前目录
- MAKEFLAGS :环境变量,存储make的参数信息
比如:make pwd=123 abc=888 那么MAKEFLAGS 就等于pwd=123 abc=888
1.13 Makefile中的伪目标
所谓伪目标就是这样一个目标,它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,有时我们将一个伪目标成为标签。
伪目标声明示例: .PHONY:clean
为什么要声明伪目标?
为了避免在makefile中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率。
比如:我们需要书写这样的一个规则:规则所定义的命令不是去创建目标文件,而是通过make命令行明确指定它来执行一些特点的命令,就像clean。当文件夹中没有clean这个文件的时候,我们输入“make clean”能按照初衷执行,但是一旦文件夹中出现clean文件,我们再次输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令。所以目标代码不会被执行。为了解决问题,我们将目标clean定义成伪目标。
1.13.1(clean没有声明伪目标的情况)
1.13.2 声明伪目标的方法
1.13.3 声明多个伪目标的方法
1.13.4 Makefile生成多个目标的方法
一般这种情况,我们会使用特别的伪目标——all进行表示。