Makefile工程实践
Makefile
1. 什么是Makefile
Linux下面没有类型VC等开发环境,
gcc -o hello helloworld.c 得到可执行文件 hello
一个项目中,要编译的文件很多,不可能都手动去调用gcc来一个一个编译
这时候Makefile用派上用场了,Makefile就定义了我们怎么去编译这个程序,描述了整个工程的编译、链接规则;
让软件项目自动化编译。
最简单的Makefile
hello:helloworld.c 我们称helloworld.c为依赖,hello为目标
gcc -o hello helloworld.c 这是命令规则
clean: //执行make clean就会执行下面的指令
rm hello
程序一般是先编译成目标文件,目标文件再链接成可执行文件。
所以上面的Makefile可以改为:
hello:helloworld.o
gcc -o hello helloworld.o 把目标文件.o链接生成可执行文件
helloworld.o:helloworld.c
gcc -o helloworld.o -c helloworld.c 添加目标文件.o的生成过程,当一个源文件没有修改的话,是不会重新编译的,工程中一旦源文件多可以节约大量的时间。
clean: 这里-c的意思是只编译不链接
rm hello
2. Makefile的执行过程
当我们执行make的时候,首先到当前目录下去寻找Makefile文件,
2.1 学习Makefile的必要性
Linux/Unix环境下开发必备技能
系统架构师、项目经理核心技能
研究开源项目、Linux内核的地图
加深对底层软件构造系统及过程的理解
3. GUN make工具
make是一个命令工具,它解释了Makefile中的指令(应该说是规则),Makefile中描述了工程中所有文件的编译顺序、规则。Makefile有自己的书写格式、关键字、函数。
而且在Makefile中可以使用shell所提供的任何命令来完成你想要的工作。
make -v查看GUN make工具的版本
Makefile面向依赖的思维,C语言面向过程,C++面向对象,Python一切皆对象,Linux一切皆文件。
3.1. make工具
make是一个命令工具,它解释Makefile中的指令(规则),Makefile中描述了
工程中所有文件的编译顺序、规则。Makefile有自己的书写格式、关键字、函数。
在Makefile中可以使用shell所提供的任何命令来完成你想要的工作。
Makefile(在其他的系统上可能是另外的文件名)在绝大多数的IDE开发环境中都在
使用,已经成为一种工程的编译方法。
make工具不仅仅是用来管理C语言工程的,那些编译器只要能够在shell下运行的语言所构
建的工程都可以使用make工具来管理。
3.2. 静态库
又称为文档文件(Archive File)。它是多个.o文件的集合。Linux中静态库文件的后缀为“.a”,
静态库中的各个成员(.o文件)没有特殊的存在格式,仅仅是一个.o文件的集合。使用"ar"工具
维护和管理静态库。
3.3. Makefile
在执行make之前,需要一个命名为Makefile的特殊文件来告诉make需要做什么,该怎么做。通常,
make工具主要用来 进行工程编译和程序链接。
4. Makefile文件的主要内容
(1)规则
构成Makefile的基本单元、构成依赖关系的核心部件
其他内容可以看作规则服务
(2)变量
类似于C语言的宏,引用:$(VAR)、${VAR}
可以让Makefile更加灵活
(3)条件执行
根据某一变量的值来控制make执行或者忽略Makefile的某一部分
(4)文本 、文件名处理函数
文件处理函数:字符串的替换、查找、过滤、排序、统计等。
文件名处理函数:取目录/文件名、前后缀、加前缀/后缀、单词连接等函数。
其他常用函数:if函数、shell函数、foreach函数
(5)文件包含
类似于C语言的#include,使用include命令
(6)注释
使用#开头,表示注释
Makefile的目的:构建依赖关系树
一个简单的Makefile描述规则组成:
--------------------
TARGET:PREREQUISITES
COMMAND
--------------------
target:规则的目标。通常是程序中间或者最后需要生成的文件名。可以是.o文件、也可以是最后的可执行程序的
文件名。另外,目标也可以是一个make执行的动作的名称,如目标“clean”,这样的目标是“伪目标”
prerequisites:规则的依赖。生成规则目标所需的文件名列表。通常一个目标依赖于一个或者多个文件。
command:规则的命令行。是make程序所有执行的动作(任意的shell命令或者可在shell下执行的程序)。
一个规则可以有多个命令行,每一条命令占一行。
注意:每一个命令行必须以Tab字符开始,Tab字符告诉make此行是一个命令行。make按照命令完成相应的动作。这也是
书写Makefile中容易产生而且比较隐蔽的错误。
命令就是在任何一个目标的依赖文件发生变化后重建目标的动作描述。一个目标可以没有依赖
而只有动作(指定的命令)。比如Makefile中的目标“clean”,此目标没有依赖,只有命令。
它所指定的命令用来删除make过程产生的中间文件(清理工作)。
5. 规则
目标:目标依赖
命令
目标就是我们要去make xxx的那个xxx,就是我们最终要生成的东西。
依赖是用来生成目标的原材料
命令就是加工方法,所以make xxx的过程其实就是使用命令将依赖加工成目标的过程,
注意事项:
命令必须使用Tab键开头、一般是shell命令
一个规则中可以无目标依赖,仅仅实现某种操作
例1:clean:
rm lcd.o hello player.o
例2:test:
@echo "Just for test..."
一个规则中可以没有命令,仅仅描述依赖关系
例:all:hello
一个规则中必须有一个目标
目标下面可以有多条命令
all:
@echo "---------------------------------------------------------------------"
@echo "ERROR: you should have specified an OSDRV_CROSS! "
@echo "e.g., make OSDRV_CROSS=arm-hisiv300-linux all "
@echo "e.g., make OSDRV_CROSS=arm-hisiv400-linux all "
@echo "---------------------------------------------------------------------"
每一条命令,make会开一个进程
每条命令执行完,make会检测每个命令的返回码
若命令返回成功,make继续执行下个命令
若命令执行出错,make会终止执行当前规则,退出
make -j4 开4个进程
6. 目标
一个Makefile里可以有多个目标;
一般会选择第一个作为默认目标,除非你指定编译某个目标;例如:make test
多目标:
一个规则中可以有多个目标
多个目标具有相同的生成命令
例如
test1 test2:
@echo "Just for test..."
可以写成
test1:
@echo "Just for test..."
test1:
@echo "Just for test..."
例如:
all:test1 test2
test1 test2:
@echo "Just for test:$@" //$@为目标变量
多规则目标:
多个规则可以是同一个目标
Make在解析时,会将多个规则的依赖文件合并
伪目标:
并不是一个真正的文件名,可以看做是一个标签
无依赖,相比一般文件不会去重新生成、执行
伪目标,可以无条件执行
.PONHY:all clean //把all clean 设置为伪目标
文件时间戳
根据时间戳来判断目标依赖文件是否更新
所以文件编译
7. 变量
变量的定义:
例如:CC=gcc
定义变量的好处是,换一个平台只要改定义的地方就行了,类似于C语言的宏定义
变量赋值:
追加赋值:+=
条件赋值:?= //如果之前还没有赋值,赋当前值,如果之前已经赋值,保留之前的值
变量引用:
$(CC) ${CC}
例子:
STR = hello
STR2 = hello
STR2 += world!
test1 = a
test1 ?= b
test2 ?= b
all:
@echo "STR = $(STR)"
@echo "STR2 = $(STR2)"
@echo "test1 = $(test1)"
@echo "test2 = $(test2)"
变量的分类:
立即展开变量
使用:=操作符赋值
在解析阶段直接赋值常量字符串
延迟展开变量
使用=操作符赋值
在运行阶段,实际使用变量时再进行求值
注意:
一般在目标、目标依赖中使用立即展开变量
在命令中一般使用延迟展开变量
例1:
.PHONY:all
HELLO = Good
TIME = morning!
STRING = $(HELLO) $(TIME)
$(info $(STRING))
TIME = afternoon!
$(info $(STRING))
all:
@echo "done"
例2:
.PHONY:all
HELLO = Good
TIME = morning!
STRING := $(HELLO) $(TIME)
$(info $(STRING))
TIME = afternoon!
$(info $(STRING))
all:
@echo "done"
系统环境变量:
作用范围
变量在make开始运行时被载入到Makefile文件中
对所有的Makefile都有效
若Makefile中定义同名变量,系统环境变量将被覆盖
命令行中传递同名变量,系统环境变量将被覆盖
常见的系统环境变量
CFLAGS
SHELL
MAKE
例如:
.PHONY:all
all:
@echo "CFLAGS = $(CFLAGS)"
@echo "SHELL = $(SHELL)"
@echo "MAKE = $(MAKE)"
@echo "HOSTNAME = $(HOSTNAME)"
变量的传递:
Makefile在多目录下递归执行
$(MAKE) -C subdir
cd subdir && $(MAKE)
通过命令行传递变量
cd test && make N=$(N)
通过export传递变量
export N = 3 //把N变量声明为全局变量
8. 条件判断
(1)关键字
ifeq、ifneq、else、endif
注意:条件语句从ifeq开始,ifeq与括号之间用空格隔开
例:
.PHONY:all
DEBUG = true
ifeq ($(DEBUG),true)
VERSION = debug
else
VERSION = release
endif
all:
@echo "build $(VERSION) mode"
9. 库
库的定义:目标文件的归档
10. Makefile的作用和意义
(1)工程项目c文件太多管理不方便,因此用Makefile来做项目管理,方便编译链接过程。
(2)uboot和Linux
kernel本质上都是c语言的项目,都由很多个文件组成,因此都需要通过Makefile来管理。
11.通配符%和Makefile自动推导(规则)
(1)%是Makefile中的通配符,代表一个或几个字母。
12.Makefile中定义和使用变量
(1)Makefile中定义和使用变量,和shell脚本中非常相似。相似是说都没有变量类型,直接
定义使用,引用变量时用$var
13.伪目标(.PHONY)
(1)伪目标意思是这个目标本身不代表一个文件,执行这个目标不是为了得到某个文件或东西,而是
而是单纯为了执行这个目标下面的命令,伪目标没有依赖,因为不加依赖意思就是无条件执行。
(2) 伪目标可以直接写(clean:),不影响使用;但是有时候为了明确声明这个目标是伪目标会在伪目标的前面
用.PHONY来声明它是伪目标(.PHONY clean)。
14.Makefile的文件名
(1)Makefile的文件名合法的一般有2个:Makefile或者makefile
15.Makefile中引用其他Makefile(include指令)
(1)有时候Makefile总体比较复杂,因此分成好几个Makefile来写。然后在主Makefile中引用
其他的,用include指令来引用。引用的效果也是原地展开,和C语言的头文件包含非常相似。
16.Makefile中注释用#
和shell是一样的
4.命令前面的@用来静默执行
(1)在Makefile的命令行中前面的@表示静默执行
(2)Makefile中默认情况下在执行一行命令前先把这行命令给打印出来,然后再执行这行命令。
(3)如果你不想看到命令本身,只想看到命令执行就静默执行即可。
1 all:
2 @echo "hello world"
5.Makefile中几种变量赋值运算符
(1)=
(2):=
(3)?=
如果变量前面没有赋值过,则执行这条赋值,如果前面已经赋值过了则本行被忽略。
所谓没有赋值过其实就是这个变量没有被定义过(赋值为空也算)。
1 var="abcd"
2 var ?= "efgh"
3
4 all:
5 echo $(var)
注意:makefile中=前后的空格可有可无,这一点与shell格式要求要低一些
(4)+=
用来给一个已经赋值的变量接续赋值,意思就是把这次的值加到原来的值的后面,有点类似于
stract。(在shell makefile等文件中,可以认为所有的变量都是字符串,+=就相当于给字符串
stract接续内容)(注意一个细节,+=续接的内容和原来的内容之间会自动加一个空格隔开)
1 var="abcd"
2 var += "efgh"
3
4 all:
5 echo $(var)
(1)=
最简单的赋值
A=abc
B=$(A)def
A=gh
all:
echo $(B)
ghdef从这里来看A是往后数的
用=赋值的变量,在被解析时他的值取决于最后一次赋值时的值。
(2):=
一般也是赋值
用:=来赋值的,则是就地直接解析,只用往前看即可。
A=abc
B:=$(A)def 只要引用的这行改成:=就行
A=gh
all:
echo $(B)
abcdef
19. Makefile的环境变量
(1)makefile中用export导出的就是环境变量。一般情况下要求环境变量名用大写,普通
变量名用小写。
(2)环境变量和普通变量不同,可以这么理解:环境变量类似于整个工程中所有Makefile
之间可以共享的全局变量,而普通变量只是当前本Makefile中使用的局部变量。
定义了一个环境变量会影响到工程中别的Makefile文件,因此要小心。
export CC
(3)Makefile中可能有一些环境变量可能是makefile本身自己定义的内部的环境变量或者
是当前的执行环境提供的环境变量(譬如我们在make执行时给makefile传参。
make CC=arm-linux-gcc,其实就是给当前Makefile传了一个环境变量CC,值是arm-linux-gcc。
我们在make时给makefile传的,我们在make时给makefile传的环境变量值优先级最高的,
可以覆盖makefile中的赋值),这就好像C语言中编译器预定义的宏__LIINE__ __FUNCTION__
等一样。
CC=gcc
all:
echo $(CC)
make CC=arm-linux-gcc
20. Makefile中使用的通配符
(1)*
若干个通配字符
all : 1.c 2.c 12.c test.c 1.h
echo *.c
make
echo *.c
12.c 1.c 2.c test.c
(2)?
匹配一个字符
echo ?.c
1.c 2.c
(3)[]
将[]中的字符依次去和外面的结合匹配
echo [12].c
1.c 2.c
(4)%
也是通配符,表示任意多个字符,和*很相似,但是%一般只用于规则
描述中,又叫做规则通配符。
%.o : %s
21.Makefile的自动变量
(1)为什么使用自动变量
在有些情况下文件集合中文件非常多,描述的时候很麻烦,所以我们Makefile就用一些特殊的符号来
替代符号某种条件的文件集,这就形成了自动变量。
(2)自动变量的含义:预定义的特殊意义的符号。就类似于C语言编译器中预制的那些宏__FILE__一样
(3)常用的自动变量:
$@ 规则的目标文件名
$< 规则的依赖第一个文件名
$^ 依赖的文件集合
22.程序的编译与链接
(1)程序的存储与运行
程序的存储:
(2)程序的编译和链接
源代码(helloworld.c)->预处理器(宏定义,条件编译,头文件包含)、编译->汇编代码(helloworld.s)
->汇编器->目标代码(helloworld.o)
->连接器 ->可执行程序(helloworld.exe)
库文件(printf)
(3)程序文件的分类
1)BIN文件
适合单片机等的裸机系统
2)ELF文件,比BIN文件多了头部信息(包括可执行文件,可重定位文件(例如目标.o文件),共享库文件)
一般Linux系统下需要运行这种格式的文件,在Linux下编译链接生成的可执行文件就是这种格式的文件。
生成的可执行文件带一个头部信息,可以通过命令readelf -h hello来查看
里面有一条Entry point address: 0x400430加载器会把程序加载带内存的这个地址
去执行,readelf -h helloworld.o目标文件helloworld.o入口地址是0x Entry point address: 0x0
目标文件只有等组装在一起之后才会有个入口地址,这时候我们就说目标文件是可重定位的。
(4)动态库与静态库