make工具及makefile设计
make工具及makefile设计
make工具有何价值?
-
项目源码文件较多时如何编译?
- 项目目录 /main.c f1.c f2.c …. f100.c
- 逐条编译不方便
-
修改部分源码后是否要全部重新编译一遍?
- 假设修改 f1.c,是否可以仅编译 f1.c,然后连接目标文件成可执行文件即可?
-
make
工具是一个自动的辅助编译的程序,相当于一个 Shell,通过解释Makefile 的中的规则进行工作。- Visual C++ 的 nmake
- Linux 下 GNU 的 make
make
是命令行工具, makefile 文件存放一条条规则,make
读取某文件夹下的makefile文件,对该文件夹下相应的源码文件进行处理(编译、连接或者其它处理)
规则:makefile 的基本单位,每一条规则说明一个目标文件(包括所依赖的文件和模块以及生成和更新目标文件的命令)生成的方法。
规则格式:
目标文件 [目标文件属性]: [依赖文件1], [文件2] …
[命令行1;]
[命令行2;]
……
规则的执行:
Makefile 设计示例
例:
目录下只有 hello.c, include, Makefile, max.c, min.c 这几个文件,使用make
编译。
Makefile 的内容:
# hello是目标文件,依赖文件是hello.o,max.o和min.o,规则1
hello:hello.o max.o min.o
arm-linux-gcc -o hello hello.o max.o min.o
# hello.o是目标文件,依赖文件是hello.c,规则2
hello.o:hello.c
arm-linux-gcc -c -o hello.o hello.c -I./include
# max.o是目标文件,依赖文件是max.c,规则3
max.o:max.c
arm-linux-gcc -c -o max.o max.c
# min.o是目标文件,依赖文件是min.c,规则4
min.o:min.c
arm-linux-gcc -c -o min.o min.c
这个示例中,一开始目录下只有 hello.c, include, Makefile, max.c, min.c 这几个文件。
⑤执行make max.o
,会去读取 Makefile,其中【规则3】{max.o是目标文件,依赖文件是max.c},由于目录下存在 max.c,因此会调用规则3的命令arm-linux-gcc -c -o max.o max.c
,生成目标文件 max.o。
此时目录下有hello.c, include, Makefile, max.c, min.c, max.o (多了max.o)。
⑥执行make hello
,读取 Makefile 文件,发现是【规则1】{hello是目标文件,依赖文件是hello.o,max.o和min.o}。由于目录下没有hello.o, min.o,无法执行【规则1】,因此会先去执行【规则2】(生成hello.o)、【规则4】(生成min.o)、最后再执行【规则1】(依赖文件全了)。
执行完以后,目录下有hello.c, include, Makefile, max.c, min.c, max.o, hello.o, min.o, hello(多了min.o, hello.o, hello)。
Makefile 规则–构成
Makefile 规则
- 显式规则
Makefile 的书写者明显指出要生成的目标文件,目标文件的依赖文件,生成的命令。 - 隐晦规则
make 有自动推导的功能,可以简略的书写 Makefile。
Makefile 构成
- 变量的定义
在 Makefile 中我们可以定义一系列的变量,变量一般都是字符串,像C语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。 - 文件指示
- 注释
Makefile 中只有行注释,注释是用#
字符
Makefile 变量(宏)
为了简化编辑和维护 Makefile,make
允许在 Makefile 中创建和使用变量。
所谓的变量就像是C/C++语言中的宏一样,代表文本字串,在 Makefile 中执行的时候会自动原样地展开在所使用的地方,其与C/C++所不同的是可以在 Makefile 中改变其值。在 Makefile 中,变量可以使用在**“目标”,“依赖目标”,“命令”**或是 Makefile 的其它部分中。
变量的定义格式:
- 变量名 赋值符号 变量的值
- 变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有
:
、#
、=
或是空字符(空格、回车等) - 变量名大小写敏感
赋值符号有三种:
=
直接将后面的字符串赋给变量:=
后面跟字符串常量,将它的内容赋给变量+=
变量原来的值+空格+后面的字符串=>新的变量值
变量引用格式:
$(变量名)
${变量名}
- 当变量名为单个字符时,可以省略括号
- make处理变量时会扫描一遍整个 makefile,确定所有变量的值,因此变量的使用可以在定义之后,而且使用的是最后一次赋予的值。
objects = program.o foo.o utils.o #objects为变量名
#一条规则,目标文件是program,依赖文件是program.o, foo.o, utils.o
program : $(objects)
cc -o program $(objects) #规则执行的命令
变量的定义可以出现在三个地方:
- 在 makefile 中定义
- 在
make
命令行中定义 - 使用 shell 环境中的定义
优先级:
(1)make命令行中定义
(2)在makefile中定义
(3)使用shell环境中的定义
(4)预定义的变量
GNU make预定义变量
make有许多预定义的变量,这些变量具有特殊的含义,可在规则中直接使用
常用的预定义变量:
Makefile 条件判断
条件表达式的语法为:
<conditional-directive> #ifeq,ifneq ,ifdef,ifndef
<text-if-true>
endif
其中<conditional-directive>
表示条件关键字,如 ifeq
这个关键字有四个:ifeq
,ifneq
,ifdef
,ifndef
Makefile 中 for 语句
for k in $(DIRS);
do ......
k
依次取变量DIRS
中的每一个值,执行do
后面的语句
Makefile 规则
wildcard 函数(通配符)
wildcard函数
$(wildcard PATTERN)
- 函数功能
获取目录下匹配 PATTERN 的所有对象
返回值:使用空格分割的匹配对象名列表
示例:
目录下有hello.c, include, Makefile, max.c, min.c这些文件。
#匹配目录下的所有.c文件,并返回按空格分隔后的对象名列表
SRCS := $(wildcard *.c)
该指令匹配到了hello.c, max.c, min.c三个文件,赋值给 SRCS
,那么 SRCS
就相当于hello.c max.c min.c
patsubst 函数(模式字符串替换)
$(patsubst pattern, replacement, text)
函数功能:
- 查找
text
字符串中的单词
如果符合模式pattern
,则以replacement
替换 pattern
可以包括通配符%
,表示任意长度的字串- 返回:函数返回被替换过后的字符串。
例:
OBJS := $(patsubst %.c, %.o, $(SRCS))
即查询SRCS
变量对应的字符串中的单词,将所有 .c 文件替换为 .o 文件。
隐含规则
GNU make 支持两种类型的隐含规则:
- 后缀规则(Suffix Rule)
后缀规则是定义隐含规则的老风格方法。后缀规则定义了将一个具有某个后缀的文件(例如.c
文件)转换为具有另外一种后缀的文件(例如.o
文件)的方法。每个后缀规则以两个成对出现的后缀名定义。
例如,将 .c 文件转换为 .o 文件的后缀规则可定义为:
.c.o:
$(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $<
- 模式规则(pattern rules)。
这种规则更加通用,因为可以利用模式规则定义更加复杂的依赖性规则。模式规则看起来非常类似于正则规则,但在目标名称的前面多了一个%
号,同时可用来定义目标和依赖文件之间的关系。
例如,将任意一个.c
文件转换为.o
文件:
%.o:%.c
$(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $<
伪目标
在 makefile 中,目标分为两类:实目标和伪目标
- 实目标
会生成实际的文件。
main.o:mai.c
$(CC) $(CCFLAGS) -c -o $@ $<
- 伪目标
不会生成实际的文件,大多一些辅助性操作,如打印信息、删除文件。
clean:
rm *.o
- 为了避免和文件重名的这种情况,可以使用一个特殊的标记
.PHONY
来显式地指明一个目标是“伪目标”,也可不加。
.PHONY: clean
clean:
rm *.o hello
标准版 Makefile
# 自动匹配目录下的文件列表
SRCS:=(wildcard *.c)
OBJS:=(patsubst %.c,%.o,$(SRCS))
CFLAGS += -I./include -g
all:hello #第一条目标规则
hello:$(OBJS) # 链接规则
arm-linux-gcc -o $@ $(OBJS)
%.o:%.c # C转换目标文件编译规则
arm-linux-gcc -c -o $@ $< $(CFLAGS)
clean:# 清除规则
rm *.o hello
makefile文件的命名
缺省情况下,make
按顺序:GNUmakefile、makefile、Makefile 寻找 Makefile文件。
如果要指定 makefile 文件,通过 makefile -f
或makefile --file
。
例如:makefile -f myMakeFile
指定名为myMakeFile的 makefile文件。
课后作业
(1) 下列文件是可执行程序的为(A、B、D)
A、arm-linux-gcc
B、make
C、Makefile
D、gdb
解:
缺省情况下,make
按顺序:GNUmakefile、makefile、Makefile 寻找 Makefile文件。
(2) 假设文件夹 /opt/exp 下存放C源码文件 f1.c/f2.c/…/f10.c/main.c,其中main.c 是程序主文件,其它文件为函数文件,要求编写 Makefile,要求能使用 make 命令可以生成可执行 exp,运行 make clean 则可以清除该文件夹下目标文件和 exp。
SRCS:=$(wildcard *.c)
OBJS:=$(patsubst %.c,%.o,$(SRCS))
# OBJS:=$(patsubst %.c,%.o,$(WILDCARD)) 也可以
all:exp
exp:$(OBJS)
arm-linux-gcc -o $exp $(OBJS)
# arm-linux-gcc -o $@ $(OBJS)
%.o:%.c
arm-linux-gcc -c $< -o $@ $(CFLAGS)
clean:
rm *.o exp
解析:
C语言生成可执行文件的步骤:
预定义变量$@
:目标的完整名称。
预定义变量$<
:第一个依赖文件的名称。
预定义变量$(CFLAGS)
:表示用于 C 编译器。
由于存放的文件都是.c
文件,因此用wildcard
函数获取所有.c
文件,赋值给SRCS。
SRCS:=$(wildcard *.c)
由于要求能使用make
命令可以生成可执行exp
,则需要.o
文件来链接,此规则的目标文件是exp
,依赖文件是.o
。
使用patsubst
函数将SRCS中所有.c
文件替换为.o
,赋值给 OBJS。
OBJS:=$(patsubst %.c,%.o,$(SRCS))
exp:$(OBJS)
arm-linux-gcc -o exp $(OBJS)
.o文件
需要通过.c文件
编译而来,此规则的目标文件是%.o
,依赖文件是%.c
。
%.o:%.c
arm-linux-gcc -c $< -o $@ $(CFLAGS)
要求运行 make clean 则可以清除该文件夹下目标文件和 exp。
clean:
rm *.o exp