这里我们给出一个 Makefile 规则示例:
main.o : main.c gcc -c main.c -o main.o 这个规则表示:main.o 依赖于 main.c,若依赖满足,则执行命令 gcc -c main.c -o main.o。 2.2 使用通配符的规则 Makefile 中使用 % 作为目标中的通配符。 Makefile%.o: %.c gcc -c $< -o $@ 就指定,对以 .o 作为后缀的目标,使用此条规则构建,所要求的依赖为同名的、以 .c 作为后缀名的文件。 规则中的 $< 和 $@ 是 Makefile 中的自动变量,将会在后面的小节中介绍。 通配符可以使我们不用关心具体的文件名,对同一类目标执行相同的构建命令。 同一类目标中的特例? 若要对同一类中的某一个目标执行不同的命令,也可书写一条新的规则,这条规则有着更高的优先级。例如: Makefile%.echo: @echo Hello, foo! bar.echo: @echo Hello, bar! 执行 make foo.echo 和 make bar.echo 的输出是不同的: shell$ make foo.echo Hello, foo! $ make bar.echo Hello, bar! 阅读 Makefile 的技巧 在阅读一个较为复杂的 Makefile 文件时,可以先查看 Makefile 中的目标规则,关注目标规则中的依赖关系和命令。 这样可以更快地理解 Makefile 的内容。 3 Makefile 的变量 3.1 变量的定义 Makefile 中的变量可以用于存放命令、选项、文件名等。变量的定义格式如下: Makefile<var-name> <assignment> <var-value> 各占位符的含义如下: var-name:变量名,可以由字母、数字和下划线组成,但不能以数字开头; assignment:赋值运算符,这将在下一小节介绍; var-value:变量值,可以由任意字符组成,如果变量值中包含了空格,那么需要用引号将变量值括起来。 其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。 3.2 变量的使用 变量使用的格式有两种: Makefile$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个规则表示:main.o 依赖于 main.c,若依赖满足,则执行命令 gcc -c main.c -o main.o。
gcc -c main.c -o main.o
Makefile 中使用 % 作为目标中的通配符。
%
%.o: %.c gcc -c $< -o $@ 就指定,对以 .o 作为后缀的目标,使用此条规则构建,所要求的依赖为同名的、以 .c 作为后缀名的文件。 规则中的 $< 和 $@ 是 Makefile 中的自动变量,将会在后面的小节中介绍。 通配符可以使我们不用关心具体的文件名,对同一类目标执行相同的构建命令。 同一类目标中的特例? 若要对同一类中的某一个目标执行不同的命令,也可书写一条新的规则,这条规则有着更高的优先级。例如: Makefile%.echo: @echo Hello, foo! bar.echo: @echo Hello, bar! 执行 make foo.echo 和 make bar.echo 的输出是不同的: shell$ make foo.echo Hello, foo! $ make bar.echo Hello, bar! 阅读 Makefile 的技巧 在阅读一个较为复杂的 Makefile 文件时,可以先查看 Makefile 中的目标规则,关注目标规则中的依赖关系和命令。 这样可以更快地理解 Makefile 的内容。 3 Makefile 的变量 3.1 变量的定义 Makefile 中的变量可以用于存放命令、选项、文件名等。变量的定义格式如下: Makefile<var-name> <assignment> <var-value> 各占位符的含义如下: var-name:变量名,可以由字母、数字和下划线组成,但不能以数字开头; assignment:赋值运算符,这将在下一小节介绍; var-value:变量值,可以由任意字符组成,如果变量值中包含了空格,那么需要用引号将变量值括起来。 其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。 3.2 变量的使用 变量使用的格式有两种: Makefile$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
就指定,对以 .o 作为后缀的目标,使用此条规则构建,所要求的依赖为同名的、以 .c 作为后缀名的文件。 规则中的 $< 和 $@ 是 Makefile 中的自动变量,将会在后面的小节中介绍。
.o
.c
$<
$@
通配符可以使我们不用关心具体的文件名,对同一类目标执行相同的构建命令。
若要对同一类中的某一个目标执行不同的命令,也可书写一条新的规则,这条规则有着更高的优先级。例如:
%.echo: @echo Hello, foo! bar.echo: @echo Hello, bar! 执行 make foo.echo 和 make bar.echo 的输出是不同的: shell$ make foo.echo Hello, foo! $ make bar.echo Hello, bar! 阅读 Makefile 的技巧 在阅读一个较为复杂的 Makefile 文件时,可以先查看 Makefile 中的目标规则,关注目标规则中的依赖关系和命令。 这样可以更快地理解 Makefile 的内容。 3 Makefile 的变量 3.1 变量的定义 Makefile 中的变量可以用于存放命令、选项、文件名等。变量的定义格式如下: Makefile<var-name> <assignment> <var-value> 各占位符的含义如下: var-name:变量名,可以由字母、数字和下划线组成,但不能以数字开头; assignment:赋值运算符,这将在下一小节介绍; var-value:变量值,可以由任意字符组成,如果变量值中包含了空格,那么需要用引号将变量值括起来。 其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。 3.2 变量的使用 变量使用的格式有两种: Makefile$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
执行 make foo.echo 和 make bar.echo 的输出是不同的:
make foo.echo
make bar.echo
$ make foo.echo Hello, foo! $ make bar.echo Hello, bar! 阅读 Makefile 的技巧 在阅读一个较为复杂的 Makefile 文件时,可以先查看 Makefile 中的目标规则,关注目标规则中的依赖关系和命令。 这样可以更快地理解 Makefile 的内容。 3 Makefile 的变量 3.1 变量的定义 Makefile 中的变量可以用于存放命令、选项、文件名等。变量的定义格式如下: Makefile<var-name> <assignment> <var-value> 各占位符的含义如下: var-name:变量名,可以由字母、数字和下划线组成,但不能以数字开头; assignment:赋值运算符,这将在下一小节介绍; var-value:变量值,可以由任意字符组成,如果变量值中包含了空格,那么需要用引号将变量值括起来。 其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。 3.2 变量的使用 变量使用的格式有两种: Makefile$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
在阅读一个较为复杂的 Makefile 文件时,可以先查看 Makefile 中的目标规则,关注目标规则中的依赖关系和命令。 这样可以更快地理解 Makefile 的内容。
Makefile 中的变量可以用于存放命令、选项、文件名等。变量的定义格式如下:
<var-name> <assignment> <var-value> 各占位符的含义如下: var-name:变量名,可以由字母、数字和下划线组成,但不能以数字开头; assignment:赋值运算符,这将在下一小节介绍; var-value:变量值,可以由任意字符组成,如果变量值中包含了空格,那么需要用引号将变量值括起来。 其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。 3.2 变量的使用 变量使用的格式有两种: Makefile$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
各占位符的含义如下:
其中,变量名与赋值运算符、赋值运算符与变量值之间可以有若干空格。
变量使用的格式有两种:
$(<var-name>) ${<var-name>} 这两种格式的效果完全相同。 3.3 示例 下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的: MakefileCC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这两种格式的效果完全相同。
下面给出一个 Makefile 变量使用的示例,示例中的用法是较为常见的:
CC = gcc CFLAGS = -Wall -g MAIN_OBJS = main.o $(MAIN_OBJS) : main.c $(CC) $(CFLAGS) -c main.c -o $(MAIN_OBJS) 这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。 Makefile 变量的本质 Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量: MakefileSTRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个 Makefile 中定义了两个变量 CC 和 CFLAGS,然后在目标规则中使用了这两个变量。 这样做的好处是,如果我们需要更换编译器或者编译选项,那么只需要修改变量的值即可,而不需要修改目标规则。 在多个目标规则共用同一编译策略的情况下其优势会进一步凸显。
Makefile 中所有的变量本质上都是字符串,即使使用了整型或其它类型字面量对其赋值,其仍然会被当作字符串存储。 例如,使用如下方式定义一个 STRING 变量:
STRING
STRING := "This is a string." 则 STRING 中的值为 "This is a string.",而并非 This is a string.。 小心尾随空格 在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如: MakefileHOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
则 STRING 中的值为 "This is a string.",而并非 This is a string.。
"This is a string."
This is a string.
在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如:
HOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉 这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。 因此,请务必小心变量赋值时的尾随空格。 4 Makefile 变量的赋值 Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。 此外,也可以在命令中指定环境变量和参数变量的值。 4.1 = 赋值运算符 = 赋值运算符是最常用的赋值运算符,它的格式如下: Makefile<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里 HOME 变量会被赋值为 /Users/ubuntu ,$(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。
HOME
/Users/ubuntu
$(HOME)/porject1
/Users/ubuntu /project1
因此,请务必小心变量赋值时的尾随空格。
Makefile 中的赋值运算符有四种,分别是 =, :=, ?= 和 +=。其中,=, := 和 ?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。
=
:=
?=
+=
此外,也可以在命令中指定环境变量和参数变量的值。
= 赋值运算符是最常用的赋值运算符,它的格式如下:
<var-name> = <var-value> 这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如: MakefileCC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如:
CC = gcc CC2 = $(CC) CC = g++ 这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。 4.2 := 赋值运算符 := 赋值运算符的格式如下: Makefile<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++。
CC2
g++
gcc
CC
:= 赋值运算符的格式如下:
<var-name> := <var-value> 这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如: MakefileCC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如:
CC := gcc CC2 := $(CC) CC := g++ CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。 4.3 ?= 赋值运算符 ?= 赋值运算符的格式如下: Makefile<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc。
?= 赋值运算符的格式如下:
<var-name> ?= <var-value> 这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如: MakefileCC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如:
CC ?= gcc CC ?= g++ 这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。 4.4 += 赋值运算符 += 赋值运算符的格式如下: Makefile<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc。
+= 赋值运算符的格式如下:
<var-name> += <var-value> 这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如: MakefileCC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如:
CC = gcc CC += -Wall 则 CC 的值为 gcc -Wall。 4.5 在命令行中对环境变量和参数变量赋值 在命令行中设定环境变量值的格式如下: shell$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
则 CC 的值为 gcc -Wall。
gcc -Wall
在命令行中设定环境变量值的格式如下:
$ <env-var>=<value> make 与之相对地,设定参数变量值的格式如下: shell$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
与之相对地,设定参数变量值的格式如下:
$ make <arg-var>=<value> 例如,对如下的 Makefile: Makefileall: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
例如,对如下的 Makefile:
all: @echo Hello, $(ENV-VAR) and $(ARG_VAR)! 在其所在目录下执行下列命令: shell$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
在其所在目录下执行下列命令:
$ ENV-VAR=foo make ARG-VAR=bar shell Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
Hello, foo and bar! 值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。 环境变量与参数变量的区别 咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。 例如以下的 Makefile: MakefileVAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。
"
咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。
例如以下的 Makefile:
VAR := foo all: @echo Hello, $(VAR)! 使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的: shell$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的:
VAR
$ VAR=bar make Hello, foo! $ make VAR=bar Hello, bar! 总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下: 参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值 通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。 5 Makefile 中的自动变量 为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。 5.1 $@ $@ 表示构建目标,例如: Makefilemain.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下:
参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值
通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。
为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^ 和 $?。
$*
$^
$?
$@ 表示构建目标,例如:
main.o: main.c gcc -c main.c -o $@ 此处 $@ 就表示目标 main.o。 5.2 $* $* 表示构建目标去掉后缀的部分,例如: Makefilebuild/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
此处 $@ 就表示目标 main.o。
main.o
$* 表示构建目标去掉后缀的部分,例如:
build/main.o : main.c gcc -c $* -o $@ 此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。 5.3 $< $< 表示第一个依赖文件,例如: Makefilemain: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main。
build/main.o
build/main
$< 表示第一个依赖文件,例如:
main: main.o func.o gcc $< -o main 此处 $< 就表示第一个依赖文件,即 main.o。 5.4 $^ $^ 表示所有的依赖文件,例如: Makefilemain: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
此处 $< 就表示第一个依赖文件,即 main.o。
$^ 表示所有的依赖文件,例如:
main: main.o func.o gcc $^ -o main 这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。 5.5 $? $? 符号表示比目标文件更新的所有依赖文件,例如: Makefilemain: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $^ 就表示所有的依赖文件,即 main.o 和 func.o。
func.o
$? 符号表示比目标文件更新的所有依赖文件,例如:
main: main.o func.o gcc $? -o main 这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。 6 Makefile的函数 Makefile 中的函数提供了丰富多样的功能,函数的格式如下: Makefile$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.o 或 func.o 比 main 的更新,则会被包含在 $? 中。
main
Makefile 中的函数提供了丰富多样的功能,函数的格式如下:
$(<func-name> <arg1>, <arg2>, ...) 其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数: 6.1 abspath 函数 abspath 函数用于取绝对路径,例如: MakefileBUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数:
abspath
abspath 函数用于取绝对路径,例如:
BUILD_DIR := $(abspath ./build) 这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。 6.2 addprefix 函数 addprefix 函数用于给一系列字符串添加前缀,例如: MakefileOBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。
$(abspath ./build)
./build
addprefix
addprefix 函数用于给一系列字符串添加前缀,例如:
OBJS := main.o func.o BUILD_DIR := ./build BUILD_OBJS := $(addprefix $(BUILD_DIR)/, $(OBJS)) 这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。 6.3 addsuffix 函数 addsuffix 函数用于给一系列字符串添加后缀,例如: MakefileOBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(addprefix $(BUILD_DIR)/, $(OBJS)) 表示给OBJS中的每个字符串都添加前缀 $(BUILD_DIR)/,结果为 ./build/main.o ./build/func.o。
$(addprefix $(BUILD_DIR)/, $(OBJS))
$(BUILD_DIR)/
./build/main.o ./build/func.o
addsuffix
addsuffix 函数用于给一系列字符串添加后缀,例如:
OBJS := main func BUILD_OBJS := $(addsuffix .o, $(OBJS)) 这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。 6.4 basename 函数 basename 函数用于去掉文件名中的后缀名,例如: MakefileOBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o。
$(addsuffix .o, $(OBJS))
main.o func.o
basename
basename 函数用于去掉文件名中的后缀名,例如:
OBJS = ./build/main.o ./build/func.o BASE_OBJS = $(basename $(OBJS)) 这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。 6.5 dir 函数 dir 函数用于获取文件名中的目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func。
$(basename $(OBJS))
OBJS
./build/main ./build/func
dir
dir 函数用于获取文件名中的目录部分,例如:
OBJS = ./build/main.o ./build/func.o DIR_OBJS = $(dir $(OBJS)) 这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。 6.6 notdir 函数 notdir 函数用于获取文件名中的非目录部分,例如: MakefileOBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/。
$(dir $(OBJS))
./build/ ./build/
notdir
notdir 函数用于获取文件名中的非目录部分,例如:
OBJS = ./build/main.o ./build/func.o NOTDIR_OBJS = $(notdir $(OBJS)) 这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。 6.7 shell 函数 shell 函数用于执行 Shell 命令,例如: MakefileCWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o。
$(notdir \$(OBJS))
shell
shell 函数用于执行 Shell 命令,例如:
CWD = $(shell pwd) 这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。 6.8 wildcard 函数 wildcard 函数用于获取符合通配符的所有文件,例如: MakefileCFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。
$(shell pwd)
pwd
wildcard
wildcard 函数用于获取符合通配符的所有文件,例如:
CFILES = $(wildcard *.c) 这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。 7 Makefile 的条件分支 我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如: Makefileifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。
\$(wildcard *.c)
我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如:
ifeq
ifneq
ifdef
ifeq ($(CC), gcc) LIBS = $(LIBS_FOR_GCC) LIBS = $(NORMAL_LIBS) endif 此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。 8 Makefile 的互相包含 Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下: Makefileinclude <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
此处的 ifeq (\$(CC), gcc) 表示如果 CC 的值等于 gcc,那么就执行此分支的语句,否则执行 else 分支的语句。 与 C 语言不同的是,我们需要使用 endif 表示结束 if 语句。
ifeq (\$(CC), gcc)
else
endif
if
Makefile 可以互相包含,这样可以将一些常用的规则写在一个 Makefile 中,然后在其他 Makefile 中包含这个 Makefile,以提升代码的复用性。Makefile 的包含格式如下:
include <file-path> 需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。 MakefileCC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。
CC = gcc include compile.mk 这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。 9 Makefile 的运行 Makefile 可以用 make 命令运行。make 命令的格式如下: shell$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
这样一来,compile.mk 中就可以直接使用此 Makefile 中的 CC 变量了。
Makefile 可以用 make 命令运行。make 命令的格式如下:
$ [<env-vars>] make [<arg-vars>] [<options>] [<target>] 环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。 常用的构建选项有: -f <file-path>:指定 Makefile 文件。如果不指定,则默认使用当前目录下的 Makefile 文件; -C <directory>:指定 Makefile 工作目录。此操作会相当于先将工作目录转移至目标目录,执行 make,再回到当前目录; -s:静默模式,不输出 Makefile 规则中的命令; -n:只输出要执行的命令,但是不执行。 -B:强制执行所有的目标。 -j <nproc>:指定并行执行的任务数。 如何关闭冗长的命令显示? 在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile: Makefilefoo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
环境变量、参数变量、构建选项、目标都是可选的。 当没有目标时,通常会自动构建 Makefile 中的第一个目标。
常用的构建选项有:
-f <file-path>
-C <directory>
make
-s
-n
-B
-j <nproc>
在不加 -s 选项时,构建 Makefile 中的目标时会将执行的命令也输出到命令行,这样会导致输出信息过多。 如果想要禁用此输出,可以在 Makefile 的目标规则的命令前添加一个 @,例如对于如下的 Makefile:
@
foo: echo Hello, world! bar: @echo Hello, world! 执行 make foo 与执行 make bar 会有不同的输出: shell$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
执行 make foo 与执行 make bar 会有不同的输出:
make foo
make bar
$ make foo echo Hello, world! Hello, world! $ make bar Hello, world! 前者输出了所执行的 echo 命令,而后者则没有输出。
前者输出了所执行的 echo 命令,而后者则没有输出。
echo