Makefile学习笔记

Makefile是一种工程管理文件,其定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,在C/C++项目中较为常见。

一、Makefile介绍

make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么样去编译和链接程序。

1.1 规则

1
2
3
4
target ... : prerequisites ...
command
...
...
  • target: 可以是一个object file(目标文件)、执行文件或标签。
  • prerequisites:生成该target所依赖的文件和/target
  • comm1and:该target要执行的命令(任意的shell命令)

这表示一个文件的依赖关系,即target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command

1
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。

1.2 make工作

  • make在当前目录寻找名字叫makefileMakefile的文件。
  • 如果找到,首先找第一个目标文件,并将其当作最终的目标文件。
  • 如果第一个目标文件不存在,或者依赖文件的修改时间比该目标文件新,则会执行后面定义的命令来生成该目标文件。
  • 如果所依赖的文件也不存在,那么会在当前文件找目标为依赖文件的依赖性,根据相应规则生成依赖文件。

显然,体现一个层级寻找生成的过程。其中一环出现错误,make会不工作,并爆出相关异常。

1.3 Makefile中使用变量

声明一个变量

1
2
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

objects为变量名,采用$(objects)进行引用

1.4 Makefile自动推导

make可以自动推导文件以及文件依赖关系后面的命令,故无需在每一个.o文件上都写上类似的命令。

只要make看到一个.o文件,它就会自动把.c文件加到依赖关系中,比如如果make找到一个whatever.o,那么whatever.c就会是whatever.o的依赖文件。并且cc -c whatever.c也会被推导出来。

在Makefile文件中使用.PHONY表示依赖是伪目标文件

1.5 Makefile的主要内容

  • 显示规则: 要生成的文件、文件的依赖文件、生成命令。
  • 隐晦规则。
  • 变量的定义
  • 文件指示。
    • 引用另外一个Makefile文件
      • 采用include <filename>进行引入,filename可以是当前操作系统Shell的文件模式,即可以包含路径和通配符。include前面可以有一些空字符,但是不能够从Tab键开始。
    • 根据某些情况制定Makefile的有效部分。
    • 定义一个多行命令
  • 注释:注释采用#

Makefile中的命令需要以Tab键开始

二、书写规则

2.1 在规则中使用通配符

支持~?*通配符。

  • ~:一般表示当前用户的用户目录。
  • *:一般表示匹配所有的意思。
  • ?:一般表示单个字符

想要在变量中展开通配符的写法

objects := $(wildcard *.o)

2.2 文件搜寻

采用特殊变量VPATH

1
VPATH = src:../headers

目录由冒号分隔。

也可以采用make中的vpath命令。

2.3 伪目标

“伪目标”不是一个文件,而是一个标签。不能够与文件名重名

可以采用.PHONY进行显式地指明一个目标是“伪目标”。

2.4 静态模式

1
2
3
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
  • targets:定义了一系列的目标文件,即目标的集合
  • target-pattern:指明了targets的模式,即目标集的模式。
  • prereq-patterns:是目标的依赖模式,对target-pattern形成的模式再进行一次依赖目标的定义。

即 若<target-pattern>定义成%.o,表示我们的target都是以.o结尾的。而如果prereq-pattern定义成%.c意思是对target-pattern所形成的目标集进行二次定义,其计算方式是取target-pattern模式中的%,并为其加上.c这个结尾,形成新的集合。

该方式能够更方便的生成多目标文件

2.5 自动生成依赖性

现有的C/C++编译器都支持一个”-M”的选项,能够自动找寻源文件包含的头文件,并生成一个依赖关系

GNU组织建议通过把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个name.c的文件都生成一个name.d的Makefile文件,.d文件中就存放对应.c文件的依赖关系。

三、书写命令

每条规则中的命令和操作系统Shell的命令行是一致的,make会一按顺序一条条的执行命令,每条命令的开头必须以Tab键开头,除非是跟在依赖规则后面的分号后。

3.1 显示命令

@字符在命令行前,该命令不会被make显示出来,即会执行,不会输出相关命令。

3.2 命令执行

当依赖目标新于目标是时,make会一条一条执行气候的命令,若想要上一条命令的结果应用在下一条命令时,需采用分号分隔这两条命令

要想忽略命令的出错,可以采用-(在Tab键之后)

3.3 嵌套执行make

假设有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。总控的Makefile可以这样书写:

1
2
subsystem:
cd subdir && $(MAKE)

其等价于:

1
2
subsystem:
$(MAKE) -C subdir

传递变量到下级Makefile

export <variable ...>;

不想某些变量传递到下级

unexport <variable ...>;

SHELLMAKEFLAGS这两个不管是否export总是需要传递到下层的Makefile中去的。

MAKEFLAGS变量,其中包含了 make的参数信息。若执行总控Makefile时有make参数或是在上层Makefile中定义了这个变量,那么该变量将会是这些参数,并会传递到下层Makefile中

3.4 定义命令包

定义这种命令序列的语法以define开始,以endef结束。

注意:不要与Makefile中的变量重名

四、使用变量

变量可以在“目标”、“依赖目标”、“命令”或是Makefile的其他部分中。

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 :#= 或是空字符(空格、回车等);

4.1 变量相关注意点以及用法

  • 声明时需要赋初值。使用时需要在变量名前使用$,并推荐采用小括号(){}把变量包括起来。

  • 为了避免变量的递归调用,可以采用:=,该方式表示前面的变量不能够使用后面的变量。

  • 如果想要定义一个变量其值为空格,可以采用如下方式:

    1
    2
    nullstring :=
    space :=$(nullstring) #end of the line
  • ?=操作符表示变量如果没有被定义,则定义,否则什么也不做。

    1
    2
    FOO ?= bar # 表示如果FOO没有被定义过,则定义其值为bar,否则什么也不做。

4.2 变量的高级用法

  • 变量替换

    • 格式$(var:a=b)${var:a=b},表示把变量”var”中所有以”a”字串结尾替换成”b”字串。
  • 把变量的值再当做变量。

    1
    2
    3
    x=y
    y=z
    a := $($(x)) # $(a)= z
  • 可以使用多个变量来组成一个变量的名字,然后取值。

    1
    2
    3
    4
    5
    #示例
    first_second = Hello
    a = first
    b = second
    all = $($a_$b)

4.3 追加变量值

采用+=操作符相当于给变量追加值。如果变量没有被定义过,则会自动变成=(这个不用担心会发生递归),如果之前有定义,则会继承于前次操作的赋值符。

4.4 多行变量

采用defineendef进行定义,define后面跟的是变量的名字,而重起一行定义变量的值。注意:命令需要以TAB键起头

4.5 环境变量

如果Makefile中定义了与环境变量相同的变量,那么系统的环境变量的值会被覆盖。

4.6 目标变量

在Makefile中定义的变量都是“全局变量”,”自动化变量”如$<属于规则型变量,这种变量的值依赖于规则的目标和依赖目标的定义。

也可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

语法:

1
2
3
<target ...> : <variable-assignment>;

<target ...> : overide <variable-assignment>

<variable-assignment>可以是前面讲过的各种赋值表达式,如 =:=+= 或是 ?= 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

五、使用条件

ifeqelseendif

  • ifeq 的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。
  • else 表示条件表达式为假的情况。
  • endif 表示一个条件语句的结束,任何一个条件表达式都应该以 endif 结束。

语法

1
2
3
4
5
6
7
8
9
10
11
12

<conditional-directive>
<text-if-true>
endif

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif


四个关键字

  • ifeq: 比较参数 arg1arg2 的值是否相同。当然,参数中我们还可以使用make的函数。

    1
    2
    3
    4
    5
    ifeq (<arg1>, <arg2>)
    ifeq '<arg1>' '<arg2>'
    ifeq "<arg1>" "<arg2>"
    ifeq "<arg1>" '<arg2>'
    ifeq '<arg1>' "<arg2>"
  • ifneq: 其比较参数 arg1arg2 的值是否相同,如果不同,则为真。和 ifeq 类似。

  • ifdef: 如果变量 <variable-name> 的值非空,那到表达式为真。否则,表达式为假。当然, <variable-name> 同样可以是一个函数的返回值。注意, ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ifdef <variable-name>
    bar =
    foo = $(bar)
    ifdef foo
    frobozz = yes
    else
    frobozz = no
    endif

    # $(frobozz) 为yes
    foo =
    ifdef foo
    frobozz = yes
    else
    frobozz = no
    endif
    # $(frobozz) 为no

  • ifndef:和 ifdef 是相反的意思。

注意:<conditional-directive> 这一行上,多余的空格是被允许的,但是不能以 Tab 键作为开始(不然就被认为是命令)。而注释符 # 同样也是安全的。 elseendif 也一样,只要不是以 Tab 键开始就行了,最好不要把自动化变量(如 $@ 等)放入条件表达式中,因为自动化变量是在运行时才有的。。

六、使用函数

函数调用语法

1
$(<function> <arguments>) or ${<function> <arguments>}

<function> 就是函数名,make支持的函数不多。 <arguments> 为函数的参数,参数间以逗号 , 分隔,而函数名和参数之间以“空格”分隔.函数调用以 $ 开头,以圆括号或花括号把函数名和参数括起.函数和变量的括号最好一样,如使用 $(subst a,b,$(x)) 这样的形式,而不是 $(subst a,b, ${x}) 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

七、make的运行

通常来说,make的最终目标是Makefile中的第一个目标,而其他目标一般是由这个目标连带出来的。要想指定目标,需在make命令后直接跟目标的名字就可以完成

MAKECMDGOALS存放你所指定的终极目标的列表。若在命令行上,没有指定目标,其值则为空值。

八、隐含规则

8.1 模式规则

目标中的%定义表示对文件名的匹配,%表示长度任意的非空字符串。

如果%定义在目标中,那么目标中的%的值决定了依赖目标中的%的值

8.2 自动变量

  • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。
  • $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o$@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $? : 所有比目标新的依赖目标的集合。以空格分隔。
  • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。
  • $+ : 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值。

Makefile学习笔记
http://1291945816.github.io/2022/01/01/makefile学习笔记/
作者
Hps
发布于
2022年1月1日
更新于
2024年4月7日
许可协议