目录
——这篇文章主要是记录常用的
1、Makefile 格式
目标 : 依赖文件....
命令
注意:每条命令必须以 TAB 键开始,不能使用空格!
举例1:
main : main.o 1.o 2.o
gcc main.o 1.o 2.o -o main
目标是 main, main.o、 1.o 和 2.o 是生成 main 的依赖文件
那么这只是将.o文件链接,如果还没生成呢?下面继续看例子2:(#为注释符)
main: main.o 1.o 2.o #第一条规则
gcc main.o 1.o 2.o -o main
main.o: main.c #第二条规则
gcc -c main.c
1.o: 1.c #第三条规则
gcc -c 1.c
2.o: 2.c # 第四条规则
gcc -c 2.cclean: #第五条规则
rm *.o
rm main
make执行步骤如下:
第一条规则的目标成为默认目标,在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、 1.o 和 2.o 这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。
make 会查找以这三个.o 文件为目标的规则并执行,发现更新 main.o 的是第二条规则,因此会执行第二条规则,规则的命令就是不链接编译 main.c,生成 main.o,接着同理,发现第三条和第四条规则后也陆续生成了1.o和2.o文件
最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以这条规则的命令不会执行,当使用命令“make clean”的时候,才会执行,会删除当前目录下所有的.o 文件以及 main,完成工程的清理。
2、变量
继续用前面例子1
main : main.o 1.o 2.o
gcc main.o 1.o 2.o -o main
三个依赖文件输入了两遍,这种重复输入的工作就会非常费时间,且会出现输错的风险
为了解决这个问题,Makefile加入了变量支持,变量都是字符串,类似 C 语言中的宏
直接上例子
objects = main.o 1.o 2.o #定义了一个变量 objects,并赋值为字符串“main.o 1.o 2.o"
main: $(objects)
gcc -o main $(objects)
Makefile 中变量的引用方法是“$(变量名)”,$(objects)等于 main.o 1.o 2.o ,这样方便多了
3、赋值符
在Makefile中有4种不同意义的赋值符,下面一一分析一下
①“ = ”
在上面例子中也有出现,下面讨论一下细节
#假如在makefile中有下面这几句,请问getname是123还是456?
name = 123
getname = $(name)
name = 456
getname是456,赋值符“="可以将变量的真实值推到后面去定义,也就是引用的真实值取决于所引用变量的最后一次赋值
②“ := ”
用上面的例子修改分析
#假如在makefile中有下面这几句,请问getname是123还是456?
name = 123
getname := $(name)
name = 456
getname是123,“:=”不会使用后面定义的变量,只能使用前面已经定义好的
③“?=”
getname ?= 789
如果变量 getname 前面没有被赋值,那么此变量就是“789”;如果前面已经赋过值了,那么就使用前面赋的值。
④“+=”
#变量追加
objects = main.o 1.o
objects += 2.o
变量 objects 的值为“main.o 1.o”,后面我们给他追加了一个“2.o”,因此变量 objects 变成了“main.o 1.o 2.o”
4、模式规则“%”
上面写的都是一般规则的例子,模式规则中,至少在规则的目标定定义中要包涵“%”
%.o : %.c
命令
”%.o”就是所有的以.o 结尾的文件;“%.c”就是所有的以.c 结尾的文件,类似与通配符,比如a.%.c 就表示以 a.开头,以.c 结束的所有文件
这样我们就可以修改优化上面的例子2了,这里称为例子3
objects = main.o 1.o 2.o #定义变量
main: $(objects) #引用变量
gcc $(objects) -o main
%.o: %.c #模式规则
#要执行的命令,这里先不写,下面再优化clean:
rm *.o
rm main
5、自动化变量
在 Makefile 中自动化变量是一种特殊的变量,它们具有特殊的含义,可以在 Makefile 的编写中使用它们来简化代码的编写和提高代码的可读性。以下是一些常用的自动化变量及其含义:
①$^:表示所有的依赖文件,使用空格分开
假设有以下 Makefile 规则如下:
```
object_files: file1.c file2.c file3.c
gcc $^ -o object_files
```
$^ 表示所有的依赖文件 file1.c、file2.c 和 file3.c,make命令将把这些文件编译并链接为一个输出文件 object_files
②$@:表示规则中的目标文件
假设有以下 Makefile 规则如下:
```
hello: main.c 1.c
gcc $^ -o $@
```
在执行make命令时,当hello目标需要被构建时,它会执行上述规则,即编译main.c和1.c,并将它们链接起来生成可执行文件hello。$@就代表了目标文件hello,因此可以用$@来代表目标文件的名字,而不必手动输入它,这提高了makefile的可读性和灵活性
在这就可以把两个自动化变量合起来用,修改第一个例子如下
(例1)假设有以下 Makefile 规则如下:
```
object_files: file1.c file2.c file3.c
gcc $^ -o $@
```
这样就方便了一些,不用再输入长的字符串
③$<:表示依赖文件中的第一个文件名
如果依赖文件是以模式(即“%” )定义的,那么“$<”就是符合模式的一系列的文件集合
假设有以下 Makefile 规则如下:
```
main.o: main.c header.h
gcc -c $<
```
`$<` 表示依赖文件中的第一个文件名 `main.c`,所以最后一句等价于:gcc -c main.c
下面继续优化例子3
main: main.o 1.o 2.o
gcc $^ -o $@ #自动化变量
%.o: %.c #模式规则
gcc -c $< #自动化变量clean:
rm *.o
rm main
这里将三个 C 语言源文件 main.c
、1.c
和 2.c
编译成可执行文件,并将输出文件名设置为 main,
规则使用"$^"表示所有依赖项,"-o $@” 表示输出文件名
main目标文件依赖.o文件,没有.o文件就会找到“%.o: %.c”这条规则,全部.o文件依赖.c文件,通过该规则下的命令来编译出.o文件,这样就可以输出main目标文件了
6、伪目标
伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出
现名字冲突,比如我们清理功能
clean:
rm *.o
rm main
输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。可是如果在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,为了避免这个问题,我们可以将 clean 声明为伪目标:
.PHONY : clean
clean:
rm *.o
rm main
就加一行声明就可以了,声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行
综合实战
假如文件工程目录如下
利用makefile编译出一个bin文件,编写的makefile如下,详细解释都在包含在内容里面
#自定义变量
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= bsp
#默认的编译器名称变量
CC := $(CROSS_COMPILE)gcc
#默认的链接器名称变量
LD := $(CROSS_COMPILE)ld
#ELF文件转换为二进制bin文件
OBJCOPY := $(CROSS_COMPILE)objcopy
#反汇编器和调试器
OBJDUMP := $(CROSS_COMPILE)objdump
#常用的默认头文件变量,指定编译器在编译过程中需要搜索的头文件目录列表,每个目录之间用空格分隔
INCDIRS := imx6ull \
bsp/clk \
bsp/led \
bsp/delay
#常用的默认源文件变量,指定编译器在编译过程中需要搜索的源文件目录列表,每个目录之间用空格分隔
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay
#自定义变量
#patsubst 字符串替换函数:$( patsubst 原模式, 目标模式, 文件列表)
#从文件列表中查找出符合原模式文件类型的文件,然后一一替换成目标模式,“%”,表示任意长度的字符串
#也就是把全部路径抽出来加上-I
##-I 是编译器的一个选项,用于告诉编译器在编译过程中需要搜索头文件的目录
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
#自定义变量
#dir:dir是一个内置变量,默认情况下make会将工作目录设置为当前目录
#$(dir)/*.S:代表在当前执行make命令的工作目录下所有以.S为后缀名的文件
#wildcard函数:$(wildcard 指定文件类型),用于查找指定目录下指定类型的文件,函数参数:目录+文件类型
#$(wildcard $(dir)/*.S):查找当前执行make命令的工作目录下所有以.S为后缀名的文件
#foreach循环函数:$(foreach var,list,text):把参数<list>中的单词逐一取出来放到参数<var>中,
# 然后再执行<text>所包含的表达式,
# 每次<text>都会返回一个字符串,循环的过程中,
# <text>中所包含的每个字符串会以空格隔开,
# 最后当整个循环结束时, <text>所返回的每个字符串组成 foreach 函数的返回值
#也就是(当前工作目录下,每一个目录路径,查找这个路径下.S文件),获取到.S文件存在的路径
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
#同理,这次获取保存工程中的是所有的.c文件保存的路径
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
#自定义变量
#notdir提取函数:$(notdir names ...),从names中提取出文件名非目录部分,就是把路径去掉就保留文件名
#把上面获取到.S文件的路径去掉
SFILENDIR := $(notdir $(SFILES))
#同理,把上面获取到.c文件的路径去掉
CFILENDIR := $(notdir $(CFILES))
#自定义变量
#patsubst 字符串替换函数:$( patsubst 原模式, 目标模式, 文件列表)
#从文件列表中查找出符合原模式文件类型的文件,然后一一替换成目标模式,“%”,表示任意长度的字符串
#$(SFILENDIR:.S=.o):进行变量替换,会将这些.S文件编译成对应的.o目标文件(注意替换不是修改文件后缀的意思,可以理解为编译)
#obj/%:把.o文件的都添加obj路径,SOBJS就是这些文件的文件路径
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
#同理,把.c文件的都替换成.c文件,添加obj路径
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
#自定义变量
#一个包含所有编译目标文件的列表集合,把全部.o文件都并成一个总的编译目标文件列表
OBJS := $(SOBJS) $(COBJS)
#VPATH是一个特殊的Makefile变量,代表了Makefile的搜索路径
#将所有的源代码路径添加到Makefile的搜索路径中,以便在编译时能够正确地找到需要的头文件和源文件
#指定make在进行依赖文件搜索时,需要搜索的路径列表
VPATH := $(SRCDIRS)
#单独一个冒号是一个规则的开始,表示目标文件和依赖关系的声明
#$(TARGET).bin 是目标文件,$(OBJS) 是依赖文件
#这个规则告诉make如何生成目标文件$(TARGET).bin
#告诉make当$(OBJS)中任何一个文件被修改时,重新生成$(TARGET).bin
$(TARGET).bin : $(OBJS)
#目的:将所有输入文件编译成一个名为 .elf 的目标文件
#"-T"通常是一个gcc的命令行选项,用于指定链接器使用的链接脚本文件
#"-Timx6ull.lds"表示将链接器脚本文件指定为"Timx6ull.lds"
#$^:表示所有的依赖文件
$(LD) -Timx6ull.lds -o $(TARGET).elf $^
#目的:将 elf 文件转换为二进制文件,并去除elf 文件中的代码段和数据段等信息删除,从而生成一个更小的二进制可执行文件
#-O binary:将目标文件转换为纯二进制文件格式,即可执行bin文件,意味着去掉所有目标文件的元数据和头部信息,只保留代码和数据部分
#-S:去掉所有的符号表和调试信息
#$(TARGET).elf:指定了要操作的目标文件
#$@:表示规则中的目标文件,即$(TARGET).bin
$(OBJCOPY) $(TARGET).elf -O binary -S $@
#将生成的 ELF 文件反汇编成可读的汇编代码并输出到一个.dis文本文件中
#-D: 参数表示要进行反汇编
#-m arm: 参数表示使用 ARM 体系结构进行反汇编
#$(TARGET).elf:指定了要操作的目标文件
#>:重定向到$(TARGET).dis文件中
#$(TARGET).dis:输出的汇编代码的文件名
$(OBJDUMP) $(TARGET).elf -D -m arm > $(TARGET).dis
#将所有以.S结尾的汇编文件编译成.o文件,放在obj目录下
$(SOBJS) : obj/%.o : %.S
#CC:指定的编译器进行编译
#$<:表示依赖文件中的第一个文件名, 即 %.S
#$(INCLUDE):指定头文件路径的变量
#Wall:开启编译器的所有警告提示
#nostdlib:不使用标准库,可以减小可执行文件的大小
#O2:幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢
#-c:编译源文件为目标文件,而不进行链接操作
#-o: 表示指定输出文件名
#$@:表示规则中的目标文件,即 obj/%.o
$(CC) $< $(INCLUDE) -Wall -nostdlib -O2 -c -o $@
#同理,将所有以.c结尾的源文件编译成.o文件,放在obj目录下
$(COBJS) : obj/%.o : %.c
$(CC) $< $(INCLUDE) -Wall -nostdlib -O2 -c -o $@
.PHONY:clean
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
执行结果,已经生成了bin文件和反汇编文件
执行清理,就把编译生成的全部文件删除了