文章目录
一、简介
makefile可以简单的认为是一个工程文件的编译规则,描述了整个工程的自动编译和链接的规则。
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
(1)显式规则
显式规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
(2)隐晦规则
由于我们的 make 命名有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 命令所支持的。
(3) 变量的定义
在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
(4)文件指示
其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像C语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像C语言中的预编译 #if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
(5)注释
Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++ 中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如:“#”。
基本格式如下:
targets: prerequisites
command
targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
具体流程可参考下图:
Makefile执行的具体过程如下图:
Makfile程序的编写首先要了解程序的编译过程,可参考下面的链接:
二、常用规则介绍
2.1 递归扩展变量
变量 | 解析 |
---|---|
= | 是最基本的赋值 |
:= | 是覆盖之前的值 |
?= | 是如果没有被赋值过就赋予等号后面的值 |
+= | 是添加等号后面的值 |
obj-y := | 编译进内核的文件(夹)列表 |
obj-m := | 编译成外部可加载模块的列表 |
lib-y := 和 lib-m := | 编译成库文件 |
always := | 总是需要被编译的模块 |
targets := | 编译目标 |
subdir-y := 和 subdir-m := | 表示需要递归进入的子目录 |
内核中常用的Makefile例子:
//例如: fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o ialloc.o inode.o ioctl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
CONFIG_EXT2_FS在/fs/ext2/Kconfig中,可以通过make menuconfig设置CONFIG_EXT2_FS=y
config EXT2_FS
tristate "Second extended fs support"
help
Ext2 is a standard Linux file system for hard disks.
在这个例子中,如果$(CONFIG_EXT2_FS_XATTR)表示’y’,则xattr.o xattr_user.o和xattr_trusted.o都将是复合对象ext2.o的一部分.
注意: 当然,当你将编译目标文件到内核时,以上语法同样有效.因此,如果CONFIG_EXT2_FS=y,Kbuild将建立一个ext2.o来输出各个部分,然后将其链接到 built-in.o中,正如您期望的那样。
2.2 常见的自动化变量解析
变量 | 解析 |
---|---|
$0 | 当前脚本的文件名。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2>。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 表示目标文件。 |
$? | 上个命令的退出状态,或函数的返回值。 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
$^ | 表示所有的依赖文件 |
$< | 表示第一个依赖文件 |
# Makefile内容
.PHONY:all
all:first second third
@echo "\$$@ = $@"
@echo "\$$^ = $^"
@echo "\$$< = $<"
[root@localhost /]# make
first second third:
$@ = all
$^ = first second third
$< = first
# Makefile内容
%.o:%.c
gcc -o $@ $^
OBJ=$(wildcard *.c)
test:$(OBJ)
gcc -o $@ $^
当用通配符赋值给变量时需要用wildcard通配符函数,通过它可以得到我们所需的文件,这个函数类似我们在 Windows 或Linux命令行中的“*”
2.3 常用的编译器宏定义
宏定义 | 含义 |
---|---|
AR | 归档维护程序的名称,默认值为 ar。 |
ARFLAGS | 归档维护程序的选项。 |
AS | 汇编程序的名称,默认值为 as。 |
ASFLAGS | 汇编程序的选项。 |
CC | C 编译器的名称,默认值为 cc。 |
CCFLAGS | C 编译器的选项。 |
CPP | C 预编译器的名称,默认值为 $(CC) -E。 |
CPPFLAGS | C 预编译的选项。 |
CXX | C++ 编译器的名称,默认值为 g++。 |
CXXFLAGS | C++ 编译器的选项。 |
FC | FORTRAN编译器的名称,默认值为 f77。 |
FFLAGS | FORTRAN编译器的选项。 |
实例使用:
ccflags-y := -DDEBUG
ccflags-y := -DVERBOSE_DEBUG
opps-objs :=oops_test.o
obj-make := opps.o
KBUILD_CFLAGS +=-g
CC = gcc
CCFLAGS = -D_DEBUG -g -m486
test.o: test.c test.h
$(CC) -c $(CCFLAGS) test.c
在上面的例子中,CC和 CCFLAGS 就是 make 的变量。GNU make通常称之为变量,而其他 UNIX 的 make工具称之为宏。
2.4 条件语法
当 make 看到条件语法时将⽴即对其进⾏分析,这包括 ifdef、ifeq、ifndef 和 ifneq 四种语句形式。
conditional-directive
text-if-true
else
text-if-false
endif
# 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)
[root@localhost /]# make
desk == sharp
table != square
2.5 其他特殊变量
(1)VPATH变量
Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
vapth使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
vpath %.h ../headers
该语句表示,要求make在“…/headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
(2).PHONY变量
因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY: clean
clean:
rm -rf *.o
# “.PHONY”表示,clean是个伪目标文件,当执行make clean时,直接执行clean下面的命令rm -rf *.o
(3)include变量
"include"指示符告诉 make 暂停读取当前的 Makefile,而转去读取"include"指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。
为什么要include其他文件呢?
对于一些通用的变量定义、通用规则,写在一个文件中,任意目录结构中的makefile想要使用这些通用的变量或规则时,include指定的文件就好了,而不用在每个makefile中又重写一遍。
对于源文件自动生成依赖文件(makefile之目录搜索&自动依赖)时,将这些个依赖关系保存成文件,在需要使用时include进来,这样少了人为的干预,同时也减少的错误的发生
我们就不希望mkdir出错而终止规则的运行。
为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。
“-include” 来代替 “include” 来忽略文件不存在或者是无法创建的错误提示,使用格式如下:-include
名称 | 功能 |
---|---|
.PHONY: | 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用make命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。 |
.SUFFIXES: | 这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名 |
.DEFAULT: | Makefile中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 “.DEFAULT” 所指定的命令。 |
.PRECIOUS: | 这个特殊目标所在的依赖文件在make的过程中会被特殊处理:当命令执行的过程中断时,make不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。 |
.INTERMEDIATE: | 这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。 |
.SECONDARY: | 这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。 |
(4)$(Q) 变量
本质就是 Makefile中的命令显示与否,命令前有@字符,则不显示命令本身而只显示命令的执行结果。
2.6 Makefile实例
# Makefile内容
.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)
2.7 添加打印信息
//直接添加方式
$(warning "this is test========================")
$(info $(KBUILD_CFLAGS))
$(error $(KBUILD_CFLAGS))
//使用echo命令打印
test:
@echo $(KBUILD_CFLAGS)
执行make test 即可打印echo内容
具体链接可参考:C语言、Makefile和shell中添加打印调试信息总结
2.8 实例解析
objs := head.o init.o nand.o main.o
nand.bin : $(objs) //冒号前面的是表示目标文件, 冒号后面的是依赖文件,这里是将所有*.o文件编译出nand.bin可执行文件
arm-linux-ld -Tnand.lds -o nand_elf $^ //将*.o文件生成nand_elf链接文件
//-T:指向链接脚本, $^:指向所有依赖文件,
arm-linux-objcopy -O binary -S nand_elf $@ //将nand_elf链接文件生成nand.bin文件
//$@:指向目标文件:nand.bin
//-O :选项,其中binary就是表示生成的文件为.bin文件
arm-linux-objdump -D -m arm nand_elf > nand.dis //将nand.bin文件反汇编出nand.dis文件
//-D :反汇编nand.bin里面所有的段, -m arm:指定反汇编文件的架构体系,这里arm架构
%.o:%.c
arm-linux-gcc -Wall -c -O2 -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -c -O2 -o $@ $<
clean:
rm -f nand.dis nand.bin nand_elf *.o
其中 objs 是代表的一个变量,表示obj文件,也可以是objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,后面就可以使用$(objs)来使用这个变量了。
$@ 目标文件
$^ 所有的依赖文件
$< 第一个依赖文件
例如:
arm-linux-ld -Tnand.lds -o nand_elf $^
<<—— 等价于 ——>>
arm-linux-ld -o nand_elf head.o init.o nand.o main.o
%.o:%.c 表示所有的.o文件,依赖于对应的.c文件
%.o:%.S 表示所有的.o文件,依赖于对应的.S文件
2.9 objs的用法
obj-y = main.o
main-objs := a.o b.o c.o
//将a.c b.c c.c三个文件编译后链接生成main.o