添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • target :需要构建的目标,可以是一个可执行文件,也可以是一个标签,用来指定执行一系列命令;
  • prerequisites :生成目标所依赖的文件或目标。 如果依赖没有满足,那么会先根据依赖项的构建规则生成依赖项,然后再生成目标;
  • command :是一个 Shell 命令,每条规则可以有多条命令,每条命令占一行。 如果同一行有多条命令,那么需要使用 && 分隔;
  • tab :若干个 tab。 Makefile 规则的命令必须以 tab 字符开始。
  • 这里我们给出一个 Makefile 规则示例:

    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.echomake 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 变量使用的示例,示例中的用法是较为常见的:

    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 变量:

    Makefile
    STRING := "This is a string."
    

    STRING 中的值为 "This is a string.",而并非 This is a string.

    小心尾随空格

    在定义变量和赋值时,Makefile 会裁剪掉赋值运算符后面,变量值前面的空格。 然而,行尾的空格并不会被裁剪掉。例如:

    Makefile
    HOME := /Users/ubuntu # 这里是注释,变量赋值时会忽视掉
    

    这里 HOME 变量会被赋值为 /Users/ubuntu $(HOME)/porject1 则会被替换成 /Users/ubuntu /project1,这显然不是我们希望的结果。

    因此,请务必小心变量赋值时的尾随空格。

    4 Makefile 变量的赋值

    Makefile 中的赋值运算符有四种,分别是 =, :=, ?=+=。其中,=, :=?= 是覆盖变量值的赋值运算符,而 += 则是用于给变量追加值的赋值运算符。

    此外,也可以在命令中指定环境变量和参数变量的值。

    4.1 = 赋值运算符

    = 赋值运算符是最常用的赋值运算符,它的格式如下:

    Makefile
    <var-name> = <var-value>
    

    这个赋值会在 Makefile 全部展开后进行,对同一个变量的多次 = 赋值会使其值为最后一次所赋的值。例如:

    Makefile
    CC = gcc
    CC2 = $(CC)
    CC = g++
    

    这里 CC2 的值为 g++,而不是 gcc。因为 CC2 的赋值是在 Makefile 全部展开后进行的,此时 CC 的值为 g++

    4.2 := 赋值运算符

    := 赋值运算符的格式如下:

    Makefile
    <var-name> := <var-value>
    

    这个赋值和其在 Makefile 中的位置有关,比较符合一般的赋值逻辑。例如:

    Makefile
    CC := gcc
    CC2 := $(CC)
    CC := g++
    

    CC2 的赋值是在 Makefile 展开到当前时进行的,而此时 CC 的值为 gcc,故 CC2 被赋值为 gcc

    4.3 ?= 赋值运算符

    ?= 赋值运算符的格式如下:

    Makefile
    <var-name> ?= <var-value>
    

    这个赋值的特点是,如果变量已经被赋值,那么就不会重新赋值,否则就会赋值。例如:

    Makefile
    CC ?= gcc
    CC ?= g++
    

    这里的第一条赋值语句会覆盖第二条赋值语句,最终 CC 的值为 gcc

    4.4 += 赋值运算符

    += 赋值运算符的格式如下:

    Makefile
    <var-name> += <var-value>
    

    这个赋值会将新的值拼接到旧值的后面,并在中间补空格。例如:

    Makefile
    CC = gcc
    CC += -Wall
    

    CC 的值为 gcc -Wall

    4.5 在命令行中对环境变量和参数变量赋值

    在命令行中设定环境变量值的格式如下:

    shell
    $ <env-var>=<value> make
    

    与之相对地,设定参数变量值的格式如下:

    shell
    $ make <arg-var>=<value>
    

    例如,对如下的 Makefile:

    Makefile
    all:
        @echo Hello, $(ENV-VAR) and $(ARG_VAR)!
    

    在其所在目录下执行下列命令:

    shell
    $ ENV-VAR=foo make ARG-VAR=bar
    
    shell
    Hello, foo and bar!
    

    值得注意的是,在命令行中设定环境变量或参数变量的值时,若所赋的值中包含空格,则需要使用 " 包裹。

    环境变量与参数变量的区别

    咋一看,环境变量和参数变量是完全一样的,只是在执行时处在命令的不同位置。 事实上,它们还是有一定的区别:两者的覆盖性不同。

    例如以下的 Makefile:

    Makefile
    VAR := foo
    all:
        @echo Hello, $(VAR)!
    

    使用环境变量和使用参数变量对 VAR 赋值,输出的结果是不同的:

    shell
    $ VAR=bar make
    Hello, foo!
    $ make VAR=bar
    Hello, bar!
    

    总体来说,可以将各种赋值方式按照覆盖性从高到低的顺序排列如下:

    参数变量赋值 > =/:=/+= 赋值 > 环境变量赋值 > ?= 赋值

    通常的做法是,将需要使用命令行传入的参数使用 ?= 赋值,并在命令行中使用环境变量修改为所需的值。

    5 Makefile 中的自动变量

    为了简便地书写规则,Makefile 提供了一些自动变量吗,常用的有 $@, $*, $<, $^$?

    5.1 $@

    $@ 表示构建目标,例如:

    Makefile
    main.o: main.c
        gcc -c main.c -o $@
    

    此处 $@ 就表示目标 main.o

    5.2 $*

    $* 表示构建目标去掉后缀的部分,例如:

    Makefile
    build/main.o : main.c
        gcc -c $* -o $@
    

    此处 $* 就表示目标 build/main.o 去掉后缀的部分,即 build/main

    5.3 $<

    $< 表示第一个依赖文件,例如:

    Makefile
    main: main.o func.o
        gcc $< -o main
    

    此处 $< 就表示第一个依赖文件,即 main.o

    5.4 $^

    $^ 表示所有的依赖文件,例如:

    Makefile
    main: main.o func.o
        gcc $^ -o main
    

    这里的 $^ 就表示所有的依赖文件,即 main.ofunc.o

    5.5 $?

    $? 符号表示比目标文件更新的所有依赖文件,例如:

    Makefile
    main: main.o func.o
        gcc $? -o main
    

    这里的 $? 就表示比目标文件 main 更新的所有依赖文件。 若 main.ofunc.omain 的更新,则会被包含在 $? 中。

    6 Makefile的函数

    Makefile 中的函数提供了丰富多样的功能,函数的格式如下:

    Makefile
    $(<func-name> <arg1>, <arg2>, ...)
    

    其中,函数名和第一个参数之间用空格分隔,参数之间使用逗号分隔。 下面将介绍一些 Makefile 中常用的函数:

    6.1 abspath 函数

    abspath 函数用于取绝对路径,例如:

    Makefile
    BUILD_DIR := $(abspath ./build)
    

    这里的 $(abspath ./build) 表示取 ./build 的绝对路径。 这个函数的主要用途是避免在不同位置使用同一个 Makefile 时,找不到目录或文件的问题。

    6.2 addprefix 函数

    addprefix 函数用于给一系列字符串添加前缀,例如:

    Makefile
    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 函数用于给一系列字符串添加后缀,例如:

    Makefile
    OBJS := main func
    BUILD_OBJS := $(addsuffix .o, $(OBJS))
    

    这里的 $(addsuffix .o, $(OBJS)) 表示给OBJS中的每个字符串都添加后缀 .o,结果为 main.o func.o

    6.4 basename 函数

    basename 函数用于去掉文件名中的后缀名,例如:

    Makefile
    OBJS = ./build/main.o ./build/func.o
    BASE_OBJS = $(basename $(OBJS))
    

    这里的 $(basename $(OBJS)) 表示去除 OBJS 中每个文件名的后缀名,结果为 ./build/main ./build/func

    6.5 dir 函数

    dir 函数用于获取文件名中的目录部分,例如:

    Makefile
    OBJS = ./build/main.o ./build/func.o
    DIR_OBJS = $(dir $(OBJS))
    

    这里的 $(dir $(OBJS)) 表示获取 OBJS 中每个字符串的目录部分,即 ./build/ ./build/

    6.6 notdir 函数

    notdir 函数用于获取文件名中的非目录部分,例如:

    Makefile
    OBJS = ./build/main.o ./build/func.o
    NOTDIR_OBJS = $(notdir $(OBJS))
    

    这里的 $(notdir \$(OBJS)) 表示获取 OBJS 中每个字符串的非目录部分,即 main.o func.o

    6.7 shell 函数

    shell 函数用于执行 Shell 命令,例如:

    Makefile
    CWD = $(shell pwd)
    

    这里的 $(shell pwd) 表示执行 pwd 命令,其结果为 pwd 命令输出的当前工作目录。

    6.8 wildcard 函数

    wildcard 函数用于获取符合通配符的所有文件,例如:

    Makefile
    CFILES = $(wildcard *.c)
    

    这里的 \$(wildcard *.c) 表示获取当前目录下的所有后缀名为 .c 的文件。

    7 Makefile 的条件分支

    我们可以使用 ifeq, ifneq, ifdef 等关键字控制 Makefile 的条件分支,例如:

    Makefile
    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 的包含格式如下:

    Makefile
    include <file-path>
    

    需要注意的是,include 命令会将被包含的 Makefile 在当前位置完全展开。 可以利用这个性质在 Makefile 使用一些没有定义的变量,而将其定义放在需要包含此 Makefile 的别的 Makefile 中相应的 include 命令前。