通常一个大型程序由多个模块文件构成的,按照其功能划分,模块文件会分布在不同的目录中,模块文件之间有包含有头文件,调用函数的情况,他们之间存在依赖关系。
大多数情况下,我们只是修改了其中某些文件,而不是所有文件,按理说,我们只需要把那些修改过的文件,并且依赖于这些文件的相关文件重新编译即可,用不着重新编译全部文件。
所以问题来了,有没有办法自动对那些有改过的文件重新编译呢?这实际上要分为 2 个问题:
- 目标文件依赖哪些文件?
- 依赖的文件是否更新?
为了解决这2个问题,业界就有了 make 和 makefile。总的来说,它俩并不是用来编译程序,它们只负责找出哪些文件变化,并且根据依赖关系找出受影响的文件,然后执行事先在 makefile 中定义好的命令规则。只是该规则大多数情况是调用 gcc 或 nasm 进行编译。因此这也就完成了我们上面提到的 2 个问题。
makefile 基本语法
目标文件:依赖文件
[tab] 命令
eg:
a.o: a.c
gcc -c a.c -o a.o
a.out: a.o
gcc a.o -o a.out
- 目标文件是指此规则中想要生成的文件,可以是 .o 结尾的目标文件,也可以是可执行文件,也可以是伪目标。
- 依赖文件是指要生成此规则中的目标文件。通常不止一个,是一个依赖文件的列表。
- 命令是指此规则中要执行的动作,各种 shell命令,一个命令单独占用一行,行首必须以 Tab 开头。
根据以上规则,我们已经知道了目标文件依赖哪些文件,但还需要解决第2个问题。怎么判断依赖的文件是否更新?
在 Linux 中,每个文件有3种时间:
- atime
access time 访问文件数据部分时间 - ctime
change time 文件属性部分或数据部分的改变时间 - mtime
modify time 文件数据部分的修改时间
1:2
@echo "makefile test ok"
这个makefile文件的意义就是 " 如果文件2的 mtime 比文件1的 mtime 要新,则打印 "makefile test ok" "。
综上,依赖关系定义在文件 makefile 中,make 程序通过解析 makefile 文件,根据mtime标签自动找出 变更的文件 以及 依赖此变更文件的目标文件,然后对所有受影响的相关文件执行事先定义好的命令规则。
makefile 的文件名并不固定,可以用 -f 参数指定。默认是先寻找名为 GNUmakefile 的文件,若该文件不存在再去找名为 makefile 的文件,若 makefile 也不存在,最后去找名为 Makefile 的文件。
t1:1
@echo "target1"
t2:1
@echo "target2"
若采用make 目标名称
,比如make t2
,则会单独执行目标名称处的规则。
若采用make
,则会单独执行在 makefile 中第一个出现的目标。
变量
= := ?= 区别
目标变量
伪目标
有时我们有"并不关心是否产生真实的目标文件,我们只希望通过 make 不要考虑 mtime,而是总能去执行一些命令",比如
clean:
rm *.o # 清理文件
这时,make规定,当规则中不存在依赖文件时,这个目标文件名就称为 -- 伪目标。
-
.PHONY
-
约定俗成的伪目标名称
伪目标名称 | 功能描述 |
---|---|
all | 通常用于完成所有模块的编译工作 |
clean | 通常用于清空编译完成的所有目标文件 |
dist | 将打包文件再压缩成gz 文件 |
Install | 通常将编译好的程序复制到安装目录下 |
printf | 通常用于打印已经发生改变的文件 |
tar | 通常用于将文件打包成tar文件 |
test | 测试makefile 流程 |
make: 递归式推导目标
test1.o : test1.c
gcc -c test1.c -o test1.o
test2.o : test2.c
gcc -c test2.c -o test2.o
test.bin : test1.o test2.o
gcc test1.o test2.o -o test.bin
all : test.bin
@echo "compile done"
当执行make all
时,make 首先发现 all 依赖于 test.bin,寻找 test.bin 不存在,于是便去寻找 test.bin的依赖文件 test1.o test2.o,同样不存在,于是寻找能生成 test1.o 和 test2.o 规则。找到后执行相应规则,有了test1.o 和 test2.o 再返回生成 test.bin,最后在执行生成 all 的规则。
自定义变量与系统变量
makefile中定义变量基本格式:
变量名=值 # 多个值之间用空格分开,值仅仅支持字符串类型。即使数字也会当作字符串处理
eg:
objfiles = test1.o test2.o
test.bin: $(objfiles)
除了自定义的变量,make也定义了很多系统变量
变量名 | 描述 |
---|---|
AS | 汇编语言编译器,默认是 as |
CC | C语言编译器,默认是 gcc |
CXX | C++语言编译器,默认 g++ |
RM | 删除命令,默认是 rm -f |
ASFLAGS | 汇编语言编译器参数,无默认值 |
CFLAGS | C语言编译器参数,无默认值 |
CXXFLAGS | C++编译器参数,无默认值 |
LDFLAGS | 链接器参数,无默认值 |
隐含规则
对于一些使用频率非常高的规则,make 把它们当成是默认的,不需要显式地写出来,当用户未在 makefile 中显示定义规则时,将默认使用隐含规则进行推导。
隐含规则只限于那些编译过程中基本固定的依赖关系,比如C语言代码文件扩展名为.c,编译生成的目标文件扩展名是.o。并且若想通过隐含规则自动推导生成目标,存在于文件系统上的文件,除扩展名之外的文件名部分必须相同。
自动化变量
为了方便,make 还支持一种自动化变量:
自动化变量 | 描述 |
---|---|
$@ | 表示规则中目标文件名集合 |
$< | 表示规则中依赖文件中的第1个文件 |
$^ | 表示规则中所有依赖文件的集合 |
$? | 表示规则中所有比目标文件mtime更新的依赖文件的集合 |
test1.o : test1.c
gcc -c test1.c -o test1.o
test2.o : test2.c
gcc -c test2.c -o test2.o
objfiles = test1.o test2.o
test.bin : $(objfiles)
gcc $^ -o $@
all : test.bin
@echo "compile done"
再方便的就是利用正则表达式匹配,%.o 表示多有以 .o 结尾的文件,make 会拿这个字符串模式去文件系统上查找文件,默认当前路径。
%.o : %.c # test1.c test2.c 都在当前目录。
gcc -c $^ -o $@
objfiles = test1.o test2.o
test.bin : $(objfiles)
gcc $^ -o $@
all : test.bin
@echo "compile done"
makefile 中执行shell命令
SVN_VERSION:=$(shell svnversion)
COMP_DATE:=$(shell date)
makefile中常用函数
- foreach
- wildcard
- notdir
去除目录信息,只保留文件名。 - patsubst
替换。
example
- 项目文件
a.h a.cpp b.h b.cpp c.h Makefile
- Makefile
输出:SRC_PATH=./ SRC_FILE=$(foreach SUB_DIR, $(SRC_PATH), $(wildcard $(SUB_DIR)*.cpp)) ALL_FILE=$(notdir $(SRC_FILE)) # patsubst把$(ALL_FILE)中的变量符合后缀是.cpp的全部替换成.o OBJ_FILE=$(patsubst %.cpp, %.o, $(ALL_FILE)) all: $(info $(SRC_FILE)) $(info $(ALL_FILE)) $(info $(OBJ_FILE))
./b.cpp ./a.cpp b.cpp a.cpp b.o a.o
一个简单的Makefile模板
- 项目
--Makefile --h --lib --src
- Makefile
VERSION = 1.0.0 # 程序版本号 SOURCE = $(wildcard ./src/*.c) # 获取所有的.c文件 OBJ = $(patsubst %.c, %.o, $(SOURCE)) # 将.c文件转为.o文件 INCLUDES = -I./h # 头文件路径 LIBS = -ldylib # 库文件名字 LIB_PATH = -L./lib # 库文件地址 DEBUG = -D_MACRO # 宏定义 CFLAGS = -Wall -c # 编译标志位 TARGET = app CC = gcc $(TARGET): $(OBJ) @mkdir -p output/ $(CC) $(OBJ) $(LIB_PATH) $(LIBS) -o output/$(TARGET).$(VERSION) %.o: %.c $(CC) $(INCLUDES) $(DEBUG) $(CFLAGS) $< -o $@ .PHONY: clean clean: rm -rf $(OBJ) output/
网友评论