Makefile 文件
makefile 是个文件,这个文件中描述了程序的编译规则.
采用 Makefile 的好处
- 简化编译程序的时候输入得命令,编译的时候只需要敲 make 命令就可以了
- 可以节省编译时间,提高编译效率
make命令
是什么
代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。
make 作为最常用的构建工具,是个 命令 即 可执行程序,用来解析 Makefile 文件的命令
- 规定要构建哪个文件、
- 它依赖哪些源文件,
- 当那些文件有变动时,如何重新构建它。
- …
命令格式
# 一般使用的时候直接在makefile文件的目录下 make就可以
# 默认的找名为GNUmakefile、makefile、Makefile的文件
# 一般在makefile文件目录下,就直接使用make命令
make
# 完整命令
make [ ‐f file ] [ targets ] [变量=值]
输入文件
-
[ -f file ]
: 指定给定名字以外的文件作为makefile输入文件-
一般是不填的,在makefile文件目录下,直接使用make命令
默认找
GNUmakefile
、makefile
、Makefile
作为 makefile输入文件 -
,也可以指定以上名字以外的文件作为makefile输入文件
-
使用
-f
指定
-
目标文件
[ targets ]
: 指定了make工具要实现的目标- 没有指定目标时,
- 默认会实现makefile文件内的第一个目标
- 然后退出,
- 也可以指定了make工具要实现的目标,
- 目标可以是一个或多个(多个目标间用空格隔开)
- 没有指定目标时,
传参
-
传参
- make 工具传给 makefile 的变量 执行 make 命令时,
- make 的参数 options 也可以给 makefile 设置变量。
make cc=arm-linux-gcc # 传参cc=arm-linux-gcc,交叉编译工具
示例
# 目标: main # ./main 执行
# 依赖文件: main.o sub.o sum.o
main:main.o sub.o sum.o
gcc main.o sub.o sum.o ‐o main
main.o:main.c
gcc ‐c main.c ‐o main.o
sub.o:sub.c
gcc ‐c sub.c ‐o sub.o
# 假想目标: clean # make clean # 发动清理
clean:
rm *.o main a.out ‐rf # 清除中间文件
makefile 结构
使用TAB而不是空格
目标:依赖文件列表 # 完成链接
命令列表(第一行) # 预处理、编译、汇编,生成二进制文件
命令列表(第二行)
...
clean: # 假想目标
rm main *.o
简单的例子:
# 目标: main # ./main 执行
# 依赖文件: main.c main.h
main:main.c main.h
# 依赖文件1: main.c
# 依赖文件2: main.h
# 目标文件: main
gcc main.c main.h -o main
# 假想目标: clean
# make clean 发动清理
clean:
rm main *.o # 清除中间文件
规则
Makefile文件由一系列规则(rules)构成。每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建,形式如下:
<target> : <prerequisites>
[tab] <commands>
-
完成链接
<target> : <prerequisites>
$(target):$(prerequisites)
-
目标(target)必选:
通常是要产生的文件名称,目标可以是可执行文件或其它 obj 文件,也可是一个动作的名称
-
依赖文件/前置条件(prerequisites):
用来输入从而产生目标的文件一个目标通常有几个依赖文件(可以没有)
-
-
生成二进制文件
所有的 .c到.o文件的转化 (完成 预处理、编译、汇编的工作)
.o:%*.c
-
tab键起首([tab])
-
命令(commands):
-
make 执行的动作,一个规则可以含几个命令(可以没有)
-
有多个命令时,每个命令占一行
-
-
命令合集
命令包 define
命令包有点像是个函数, 将连续的相同的命令合成一条, 减少 Makefile
中的代码量, 便于以后维护。
语法:
define <command-name>
command
...
endef
# Makefile 内容
define run_demo_makefile
@echo -n "Hello"
@echo " Makefile!"
@echo "这里可以执行多条 Shell 命令!"
echo "参数1 $(1)" >> $(2)
endef
all:
$(call run_demo_makefile)
define generate-common-build-props-with-product-vars-set
BUILD_FINGERPRINT="$(BUILD_FINGERPRINT_FROM_FILE)" \
BUILD_ID="$(BUILD_ID)" \
BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
DATE="$(DATE_FROM_FILE)" \
PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
PLATFORM_VERSION="$(PLATFORM_VERSION)" \
TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
endef
$ make
Hello Makefile!
这里可以执行多条 Shell 命令!
假想目标
默认执行第一个假想目标。
假想目标(可选)
隐式声明
伪目标可以这样来理解,伪目标的存在可以帮助我们找到命令并执行。
-
它并不会创建目标文件,假想目标并不是一个真正的文件名,
-
通常是一个目标集合或者动作(可以没有依赖或者命令)
只是想去执行这个目标下面的命令。
clean: # clean 就是假想目标
rm *.o main a.out ‐rf
一般需要显示的使用make + 名字 显示调用
make clean // 发动清理
当工作目录下不存在以
clean
命令的文件时,在shell
中输入make clean
命令,命令
rm *.o main a.out ‐rf
会被执行(执行假想对象clean的命令)
显式声明 .PHONY
而且当一个目标被声明为伪目标之后,make
在执行此规则时不会去试图去查找隐含的关系去创建它。
-
提高了
make
的执行效率, -
不用担心目标和文件名重名而使编译失败。
.PHONY:clean
clean:
rm -rf *.o test
.PHONY
后面跟的目标都被称为伪目标,也就是说 make 命令后面跟的参数,
- 如果 make 命令后的参数出现在伪目标中
- 直接在Makefile中就执行伪目标的依赖和命令。
- 不管Makefile同级目录下是否有该伪目标同名的文件,即使有也不会产生冲突。
- 可以提高执行makefile时的效率。
同名文件
均存在的情况下,优先执行 伪目标。否则有啥执行啥。
比如 make objectX
同级目录下
-
有同名文件
objectX
,Makefile
中-
没有伪目标。执行
objectX
文件中的内容 -
有伪目标。执行伪目标中的内容
.PHONY: objectX objectX: $(modules_to_install) \ $(INSTALLED_ANDROID_INFO_TXT_TARGET)
-
-
没有同名文件
objectX
,执行
Makefile
中的伪目标
符号和变量
常见符号
$ @ * % -
注释
井号(#)在 Makefile 中表示注释
# 这是注释
result.txt: source.txt
# 这是注释
cp source.txt result.txt # 这也是注释
执行命令 @
@
通常用在“规则”行,在命令的前面加上@。
表示不显示命令本身只显示它的结果,可以关闭回声。
回声:@echo
正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)
test:
@echo TODO
CMD_MKOBJDIR=if [ -d ${DIR_OBJ} ]; then exit 0; else mkdir ${DIR_OBJ}; fi
@${CMD_MKOBJDIR}
通配符 * ? []
通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 []:
通配符 | 含义 |
---|---|
* | 0个或者是任意个字符 |
? | 任意一个字符 |
[] | 指定匹配的字符放在 “[]” 中 |
比较常用的就是 * 号
.PHONY:clean
clean:
rm -f *.o
模式匹配 %
Make 命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是 %。比如,假定当前目录下有 f1.c 和 f2.c 两个源码文件,需要将它们编译为对应的对象文件。
%.o: %.c
等同于下面的写法。
f1.o: f1.c
f2.o: f2.c
使用匹配符 %,可以将大量同类型的文件,只用一条规则就完成构建。
忽略错误-
忽略掉错误,继续执行,
通常删除,创建文件如果碰到文件不存在或者已经创建,会选择忽略并继续执行
-rm dir
-mkdir aaadir
使用 -include
-
忽略由于包含文件不存在或者无法创建时的错误提示
-
-
的意思是告诉make,忽略此操作的错误。make继续执行
使用 include
- 不加
-
,当文件出错或者不存在的时候, make 会报错并退出。
-include $(TARGET_DEVICE_DIR)/AndroidBoard.mk
inherit-product
函数:表示继承另外一个文件
- 使用 inherit-product包含其它文件。同样的变量会被追加而不是覆盖。
- 并确保不会两次包含同一个 makefile 。
例如:
在 A.mk 中 PRODUCT_VAR := a
,在 B.mk 中PRODUCT_VAR := b
。在 A.mk 中:
include B.mk
,得到PRODUCT_VAR := b
。inherit-product B.mk
,得到PRODUCT_VAR := a b
。
变量
概念
makefile 变量类似于 C 语言中的宏,当 makefile 被 make 工具解析时,其中的变量会被展开。
变量的作用:
保存 - 文件名列表 文件目录列表 编译器名 编译参数编译的输出…
分类:
- 自定义变量
- 系统环境变量 setenv设置的
- 预定义变量(自动变量)
$@ $^ $< $? $%
变量引用 $ $$
$
:扩展打开makefile中定义的变量
$ VAR
$(VAR)
${VAR}
$$
:扩展打开makefile中定义的shell变量
$$ VAR
$$(VAR)
$${VAR}
引号 ""
''
若变量中本身就包含了空格,则整个字符串都要用双引号或单引号括起来。
双引号内的特殊字符可以保有变量特性,但是单引号内的特殊字符则仅为一般字符
# 输出的可能是 str = abcde...等等
echo "str = $str"
#输出一定是 str = $str
echo 'str = $str'
变量赋值 =
=
递归展开
=
赋值,赋予整个makefile中最后被指定的值。
使用 =
来定义的变量是递归展开的 (Recursively Expanded),直到该变量被使用时等号右边的内容才会被展开。而且每次使用该变量时,等号右边的内容都会被重新展开。
- 好处:可以把变量的真实值推到后面来定义。
- 缺点:递归定义可能导致出现无限循环展开,尽管 make 能检测出这样的无限循环展开并报错。
VIR_A = A
VIR_B = $(VIR_A) # B的值是之后的AA
VIR_A = AA
:=
简单展开
直接赋值,赋予当前位置的值
简单展开 (Simply Expanded)。读到变量定义这一行时 等号右边立即被展开,引用的所有变量也会被立即展开。前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
VIR_A := A
VIR_B := $(VIR_A) # B的值是此时的A
VIR_A := AA
使用这种方法可以在变量中引入开头空格。见下面的示例:
nullstring :=
space := $(nullstring) # end of the line
nullstring 是一个 Empty 变量,其中什么也没有,而 space 的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用。先用一个 Empty 变量来标明变量的值开始了,而后面采用 #
注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。
?=
条件变量赋值
如果该变量没有被赋值,则赋予等号后的值。如果先前被定义过,那么将什么也不做。
?= 是递归展开的。会使用最后的值。见上文 =
递归展开
VIR ?= old_value # VIR在之前没有被赋值, 设置为old_value
VIR ?= new_value # VIR在之前被赋值, 保留old_value
+=
追加变量值
- 字符串拼接
- 将等号后面的值添加到前面的变量上
自定义变量
在 makefile 文件中定义的变量。 make 工具传给 makefile 的变量。
一般都在 makefile 的头部定义
可以以数字开头,大小写敏感
定义 清除
# 自定义变量语法
变量名=变量值 # 不可以有空格
# 清除变量
## 使用 unset 命令清除变量
unset varname
CC=gcc
target=main
obj1=sub
obj2=sum
OBJ=main.o sub.o sum.o
显示 只读 读入
# 显示变量
## 使用 echo 命令可以显示单个变量取值
echo $num
echo "num = $num"
# 只读变量
## 使用readonly创建只读变量
readonly n=999
# 读取数据
## 使用read从终端读取数据保存在变量中
read str
# 可以通过 `$1 $2... ${10}...`获取函数参数
系统环境变量(与shell联系)
make 工具解析 makefile 前,读取系统环境变量并设置为 makefile 的变量。
在此之前,shell已经介绍了系统环境变量。
见:shell
- 变量
- 变量分类
- 系统环境变量
传统上,所有环境变量均为大写.
预定义变量
makefile中有许多预定义变量,这些变量具有特殊的含义,可在makefile中直接使用。
$@ $^ $< $? $%
Make 命令还提供一些自动变量,它们的值与当前规则有关。
自动变量 | 含义 |
---|---|
$@ | 目标文件(target)——就是 Make 命令当前构建的那个目标。比如,make foo 的 $@ 就指代 foo 。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么其值为空。 |
$^ | 所有的依赖文件(components) |
$< | 第一个依赖文件(components中最左边的那个) |
$? | 比目标还要新的依赖文件列表。以空格分隔。 |
$% | 仅当目标是函数库文件中,表示规则中的目标成员名。 |
$* | 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1 |
例如,$@
- 如果一个目标是"foo.a(bar.o)“,那么,” %"就是"bar.o"," @“就是"foo.a”。
- 如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
# * 0 ? $
入参 函数
预设变量 | 含义 |
---|---|
$# | 传给shell脚本参数的数量 |
$0 | 当前执行的进程名 |
$* | 位置变量$0 - $9 保存从终端输入的每一个参数 传给shell脚本参数的内容 $1、$2、$3 … |
${10} | 编号大于9使用{},运行脚本时传递给其的参数,用空格隔开 |
$? | 命令执行后返回的状态 |
$? | 获取函数返回值用于检查上一个命令执行是否正确. (在Linux中,命令退出状态为0表示该命令正确执行,任何非0值表示命令出错)。但是$?获取到返回值如果超过255,会出错 |
$$ | 当前进程的进程号 最常见的用途是用作临时文件的名字以保证临时文件不会重复 |
@ < ^
编译有关
预设变量 | 含义 |
---|---|
$@ | 目标名,目标文件 |
$< | 依赖文件列表中的第一个文件 |
$^ | 依赖文件列表中除去重复文件的部分(所有的依赖文件) |
CC | C编译器的名称,默认值为cc |
CFLAGS | C编译器的选项 |
CPP | C预编译器的名称,默认值为$(CC) -E |
CPPFLAGS | C预编译的选项 |
CXX | C++编译器的名称,默认值为g++ |
CXXFLAGS | C++编译器的选项 |
AR | 归档维护程序的程序名,默认值为ar |
ARFLAGS | 归档维护程序的选项 |
AS | 汇编程序的名称,默认值为as |
ASFLAGS | 汇编程序的选项 |
示例
精简版:
CC=gcc
obj=main
obj1=sub
obj2=sum
OBJ=main.o sub.o sum.o
CFLAGS=-Wall -g
$(obj):$(OBJ)
$(CC) $^ -o $@
$(obj).o:$(obj).c
$(CC) $(CFLAGS) -c $< -o $@
$(obj1).o:$(obj1).c
$(CC) $(CFLAGS) -c $< -o $@
$(obj2).o:$(obj2).c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm *.o $(obj) a.out -rf
最精简版
CC=gcc
obj=main
obj1=sub
obj2=sum
OBJ=main.o sub.o sum.o
CFLAGS=-Wall -g
# 完成链接的工作。
$(obj):$(OBJ)
$(CC) $^ -o $@
# 所有的 .c到.o文件的转化 (完成 预处理、编译、汇编的工作)
%*.o:%*.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm *.o $(obj) a.out -rf
.o:%*.c
所有的 .c到.o文件的转化 (完成 预处理、编译、汇编的工作)
$(obj):$(OBJ)
完成链接的工作。
gcc -E hello.c -o hello.i 1、预处理
gcc -S hello.i -o hello.s 2、编译
gcc -c hello.s -o hello.o 3、汇编
gcc hello.o –o hello 4、链接
gcc *.c 编译所有.c文件
gcc *.o 编译所有.o文件
判断和循环
Makefile 条件判断作用
条件语句可以根据一个变量的值来控制 make 执行或者时忽略 Makefile 的特定部分,条件语句可以是两个不同的变量或者是常量和变量之间的比较。
Makefile 使用 Bash 语法,完成判断和循环。
条件语句
下面是条件判断中使用到的一些关键字:
关键字 功能
ifeq
判断参数是否不相等,相等为 true,不相等为 false
ifneq
判断参数是否不相等,不相等为 true,相等为 false
ifdef
判断是否有值,有值为 true,没有值为 false
ifndef
判断是否有值,没有值为 true,有值为 false
ifeq ifneq
判断两个参数是否 相等/不相等
使用方式如下
ifeq (first, second)
ifeq 'first' 'second'
ifeq `first` `second`
ifeq `first` 'second'
ifeq 'first' `second`
#判断当前编译器是否 gcc ,然后指定不同的库文件
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
三个关键字 ifeq、else、endif。其中:
-
ifeq
表示条件语句的开始,并指定一个比较条件(相等)。括号和关键字之间要使用空格分隔,两个参数之间要使用逗号分隔。参数中的变量引用在进行变量值比较的时候被展开。ifeq后面的是条件满足的时候执行的,条件不满足忽略; -
else
表示当条件不满足的时候执行的部分,不是所有的条件语句都要执行此部分; -
endif
是判断语句结束标志,Makefile 中条件判断的结束都要有;其实 ifneq 和 ifeq 的使用方法是完全相同的,只不过是满足条件后执行的语句正好相反
使用
first = $(CXX)
second = g++
all:
ifeq ($(first), $(second))
echo `first == second`
else
echo `first != second`
endif
输出
$ make
first == second
ifdef ifndef
主要功能是判断变量是否 已定义(不为空)
当我们需要判断一个变量的值是否为空的时候需要使用
ifeq
而不是ifdef
。
ifdef VARIABLE_NAME
a =
b = $(a)
TestIfdefA:
ifdef a
echo yes
else
echo no
endif
TestIfdefB:
ifdef b
echo yes
else
echo no
endif
$ make TestIfdefA
no
$ make TestIfdefB
yes
循环
# 循环
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
# 等同于
all:
for i in one two three; do \
echo $$i; \
done
常用函数
wildcard
通配符函数
$(wildcard _pattern)
wildcard函数,通配符函数,得到当前工作目录中满足_pattern模式的文件或目录名列表
SRCS = $(wildcard *.c)
all:
echo $(SRCS)
测试结果如下:
# ls
abc.h a.c b.c c.c Makefile
# make
echo a.c b.c c.c
a.c b.c c.c
路径
abspath
$(abspath _names)
将_names中的各路径转换成绝对路径,并将转换后的结果返回。
ROOT := $(abspath /usr/../lib)
all:
echo $(ROOT)
realpath
$(realpath _names)
用于获取_names所对应的真实路径,测试代码如下
ROOT := $(realpath ./..)
all:
@echo $(ROOT)
前缀后缀
addprefix
前缀
$(addprefix _prefix, _names)
给名字列表_names
中的每一个名字增加前缀_prefix,将增加了前缀的名字列表返回。
without_dir = main.c bar.c foo.c
with_dir := $(addprefix src/, $(without_dir))
all:
echo $(with_dir)
addsuffix
后缀
$(addsuffix _suffix, _names)
给名字列表_names
中的每一个名字增加后缀_suffix
,将增加了后缀_suffix
的名字列表返回。
without_suffix = main foo bar
with_suffix := $(addsuffix .c, $(without_suffix))
all:
echo $(with_suffix)
eval
执行
$(eval _text)
使make将再一次解析_text
语句。
例如:执行过滤,并将过滤后的结果赋给 source变量
sources = foo.c bar.c baz.s ugh.h
$(eval sources := $(filter %.c %.s, $(sources)))
all:
echo $(sources)
filter
filter 返回满足
filter: 过滤语句,
$(filter _pattern, _text)
从一个名字列表_text
中根据模式_pattern
得到满足需要的名字列表返回。
-
过滤掉不符合指定的模式的内容,仅保留符合指定的模式的内容。
-
在过滤时,大小写敏感(区分大小写)
SOURCE := 1 2 3 4 5
# 指定的模式为 1 2 3,多个模式之间用空格区分
$(filter 1 2 3 , $(SOURCE))
# 上式返回值为
# a b c
例如:返回后缀为 .c .s的文件
sources = foo.c bar.c baz.s ugh.h
sources := $(filter %.c %.s, $(sources))
all:
echo $(sources)
ifneq + filter 场景:
-
某项目多个版本(A,B, C),同时进行开发。
-
除了代码中的一些宏开关外,在编译时,也需要进行不同版本的判断。
-
版本A、B,编译某模块时需要一个特殊参数。
-
版本C,编译该模块时,不需要该特殊参数。
-
# 如果 TARGET 为A 或 B(即不为空),那么加入某些特殊参数
# 这里ifneq第二个参数为NULL
ifneq ($(filter A B , $(TARGET)),)
# 版本A、B才需要的某些特殊参数
endif
filter-out 不满足
$(filter-out _pattern, _text)
从名字列表_text
中根据模式_pattern
滤除一部分名字,将滤除后的列表返回。
- 返回不满足
objects = main1.o foo.o main2.o bar.o
result = $(filter-out main%.o, $(objects))
all:
@echo $(result)
notdir 获取文件名
$(notdir _names)
从路径_names中抽取文件名,并将文件名返回。(去除路径,获取文件名)
file_name := $(notdir code/foo/src/foo.c code/bar/src/bar.c)
all:
@echo $(file_name)
替换
patsubst
$(patsubst _pattern, _replacement, _text)
将名字列表_text
中符合_pattern
模式的名字替换为_replacement
,将替换后的名字列表返回。
该函数可以用于替换变量后缀
例如:c文件后缀名全部替换成了.o格式
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRCS))
all:
echo $(SRCS)
echo $(OBJS)
测试结果如下所示:
# make
echo a.c b.c c.c
a.c b.c c.c
echo a.o b.o c.o
a.o b.o c.o
=
可以采用变量赋值的高级用法,即在赋值的时候,完成文件名后缀替换工作
mixed = foo.c bar.c main.o
objects = $(mixed:.c=.o)
all:
@echo $(objects)
以上两种替换方法都是可行的
strip 清除空格
strip 去空字符语句,去掉字串中开头和结尾的空字符(空字符包括空格、[Tab]等不可显示字符)。
VAR= 1 2 3
$(strip $(VAR))
# 结果是:
# 1 2 3
代码控制
宏 CFLAGS
控制源码:在Makefile 中添加宏定义可以用来控制源码的编译,
通过CFLAGS中的选项-D定义
CFLAGS += -DMY_DEBUG
#ifdef MY_DEBUG
printf("debug on...\n");
#endif
-include
控制 Makefile 中 的相互导入
使用 -include
-
忽略由于包含文件不存在或者无法创建时的错误提示
-
-
的意思是告诉make,忽略此操作的错误。make继续执行
使用 include
- 不加
-
,当文件出错或者不存在的时候, make 会报错并退出。
-include $(TARGET_DEVICE_DIR)/AndroidBoard.mk
inherit-product
函数:表示继承另外一个文件
- 使用 inherit-product包含其它文件。同样的变量会被追加而不是覆盖。
- 自动确保不会两次包含同一个 makefile 。
# # 例如
PRODUCT_VAR := a # 在 A.mk 中
PRODUCT_VAR := b # 在 B.mk 中
# 在 A.mk 中
inherit-product B.mk
# # 得到 PRODUCT_VAR := a b
include B.mk 或 -include B.mk
# # 得到 PRODUCT_VAR := b