makefile学习笔记(四)

上一篇:makefile学习笔记(三)介绍了makefile的多目标规则、多规则目标、静态模式规则和伪目标。本节将介绍makefile的更多内容。

1. 命令

  • 命令的回显

通常,make在执行命令行之前会把要执行的命令行进行输出,这就是命令的回显。如以下makefile所示:

all:
    echo "Hello csdn!"

在执行make时输出:

$ make
  echo "Hello csdn!"
  Hello csdn!

有时候命令的回显看起来会很烦人。幸运的是,它是可以被关闭的。关闭命令回显有以下几种方式:

  1. 每个需要关闭回显的命令行前加上"@"字符;
  2. 执行make时带上参数 -s–slient 禁止所有执行命令的显示;
  3. 在Makefile中使用没有依赖的特殊目标 .SILENT 也可以禁止所有命令的回显。
  • 命令的执行

在Makefile中,书写在同一行中的多个命令属于一个完整的shell命令行书写在独立行的一条命令是一个独立的shell命令行。所以需要注意:在一个规则的命令中,命令行cd改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用cd进入的那个目录。如果要实现这个目的,就不能把cd和其后的命令放在两行来书写,而应该把这两条命令写在一行上,用分号分隔。这样它们才是一个完整的shell命令行。

下面用一个名为cmd.mk的makefile对比一下两个规则的输出:

target1:
    @echo "target1"
    @cd ~
    @pwd

target2:
    @echo "target2"
    @cd ~; pwd

演示如下:
在这里插入图片描述

  • 命令执行的错误处理

通常情况下,规则中的每一条命令在运行结束后,make都会检测命令执行的返回状态,如果返回成功,就执行下一条命令;命令出错(返回状态非0),make就会放弃对当前规则的执行,或者终止对当前makefile的解析执行。

在一些情况下,规则中的一个命令的执行失败并不代表规则执行的错误。为了忽略一些无关紧要的命令执行失败的情况,我们可以在命令之前加一个减号-,来告诉make忽略此命令的执行失败检查。

2. 内嵌函数

make的内嵌函数为我们提供了处理文件名、变量、文本和命令的方法,使我们的makefile更为灵活和健壮。我们可以在需要的地方调用函数来处理指定的文本(参数),函数在调用它的地方被替换为它的处理结果。函数调用(引用)的展开和变量引用的展开方式类似。

函数调用方式1:

$(FUNCTION ARGUMENTS)

函数调用方式2:

${FUNCTION ARGUMENTS}
  • 内嵌函数的分类

内嵌函数的内嵌函数有很多个,如下图所示:
在这里插入图片描述
下面以文件名处理函数中的wildcard函数作为例子。

  • 使用wildcard函数改进project_demo2项目的makefile

wildcard函数的使用范式如下:

$(wildcard PATTERN)

函数名称:wildcard
函数功能:列出当前目录下所有符合模式“PATTERN”格式的文件名。
返回值:空格分割的、存在当前目录下的所有符合模式“PATTERN”的文件名。
函数说明:“PATTERN”使用shell可识别的通配符,包括?(单字符)、*(多字符)等。

举一个最简单的例子:

$(wildcard *.c) 

上述函数的返回值为当前目录下所有.c源文件列表。

有了wildcard函数,就可以改进project_demo2项目的makefile,使其能够自动扫描当前目录下特定后缀的源文件:

# 定义可执行文件变量
executbale := hello

# 新修改:引进wildcard函数扫描源文件,定义列表变量,简化代码
sources := $(wildcard *.c)

# 使用变量的引用替换,定义objects文件列表
objects := $(sources:.c=.o)
# 定义编译命令变量
CC := gcc
RM := rm -rf

# 终极目标规则,生成hello可执行文件
$(executbale): $(objects)
# 使用自动化变量改造我们的编译命令
    $(CC) $^ -o $@ 

# 子规则, main.o和hello.o的生成规则,使用静态模式规则
$(objects):%.o:%.c
    $(CC) -c $< -o $@ 

# clean规则
.PHONY: clean
clean:
    $(RM) $(executbale) $(objects) 

3. 自动生成依赖关系

至此,project_demo2项目的makefile已经完成的比较完善了,但还存在一个问题:更新头文件hello.h的内容后,执行make时,项目并不会重新编译。这是因为项目的makefile还没能精确的反映整个项目工程的依赖关系。项目的原依赖关系如下图所示:
在这里插入图片描述
可以看到依赖关系图中并没有hello.h。hello.h是以头文件的形式被包含在了hello.c文件和main.c文件中中,然后再让hello.o和main.o分别依赖hello.c和main.c,而并没有直接依赖hello.h。在这种情况下,就算是修改了hello.h的内容,项目的依赖关系也依旧没有变化,生成的可执行文件也不会有变化。所以要解决这个问题,就应该在项目的依赖关系中加入对于头文件的依赖。加入的语句如下:

$(objects): hello.h

上述规则加入虽然能解决我们的问题,但是对于大型复杂的项目,像这样逐个去分析头文件被依赖的关系,几乎是不可能的。所以我们需要工具来帮我们做这个事。下面将介绍如何实现自动生成依赖关系。

gcc为我们提供了这样的功能,帮助我们分析一个文件对其他文件的依赖关系列表。当我们在执行gcc时带上 -MM 选项,gcc工具就会为我们列出指定文件对其他文件的依赖关系列表。看下列例子:
在这里插入图片描述
接下来要考虑的问题就是如何将gcc的输出导入到makefile中了。

makefile支持使用sinclude关键字将指定文件导入到当前的makefile当中,它的作用与C语言的 #inlucde 预处理命令是一样的。使用方式为:sinclude <other_makefiles>。因此,我们可以将gcc对于源文件的依赖关系分析输出到依赖描述文件当中,该类文件一般与源文件同名,但以 .d 作为结尾。然后再将依赖描述文件导入到makefile中。这部分操作的代码如下:

# 使用变量的引用替换,定义依赖描述文件列表
deps := $(sources:.c=.d)
# 导入依赖描述文件列表
sinclude $(deps)

当我们使用sinclude关键字向当前makefile导入文件时,如果所导入的文件不存在,make会试图去执行可以产生导入文件的规则去生成被导入的文件,然后再执行导入。因此我们可以使用静态模式规则,让make在执行时,去调用gcc生成依赖关系文件。可以这么写:

$(deps):%.d:%.c
# > 为重定向符号,表示将项目中对 main.c和 hello.c的依赖关系分别
#输出到 main.d和 hello.d文件中
    gcc -MM $< > $@

经过改进后,最终版的project_demo2项目makefile文件如下:

# 定义可执行文件变量
executbale := hello
# wildcard函数扫描源文件,定义列表变量
sources := $(wildcard *.c)
# 使用变量的引用替换,定义object文件列表
objects := $(sources:.c=.o)
# 使用变量的引用替换,定义依赖描述文件列表
deps := $(sources:.c=.d)

# 定义编译命令变量
CC := gcc
RM := rm -rf

# 终极目标规则,生成 hello可执行文件
$(executbale): $(objects)
#  使用自动化变量改造我们的编译命令
    $(CC) -o $@ $^

# 子规则, main.o和 hello.o的生成规则,使用静态模式规则
$(objects):%.o:%.c
    $(CC) -o $@  -c $<

# clean规则
.PHONY: clean
clean:
    $(RM) $(executbale) $(objects) $(deps)

# 自动规则依赖
sinclude $(deps)

$(deps):%.d:%.c
    $(CC) -MM $< > $@

演示结果如下:
在这里插入图片描述
至此,makefile笔记系列完美收官。

总结

这一系列笔记介绍了 makefile 的编写和 make 工具的使用,主要包括以下的内容:

  • makefile 规则的概念
  • makefile 变量的使用
  • makefile 函数的使用
  • makefile 如何实现自动生成依赖
发布了13 篇原创文章 · 获赞 43 · 访问量 2769

猜你喜欢

转载自blog.csdn.net/qq_42554780/article/details/104260251