Makefile
meke命令一般用于编译程序,而make命令都依赖于 Makefile 文件。
最简单的Makefile如下:
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
注:缩进使用 tab 键 , 因为Makefile 会为每个以 tab 键开头的命令创建一个 shell 去执行。
具体规则参考 《GUN Make 使用手册》
以下为粗略笔记
Makefile规则与示例
为什么需要Makefile
提高编译效率。
Makefile样式
一个简单的Makefile文件包含一系列"规则":
目标...:依赖...
<tab>命令
- 命令被执行的两个简单条件:
- 目标文件还没有生成
- 依赖文件比目标文件新
先介绍Makefile的两个函数
- $(foreach var,list,text)
for each var in list,change it to text。
对list中的每一个元素,取出赋值给var,然后把var改为text所描述的形式。
例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
- $(wildcard pattern)
把存在的 pattern 文件列出来
例子:
src_files := $(wildcard *.c)
完善Makefile
- 先来一个简单粗暴、效率低的:
test: main.c hello.c hello.h
gcc -o test main.c hello.c
- 再来一个Makefile,效率高、精炼,支持自动检测头文件
objs := main.o hello.o
test : $(objs)
gcc -o test $^
# 需要判断是否存在依赖文件
# .main.o.d .hello.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
# 把依赖文件包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
rm *.o test -f
distclean:
rm $(dep_files) *.o test -f
了解自动化变量。
通用Makefile的使用
参考linux内核的Makefile来编写一个通用的Makefile,特点:
- 支持多个目录、多层目录、多个文件;
- 支持给所有文件设置编译选项;
- 支持给目录设置编译选项;
- 支持给某个文件单独设置编译选项;
- 简单、好用。
通用的Makefile解释
零星知识点
make命令的使用
执行make命令时,它会去当前目录下查找名为Makefile的文件,并根据它的只是去执行操作,生成第一个目标。
也可以用 -f 选项指定文件,如:
make -f Makefile.build
指定文件的名字随意定义。
可以使用 -C 指定目录,切换到其他目录去,如:
make -C a/ abc.def
可以指定目标,不再默认生成第一个目录,如:
make -C a/ abc.def other_target
即时变量与临时变量
变量定义语法:
形式 | 说明 |
---|---|
A = xxx | 延时变量 |
B ?= xxx | 延时变量,只有第一次定义时赋值才成功,若曾被定义过,则此赋值无效。 |
C := xxx | 立即变量 |
D += yyy | 如果D在前面是延时变量,那么现在它还是延时变量 如果D在前面是立即变量,那么它现在还是立即变量 |
延时变量:
使用时才确定该值。
如:
A = $@
tets:
@echo $A
输出 A 的值为 test。
即时变量:
定义时立即确定该值。
如:
A := $@
tets:
@echo $A
输出 A 的值为 空。
变量的导出(export)
export 是供给子目录的 Makefile 使用的(即 sub-make),同一级的makefile是访问不到的。
可以通过makefile中的内置变量MAKELEVEL可以查看当前的makefile的level。
Makefile中的shell
Makefile中可以使用shell命令,如:
TOPDIR := $(shell_pwd)
Makefile中放置第一个命令
执行目标时,如果不指定目标,则默认执行第一个目标。
所以,第一个目标的位置很重要。有时候不太方便把第一个目标完整地放在文件的前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖和命令。比如:
First_target: // 这句话放在前面
........ // 其他代码,比如include其它文件得到后的 xxx 变量
First_target : $(xxx) $(xxx)
command
假想目标
Makefile中可能会有这样的目标:
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
注:如果当前目录下更好有个名为“clean”的文件,那么执行“make clean”时就不会执行makefile中目标clean的内容,所以要把clean设置为假想目标即可:
.PHONY : clean
小知识
PHONY 是 phoney,即伪造的意思。PHONY后面的target是伪造的target,不是真实存在的文件。同时,注意make命令中后面的target默认是文件。
常用函数
- $(foreach var, list, text)
for each var in list,change it to text.
直接上例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // dep_files 的最终结果为“**.a.o.d .b.o.d**”
- $(wildcard pattern)
列出 pattern 存在的文件。
例子:
src_files = $(wildcard *.c) // src_files的值为当前目录下所有 .c 文件。
- $(filter pattern..., text)
把text中符合pattern格式的内容留下来。
obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y)) //结果为:c/ d/
- $(filter-out pattern..., text)
把text中符合pattern格式的内容删除掉。
obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y)) // 结果为:a.o b.o
- $(patsubst pattern, replacement, text)
寻找text中符合pattern格式的内容,用replacement代替他们。
subdir-y := a.o b.o c/ d/
subdir := $(patsubst %/, %, $(obj-y)) // 结果为:c d
*通用Makefile设计思想
- 在Makefile文件中确定要编译的文件、目录,比如:
obj-y += main.o
obj-y += a/
Makefile 文件总是被 Makefile.build 包含的。
- 在 Makefile.build 中设置编译规则,有 3 条编译规则:
- 怎么编译子目录?
- 进入子目录编译即可:
- 怎么编译子目录?
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
2. 如何编译当前目录中的目标文件?
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
3. 当前目录下的 **.o** 和子目录下的 **built-in.o** 要打包起来:
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
思想总结:
- Makefile文件确定要编译的文件和目录
- Makefile.build文件包含规则
- 每个需要编译的子目录都放个Makefile文件,把需要编译的文件编译成built-in.o
- 再把所有子目录的built-in.o链接到顶层的built-in.o
- 最后把顶层built-in.o链接到APP
最好重新分析一下通用Makefile
一个通用Makefile
本程序的Makefile分为3类:
- 顶层目录的Makefile
- 顶层目录的Makefile.build
- 各级子目录的Makefile
源码(注释版)
Makefile
CROSS_COMPILE = # 交叉编译工具头,如:arm-linux-gnueabihf-
AS = $(CROSS_COMPILE)as # 把汇编文件生成目标文件
LD = $(CROSS_COMPILE)ld # 链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者一个可执行文件
CC = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar # 打包器,用于库操作,可以通过该工具从一个库中删除或则增加目标代码模块
NM = $(CROSS_COMPILE)nm # 查看静态库文件中的符号表
STRIP = $(CROSS_COMPILE)strip # 以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码
OBJCOPY = $(CROSS_COMPILE)objcopy # 复制一个目标文件的内容到另一个文件中,可用于不同源文件之间的格式转换
OBJDUMP = $(CROSS_COMPILE)objdump # 查看静态库或则动态库的签名方法
# 共享到sub-Makefile
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
# -Wall : 允许发出 GCC 提供的所有有用的报警信息
# -O2 : “-On”优化等级
# -g : 在可执行程序中包含标准调试信息
# -I : 指定头文件路径(可多个)
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
# LDFLAGS是告诉链接器从哪里寻找库文件,这在本Makefile是链接最后应用程序时的链接选项。
LDFLAGS :=
# 共享到sub-Makefile
export CFLAGS LDFLAGS
# 顶层路径
TOPDIR := $(shell pwd)
export TOPDIR
# 最终目标
TARGET := test
# 本次整个编译需要源 文件 和 目录
# 这里的“obj-y”是自己定义的一个格式,和“STRIP”这些一样,*但是 一般内核会搜集 ”obj-”的变量*
obj-y += main.o # 需要把当前目录下的 main.c 编进程序里
obj-y += sub.o # 需要把当前目录下的 sub.c 编进程序里
obj-y += subdir/ # 需要进入 subdir 这个子目录去寻找文件来编进程序里,具体是哪些文件,由 subdir 目录下的 Makefile 决定。
#obj-y += $(patsubst %.c,%.o,$(shell ls *.c))
# 第一个目标
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built !
# 处理第一个依赖,**转到 Makefile.build 执行**
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
# 处理最终目标,把前期处理得出的 built-in.o 用上
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
# 清理
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
# 彻底清理
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
Makefile.build
注意,include 命令,相对路径为 执行本 Makefile.build 的路径
# 伪目标
PHONY := __build
__build:
# 清空需要的变量
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
# 包含同级目录Makefile
# 这里要注意,相对路径为 执行本 Makefile.build 的路径
include Makefile
# 获取当前 Makefile 需要编译的子目录的目录名
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# 把子目录的目标定为以下注释
# built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 获取当前目录需要编进程序的文件名作为,并写为目标
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
# 使修改头文件 .h 后,重新make后可以重新编译(重要)
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 列出存在的文件
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
# 第一个目标
__build : $(subdir-y) built-in.o
# 优先编译 子目录的内容
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
# 链接成 目标
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
# 生成 cur_objs 目标
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
使用说明
- 本程序的Makefile分为3类:
- 顶层目录的 Makefile
- 顶层目录的 Makefile.build
- 各级子目录的 Makefile
1. 各级子目录的Makefile
- 参考:
# 可以不添加下面两个变量
EXTRA_CFLAGS :=
CFLAGS_test.o :=
obj-y += test.o
obj-y += subdir/
- obj-y += file.o 表示把当前目录下的file.c编进程序里,
- obj-y += subdir/ 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
- EXTRA_CFLAGS, 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
- CFLAGS_xxx.o, 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
注意
- "subdir/"中的斜杠"/"不可省略
- 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
- CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项
2. 顶层目录的Makefile
主要作用:
- 整个工程的参数初期定义
- 架构
- 工具链
- 编译工具
- 编译参数
- 需要导出的变量
- 等等
- 定义最终目标
- 跳转到 Makefile.build
3. 顶层目录的Makefile.build **
注意:该文件虽然放在顶层,但是也是提供给各级子目录使用的。
主要功能:
- 把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,把各个subdir/built-in.o和当前目录的目标 .o 合并打包为当前目录的built-in.o 。
使用提示
- 执行"make"来编译,执行 make clean 来清除,执行 make distclean 来彻底清除。
Makefile附件
一些符号
符号 | 说明 |
---|---|
$@ | 表示规则中的目标文件集 |
$% | 当目标为函数库的时候,则表示规则中的目标成员名。反之为空。如一个目标为"foo.a(bar.o)",那么,"$%"就是"bar.o",以空格分隔开。 |
$< | 依赖文件集合中的第一个文件,如果依赖文件以"%"形式出现,则表示符合模式的一系列文件集合 |
$? | 所有比目标新的依赖集合,以空格分隔开。 |
$^ | 所有依赖文件集合,以空格分隔开。如果依赖有相同,则取其一。 |
$+ | 和 "$^"类同,但是不会把相同的删除掉。 |
$* | 这个变量表示目标模式中 "%"及其之前的部分,如果目标是 test/a.test.c,目标模式为 a.%.c, 那么 "$* " 就是 test/a.test。 |
参考
- 以上笔记为学习和整理韦东山老师资料而来的