跟我一起写Makefile--个人总结

news2024/9/25 7:14:17

此篇笔记是根据陈皓大佬《跟我一起写Makefile》学习所得

文章目录

  • 换行符
  • clean
  • 变量
  • make的自动推导
  • 另类风格的Makefile
  • 清空目标文件的规则clean
  • Makefile总述
    • 显示规则
    • 隐晦规则
    • 变量的定义
    • 注释
    • 引用其它的Makefile
    • 环境变量MAKEFILES
    • make的工作方式
  • 书写规则
    • 规则举例
    • 规则的语法
    • 在规则中使用通配符
    • 文件搜寻
    • 伪目标
    • 多目标
    • 静态模式
    • 自动生成依赖性 未完全
  • 书写命令
    • 显示命令
    • 命令执行
    • 命令出错
    • 嵌套执行make
    • 定义命令包
  • 使用变量
    • 变量的基础
    • 变量中的变量
      • `=`和`:=`的区别
      • 定义一个空格变量
      • ?= 操作符
    • 变量高级用法
      • 替换变量中的共有的部分
      • 把变量的值再当成变量
    • 追加变量值
    • override指示符
    • 多行变量
    • 环境变量
    • 目标变量
    • 模式变量
  • 使用条件判断
    • 示例
    • 语法关键字
      • ifeq
      • ifneq
      • ifdef
      • ifndef
  • 使用函数
    • 函数调用语法
    • 字符串处理函数
      • subst字符串替换函数
      • patsubst模式字符串替换函数
      • strip去空格函数
      • findstring查找字符串函数
      • filter过滤函数
      • filter-out反过滤函数
      • sort排序函数
      • word取单词函数
      • wordlist取单词串函数
      • words单词个数统计函数
      • firstword首单词函数
    • 文件名操作函数
      • dir取目录函数
      • notdir取文件函数
      • suffix取后缀函数
      • basename取前缀函数
      • addsuffix加后缀函数
      • addprefix加前缀函数
      • join连接函数
    • foreach函数
    • if函数
    • call函数
    • origin函数
      • 返回值清单
        • undefined
        • default
        • file
        • command line
        • override
        • automatic
      • 用法
    • 控制make的函数
      • error
      • warning
  • make的运行
    • make退出码
    • 指定Makefile
    • 指定目标
      • all
      • clean
      • install
      • print
      • tar
      • dist
      • TAGS
      • check和test
      • 意义
    • 检查规则
    • make的参数
  • 隐含规则
    • 使用隐含规则
    • 隐含规则一览
      • 编译C程序的隐含规则
      • 编译 C++程序的隐含规则
      • 编译 Pascal 程序的隐含规则
      • 编译 Fortran/Ratfor 程序的隐含规则
      • 预处理 Fortran/Ratfor 程序的隐含规则
      • 编译 Modula-2 程序的隐含规则
      • 汇编和汇编预处理的隐含规则
      • 链接 Object 文件的隐含规则
    • 定义模式规则
      • 模式规则介绍
      • 模式规则示例
      • 自动化变量
        • $@
        • $%
        • $<
        • $?
        • $^
        • $+
        • $*

换行符

在Makefile可以用反斜杠(\) 来表示换行符的意思

clean

clean

​ rm xxx

clean不是一个文件,它知识一个动作的名字 有点像C语言中的lable一样 其冒号后面什么都没有,因此make就不会自动去找文件的依赖性 也就是不会自动执行其后所定义的命令。如要执行可以在make命令后明显指出这个lable

例如make clean

make会根据一个最终的目标文件 然后根据依赖来决定是否需要更新文件

变量

在一个复杂的Makefile中出现了多次的字符串我们可以使用变量进行一个整理

语法

​ 变量名 = main.o files.o(目标文件)

当好几个地方都需要使用的时候我们可以使用$(变量名),来引用变量

make的自动推导

它可以自动推导文件以及文件依赖关系后面的命令

于是我们没必要在每一个.o文件都写上类似的编译命令

简化前

image-20230206231654332

简化后

image-20230206231715412

另类风格的Makefile

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rwz4ImcB-1676183154207)(C:/Users/25394/AppData/Roaming/Typora/typora-user-images/image-20230206235500782.png)]

把.o和.h文件收拢起来

不过看起来关系比较乱,不够清晰明确。依赖关系看不清楚。加入新的.o文件就不清楚了。

清空目标文件的规则clean

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则

风格

.PHONY:clean

clean:

​ -rm 最终目标文件 $(objects)

Makefile总述

显示规则

显示规则说明如何生成一个或多个目标文件。这是有作者明显指出,要生成的文件,文件的依赖文件,生成的命令

隐晦规则

make有自动推导的功能 所以隐晦的规则可以让我们比较粗糙简略书写Makefile

变量的定义

make可以定义一系列变量 变量一般都是字符串。类型c语言中的宏定义

注释

Makefile中只有行注释。注释使用"#"字符。

在Makefile中的命令必须要以Tab打头

引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来。很像C语言的#include

语法是

include 文件名

include前面可以有一些空字符,但绝不能是Tab打头

image-20230207002201225

环境变量MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么make会把这个变量中的值做一个类型与include的动作

这个变量中的值是其它的 Makefile,用空格分隔。只是, 它和 include不同的是,从这个环境变中引入的 Makefile 的“目标”不会起作用,如果环境变量中定义
的文件发现错误,make 也会不理

不建议使用环境变量

Tips:在这里提这个事,只是为了告诉大家,也许有时候你的 Makefile 出现了怪事,那么你可以看看当前环境中有没有定义这个变量。

make的工作方式

GNU的make工作时的执行步骤如下

  1. 读入所有的Makefile
  2. 读入被include的其他Makefile
  3. 初始化文件中的变量
  4. 推导隐晦规则 并分析所有规则
  5. 为所有的目标文件创建依赖关系链
  6. 根据依赖关系,决定哪些目标要重新生成
  7. 执行生成命令 make

书写规则

规则包含两个部分,一个是依赖关系,一个是生成目标的方法

在Makefile中,规则的顺序很重要,因为,Makefile中应该只有一个最终的目标,其他目标都是被这个目标连带出来的。所以一定要让make知道最终目标。

一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中目标将被确立为最终的目标。如果第一条规则很多目标,那么第一个目标将成为最终目标。先来后到的关系。

规则举例

hello.o : hello.c def.h

​ cc -c -g hello.c

根据这个规则可以分析出

1、文件的依赖关系,hello.o 依赖于 hello.c 和 def.h 的文件,如果 hello.c 和 def.h 的文件日期要比 hello.o 文件日期要新,或是 hello.o 不存在,那么依赖关系发生。

2、如果生成(或更新)hello.o 文件。也就是那个 cc 命令,其说明了,如何生成 hello.o这个文件。 (当然 hello.c 文件 include 了 def.h 文件)

规则的语法

targets : prerequisites

​ command

或是这样:
targets : prerequisites ; command
command

​ targets 是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。

​ command 是命令行 。如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头, 如果和 prerequisites 在一行, 那么可以用分号做为分隔。

​ prerequisites 是目标的依赖文件。如果依赖文件比目标文件的要新,那么目标将被make认为是“过时的”,需要重新生成。

在规则中使用通配符

定义一系列比较类似的文件,可以使用通配符。

make支持三种通配符:“*”, “?” , “[…]”

例子:

clean:
rm -f *.o

这里就是删除所有的.o文件

文件搜寻

​ 在一些大的工程中,有大量的源文件,我们通常做法是把这些源文件进行分类存在不同的目录中。所以,当make需要去找寻文件的依赖关系时,可以在文件前加上路径,把路径告诉make,让make自动去找。

​ Makefile文件中的特殊变量"VPATH"就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件

如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到所指定的目录中去寻找文件。

例子

​ VPATH = src:…/headers

​ 上面的的定义指定两个目录,“src”和“…/headers”,make 会按照这个顺序进行搜索。目录由“冒号”分隔。 (当然,当前目录永远是最高优先搜索的地方)

另外一个设置文件搜索路径的方法是使用make的“vpath”关键字(是全小写的)不是变量。这是一个make的关键字,在make的时候可以指定不同的文件在不同的搜索目录中。

1、vpath
为符合模式的文件指定搜索目录。
2、vpath
清除符合模式的文件的搜索目录。
3、vpath
清除所有已被设置好了的文件搜索目录。
vapth 使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符,
例如,“%.h”表示所有以“.h”结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的目录。

例如:
vpath %.h …/headers
该语句表示,要求 make 在“…/headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)

我们可以连续地使用 vpath 语句,以指定不同搜索策略。如果连续的 vpath 语句中出现了相同的,或是被重复了的,那么,make 会按照 vpath 语句的先后顺序来执行搜索。

如:
vpath %.c foo
vpath % blish
vpath %.c bar

其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

vpath %.c foo:bar
vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录

伪目标

最早的例子的clean就是一个伪目标。

clean:

​ -rm 最终目标文件 $(objects)

并不产生clean这个目标文件,而是一个类似lable的东西。我们是由显示地指明这个目标才能让其生效。伪目标地取名不能和文件名重名。

为了避免从和文件重名地情况。可以使用特殊标记“.PHONY”来显示地指明这个目标是“伪目标”,告诉make,不管是否有这个文件,你都把它当作伪目标

多目标

Makefile的规则中目标不止一个,支持多目标。有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。

例子

bigoutput littleoutput : text.g

​ generate test.g - ( s u b s t p u t p u t , , (subst putput,, (substputput,,@) > $@

等价于

bigoutput : text.g

​ generate text.g -big > $bigoutput

littleoutput : text.g

​ generate text.g -little> $littleoutput

其中$@是一个自动化变量,可以粗略理解为表示目前规则中所有目标的集合。

- ( s u b s t o u t p u t , , (subst output,, (substoutput,,@) ,== 表示执行一个 M a k e f i l e 函数 = = ,函数名为 s u b s t ,后面的为参数。这个函数是截取字符串的意思,“ 表示执行一个Makefile函数==,函数名为subst,后面的为参数。这个函数是截取字符串的意思,“ 表示执行一个Makefile函数==,函数名为subst,后面的为参数。这个函数是截取字符串的意思,@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

静态模式

​ 静态模式可以容易地定义多目标地规则,可以让我们的规则变得更加的有弹性和灵活。

<targets …> : : <prereq-patterns …>

targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern 是指明了 targets 的模式,也就是的目标集模式。

prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。

objects = foo.o bar.o

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

​ 上面的例子中,指明了我们的目标从变量objects中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加上后缀成为“foo.c bar.c”。而命令中的“ < ”和“ <”和“ <@”则是自动化变量,“ < ”表示所有的依赖目标集(也就是“ f o o . c b a r . c ”),“ <”表示所有的依赖目标集(也就是“foo.c bar.c”), “ <表示所有的依赖目标集(也就是foo.cbar.c),@”表示目标集(也就是“foo.o bar.o”)。

自动生成依赖性 未完全

​ 在Makefile中,我们的依赖关系可能会包含一系列的头文件。比如我们的main.c会有一句“#include “defs.h””

那么我们的依赖关系应该是:
main.o : main.c defs.h

一个大型的工程,必须要清楚哪些C文件包含了哪些头文件,并且在加入或删除头文件时,需要小心地修改Makefile文件。为了避免繁重又容易出错的事,可以使用C/C++编译的一个功能。大多数编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成依赖关系。

例如

cc -M main.c

其输出是

main.o : main.c defs.h

​ 编译器自动生成的依赖关系,就不必在手动书写若干文件的依赖关系。如果你使用GNU的C/C++编译器,得用“-MM”参数,不然“-M”参数会把一些标准库的头文件也包含进来。Linux则是GNU操作系统的内核(Kernel)

书写命令

显示命令

每条命令的开头都是以Tab键打头。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make将会认为是一个空命令。

“#”是注释符

使用“@”字符在命令行前,这个命令将不被make显示出来

例如

@echo 正在编译XXX模块…

make执行,会输出“正在编译XXX模块…”,但不会输出命令,如果没有“@”,那么make将会输出

echo 正在编译XXX模块...

正在编译XXX模块....

​ 如果make执行时,带入make参数“-n”或“–just-print” ,那么只是显示命令,但不会去执行命令,可以方便我们调试我们的makefile

而 make 参数“-s”或“–slient”则是全面禁止命令的显示

命令执行

​ 当依赖目标新与目标时,也就是规则的目标需要被更新时。make就会一条条执行其后的命令。需要注意的是,如果要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。

比如

exec:

​ cd /home/zzn

​ pwd

这种情况只会打印出当前Makefile文件的目录

不能将两条命令写在两行上,而应该把这两条命令写在一行上用分号分隔。

exec:

​ cd /home/zzn;pwd

这里会打印出“home/zzn”

命令出错

​ 每个命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是完成了,如果某个命令出错了,那么make就会终止当前规则的执行,这将有可能导致终止所有规则的执行。

​ 有些时候,命令的出错并不存在错误,比如mkdir命令要是目录存在也会报错但是真的有错嘛?我们并不希望一个mkdir出错了就导致整个规则都终止。

​ 解决方法就是在命令行前加一个“-”,在Tab键之后

例如

clean:

​ -rm -f *.o

​ 还有一个全局的方法就是make的时候带上参数“-i”或“–ignore-errors”参数,那么make将会忽略所有命令的错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。

​ 还有一个要提一下的 make 的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则

嵌套执行make

​ 在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于我们的Makefile变得简洁。而不是将所有东西都写在顶层的Makefile中。

​ 例如有一个子目录叫subdir里面有一个Makefile文件,顶层Makefile文件可以写

subsystem:

​ cd subdir && $(MAKE)

等价于

subsystem:

​ $(MAKE) -C subdir

​ 定义$(MAKE)宏变量的意思是,也许我么的make需要一些参数,所以定义成一个变量利于维护。意思都是进入subdir目录执行make命令

顶层Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

如果要传递变量到下级Makefile中,可以使用export来声明

例如

export <variable1、variable2…>

如果不想让某些变量传递到下级Makefile中,可以使用unexport

unexport <variable1、variable2…>

值得一提,有两个变量SHELLMAKEFLAGS,这两个变量不管是否export,总是会传递到下层Makefile,特别是MAKEFLAGS变量,其中包含了make的参数信息,如果我们执行顶层Makefile时有make参数或是在上层Makefile中定于了MAKEFLAGS这个变量,那么MAKEFLAGS就等于这些参数,传递到下层Makefile。

但是 make 命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关 Makefile 参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量 MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌 。

​ 还有在嵌套执行中有一个很有用的make参数就是,“-w”或“–print-directory”会在make的过程输出一些信息,比如当前的工作目录

比如,如果我们的下级 make 目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,
我们会看到:
make: Entering directory /home/hchen/gnu/make

而在完成下层 make 后离开目录时,我们会看到:
make: Leaving directory /home/hchen/gnu/make

当你使用“-C”参数来指定 make 下层 Makefile 时,“-w”会被自动打开的。如果参数中有“-s”(“–slient”) 或是“–no-print-directory”,那么,“-w”总是失效的。

定义命令包

​ 如果Makefile中出现一些相同命令序列,那么可以为相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束

例如

define run-yacc

​ yacc $(firstword $^)

​ mv y.tab.c $@

endef

​ run-yacc就是包的名字,不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。 第一行命令就是运行Yacc程序,因为 Yacc 程序总是生成“y.tab.c”的文件 ,所以第二行的命令就是改这个文件的名字。

foo.c : foo.y

​ $(run-yacc)

命令包中的“ ” 就是“ f o o . y ”,“ ^”就是“foo.y”,“ 就是foo.y@”就是“foo.c”,这一段的作用是,以foo.y文件运行yacc程序会产生一个y.tab.c的文件我们会把他更名为foo.c

使用变量

​ 在Makefile中定义的变量,就像是c语言的宏一样

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

​ 变量是大小写敏感的,foo FOO Foo是三个不同的变量,传统的Makefile变量名是全大写的命名方式,但是采用大小写搭配的方式来命名能避免和系统变量冲突的情况。

​ 如“ < ”、“ <”、“ <@”等,这些是自动化变量

变量的基础

​ 变量在声明时需要赋初值,而在使用时,需要在变量名前加上“ ”符号,最好在加上小括号 ( ) 或是大括号 把变量包起来。如果你要使用真是的“ ”符号,最好在加上小括号()或是大括号{}把变量包起来。如果你要使用真是的“ 符号,最好在加上小括号()或是大括号把变量包起来。如果你要使用真是的”字符,需要用“$$”来表示。

​ 变量可以使用在许多地方。如规则中的目标、依赖、命令以及新的变量中。

例如

objects = program.o foo.o utils.o

program : $(objects )

​ cc -o program $(objects )

$(objects ) : defs.h

变量中的变量

​ 在定义变量的值时,我们可以使用其它变量来构造变量的值,在 Makefile 中有两种方式来在用变量定义变量的值。

foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
	echo $(foo)  

最终打印的是 Huh?

好处就是我们可以把变量的真实值推到后面来定义,如:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

当“CFLAGS”在命令中被展开时,会是“-Ifoo -Ibar -O”。

坏处就是会产生递归定义

A = $(B)
B = $(A)  

这会让 make 陷入无限的变量展开过程中去,当然,我们的 make 是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的 make 运行时非常慢,更糟糕的是,他会使用得两个 make 的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

make另外一种定义变量的方法,使用“:=”操作符

x := foo
y := $(x) bar
x := later

使用这种方法前面的变量不能使用后面的变量 只能使用前面定义好了的变量

y := $(x) bar
x := foo

那么,y 的值是“bar”,而不是“foo bar”。

=:=的区别

  1. =
    make会将makfile展开之后再决定变量的值,也就是说,变量的值将会是整个makefile中最终的值

    x = foo
    y = $(x) bar
    x = xyz
    
    在上例中,y的值是xyz bar,而不是foo bar
    12345
    
  2. :=
    :=表示变量的值取决于变量的位置,而不是整个makefile 展开后的最终值

    x := foo
    y := $(x) bar
    x = xyz
    
    在上例中,y的值会是foo bar,而不是 xyz bar
    

定义一个空格变量

nullstring :=
space := $(nullstring) # end of the line

​ nullstring 是一个 Empty 变量,其中什么也没有,而我们的 space 的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个 Empty 变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量

?= 操作符

FOO ?= bar

使用这个操作符,如果FOO变量之前没有定义那么FOO的值就是bar,如果前面有定义那么不执行。

等价于

ifeq ($(origin FOO), undefined)
FOO = bar
endif

变量高级用法

替换变量中的共有的部分

“$(var:=)”

第一种

格式是“ ( v a r : a = b ) ”或“ (var:a=b)”或“ (var:a=b){var:a=b}”,意思是把变量var中所有以“a”字串结尾的“a”替换成“b”

例如

foo := a.o b.o c,o
bar := $(foo:=.o=.c)

这个示例中,我们先定义了一个“ ( f o o ) ”变量,而第二行的意思是把“ (foo)”变量,而第二行的意思是把“ (foo)变量,而第二行的意思是把(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c
c.c”。

第二种使用“静态模式”

foo := a.o b.o c.o
bar:= $(foo:%.o:%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,这个例子同样让==$(bar)变量的值为“a.c b.c c.c”==。

把变量的值再当成变量

x = y
y = z
a := $($(x))

$(a)的值就是“z”

追加变量值

可以使用“+=”操作符给变量追加值

obj = a.o b.o c.o
obj += d.o

等价于

obj = a.o b.o c.o
obj :=$(obj) d.o

使用+=更为简洁

override指示符

如果有变量是make命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果想在 Makefile 中设置这类参数的值,那么,你可以使用“override”指示符。

override =

override :=

还可以

override +=

多行变量

还有一种设置变量值的方法使用define关键字。使用define关键字设置变量的值可以有换行。

define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束.

变量的值可以包含函数、命令、文字,或是其它变量。 因为命令需要以[Tab]键开头, 所以如果你用 define 定义的命令变量中没有以[Tab]键开头,那么 make 就不会把其认为是命令。

define two-lines
echo foo
echo $(bar)
endef

环境变量

​ make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义这个变量或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果 make 指定了“-e”参数,那么,系统环境变量将覆盖 Makefile 中定义的变量)

​ 如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile 中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果
Makefile 中定义了 CFLAGS,那么则会使用 Makefile 中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。

​ 当 make 嵌套调用时(参见前面的“嵌套调用”章节),上层 Makefile 中定义的变量会以系统环境变量的方式传递到下层的 Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层 Makefile 传递,则需要使用exprot 关键字来声明。

目标变量

全面定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。

当然,“自动化变量”除外, 如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。

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

其语法是:
<target …> :
<target …> : overide

可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”

第二个语法是针对于 make 命令行带入的变量,或是系统环境变量。这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
$(CC) $(CFLAGS) prog.c

foo.o : foo.c
$(CC) $(CFLAGS) foo.c

bar.o : bar.c
$(CC) $(CFLAGS) bar.c

​ 在这个示例中,不管全局的 ( C F L A G S ) 的值是什么,在 p r o g 目标,以及其所引发的所有规则中( p r o g . o f o o . o b a r . o 的规则), (CFLAGS)的值是什么,在 prog 目标,以及其所引发的所有规则中(prog.o foo.o bar.o 的规则), (CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.ofoo.obar.o的规则),(CFLAGS)的值都是“-g”

模式变量

​ 在GNU的make中还支持模式变量(Pattern-specific Variable) ,通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式” 可以把变量定义在符合这种模式的所有目标上。

​ make的模式一般是至少含有一个“%”的,所以我们可以用一下方式给所有的【.o】结尾的目标定义目标变量。

%.o : CFLAGS = -o

同样,模式变量的语法和“目标变量”一样:

其语法是:
<target …> :
<target …> : overide

override 同样是针对于系统环境传入的变量,或是 make 命令行指定的变量。

使用条件判断

示例

使用条件判断可以让make根据运行时的不同情况选择不同的执行分支

libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。

三个关键字:ifeq、else 和 endif .

ifeq的意思是表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔, 表达式以圆括号括起。

endif表示一个条件语句的结束

任何一个条件表达式都应该以endif结束

简洁版

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

foo: $(objects)
	$(CC) -o foo $(objects) $(libs)

语法关键字

条件表达式的语法为:


endif

else endif

ifeq

这个关键字是用来比较“arg1”和“arg2”的值是否相同。如果相同则为真参数中也可以使用函数

ifneq

这个关键字是用来比较“arg1”和“arg2”的值是否相同。如果不同则为真参数中也可以使用函数

ifdef

语法

ifdef 变量

如果变量的值非空,那么表达式为真。否则表达式为假。同样可以是一个函数的返回值

注意,ifdef 只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子

bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

ifndef

如果变量的值为空,那么表达式为真。否则表达式为假。同样可以是一个函数的返回值

使用函数

​ 在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make函数不多,但足够使用。函数调用后函数的返回值可以当作变量来使用。

函数调用语法

很像变量的使用也是用"$"来标识的

$( )
或是
${ }

就是函数名,make 支持的函数不多。是函数的参数,参数间
==以逗号“,”分隔,而函数名和参数之间以“空格”分隔。==函数调用以“$”开头,以圆括号
或花括号把函数名和参数括起。

字符串处理函数

subst字符串替换函数

语法

$(subst ,, )

名称:字符串替换函数——subst。
功能:把字串 中的字符串替换成。
返回:函数返回被替换过后的字符串。
示例

str := feet on the street
$(subst ee,EE,str)

返回结果是“fEEt on the strEEt”

patsubst模式字符串替换函数

语法

$(patsubst ,, )

名称:模式字符串替换函数——patsubst。
功能:查找 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以替换。这里,可以包括通配符“%”, 表示任意长度的字串。 如果中也包含“%”, 那么,
中的这个“%”将是中的那个“%”所代表的字串。(可以用“\”来转义, 以“%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串

示例

$(patsubst %.c,%.o,x.c.c bar.c)

返回结果是“x.c.o bar.o”

tips:

​ 这个替换和前面所学的变量替换类型

例如有:objects = foo.o bar.o baz.o,
那么,

“$(objects:.o=.c)” --使用变量高级用法

( p a t s u b s t (patsubst %.o,%.c, (patsubst(objects))” --使用函数的方法

二者效果是一样的

strip去空格函数

语法

$(strip )

名称:去空格函数——strip。
功能:去掉字串中开头和结尾的空字符
返回:返回被去掉空格的字符串值。

示例

$(strip a b c)
返回a b c

findstring查找字符串函数

语法

$(findstring ,)

名称:查找字符串函数——findstring。
功能:在字串中查找字串。
返回:如果找到,那么返回,否则返回空字符串

示例

$(findstring a,a b c)
$(findstring a,b c)

第一个返回“a”字符串
第二个返回“ ”空字符串

filter过滤函数

语法

$(filter <pattern…>, )

名称:过滤函数——filter。
功能:以模式过滤 字符串中的单词,保留符合模式的单词。可以
有多个模式。
返回:返回符合模式的字串。

例如

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo

$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。ugh.h被过滤了

filter-out反过滤函数

语法

$(filter-out <pattern…>, )

名称:反过滤函数——filter-out。
功能:以模式过滤 字符串中的单词,去除符合模式的单词。可以
有多个模式。
返回:返回不符合模式的字串。
示例:

objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 
返回值是“foo.o bar.o”。

sort排序函数

语法

$(sort )
名称:排序函数——sort。
功能:给字符串中的单词排序(升序)。
返回:返回排序后的字符串。

示例:

$(sort foo bar lose)

返回“bar foo lose” 。

tips:

sort 函数会去掉中相同的单词。

word取单词函数

语法

$(word , )

名称:取单词函数——word。
功能:取字符串 中第个单词。 (从一开始)
返回:返回字符串 中第个单词。如果比 中的单词数要大,那么返回空字符串。

示例:

$(word 2, foo bar baz)

返回bar

wordlist取单词串函数

语法

$(wordlist ,, )

名称:取单词串函数——wordlist。
功能:从字符串 中取从 开始到的单词串。和是一个数字。
返回:返回字符串 中从 到的单词字串。如果 中的单词数要大,那
么返回空字符串。如果大于 的单词数,那么返回从 开始,到 结束的单词串。

例如

$(wordlist 2, 3, foo bar baz)

返回值是“bar baz”

words单词个数统计函数

语法

$(words )

名称:单词个数统计函数——words。
功能:统计 中字符串中的单词个数。
返回:返回 中的单词数。

例如

$(words, foo bar baz)
返回值是3

tips

如果我们要取 中最后的一个单词,我们可以这样:$(word $(words ), )。

firstword首单词函数

语法

$(firstword )

名称:首单词函数——firstword。
功能:取字符串 中的第一个单词。
返回:返回字符串 的第一个单词 。

示例

$(firstword foo bar)

返回值是“foo”。

tips

​ 这个函数可以用 word 函数来实现:$(word 1, )。

文件名操作函数

dir取目录函数

语法

$(dir <names…>)

名称:取目录函数——dir。
功能:从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前
的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列的目录部分

示例:

$(dir src/foo.c)
返回值是“src/ ”。
$(dir hacks)
返回值是“./”。

notdir取文件函数

语法

$(notdir <names…>)

名称:取文件函数——notdir。
功能:从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“ /”)之后的部分。
返回:返回文件名序列的非目录部分。

示例

$(notdir src/foo.c hacks)
返回值是“foo.c hacks”。

suffix取后缀函数

语法

$(suffix <names…>)

名称:取后缀函数——suffix。
功能:从文件名序列中取出各个文件名的后缀。
返回:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串

示例

$(suffix src/foo.c src-1.0/bar.c hacks)
返回值是“.c .c”。

basename取前缀函数

语法

$(basename <names…>)

名称:取前缀函数——basename。
功能:从文件名序列中取出各个文件名的前缀部分。
返回:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。

示例

$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

addsuffix加后缀函数

语法

$(addsuffix ,<names…>)

名称:加后缀函数——addsuffix。
功能:把后缀加到中的每个单词后面。
返回:返回加过后缀的文件名序列

$(addsuffix .c,foo bar)
返回值是“foo.c bar.c”。

addprefix加前缀函数

语法

$(addprefix ,<names…>)

名称:加前缀函数——addprefix。
功能:把前缀加到中的每个单词后面。
返回:返回加过前缀的文件名序列。

示例

$(addprefix src/,foo bar)
返回值是“src/foo src/bar”

join连接函数

语法

$(join ,)

名称:连接函数——join。
功能:==把中的单词对应地加到的单词后面。==如果的单词个数要比的多,那么,中的多出来的单词将保持原样。如果的单词个数要比多,那么,多出来的单词将被复制到中。

示例

$(join aaa bbb , 111 222 333)
返回值是“aaa111 bbb222 333”

foreach函数

循环用的函数

语法

$(foreach ,, )

这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,
然后再执行 所包含的表达式。每一次 会返回一个字符串,循环过程中, 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。

所以, 最好是一个变量名, 可以是一个表达式,而 中一般会使用 这个参数来依次枚举中的单词。举个例子:

names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中, ( n a m e ) 中的单词会被挨个取出,并存到变量“ n ”中,“ (name)中的单词会被挨个取出,并存到变量“n”中,“ (name)中的单词会被挨个取出,并存到变量n中,(n).o”每次
根据“ ( n ) ”计算出一个值,这些值以空格分隔,最后作为 f o r e a c h 函数的返回,所以, (n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以, (n)计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,(files)的值是==“a.o b.o c.o d.o”==

if函数

语法

$(if ,)

或是

$(if ,,)

if 函数可以包含“else”部分,或是不含 。

if 函数的参数可以是两个,也可
以是三个。参数是 if 的表达式,如果其返回的为非空字符串,那么这个表达式
就相当于返回真,于是,会被计算,否则会被计算 。

而 if 函数的返回值是,如果为真(非空字符串),那个会是整
个函数的返回值,如果为假(空字符串),那么会是整个函数的返回值,此时如果没有被定义,那么,整个函数返回空字串。所以,和只会有一个被计算

call函数

call函数是唯一一个可以用来创建新的标准化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用 call 函数来向这个表达式传递参
数。其语法是:

$(call ,,,…)

当 make 执行这个函数时,参数中的变量, ( 1 ) , (1), (1)(2),$(3)等,会被
参数,,依次取代。而的返回值就是 call 函数的返回值。

reverse = $(1) $(2)
foo = $(call reverse,a,b)

那么,foo 的值就是“a b”。

tips

当然,参数的次序是可以自定义的,不一定是顺序的,
如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的 foo 的值就是“b a”。

origin函数

不像其他函数,他并不操作变量的值。

只是告诉你你这个变量是哪里来的

语法

$(origin )

tips

是变量的名字,不应该是引用。所以你最好不要在中使用
“$”字符。 Origin 函数会以其返回值来告诉你这个变量的“出生情况

返回值清单

undefined

如果从来没有定义过,origin 函数返回这个值“undefined”。

default

如果是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面
讲述。 environment” 如果是一个环境变量, 并且当 Makefile 被执行时, “-e”
参数没有被打开。

file

如果这个变量被定义在 Makefile 中

command line

如果这个变量是被命令行定义的

override

如果是被 override 指示符重新定义的。

automatic

如果是一个命令运行中的自动化变量

用法

这些信息对于我们编写 Makefile 是非常有用的,例如,假设我们有一个 Makefile 其包
了一个定义文件 Make.def,在 Make.def 中定义了一个变量“bletch”,而我们的环境中也
有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把
之重定义了,如果来源于 Make.def 或是命令行等非环境的,那么我们就不重新定义它。于
是,在我们的 Makefile 中,我们可以这样写:

ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif

控制make的函数

error

语法

$(error <text …>)

产生一个致命的错误,<text …>是错误信息。注意,error 函数不会在一被使用就会
产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么
也是可以的。

例如

ifdef ERROR_001
$(error error is $(ERROR_001))
endif
ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)

示例一会在变量 ERROR_001 定义了后执行时产生 error 调用,而示例二则在目录 err被执行时才发生 error 调用。

warning

语法

$(warning <text …>)

不会让make退出,只是输出一段警告信息,make会继续执行

make的运行

make退出码

make执行后有三个退出码

0-表示成功

1-make是出现任何错误其返回1

2-使用make "-q"选项并且make使得一些目标不需要更新返回2

指定Makefile

GNU make 找寻默认的 Makefile 的规则是在当前目录下依次找三个文件
——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行

我们也可以给 make 命令指定一个特殊名字的 Makefile。

要达到这个功能,我们要使用 make 的“-f”或是“–file”参数(“–makefile”参数也行)。

例如

我们有个
makefile 的名字是“zzn.mk”,那么,我们可以这样来让 make 来执行这个文件

make -f zzn.mk

指定目标

make 的最终目标是 makefile 中的第一个目标,而其它目标一般是由这个目标连带出来的。这是 make 的默认行为

但是我们可以指定目标执行例如make clean就是一个例子

任何在Makefile中的目标都可以被指定成终极目标,但是除了以“-”打头或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要 make 可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。

all

这个伪目标是所有目标的目标其功能一般是编译所有的目标

clean

这个伪目标功能是删除所有make创建的文件

install

这个伪目标是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去

print

这个伪目标是列出改变过的源文件

tar

这个伪目标功能是把源程序打包备份也就是一个tar文件

dist

这个伪目标功能是创建一个压缩文件一般是把tar文件压成Z文件或是gz文件

TAGS

更新所有的目标 以备完整地重编译使用

check和test

一般用来测试makefile的流程

意义

如果你要书写这种功能,最好使用这种名
字命名你的目标,这样规范一些,规范的好处就是——不用解释,大家都明白。而且如果你
的 makefile 中有这些功能,一是很实用,二是可以显得你的 makefile 很专业(

检查规则

有时候我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令或是执行的序列。

于是我们就可以使用make命令的下述参数

“-n”

“–just-print”

“–dry-run”

“–recon”

image-20230211182940287

不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行。

“-t”

“–touch”

这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态

“-q”

“–question”

这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,如果目标不存在,其会打印出一条出错信息

image-20230211214012887

“-W ”

“–what-if=”
“–assume-new=”
“–new-file=”

这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make 会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

另外一个很有意思的用法是结合“-p”和“-v”来输出 makefile 被执行时的信息(这个将在后面讲述)。

make的参数

下面列举了 所有 GNU make 3.80 版的参数定义

“-b”
“-m”
这两个参数的作用是忽略和其它版本 make 的兼容性。

“-B”
“–always-make”
认为所有的目标都需要更新(重编译)。

-C


“–directory=
指定读取 makefile 的目录。如果有多个“-C”参数,make 的解释是后面的路径以前面
的作为相对路径,并以最后的目录作为被指定目录。如: “make –C ~hchen/test –C prog”
等价于“make –C ~hchen/test/prog”。

“—debug[=]”
输出 make 的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。

下面是的取值:
a —— 也就是 all,输出所有的调试信息。(会非常的多)
b —— 也就是 basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是 verbose,在 b 选项的级别之上。输出的信息包括哪个 makefile 被解析,不
需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是 implicit,输出所以的隐含规则。
j —— 也就是 jobs,输出执行规则中命令的详细信息,如命令的 PID、返回码等。
m —— 也就是 makefile,输出 make 读取 makefile,更新 makefile,执行 makefile 的信
息。

“-d”
相当于“–debug=a”。

“-e”
“–environment-overrides”
指明环境变量的值覆盖 makefile 中定义的变量的值

“-f=”
“–file=”
“–makefile=”
指定需要执行的 makefile。

“-h”
“–help”
显示帮助信息。

-i”
“–ignore-errors”
在执行时忽略所有的错误。

“-I

–include-dir=


指定一个被包含 makefile 的搜索目标。可以使用多个“-I”参数来指定多个目录。

“-j []”
“–jobs[=]”
指同时运行命令的个数。如果没有这个参数,make 运行命令时能运行多少就运行多少。如
果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在 MS-DOS中是无用的)

“-k”
“–keep-going”
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。

“-l ”
“–load-average[=<load]”
“—max-load[=]”
指定 make 运行命令的负载

“-n”
“–just-print”
“–dry-run”
“–recon”
仅输出执行过程中的命令序列,但并不执行。

“-o ”
“–old-file=”
“–assume-old=”
不重新生成的指定的,即使这个目标的依赖文件新于它。

“-p”
“–print-data-base”
输出 makefile 中的所有数据,包括所有的规则和变量。这个参数会让一个简单的 makefile
都会输出一堆信息。如果你只是想输出信息而不想执行 makefile,你可以使用“make -qp”
命令。如果你想查看执行 makefile 前的预设变量和规则,你可以使用“make –p –f
/dev/null”。这个参数输出的信息会包含着你的 makefile 文件的文件名和行号,所以,用这个参数来调试你的 makefile 会是很有用的,特别是当你的环境变量很复杂的时候

“-q”
“–question”
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是 0 则说明要更新,如果是 2 则说明有错误发生。

“-r”
“–no-builtin-rules”

禁止 make 使用任何隐含规则

“-R”
“–no-builtin-variabes”

禁止 make 使用任何作用于变量上的隐含规则。

“-s”
“–silent”
“–quiet”
在命令运行时不输出命令的输出

“-S”
“–no-keep-going”
“–stop”
取消“-k”选项的作用。因为有些时候,make 的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。

“-t”
“–touch”
相当于 UNIX 的 touch 命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。

“-v”
“–version”
输出 make 程序的版本、版权等关于 make 的信息。

image-20230211231124336

“-w”
“–print-directory”
输出运行 makefile 之前和之后的信息。这个参数对于跟踪嵌套式调用 make 时很有用。

“–no-print-directory”
禁止“-w”选项

“-W ”
“–what-if=”
“–new-file=”
“–assume-file=”
假定目标需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行 UNIX 的“touch”命令一样,使得的修改时间
为当前时间。

“–warn-undefined-variables”
只要 make 发现有未定义的变量,那么就输出警告信息。

隐含规则

在我们使用 Makefile 时,有一些我们会经常使用,而且使用频率非常高的东西。比如,我们编译 C/C++的源程序为中间目标文件(Unix 下是[.o]文件,Windows 下是[.obj]文件)。本章讲述的就是一些在 Makefile 中的“隐含的”,早先约定了的,不需要我们再写出来的规则

使用隐含规则

如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make 会试图去自动推导产生这个目标的规则和命令,如果 make 可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是 make 事先约定好的一些东西。例如,我们有下面的一个 Makefile:

foo : foo.o bar.o
	cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

这个 Makefile 中并没有写下如何生成 foo.o 和 bar.o 这两目标的规则和命令。因为 make 的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令

make 会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make 调用的隐含规则是,把[.o]的目标 的依赖文件置成[.c],并使用 C 的编译命令“cc –c $(CFLAGS) [.c]”来生成[.o]的目标。也就是说,我们完全没有必要写下下面的两条规则:

foo.o : foo.c
	cc –c foo.c $(CFLAGS)
bar.o : bar.c
	cc –c bar.c $(CFLAGS)

因为,这是约定好的事,make和我们约定好了用C编译器"cc"生成[.o]文件的规则,这就是隐含规则。

当然,如果我们为[.o]文件书写了自己的规则,那么 make 就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。

还有,在 make 的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make
也不会管。如下面这条规则(没有命令):
foo.o : foo.p

依赖文件“foo.p”(Pascal 程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么我们的隐含规则一样会生效,并会通过“foo.c”调用 C 的编译器生成foo.o 文件。因为,在隐含规则中,Pascal 的规则出现在 C 的规则之后,所以,make 找到可以生成 foo.o 的 C 的规则就不再寻找下一条规则了。

tips

如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。

隐含规则一览

这里我们将讲述所有预先设置(也就是 make 内建)的隐含规则,如果我们不明确地写
下规则,那么,make 就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用 make的参数“-r”或“–no-builtin-rules”选项来取消所有的预设置的隐含规则

编译C程序的隐含规则

“.o”的目标的依赖目标会自动推导为“.c”,并且其生成命令是

“$(CC) –c $(CPPFLAGS) $(CFLAGS)”

编译 C++程序的隐含规则

“.o”的目标的依赖目标会自动推导为“.cc”或是“.C”, 并且其生成命令
是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。 (建议使用“.cc”作为 C++源文件的后缀,而
不是“.C”)

编译 Pascal 程序的隐含规则

“.o”的目标的依赖目标会自动推导为“.p”, 并且其生成命令是“$(PC) –c $
(PFLAGS)”。

编译 Fortran/Ratfor 程序的隐含规则

“.o”的目标的依赖目标会自动推导为“.r”或“.F”或“.f”, 并且其
生成命令是:
“.f” “$(FC) –c ( F F L A G S ) ”“ . F ”“ (FFLAGS)” “.F” “ (FFLAGS)”“.F”“(FC) –c $(FFLAGS) ( C P P F L A G S ) ”“ . f ”“ (CPPFLAGS)” “.f” “ (CPPFLAGS)”“.f”“(FC) –c $(FFLAGS) $(RFLAGS)”

预处理 Fortran/Ratfor 程序的隐含规则

“.f”的目标的依赖目标会自动推导为“.r”或“.F”。这个规则只是转换
Ratfor 或有预处理的 Fortran 程序到一个标准的 Fortran 程序。其使用的命令是:
“.F” “$(FC) –F $(CPPFLAGS) ( F F L A G S ) ”“ . r ”“ (FFLAGS)” “.r” “ (FFLAGS)”“.r”“(FC) –F $(FFLAGS) $(RFLAGS)”

编译 Modula-2 程序的隐含规则

.sym”的目标的依赖目标会自动推导为“.def”,并且其生成命令是:
( M 2 C ) (M2C) (M2C)(M2FLAGS) ( D E F F L A G S ) ”。“ < n . o > ”的目标的依赖目标会自动推导为“ < n > . m o d ”,并且其生成命令是:“ (DEFFLAGS)” 。 “<n.o>” 的 目 标 的 依 赖 目 标 会 自 动 推 导 为 “<n>.mod”, 并且其生成命令是:“ (DEFFLAGS)<n.o>的目标的依赖目标会自动推导为<n>.mod,并且其生成命令是:(M2C) $(M2FLAGS) $(MODFLAGS)”。

汇编和汇编预处理的隐含规则

“.o” 的目标的依赖目标会自动推导为“.s”,默认使用编译器“as”,并且
其生成命令是:“$(AS) ( A S F L A G S ) ”。“ < n > . s ”的目标的依赖目标会自动推导为“ < n > . S ”,默认使用 C 预编译器“ c p p ”,并且其生成命令是:“ (ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为 “<n>.S”,默认使用 C 预编译器“cpp”,并且其生成命令是:“ (ASFLAGS)<n>.s的目标的依赖目标会自动推导为<n>.S,默认使用C预编译器cpp,并且其生成命令是:(AS) $(ASFLAGS)”。

链接 Object 文件的隐含规则

“”目标依赖于“ .o”,通过运行 C 的编译器来运行链接程序生成(一般是
“ld”),其生成命令是:“$(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)”。这个规
则对于只有一个源文件的工程有效,同时也对多个 Object 文件(由不同的源文件生成)的
也有效。例如如下规则:
x : y.o z.o
并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o

定义模式规则

模式规则介绍

可以使用模式规则来定义一个隐含规则,一个模式规则就好像一个一般的规则。只是在规则中,目标的定义需要有“%”字符。“%”意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%“,只是依赖目标中的”%"的取值,取决于其目标。

如果"%“定义在目标中,那么,目标中的”%“的值决定了依赖目标中的”%“的值,也就是 说,目标中的模式的”%“决定了依赖目标中”%"的样子。例如有一个模式规则如下:

%.o : %.c ; <command ......>

其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是"a.o b.o",那么"%c"就是"a.c b.c"。

模式规则示例

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

" @ " 表示所有的目标的挨个值, " @"表示所有的目标的挨个值," @"表示所有的目标的挨个值,"<"表示了所有依赖目标的挨个值。

自动化变量

在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。

$@

​ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么

"$@"就是匹配于目标中模式定义的集合

$%

​ 仅当目标是函数库文件中,表示规则中的目标成员名。

例如,如果一个目标是"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 的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340480.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter Windows端打包并生成可安装文件流程

Windows打包 1.首先安装visual Studio 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/ 下载成功后按照下图勾选桌面应用和移动应用下的使用C的桌面开发&#xff0c;勾选右侧安装详细信息中的windows 11/10 sdk 中的任意一个完成安装即可 2.打包Windows …

IC封装常见形式

参考&#xff1a;https://blog.csdn.net/dhs888888/article/details/127673300?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-127673300-blog-115610343.pc_relevant_multi_platform_whitelistv4&spm1001.2101.3001.4242…

truffle 创建测试合约并部署到测试网络

1、npm 安装truffle npm install -g truffle2、创建truffle项目 mkdir imooc-on-blockchain-truffle && cd imooc-on-blockchain-truffle3、初始化truffle目录&#xff0c;会生成如下几个目录 contracts 存放.sol合约文件migrations 部署脚本目录test 测试文件目录t…

20230210组会论文总结

目录 【Ultra-High-Definition Low-Light Image Enhancement: A Benchmark and Transformer-Based Method】 【ShuffleMixer: An Efficient ConvNet for Image Super-Resolution】 【A Close Look at Spatial Modeling: From Attention to Convolution 】 【DEA-Net: Single i…

自动机,即有限状态机

文章目录一、问题来源二、题目描述三、题解中的自动机四、自动机学习五、有限状态机的使用场景一、问题来源 今天做力克题目的时候看到了字符串转换整数的一道算法题&#xff0c;其中又看到了题解中有自动机的概念&#xff0c;所以在这里对自动机做个笔记。题目链接 二、题目描…

详解Python正则表达式中group与groups的用法

在Python中&#xff0c;正则表达式的group和groups方法是非常有用的函数&#xff0c;用于处理匹配结果的分组信息。 group方法是re.MatchObject类中的一个函数&#xff0c;用于返回匹配对象的整个匹配结果或特定的分组匹配结果。而groups方法同样是re.MatchObject类中的函数&am…

MySQL主从同步-(一)搭建主机服务器

准备工作&#xff1a;搭建mysql 数据库 主从复同步系统 centos 7.5 运行环境 docker mysql版本 8.0.3 准备主机服务器第一步&#xff1a;停止虚拟机中其他 docker &#xff0c;关闭防火墙&#xff0c;关闭系统的mysql&#xff08;防止端口冲突&#xff09;docker stop $(docker…

C++11实现计算机网络中的TCP/IP连接(Windows端)

目录引言1、TCP2、IP2.1 IP路由器3、TCP/IP4、TCP/IP协议C11实现参考文献引言 TCP/IP 指传输控制协议/网际协议&#xff08;Transmission Control Protocol / Internet Protocol&#xff09;。[1] 在TCP/IP协议簇中主要包含以下内容&#xff1a; TCP (传输控制协议) - 应用程序…

Bing引擎引入ChatGPT申请候补名单方式

Bing引入ChatGPT申请候补名单方式ChatGPT一、ChatGPT简介二、OpenAI和Google的"军备军赛"三、ChatGPT版本⭐Bing引擎引入ChatGPT申请候补名单方式1.下载chrome拓展程序2.配置拓展程序3.Bing 申请候补名单3.1旧版本Bing3.2新版本Bing4.等待获取结尾ChatGPT 一、ChatG…

Linux ALSA 之十:ALSA ASOC Machine Driver

ALSA ASOC Machine Driver一、Machine 简介二、ASoC Machine Driver2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型2.2 在 Probe() 中注册声卡三、snd_soc_register_card 函数3.1 bind DAIs3.2 New a sound card3.3 Create card new widgets3.4 Probe …

JointBERT代码复现详解【上】

BERT for Joint Intent Classification and Slot Filling代码复现【上】 源码链接&#xff1a;JointBERT源码复现&#xff08;含注释&#xff09; 一、准备工作 源码架构 data&#xff1a;存放两个基准数据集&#xff1b;model&#xff1a;JointBert模型的实现&#xff1b…

修改本地host文件加入可用ip使谷歌浏览器翻译插件重新生效

修改本地host文件加入可用ip使谷歌浏览器翻译插件重新生效 第一步&#xff1a;找到host文件&#xff1a; 可以使用这个工具进行对Hosts文件进行一个查找 鼠标放到对应路径上面 点击鼠标右键&#xff0c;选择打开路径就到对应 路径了 也可以复制到这个路径下面去找hosts文件 …

【Node.js实战】一文带你开发博客项目之Koa2重构(实现session、开发路由、联调、日志)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Redis集群搭建(主从、哨兵、分片)

1.单机安装Redis 首先需要安装Redis所需要的依赖&#xff1a; yum install -y gcc tcl然后将课前资料提供的Redis安装包上传到虚拟机的任意目录&#xff1a; 例如&#xff0c;我放到了/tmp目录&#xff1a; 解压缩&#xff1a; tar -xzf redis-6.2.4.tar.gz解压后&#xff1…

C# Lambda表达式含义及各种写法

Lambda表达式在各个语言中的表达方式都不太相同&#xff0c;本文重点介绍C#的Lambda表达式。 首先&#xff0c;Lambda表达式就是一个匿名的方法/函数。 以下面的一个完整版作为例子&#xff0c;前面是参数&#xff0c;后面是返回值&#xff1a; 由于 Lambda表达式和委托常常一起…

AI推理计算框架中的内存优化

背景 内存管理是AI计算中非常重要的一部分。我们希望模型计算时占用内存尽可能小&#xff0c;这样我们训练或推理时就可以用更大的batch size使其尽快收敛&#xff0c;或者提高吞吐率。又或者让我们可以使用参数更多、或更复杂的模型从而达到更好的准确率。由于现代深度学习模…

【MySQL数据库】主从复制原理和应用

主从复制和读写分离1. 主从复制的原理2. 主从复制的环境配置2.1 准备好数据库服务器2.2 配置master2.3 配置slave2.4 测试3. 主从复制的应用——读写分离3.1 读写分离的背景3.2 Sharding-JDBC介绍3.3 Sharding-JDBC使用步骤1. 主从复制的原理 MySQL主从复制是一个异步的过程&a…

微服务 RocketMQ-延时消息 消息过滤 管控台搜索问题

~~微服务 RocketMQ-延时消息 消息过滤 管控台搜索问题~~ RocketMQ-延时消息实现延时消息RocketMQ-消息过滤Tag标签过滤SQL标签过滤管控台搜索问题RocketMQ-延时消息 给消息设置延时时间&#xff0c;到一定时间&#xff0c;消费者才能消费的到&#xff0c;中间件内部通过每秒钟扫…

TCP的运输连接管理

TCP的运输连接管理 文章目录TCP的运输连接管理TCP报文格式简介首部各个字段的含义控制位(flags)TCP的连接建立抓包验证一些细节及解答TCP连接释放抓包验证一些细节及解答参考TCP是面向连接的协议。运输连接是用来传送TCP报文的。TCP运输连接的建立和释放时每一次面向连接的通信…

第一部分:简单句——第一章:简单句的核心——二、简单句的核心变化(主语/宾语/表语的变化)

二、简单句的核心变化 简单句的核心变化其实就是 一主一谓&#xff08;n. v.&#xff09; 表达一件事情&#xff0c;谓语动词是其中最重要的部分&#xff0c;谓语动词的变化主要有四种&#xff1a;三态加一否&#xff08;时态、语态、情态、否定&#xff09;&#xff0c;其中…