Makefile基础
一、Makefile是什么
(1)目标文件依赖哪些文件?
(2)依赖的文件是否更新?
make 和makefile 并不是用来编译程序的,它只负责找出哪些文件有变化,并且根据依赖关系找出受影响的文件,然后执行事先在makefile 中定义好的命令规则。
因为make 就是在shell 下执行的,所以在makefile 中,位于命令规则里的那些命令,都是shell 命令。
二、Makefile基本语法
目标文件:依赖文件
[Tab键]命令
(1)目标文件是指此规则中想要生成的文件,可以是.o 结尾的目标文件,也可以是可执行文件,也可以是个伪目标,后面会介绍伪目标。
(2)依赖文件是指要生成此规则中的目标文件,需要哪些文件。通常依赖文件不是1 个,所以此处是个依赖文件的列表。
(3)命令是指此规则中要执行的动作,这些动作是指各种shell 命令。命令可以有多个,但一个命令要单独占用一行,在行首必须以Tab 开头。这是make 规定的用法,这样make 在解析到以Tab 开头的行时便知道这是要执行的命令
2.1 make 程序是怎样判断文件有过更新呢?
Linux 中,文件分为属性和数据两部分,每个文件有三种时间,分别用于记录与文件属性和文件数据相关的时间,这三个时间分别是atime、ctime、mtime。
时间 | 含义 | |
---|---|---|
atime | access time | 访问文件的时间,是读取就会改变,比如cat/less等命令访问文件就会更新atime,但ls查看文件不会更新atime |
ctime | change time | 文件属性或数据修改时,就会被更新 |
mtime | modify time | 文件数据部分修改时,会被更新。如果mtime被更新,则ctime肯定也被更新了 |
make就是判断依赖文件和目标文件的mtime是否一致,如果依赖文件的mtime比目标文件的mtime新,则需要执行命令(命令不一定是编译命令,可以是任何shell命令)。比如如下这个makefile例子:
1:2
@echo "2 is newer than 1"
然后新建两个文件1和2
# 首先touch 两个文件1和2,可以看到起mtime是一致的
[root@localhost test]# touch 1 2
[root@localhost test]# stat -c %y 1 2 #mtime一致
2021-03-31 23:11:32.926225333 -0400
2021-03-31 23:11:32.926225333 -0400
[root@localhost test]# make
make: '1' is up to date. #make命令并没有做什么
[root@localhost test]# vi 2 #修改文件2的内容
[root@localhost test]# stat -c %y 1 2 #2的mtime要比1新了
2021-03-31 23:11:32.926225333 -0400
2021-03-31 23:11:56.588623644 -0400
[root@localhost test]# make #make执行了echo命令
2 is newer than 1
如果目标文件1的mtime新于依赖文件2会什么结果?
[root@localhost test]# vi 1 #修改目标文件1的内容
[root@localhost test]# stat -c %y 1 2 #目标文件1的mtime新于依赖文件2
2021-03-31 23:13:56.341639459 -0400
2021-03-31 23:11:56.588623644 -0400
[root@localhost test]# make
make: '1' is up to date.
2.2 跳到目标处执行
如下makefile例子,当执行make时,执行完第一个目标后就退出了。
[root@localhost test]# cat Makefile
t1:1
@echo "make t1"
t2:1
@echo "make t2"
[root@localhost test]# make #执行完第一个目标就退出了
make t1
[root@localhost test]# make t2 #指定目标t2
make t2
[root@localhost test]# make t1 #指定目标t1
make t1
2.3 伪目标
通过上面的例子您看到了,规则中的命令并不总是被执行,有时候我们并不关心是否产生真实的目标文件,我们只希望make 不要考虑mtime,而是总能去执行一些命令。
对于这个需求还是有办法的,make 规定,当规则中不存在依赖文件时,这个目标文件名就称为—伪目标。
[root@localhost test]# cat Makefile
test:
@echo "test ok"
[root@localhost test]# make
test ok
伪目标不能和真实目标文件同名,否则就失去伪目标的意义了,为了避免伪目标和真实目标文件同名的情况,可以用关键字“.PHONY”来修饰伪目标,格式为“.PHONY:伪目标名”,这样不管与伪目标同名的文件是否存在,make 照样执行伪目标处的命令。
通常需要显式用.PHONY 修饰伪目标的场合是删除编译过程中的.o 文件,这是为了避免因旧的.o 文件已存在而影响编译。如果您在Linux 下有过编译源码的经验,就会了解make clean 的作用了,通常clean就是伪目标,用来删除编译过程中的.o 文件。
怪不得clean和all同时存在也可以,因为执行完all就退出了,再执行make clean就只是clean清除
2.4 变量
makefile可以自定义变量,其也要一些系统定义好的变量。
[root@localhost test]# cat Makefile
all:
@echo AR : $(AR)
@echo AS : $(AS)
@echo CC : $(CC)
@echo CXX : $(CXX)
@echo CPP : $(AR)
@echo FC : $(FC)
@echo GET : $(GET)
@echo PC : $(PC)
@echo MAKEINFO : $(MAKEINFO)
@echo RM : $(RM)
@echo TEX : $(TEX)
@echo WEAVE : $(WEAVE)
@echo YACC : $(YACC)
@echo YACCR : $(YACCR)
@echo ARFLAGS : $(ARFLGAS)
@echo ASFLAGS : $(ASFLAGS)
@echo CFLAGS : $(CFLAGS)
@echo CXXFLAGS : $(CXXFLAGS)
@echo CPPFLAGS : $(CPPFLAGS)
@echo FFLAGS : $(FFLAGS)
@echo LDFLAGS : $(LDFLAGS)
@echo PFLAGS : $(PFLAGS)
@echo YFLAGS : $(YFLAGS)
[root@localhost test]# make
AR : ar
AS : as
CC : cc
CXX : g++
CPP : ar
FC : f77
GET : get
PC : pc
MAKEINFO : makeinfo
RM : rm -f
TEX : tex
WEAVE : weave
YACC : yacc
YACCR :
ARFLAGS :
ASFLAGS :
CFLAGS :
CXXFLAGS :
CPPFLAGS :
FFLAGS :
LDFLAGS :
PFLAGS :
YFLAGS :
命令相关的变量名 | 含义 | 默认 |
---|---|---|
AR | 打包程序 | ar |
AS | 汇编语言编译器 | as |
CC | C语言编译器 | cc |
CXX | C++语言编译器 | g++ |
CPP | C预处理器,默认是$(CC) -E | gcc -E |
FC | Fortan的编译器和预处理器 | f77 |
GET | 从SCCS文件中提取文件程序 | get |
PC | Pascal语言编译器 | pc |
MAKEINFO | 将texinfo文件转换为info文件 | makeinfo |
RM | 删除命令 | rm -f |
TEX | 从TeX源文件中创建TexDVI文件的程序 | tex |
WEAVE | 将Web转换为TeX的程序 | weave |
YACC | 处理C程序的Yacc词法分析器 | yacc |
YACCR | 处理Ratfor程序的Yacc词法分析器 | yacc -r |
参数相关的系统变量(列举部分),几乎都没有默认值
变量名 | 含义 | 默认 |
---|---|---|
ARFLAGS | 打包程序$(AR)的参数 | rv |
ASFLAGS | 汇编语言编译器参数 | |
CFLAGS | C语言编译器参数 | |
CXXFLAGS | C++编译器参数 | |
CPPFLAGS | C预处理器参数 | |
FFLAGS | Fortan语言编译器参数 | |
LDFLAGS | 链接器参数 | |
PFLAGS | Pascal语言编译器参数 | |
YFLAGS | Yacc词法分析器参数 |
2.5 隐含规则
一行写不下,用 \
进行换行。
注释,用#
进行注释。
什么是隐含规则?对于一些使用频率非常高的规则,make 把它们当成是默认的,不需要显式地写出来,当用户未在makefile 中显式定义规则时,将默认使用隐含规则进行推导。
隐含规则只限于那些编译过程中基本固定的依赖关系,比如C 语言代码文件扩展名为.c,编译生成的目标文件扩展名是.o,这一般是一对一的。而一个可执行程序可能是由多个.o 文件共同链接生成的,所以,从可执行程序到.o 文件的关系有可能是一对多,这种不确定性无法使之成为隐含的规则。所以,对于C语言的依赖关系是:文件名.o 依赖于文件名.c,仅限于源文件生成.o 目标文件,不存在.o 文件生成可执行程序的隐含规则。
2.6 自动化变量
变量名 | 含义 |
---|---|
$@ | 规则中的目标文件名集合 |
$< | 规则中依赖文件中的第一个文件 |
$^ | 规则中所有依赖文件的集合 |
$? | 规则中,所有比目标文件mtime更新的依赖文件集合 |
2.7 将每个.c文件生成对应的二进制
# File paths
SRC_DIR := .
BUILD_DIR := .
OBJ_DIR := $(BUILD_DIR)
# Compilation flags
CC := gcc
LD := gcc
CFLAGS := -Wall
# Files to be compiled
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
BUILD := $(OBJS:$(OBJ_DIR)/%.o=$(BUILD_DIR)/%)
# Don't remove *.o files automatically
.SECONDARY: $(OBJS)
all: $(BUILD)
# Compile each *.c file as *.o files
# @mkdir -p $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
@echo + CC $<
@$(CC) $(CFLAGS) -c -o $@ $<
# Link each *.o file as executable files
# @mkdir -p $(BUILD_DIR)
$(BUILD_DIR)/%: $(OBJ_DIR)/%.o
@echo + LD $@
@$(LD) $(CFLAGS) -o $@ $<
.PHONY: all clean
clean:
rm -rvf $(OBJS) $(BUILD)
参考:《操作系统真相还原》一书,网络博客