理解学习make规则

Last Updated: 2023-09-04 13:41:25 Monday

-- TOC --

make就像个厨师,按照既定规则,先加工原料,然后将原料加工成目标结果。稍微多几个源文件的软件项目,都离不开make。有一些简化版的make工具,比如cmake,qmake,autoconf等,这些工具的最终结果,就是输出一个Makefile,然后被make使用。

make并不复杂,它的规则是比较多,但常用的就那些,学会之后,做项目看源码倍感轻松。

Basics

基本规则:

# comments
target: prerequisites
<tab>     shell-commands

target是目标,prerequisites是前置条件,也就是原料,shell-commands是达成目标的方法(默认在command前使用tab)。每当命令行出现:

$ make [target]

make首先检查target是否已经存在(在文件目录中是否存在),如果不存在,就必然要执行commands生成。如果target已经存在,make就会检查prerequisites,如果其中有一个前置条件的mtime比target更新,此时就要重新执行commands,重新生成target。如果有部分prerequisites不存在,Makefile中就必须有生成此prerequisite的规则,而make会先生成prerequisite,然后再重新生成target(此时prerequisite的mtime一定更新)。

Makefile

make执行时,默认读取当前目录下的Makefile文件(或小写的makefile,建议保持M大写,Linux内核貌似只能用大写的Makefile),也可以通过-f参数指定规则文件:

$ make -f rules.txt
# Or
$ make --file=rules.txt
# Or
$ make --makefile=rules.txt

如果命令行中没有指定target,默认target为Makefile中的第一个target!

.RECIPEPREFIX

基本上tab符号都会被程序员替换成4个空格,输入tab有时并不容易,此时可以用.RECIPEPREFIX指令来指定一个符号替换tab:

.RECIPEPREFIX = >
target: prerequisites
> shell-commands

.PHONY

通过.PHONY定义伪目标。

所谓伪目标,即不会在目录中存在的target,对于phony target,make不会检查其是否存在,直接进入下一步。

.PHONY: clean
clean:
        rm -f *.o

clean就是个phony target,不管clean是否是一个目录中存在的文件,make不做检查,make clean都会执行rm(无prerequisites)。加入目录中真有一个文件名为clean,也不会影响make这个伪目标。

phony target可以有prerequisites,常见用法如下:

.RECIPEPREFIX = >
.PHONY: all clean

all: abc.txt 123.txt

clean:
> rm -f abc.txt 123.txt

abc.txt: 123.txt
> cat -n 123.txt > abc.txt

123.txt:
> echo 'abc' > 123.txt

make时,默认target是all,all是phony target,此时就成了确保all的两个prerequsites必须存在(target all没有command),而生成它俩的规则在后面。

-jN

加速编译:

$ make -j8 <target>   # 8 jobs
$ make -j  <target>   # infinite jobs

多进程时,可能存在一些错误不能及时被发现,我个人的经验是:此时用make,不带-j,就能重现错误。

%模式匹配

假设目录下有两个.c文件,分别是a.c和b.c,make规则可以写成这样:

# 为每一个.c文件设立一个生成对应.o文件的规则
%.o: %.c

等同于:

a.o: a.c
b.o: b.c

变量

Makefile中可以直接定义变量,这些变量的值就是一段字符串,可以在command中被引用替换,注意command中引用Makefile中定义的变量,和引用shell环境变量,语法上有点不同:

.PHONY: test
var = 4096! 1234.      # define var which value is '4096! 1234.'
test:
        echo $(var)   # var in makefile
        echo $$PATH   # env PATH

给变量赋值,有以下4种情况(=:=?=+=):

# Lazy Set
# Normal setting of a variable, but any other variables mentioned
# with the value field are recursively expanded with their value
# at the point at which the variable is used, not the one it had
# when it was declared
VARIABLE = value

# Immediate Set
# Setting of a variable with simple expansion of the values inside
# - values within it are expanded at declaration time.
VARIABLE := value

# Lazy Set if Absent
# Setting of a variable only if it doesn't have a value.
# Value is always evaluated when VARIABLE is accessed.
VARIABLE ?= value

# Append
# Appending the supplied value to the existing value
# (or setting to that value if the variable didn't exist)
VARIABLE += value

示例:

var := 4096! 1234.
v1 := $(var)
v2 := $(v1)
test:
        echo $(v2)

v2具有与var完全一样的值。

make的规则,处处体现了递归,比如原料也是目标。

内置变量

make提供了一系列的内置变量:

自动变量

$@,当前目标

$(@D),当前目标的目录名

$(@F),当前目标的文件名

$<,第1个前置条件

%.o : %.c
    $(CC) $(CFLAGS) -o $@ $<

$(<D),第1个前置条件的目录名

$(<F),第1个前置条件的文件名

dest/%.txt: src/%.txt
        @[ -d dest ] || mkdir dest
        cp $< $@

$^,所有前置条件

$?,比target更新的所有前置条件,用空格分开

$*,%匹配的部分

.ONESHELL

每一行command,都在一个独立的shell中执行,这些shell之间没有继承关系(都由make创建)。

如果希望多个命令在一个shell进程中执行,有下面两个方法:

.ONESHELL:
test:
        export AA=123
        echo $$AA

@(no echo)

默认情况下,make会打印出它执行的command都有哪些,也可以用@符号抑制这个打印。将此符号放在command前即可。

test:
        @echo 123abc

函数

make提供了很多内置函数,它们需要在$()${}中执行。

shell,执行shell命令

src := $(shell echo *.c)
src := ${shell echo *.c}

wildcard,按shell通配符解释

src := $(wildcard *.c)

subst,替换文本

替换规则:

$(subst from,to,text)

例如:

# 将ee替换成EE
var := $(subst ee,EE,feet on the street)

patsubst,基于pattern的文本替换

规则:

$(patsubst pattern,replacement,text)

子目录中的Makefile

关键是使用make的-C参数:先改变工作路径到指定目录,然后再读取目录中的Makefile文件并开始执行。

$ make -C test_make
make: Entering directory '/home/xinlin/test/test_make'
echo 'abc' > 123.txt
cat -n 123.txt > abc.txt
make: Leaving directory '/home/xinlin/test/test_make'

$ make -C test_make clean
make: Entering directory '/home/xinlin/test/test_make'
rm -f abc.txt 123.txt
make: Leaving directory '/home/xinlin/test/test_make'

Linux内核模块的Makefile

本文链接:https://cs.pynote.net/sf/c/cdm/202307042/

-- EOF --

-- MORE --