This commit is contained in:
2021-11-14 15:52:46 +08:00
parent 915c231124
commit 1e344dc204
112 changed files with 1039 additions and 1039 deletions

View File

@@ -6,8 +6,8 @@ tags: ["makefile"]
categories: ["dev/ops"]
---
# Makefile 介绍
## 介绍
## Makefile 介绍
### 介绍
- make命令执行时需要一个 Makefile 文件以告诉make命令需要怎么样的去编译和链接程序。
- 首先我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册在这个示例中我们的工程有8个C文件和3个头文件我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是
- 如果这个工程没有编译过那么我们的所有C文件都要编译并被链接。
@@ -15,7 +15,7 @@ categories: ["dev/ops"]
- 如果这个工程的头文件被改变了那么我们需要编译引用了这几个头文件的C文件并链接目标程序。
- 只要我们的Makefile写得够好所有的这一切我们只用一个make命令就可以完成make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译从而自己编译所需要的文件和链接目标程序。
## Makefile的规则
### Makefile的规则
- 在讲述这个Makefile之前还是让我们先来粗略地看一看Makefile的规则。
```makefile
target... : prerequisites ...
@@ -28,7 +28,7 @@ categories: ["dev/ops"]
- command 也就是make需要执行的命令任意的Shell命令
- 这是一个文件的依赖关系也就是说target这一个或多个的目标文件依赖于prerequisites中的文件其生成规则定义在command中。说白一点就是说prerequisites中如果有一个以上的文件比target文件要新的话command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
## 一个示例
### 一个示例
- 正如前面所说的如果一个工程有3个头文件和8个C文件我们为了完成前面所述的那三个规则我们的Makefile应该是下面的这个样子的。
```makefile
edit : main.o kbd.o command.o display.o \
@@ -61,7 +61,7 @@ categories: ["dev/ops"]
- 在定义好依赖关系后后续的那一行定义了如何生成目标文件的操作系统命令一定要以一个Tab键作为开头。记住make并不管命令是怎么工作的他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期如果prerequisites文件的日期要比targets文件的日期要新或者target不存在的话那么make就会执行后续定义的命令。
- 这里要说明一点的是clean不是一个文件它只不过是一个动作名字有点像C语言中的lable一样其冒号后什么也没有那么make就不会自动去找文件的依赖性也就不会自动执行其后所定义的命令。要执行其后的命令就要在make命令后明显得指出这个lable的名字。这样的方法非常有用我们可以在一个makefile中定义不用的编译或是和编译无关的命令比如程序的打包程序的备份等等。
## make 是如何工作的
### make 是如何工作的
- 在默认的方式下也就是我们只输入make命令。那么
- make会在当前目录下找名字叫"Makefile"或"makefile"的文件。
- 如果找到它会找文件中的第一个目标文件target在上面的例子中他会找到"edit"这个文件,并把这个文件作为最终的目标文件。
@@ -73,7 +73,7 @@ categories: ["dev/ops"]
- 于是在我们编程中如果这个工程已被编译过了当我们修改了其中一个源文件比如file.c那么根据我们的依赖性我们的目标file.o会被重编译也就是在这个依性关系后面所定义的命令于是file.o的文件也是最新的啦于是file.o的文件修改时间要比edit要新所以edit也会被重新链接了详见edit目标文件后定义的命令
而如果我们改变了"command.h"那么kdb.o、command.o和files.o都会被重编译并且edit会被重链接。
## makefile中使用变量
### makefile中使用变量
- 在上面的例子中先让我们看看edit的规则
```makefile
edit : main.o kbd.o command.o display.o \
@@ -114,7 +114,7 @@ categories: ["dev/ops"]
```
- 如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。
## 让make自动推导
### 让make自动推导
- GNU的make很强大它可以自动推导文件以及文件依赖关系后面的命令于是我们就没必要去在每一个[.o]文件后都写上类似的命令因为我们的make会自动识别并自己推导命令。
- 只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中如果make找到一个whatever.o那么whatever.c就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来于是我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。
```makefile
@@ -139,7 +139,7 @@ categories: ["dev/ops"]
```
- 这种方法也就是make的"隐晦规则"。上面文件内容中,".PHONY"表示clean是个伪目标文件。
## 另类风格的makefile
### 另类风格的makefile
- 既然我们的make可以自动推导命令那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h]能不能把其收拢起来好吧没有问题这个对于make来说很容易谁叫它提供了自动推导命令和文件的功能呢来看看最新风格的makefile吧。
```makefile
objects = main.o kbd.o command.o display.o \
@@ -158,7 +158,7 @@ categories: ["dev/ops"]
```
- 这种风格让我们的makefile变得很简单但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了
## 清空目标文件的规则
### 清空目标文件的规则
- 每个Makefile中都应该写一个清空目标文件.o和执行文件的规则这不仅便于重编译也很利于保持文件的清洁。这是一个"修养"(呵呵,还记得我的《编程修养》吗)。一般的风格都是:
```makefile
clean:
@@ -173,8 +173,8 @@ categories: ["dev/ops"]
- 前面说过,.PHONY意思表示clean是一个"伪目标"而在rm命令前面加了一个小减号的意思就是也许某些文件出现问题但不要管继续做后面的事。当然clean的规则不要放在文件的开头不然这就会变成make的默认目标相信谁也不愿意这样。不成文的规矩是 "clean从来都是放在文件的最后"。
# Makefile 总述
## Makefile里有什么
## Makefile 总述
### Makefile里有什么
- Makefile里主要包含了五个东西显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出要生成的文件文件的依赖文件生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile这是由make所支持的。
@@ -183,11 +183,11 @@ categories: ["dev/ops"]
- 注释。Makefile中只有行注释和UNIX的Shell脚本一样其注释是用"#"字符这个就像C/C++中的"//"一样。如果你要在你的Makefile中使用"#"字符,可以用反斜框进行转义,如:"\#"。
- 最后还值得一提的是在Makefile中的命令必须要以[Tab]键开始。
## Makefile的文件名
### Makefile的文件名
- 默认的情况下make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,找到了解释这个文件。在这三个文件名中,最好使用"Makefile"这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用"GNUmakefile"这个文件是GNU的make识别的。有另外一些make只对全小写的"makefile"文件名敏感但是基本上来说大多数的make都支持"makefile"和"Makefile"这两种默认文件名。
- 当然你可以使用别的文件名来书写Makefile比如"Make.Linux""Make.Solaris""Make.AIX"等如果要指定特定的Makefile你可以使用make的"-f"和"--file"参数make -f Make.Linux或make --file Make.AIX。
## 引用其它的Makefile
### 引用其它的Makefile
- 在Makefile使用include关键字可以把别的Makefile包含进来这很像C语言的#include被包含的文件会原模原样的放在当前文件的包含位置。include的语法是
```makefile
include<filename>filename可以是当前操作系统Shell的文件模式可以保含路径和通配符
@@ -209,11 +209,11 @@ categories: ["dev/ops"]
```
- 其表示无论include过程中出现什么错误都不要报错继续执行。和其它版本make兼容的相关命令是sinclude其作用和这一个是一样的。
## 环境变量 MAKEFILES
### 环境变量 MAKEFILES
- 如果你的当前环境中定义了环境变量MAKEFILES那么make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile用空格分隔。只是它和include不同的是从这个环境变中引入的Makefile的"目标"不会起作用如果环境变量中定义的文件发现错误make也会不理。
- 但是在这里我还是建议不要使用这个环境变量因为只要这个变量一被定义那么当你使用make时所有的Makefile都会受到它的影响这绝不是你想看到的。在这里提这个事只是为了告诉大家也许有时候你的Makefile出现了怪事那么你可以看看当前环境中有没有定义这个变量。
## make的工作方式
### make的工作方式
- GNU的make工作时的执行步骤入下
- 读入所有的Makefile。
- 读入被include的其它Makefile。
@@ -225,13 +225,13 @@ categories: ["dev/ops"]
- 步为第一个阶段6-7为第二个阶段。第一个阶段中如果定义的变量被使用了那么make会把其展开在使用的位置。但make并不会完全马上展开make使用的是拖延战术如果变量出现在依赖关系的规则中那么仅当这条依赖被决定要使用了变量才会在其内部展开。
- 当然这个工作方式你不一定要清楚但是知道这个方式你也会对make更为熟悉。有了这个基础后续部分也就容易看懂了。
# Makefile书写规则
## 规则
## Makefile书写规则
### 规则
- 规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
- 在Makefile中规则的顺序是很重要的因为Makefile中只应该有一个最终目标其它的目标都是被这个目标所连带出来的所以一定要让make知道你的最终目标是什么。一般来说定义在Makefile中的目标可能会有很多但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个那么第一个目标会成为最终的目标。make所完成的也就是这个目标。
- 好了,还是让我们来看一看如何书写规则。
## 规则举例
### 规则举例
```makefile
foo.o: foo.c defs.h # foo模块
cc -c -g foo.c
@@ -240,7 +240,7 @@ categories: ["dev/ops"]
- 文件的依赖关系foo.o依赖于foo.c和defs.h的文件如果foo.c和defs.h的文件日期要比foo.o文件日期要新或是foo.o不存在那么依赖关系发生。
- 如果生成或更新foo.o文件。也就是那个cc命令其说明了如何生成foo.o这个文件。当然foo.c文件include了defs.h文件
## 规则的语法
### 规则的语法
```makefile
targets : prerequisites
command
@@ -258,7 +258,7 @@ categories: ["dev/ops"]
- 如果命令太长,你可以使用反斜框(‘\作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事文件的依赖关系和如何成成目标文件。
- 一般来说make会以UNIX的标准Shell也就是/bin/sh来执行命令。
## 在规则中使用通配符
### 在规则中使用通配符
- 如果我们想定义一系列比较类似的文件我们很自然地就想起使用通配符。make支持三各通配符"\*""?"和"[...]"。这是和Unix的B-Shell是相同的。
- "~" 波浪号("~")字符在文件名中也有比较特殊的用途。如果是"~/test",这就表示当前用户的$HOME目录下的test目录。而"~hchen/test"则表示用户hchen的宿主目录下的test目录。这些都是Unix下的小知识了make也支持而在Windows或是MS-DOS下用户没有宿主目录那么波浪号所指的目录则根据环境变量"HOME"而定。
- "\*" 通配符代替了你一系列的文件,如"\*.c"表示所以后缀为c的文件。一个需要我们注意的是如果我们的文件名中有通配符"\*",那么可以用转义字符"\",如"\*"来表示真实的"\*"字符,而不是任意长度的字符串。
@@ -283,7 +283,7 @@ categories: ["dev/ops"]
```
- 这种用法由关键字"wildcard"指出关于Makefile的关键字我们将在后面讨论。
## 文件搜寻
### 文件搜寻
- 在一些大的工程中有大量的源文件我们通常的做法是把这许多的源文件分类并存放在不同的目录中。所以当make需要去找寻文件的依赖关系时你可以在文件前加上路径但最好的方法是把一个路径告诉make让make在自动去找。
- Makefile文件中的特殊变量"VPATH"就是完成这个功能的如果没有指明这个变量make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量那么make就会在当当前目录找不到的情况下到所指定的目录中去找寻文件了。
```makefile
@@ -312,7 +312,7 @@ categories: ["dev/ops"]
```
- 而上面的语句则表示".c"结尾的文件,先在"foo"目录,然后是"bar"目录,最后才是"blish"目录。
## 伪目标
### 伪目标
- 最早先的一个例子中,我们提到过一个"clean"的目标,这是一个"伪目标"
```makefile
clean:
@@ -360,7 +360,7 @@ categories: ["dev/ops"]
```
- "makeclean"将清除所有要被清除的文件。"cleanobj"和"cleandiff"这两个伪目标有点像"子程序"的意思。我们可以输入"makecleanall"和"make cleanobj"和"makecleandiff"命令来达到清除不同种类文件的目的
## 多目标
### 多目标
- Makefile的规则中的目标可以不止一个其支持多目标有可能我们的多个目标同时依赖于一个文件并且其生成的命令大体类似。于是我们就能把其合并起来。当然多个目标的生成规则的执行命令是同一个这可能会可我们带来麻烦不过好在我们的可以使用一个自动化变量"$@"(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
```makefile
bigoutput littleoutput : text.g
@@ -375,7 +375,7 @@ categories: ["dev/ops"]
```
- 其中,-$(subst output,,$@)中的"$"表示执行一个Makefile的函数函数名为subst后面的为参数。关于函数将在后面讲述。这里的这个函数是截取字符串的意思"$@"表示目标的集合,就像一个数组,"$@"依次取出目标,并执于命令。
## 静态模式
### 静态模式
- 静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:
```makefile
<targets...>: <target-pattern>: <prereq-patterns ...>
@@ -414,7 +414,7 @@ categories: ["dev/ops"]
```
- $(filter%.o,$(files))表示调用Makefile的filter函数过滤"$filter"集,只要其中模式为"%.o"的内容。其的它内容我就不用多说了吧。这个例字展示了Makefile中更大的弹性。
## 自动生成依赖性
### 自动生成依赖性
- 在Makefile中我们的依赖关系可能会需要包含一系列的头文件比如如果我们的main.c中有一句"#include "defs.h"",那么我们的依赖关系应该是:
```makefile
main.o : main.c defs.h
@@ -476,12 +476,12 @@ categories: ["dev/ops"]
```
- 上述语句中的"$(sources:.c=.d)"中的".c=.d"的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个"替换"的内容在后面我会有更为详细的讲述。当然你得注意次序因为include是按次来载入文件最先载入的[.d]文件中的目标会成为默认目标
# Makefile 书写命令
## 命令
## Makefile 书写命令
### 命令
- 每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令每条命令的开头必须以[Tab]键开头除非命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略但是如果该空格或空行是以Tab键开头的那么make会认为其是一个空命令。
- 我们在UNIX下可能会使用不同的Shell但是make的命令默认是被"/bin/sh" UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中"#"是注释符很像C/C++中的"//",其后的本行字符都被注释。
## 显示命令
### 显示命令
- 通常make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用"@"字符在命令行前那么这个命令将不被make显示出来最具代表性的例子是我们用这个功能来像屏幕显示一些信息。如
```makefile
@echo 正在编译XXX模块......
@@ -494,7 +494,7 @@ categories: ["dev/ops"]
- 如果make执行时带入make参数"-n"或"--just-print"那么其只是显示命令但不会执行命令这个功能很有利于我们调试我们的Makefile看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
- make参数"-s"或"--slient"则是全面禁止命令的显示。
## 命令执行
### 命令执行
- 当依赖目标新于目标时也就是当规则的目标需要被更新时make会一条一条的执行其后的命令。需要注意的是如果你要让上一条命令的结果应用在下一条命令时你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令你希望第二条命令得在cd之后的基础上运行那么你就不能把这两条命令写在两行上而应该把这两条命令写在一行上用分号分隔。如
```makefile
# 示例一:
@@ -509,7 +509,7 @@ categories: ["dev/ops"]
- 当我们执行"make exec"时第一个例子中的cd没有作用pwd会打印出当前的Makefile目录而第二个例子中cd就起作用了pwd会打印出"/home/hchen"。
- make 一般是使用环境变量SHELL中所定义的系统Shell来执行命令默认情况下使用UNIX的标准Shell /bin/sh来执行命令。但在MS-DOS下有点特殊因为MS-DOS下没有SHELL环境变量当然你也可以指定。如果你指定了UNIX风格的目录形式首先make会在SHELL所指定的路径中找寻命令解释器如果找不到其会在当前盘符中的当前目录中寻找如果再找不到其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中如果你定义的命令解释器没有找到其会给你的命令解释器加上诸如".exe"、".com"、".bat"、".sh"等后缀。
## 命令出错
### 命令出错
- 每当命令运行完后make会检测每个命令的返回码如果命令返回成功那么make会执行下一条命令当规则中所有的命令成功返回后这个规则就算是成功完成了。如果一个规则中的某个命令出错了命令退出码非零那么make就会终止执行当前规则这将有可能终止所有规则的执行。
- 有些时候命令的出错并不表示就是错误的。例如mkdir命令我们一定需要建立一个目录如果目录不存在那么mkdir就成功执行万事大吉如果目录存在那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录于是我们就不希望mkdir出错而终止规则的运行。
- 为了做到这一点忽略命令的出错我们可以在Makefile的命令行前加一个减号"-"在Tab键之后标记为不管命令出不出错都认为是成功的。如
@@ -520,7 +520,7 @@ categories: ["dev/ops"]
- 还有一个全局的办法是给make加上"-i"或是"--ignore-errors"参数那么Makefile中所有命令都会忽略错误。而如果一个规则是以".IGNORE"作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。
- 还有一个要提一下的make的参数的是"-k"或是"--keep-going",这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
## 嵌套执行make
### 嵌套执行make
- 在一些大的工程中我们会把我们不同模块或是不同功能的源文件放在不同的目录中我们可以在每个目录中都书写一个该目录的Makefile这有利于让我们的Makefile变得更加地简洁而不至于把所有的东西全部写在一个Makefile中这样会很难维护我们的Makefile这个技术对于我们模块编译和分段编译有着非常大的好处。
- 例如我们有一个子目录叫subdir这个目录下有个Makefile文件来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写
```makefile
@@ -579,7 +579,7 @@ categories: ["dev/ops"]
```
- 当你使用"-C"参数来指定make下层Makefile时"-w"会被自动打开的。如果参数中有"-s""--slient")或是"--no-print-directory",那么,"-w"总是失效的。
## 定义命令包
### 定义命令包
- 如果Makefile中出现一些相同命令序列那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以"define"开始,以"endef"结束,如:
```makefile
define run-yacc
@@ -595,8 +595,8 @@ categories: ["dev/ops"]
- 我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包"run-yacc"中的"$^"就是"foo.y""$@"就是"foo.c"(有关这种以"$"开头的特殊变量我们会在后面介绍make在执行命令包时命令包中的每个命令会被依次独立执行。
- 在 Makefile中的定义的变量就像是C/C++语言中的宏一样他代表了一个文本字串在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是你可以在Makefile中改变其值。在Makefile中变量可以使用在"目标""依赖目标""命令"或是 Makefile的其它部分中。变量的命名字可以包含字符、数字下划线可以是数字开头但不应该含有":"、"#"、"="或是空字符(空格、回车等)。变量是大小写敏感的,"foo"、"Foo"和"FOO"是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式但我推荐使用大小写搭配的变量名MakeFlags。这样可以避免和系统的变量冲突而发生意外的事情。有一些变量是很奇怪字串如"$<"、"$@"等,这些是自动化变量。
# 变量
## 变量的基础
## 变量
### 变量的基础
- 变量在声明时需要给予初值,而在使用时,需要给在变量名前加上"$"符号,但最好用小括号""或是大括号"{}"把变量给包括起来。如果你要使用真实的"$"字符,那么你需要用"$$"来表示。变量可以使用在许多地方,如规则中的"目标"、"依赖"、"命令"以及新的变量中。
- 先看一个例子:
```makefile
@@ -619,7 +619,7 @@ categories: ["dev/ops"]
```
- 当然千万不要在你的Makefile中这样干这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个"替代"的原理。另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。
## 变量中的变量
### 变量中的变量
- 在定义变量的值时我们可以使用其它变量来构造变量的值在Makefile中有两种方式来在用变量定义变量的值。
- 先看第一种方式,也就是简单的使用"="号,在"="左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好
的值,其也可以使用后面定义的值。如:
@@ -694,7 +694,7 @@ categories: ["dev/ops"]
endif
```
## 变量高级用法
### 变量高级用法
- 这里介绍两种变量的高级使用方法,第一种是变量值的替换。
- 我们可以替换变量中的共有的部分,其格式是"$(var:a=b)"或是"${var:a=b}",其意思是,把变量"var"中所有以"a"字串"结尾"的"a"替换成"b"字串。这里的"结尾"意思是"空格"或是"结束符"。
- 还是看一个示例吧:
@@ -779,7 +779,7 @@ categories: ["dev/ops"]
```
- 这个例子中定义了三个变量:"dir""foo_sources"和"foo_print"。
## 追加变量值
### 追加变量值
- 我们可以使用"+="操作符给变量追加值,如:
```makefile
objects = main.o foo.o bar.o utils.o
@@ -809,7 +809,7 @@ categories: ["dev/ops"]
```
- 由于前次的赋值符是"=",所以"+="也会以"="来做为赋值那么岂不会发生变量的递补归定义这是很不好的所以make会自动为我们解决这个问题我们不必担心这个问题。
## override 指示符
### override 指示符
- 如果有变量是通常make的命令行参数设置的那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值那么你可以使用"override"指示符。其语法是:
```makefile
override <variable> = <value>
@@ -826,7 +826,7 @@ categories: ["dev/ops"]
endef
```
## 多行变量
### 多行变量
- 还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行这有利于定义一系列的命令前面我们讲过"命令包"的技术就是利用这个关键字)。
- define 指示符后面跟的是变量的名字而重起一行定义变量的值定义是以endef关键字结束。其工作方式和"="操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头所以如果你用define定义的命令变量中没有以[Tab]键开头那么make就不会把其认为是命令。
- 下面的这个示例展示了define的用法
@@ -837,12 +837,12 @@ categories: ["dev/ops"]
endef
```
## 环境变量
### 环境变量
- make 运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中但是如果Makefile中已定义了这个变量或是这个变量由make命令行带入那么系统的环境变量的值将被覆盖。如果make指定了"-e"参数那么系统环境变量将覆盖Makefile中定义的变量
- 因此,如果我们在环境变量中设置了"CFLAGS"环境变量那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS那么则会使用Makefile中的这个变量如果没有定义则使用系统环境变量的值一个共性和个性的统一很像"全局变量"和"局部变量"的特性。 当make嵌套调用时参见前面的"嵌套调用"章节上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然默认情况下只有通过命令行设置的变量会被传递。而定义在文件中的变量如果要向下层 Makefile传递则需要使用exprot关键字来声明。参见前面章节
- 当然我并不推荐把许多的变量都定义在系统环境中这样在我们执行不用的Makefile时拥有的是同一套系统变量这可能会带来更多的麻烦。
## 目标变量
### 目标变量
- 前面我们所讲的在Makefile中定义的变量都是"全局变量",在整个文件,我们都可以访问这些变量。当然,"自动化变量"除外,如"$<"等这种类量的自动化变量就属于"规则型变量",这种变量的值依赖于规则的目标和依赖目标的定义。
- 当然,我样同样可以为某个目标设置局部变量,这种变量被称为"Target-specific Variable",它可以和"全局变量"同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
- 其语法是:
@@ -867,7 +867,7 @@ categories: ["dev/ops"]
```
- 在这个示例中,不管全局的$(CFLAGS)的值是什么在prog目标以及其所引发的所有规则中prog.o foo.o bar.o的规则$(CFLAGS)的值都是"-g"
## 模式变量
### 模式变量
- 在GNU的make中还支持模式变量Pattern-specific Variable通过上面的目标变量中我们知道变量可以定义在某个目标上。模式变量的好处就是我们可以给定一种"模式",可以把变量定义在符合这种模式的所有目标上。
- 我们知道make的"模式"一般是至少含有一个"%"的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:
```makefile
@@ -880,8 +880,8 @@ categories: ["dev/ops"]
```
- override同样是针对于系统环境传入的变量或是make命令行指定的变量。
# 条件判断
## 一个例子
## 条件判断
### 一个例子
- 下面的例子,判断$(CC)变量是否"gcc"如果是的话则使用GNU函数编译目标。
```makefile
libs_for_gcc = -lgnu
@@ -921,7 +921,7 @@ categories: ["dev/ops"]
$(CC) -o foo $(objects) $(libs)
```
## 语法
### 语法
- 条件表达式的语法为:
```makefile
<conditional-directive>
@@ -994,8 +994,8 @@ categories: ["dev/ops"]
- 特别注意的是make是在读取Makefile时就计算条件表达式的值并根据条件表达式的值来选择语句所以你最好不要把自动化变量如"$@"等放入条件表达式中因为自动化变量是在运行时才有的。而且为了避免混乱make不允许把整个条件语句分成两部分放在不同的文件中。
# 函数
## 函数的调用语法
## 函数
### 函数的调用语法
- 函数调用,很像变量的使用,也是以"$"来标识的,其语法如下:
```makefile
$(<function> <arguments> )
@@ -1015,8 +1015,8 @@ categories: ["dev/ops"]
```
- 在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是"a b c"$(bar)的定义用,调用了函数"subst",这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是"a,b,c"。
## 字符串处理函数
### 字符串替换函数 subst
### 字符串处理函数
#### 字符串替换函数 subst
- 功能:把字串\<text\>中的<from>字符串替换成<to>。
- 返回:函数返回被替换过后的字符串。
- 语法:
@@ -1029,7 +1029,7 @@ categories: ["dev/ops"]
```
- 把"feet on the street"中的"ee"替换成"EE",返回结果是"fEEt on the strEEt"。
### 模式字符串替换函数 patsubst
#### 模式字符串替换函数 patsubst
- 功能:查找\<text\>中的单词(单词以"空格"、"Tab"或"回车""换行"分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符"%",表示任意长度的字串。如果<replacement>中也包含"%",那么,<replacement>中的这个"%"将是<pattern>中的那个"%"所代表的字串。(可以用"\"来转义,以"\%"来表示真实含义的"%"字符)返回:函数返回被替换过后的字符串。
- 语法:
```makefile
@@ -1058,7 +1058,7 @@ categories: ["dev/ops"]
```
- 例如有objects = foo.o bar.o baz.o那么"$(objects:.o=.c)"和"$(patsubst %.o,%.c,$(objects))"是一样的。
### 去空格函数 strip
#### 去空格函数 strip
- 功能:去掉<string>字串中开头和结尾的空字符。
- 返回:返回被去掉空格的字符串值。
- 语法
@@ -1071,7 +1071,7 @@ categories: ["dev/ops"]
```
- 把字串"a b c "去到开头和结尾的空格,结果是"a b c"。
### 查找字符串函数findstring
#### 查找字符串函数findstring
- 功能:在字串<in>中查找<find>字串。
- 返回:如果找到,那么返回<find>,否则返回空字符串。
- 语法:
@@ -1085,7 +1085,7 @@ categories: ["dev/ops"]
```
- 第一个函数返回"a"字符串,第二个返回""字符串(空字符串)
### 过滤函数 filter
#### 过滤函数 filter
- 功能:以<pattern>模式过滤\<text\>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
- 返回:返回符合模式<pattern>的字串。
- 语法:
@@ -1101,7 +1101,7 @@ categories: ["dev/ops"]
- $(filter %.c %.s,$(sources))返回的值是"foo.c bar.c baz.s"。
### 反过滤函数 filter-out
#### 反过滤函数 filter-out
- 功能:以<pattern>模式过滤\<text\>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
- 返回:返回不符合模式<pattern>的字串。
- 语法:
@@ -1115,7 +1115,7 @@ categories: ["dev/ops"]
```
- $(filter-out $(mains),$(objects)) 返回值是"foo.o bar.o"。
### 排序函数 sort。
#### 排序函数 sort。
- 功能:给字符串<list>中的单词排序(升序)。
- 返回:返回排序后的字符串。
- 语法:
@@ -1125,7 +1125,7 @@ categories: ["dev/ops"]
- 示例:$(sort foo bar lose)返回"bar foo lose" 。
- 备注sort函数会去掉<list>中相同的单词。
### 取单词函数 word
#### 取单词函数 word
- 功能:取字符串\<text\>中第<n>个单词。(从一开始)
- 返回:返回字符串\<text\>中第<n>个单词。如果<n>比\<text\>中的单词数要大,那么返回空字符串。
- 语法:
@@ -1134,7 +1134,7 @@ categories: ["dev/ops"]
```
- 示例:$(word 2, foo bar baz)返回值是"bar"。
### 取单词串函数 wordlist
#### 取单词串函数 wordlist
- 功能:从字符串\<text\>中取从\<s\>开始到<e>的单词串,\<s\>和<e>是一个数字。
- 返回:返回字符串\<text\>中从\<s\>到<e>的单词字串。如果\<s\>比\<text\>中的单词数要大,那么返回空字符串。如果<e>大于\<text\>的单词数,那么返回从\<s\>开始,到\<text\>结束的单词串。
- 语法:
@@ -1143,7 +1143,7 @@ categories: ["dev/ops"]
```
- 示例: $(wordlist 2, 3, foo bar baz)返回值是"bar baz"。
### 单词个数统计函数 words
#### 单词个数统计函数 words
- 功能:统计\<text\>中字符串中的单词个数。
- 返回:返回\<text\>中的单词数。
- 语法:
@@ -1156,7 +1156,7 @@ categories: ["dev/ops"]
$(word $(words \<text\>),\<text\> )。
```
### 首单词函数 firstword
#### 首单词函数 firstword
- 功能:取字符串\<text\>中的第一个单词。
- 返回:返回字符串\<text\>的第一个单词。
- 语法:
@@ -1166,17 +1166,17 @@ categories: ["dev/ops"]
- 示例:$(firstword foo bar)返回值是"foo"。
- 备注这个函数可以用word函数来实现$(word 1,\<text\> )。
### 应用例子
#### 应用例子
- make使用"VPATH"变量来指定"依赖文件"的搜索路径我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS
```makefile
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
```
- 如果我们的"$(VPATH)"值是"src:../headers",那么"$(patsubst %,-I%,$(subst :, ,$(VPATH)))"将返回"-Isrc -I../headers"这正是cc或gcc搜索头文件路径的参数 。
## 文件名操作函数
### 文件名操作函数
- 下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。
### 取目录函数 dir
#### 取目录函数 dir
- 功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠("/")之
- 前的部分。如果没有反斜杠,那么返回"./"。
- 返回:返回文件名序列<names>的目录部分。
@@ -1186,7 +1186,7 @@ categories: ["dev/ops"]
```
- 示例: $(dir src/foo.c hacks)返回值是"src/ ./"。
### 取文件函数 notdir
#### 取文件函数 notdir
- 功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠("/")之后的部分。
- 返回:返回文件名序列<names>的非目录部分。
- 语法:
@@ -1195,7 +1195,7 @@ categories: ["dev/ops"]
```
- 示例: $(notdir src/foo.c hacks)返回值是"foo.c hacks"。
### 取后缀函数 suffix
#### 取后缀函数 suffix
- 功能:从文件名序列<names>中取出各个文件名的后缀。
- 返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
- 语法:
@@ -1204,7 +1204,7 @@ categories: ["dev/ops"]
```
- 示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是".c .c"。
### 取前缀函数 basename
#### 取前缀函数 basename
- 功能:从文件名序列<names>中取出各个文件名的前缀部分。
- 返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
- 语法:
@@ -1213,7 +1213,7 @@ categories: ["dev/ops"]
```
- 示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是"src/foo src-1.0/bar hacks"。
### 加后缀函数 addsuffix
#### 加后缀函数 addsuffix
- 功能:把后缀<suffix>加到<names>中的每个单词后面。
- 返回:返回加过后缀的文件名序列。
- 语法:
@@ -1222,7 +1222,7 @@ categories: ["dev/ops"]
```
- 示例:$(addsuffix .c,foo bar)返回值是"foo.c bar.c"。
### 加前缀函数 addprefix
#### 加前缀函数 addprefix
- 功能:把前缀<prefix>加到<names>中的每个单词后面。
- 返回:返回加过前缀的文件名序列。
- 语法:
@@ -1231,7 +1231,7 @@ categories: ["dev/ops"]
```
- 示例:$(addprefix src/,foo bar)返回值是"src/foo src/bar"。
### 连接函数 join。
#### 连接函数 join。
- 功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
- 返回:返回连接过后的字符串。
- 语法:
@@ -1240,7 +1240,7 @@ categories: ["dev/ops"]
```
- 示例:$(join aaa bbb , 111 222 333)返回值是"aaa111 bbb222 333"。
## foreach 函数
### foreach 函数
- foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的Makefile中的foreach函数几乎是仿照于Unix标准Shell/bin /sh中的for语句或是C-Shell/bin/csh中的foreach语句而构建的。它的语法是
```makefile
$(foreach <var>,<list>,\<text\> )
@@ -1255,7 +1255,7 @@ categories: ["dev/ops"]
- 上面的例子中,$(name)中的单词会被挨个取出,并存到变量"n"中,"$(n).o"每次根据"$(n)"计算出一个值这些值以空格分隔最后作为foreach函数的返回所以$(files)的值是"a.o b.o c.o d.o"。
- 注意foreach中的<var>参数是一个临时的局部变量foreach函数执行完后参数<var>的变量将不在作用其作用域只在foreach函数当中。
## if 函数
### if 函数
- if函数很像GNU的make所支持的条件语句 ifeq参见前面所述的章节if函数的语法是
```makefile
$(if <condition>,<then-part> )
@@ -1268,7 +1268,7 @@ categories: ["dev/ops"]
- 而if函数的返回值是如果<condition>为真(非空字符串),那个<then- part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。
- 所以,<then-part>和<else-part>只会有一个被计算。
## call函数
### call函数
- call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式这个表达式中你可以定义许多参数然后你可以用call函数来向这个表达式传递参数。其语法是
```makefile
$(call <expression>,<parm1>,<parm2>,<parm3>...)
@@ -1285,7 +1285,7 @@ categories: ["dev/ops"]
```
- 此时的foo的值就是"b a"。
## origin 函数
### origin 函数
- origin函数不像其它的函数他并不操作变量的值他只是告诉你你的这个变量是哪里来的其语法是
```makefile
$(origin <variable> )
@@ -1308,7 +1308,7 @@ categories: ["dev/ops"]
```
- 当然你也许会说使用override关键字不就可以重新定义环境中的变量了吗为什么需要使用这样的步骤是的我们用override是可以达到这样的效果可是override过于粗暴它同时会把从命令行定义的变量也覆盖了而我们只想重新定义环境传来的而不想重新定义命令行传来的。
## shell函数
### shell函数
- shell 函数也不像其它的函数。顾名思义它的参数应该就是操作系统Shell的命令。它和反引号"`"是相同的功能。这就是说shell函数把执行操作系统命令后的输出作为函数返回。于是我们可以用操作系统命令以及字符串处理命令awksed等等命令来生成一个变量
```makefile
contents := $(shell cat foo)
@@ -1317,7 +1317,7 @@ categories: ["dev/ops"]
- 注意这个函数会新生成一个Shell程序来执行命令所以你要注意其运行性能如果你的Makefile中有一些比较复杂的规则并大量使用了这个函数那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。
## 控制make的函数
### 控制make的函数
- make提供了一些函数来控制make的运行。通常你需要检测一些运行Makefile时的运行时信息并且根据这些信息来决定你是让make继续执行还是停止。
```makefile
$(error <text ...> )
@@ -1340,15 +1340,15 @@ categories: ["dev/ops"]
$(warning <text ...> )
```
# make 的运行
## make 退出码
## make 的运行
### make 退出码
- make命令执行后有三个退出码
- 0 表示成功执行。
- 1 如果make运行时出现任何错误其返回1。
- 2 如果你使用了make的"-q"选项并且make使得一些目标不需要更新那么返回2。
- Make的相关参数我们会在后续章节中讲述。
## 指定Makefile
### 指定Makefile
- 前面我们说过GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件 "GNUmakefile"、"makefile"和"Makefile"。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。
- 当前我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能我们要使用make的"-f"或是"--file"参数("-- makefile"参数也行。例如我们有个makefile的名字是"hchen.mk"那么我们可以这样来让make来执行这个文件
```bash
@@ -1356,7 +1356,7 @@ categories: ["dev/ops"]
```
- 如果在make的命令行是你不只一次地使用了"-f"参数那么所有指定的makefile将会被连在一起传递给make执行。
## 指定目标
### 指定目标
- 一般来说make的最终目标是makefile中的第一个目标而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然一般来说你的 makefile中的第一个目标是由许多个目标组成你可以指示make让其完成你所指定的目标。要达到这一目的很简单需在make命令后直接跟目标的名字就可以完成如前面提到的"make clean"形式任何在makefile中的目标都可以被指定成终极目标但是除了以"- "打头,或是包含了"="的目标因为有这些字符的目标会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标也就是说只要make可以找到其隐含规则推导规则那么这个隐含目标同样可以被指定成终极目标。
- 有一个make的环境变量叫"MAKECMDGOALS",这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比如下面的例子:
```makefile
@@ -1383,7 +1383,7 @@ categories: ["dev/ops"]
- "check"和"test" 这两个伪目标一般用来测试makefile的流程。
- 当然一个项目的makefile中也不一定要书写这样的目标这些东西都是GNU的东西但是我想GNU搞出这些东西一定有其可取之处等你的UNIX下的程序文件一多时你就会发现这些功能很有用了这里只不过是说明了如果你要书写这种功能最好使用这种名字命名你的目标这样规范一些规范的好处就是 不用解释大家都明白。而且如果你的makefile中有这些功能一是很实用二是可以显得你的makefile很专业不是那种初学者的作品
## 检查规则
### 检查规则
- 有时候我们不想让我们的makefile中的规则执行起来我们只想检查一下我们的命令或是执行的序列。于是我们可以使用make命令的下述参数
- "-n" "--just-print" "--dry-run" "--recon" 不执行参数这些参数只是打印命令不管目标是否更新把规则和连带规则下的命令打印出来但不执行这些参数对于我们调试makefile很有用处。
- "-t" "--touch" 这个参数的意思就是把目标文件的时间更新但不更改目标文件。也就是说make假装编译目标但不是真正的编译目标只是把目标变成已编译过的状态。
@@ -1391,7 +1391,7 @@ categories: ["dev/ops"]
- "-W <file>" "--what-if=<file>" "--assume-new=<file>" "--new-file=<file>" 这个参数需要指定一个文件。一般是是源文件或依赖文件Make会根据规则推导来运行依赖于这个文件的命令一般来说可以和"-n"参数一同使用,来查看这个依赖文件所发生的规则命令。
- 另外一个很有意思的用法是结合"-p"和"-v"来输出makefile被执行时的信息这个将在后面讲述
## make的参数
### make的参数
- 下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异不过其它产商的make的具体参数还是请参考各自的产品文档。
- "-b" "-m" 这两个参数的作用是忽略和其它版本make的兼容性。
- "-B" "--always-make" 认为所有的目标都需要更新(重编译)。
@@ -1428,12 +1428,12 @@ categories: ["dev/ops"]
- "-W <file>" "--what-if=<file>" "--new-file=<file>" "--assume-file=<file>" 假定目标<file>需要更新,如果和"-n"选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有"-n"那么就像运行UNIX的"touch"命令一样,使得<file>的修改时间为当前时间。
- "--warn-undefined-variables" 只要make发现有未定义的变量那么就输出警告信息。
# 隐含规则
## 隐含规则
- "隐含规则"也就是一种惯例make会按照这种"惯例"心照不喧地来运行那怕我们的Makefile中没有书写这样的规则。例如把[.c]文件编译成[.o]文件这一规则你根本就不用写出来make会自动推导出这种规则并生成我们需要的[.o]文件。
- "隐含规则"会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量"CFLAGS"可以控制编译时的编译器参数。
- 我们还可以通过"模式规则"的方式写下自己的隐含规则。用"后缀规则"来定义隐含规则会有许多的限制。使用"模式规则"会更回得智能和清楚,但"后缀规则"可以用来保证我们Makefile的兼容性。
## 使用隐含规则
### 使用隐含规则
- 如果要使用隐含规则生成你需要的目标你所需要做的就是不要写出这个目标的规则。那么make会试图去自动推导产生这个目标的规则和命令如果make可以自动推导生成这个目标的规则和命令那么这个行为就是隐含规则的自动推导。当然隐含规则是make事先约定好的一些东西。例如我们有下面的一个Makefile
```makefile
foo : foo.o bar.o
@@ -1456,7 +1456,7 @@ categories: ["dev/ops"]
```
- 依赖文件"foo.p"Pascal程序的源文件有可能变得没有意义。如果目录下存在了"foo.c"文件,那么我们的隐含规则一样会生效,并会通过 "foo.c"调用C的编译器生成foo.o文件。因为在隐含规则中Pascal的规则出现在C的规则之后所以make找到可以生成foo.o的 C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导那么你就不要只写出"依赖规则",而不写命令。
## 隐含规则一览
### 隐含规则一览
- 这里我们将讲述所有预先设置也就是make内建的隐含规则如果我们不明确地写下规则那么make就会在这些规则中寻找所需要规则和命令。当然我们也可以使用make的参数"-r"或"--no-builtin-rules"选项来取消所有的预设置的隐含规则。
- 当然,即使是我们指定了"-r"参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了"后缀规则"来定义的,所以,只要隐含规则中有"后缀列表 "(也就一系统定义在目标.SUFFIXES的依赖目标那么隐含规则就会生效。默认的后缀列表是.out,.a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节我们会在后面讲述。
- 常用的隐含规则
@@ -1530,7 +1530,7 @@ categories: ["dev/ops"]
- 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
"<n>.ln" lint生成的文件的依赖文件被自动推导为"n.c",其生成命令是:"$(LINT) $(LINTFALGS) $(CPPFLAGS) -i"。对于"<n>.y"和"<n>.l"也是同样的规则。
## 隐含规则使用的变量
### 隐含规则使用的变量
- 在隐含规则中的命令中基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值或是在make的命令行中传入这些值或是在你的环境变量中设置这些值无论怎么样只要设置了这些特定的变量那么其就会对隐含规则起作用。当然你也可以利用make的"-R"或"--no builtin-variables"参数来取消你所定义的变量对隐含规则的作用。
- 例如,第一条隐含规则 编译C程序的隐含规则的命令是"$(CC) c $(CFLAGS) $(CPPFLAGS)"。Make默认的编译命令是"cc",如果你把变量"$(CC)"重定义成"gcc",把变量"$(CFLAGS)"重定义成 "-g",那么,隐含规则中的命令全部会以"gcc c -g $(CPPFLAGS)"的样子来执行了。
- 我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如"CC";一种是参数
@@ -1571,7 +1571,7 @@ categories: ["dev/ops"]
- RFLAGS Ratfor 程序的Fortran 编译器参数。
- YFLAGS Yacc文法分析器参数。
## 隐含规则链
### 隐含规则链
- 有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成可能会是先被Yacc的[.y]文件先成[.c]然后再被C的编译器生成。我们把这一系列的隐含规则叫做"隐含规则链"。
- 在上面的例子中,如果文件[.c]存在那么就直接调用C的编译器的隐含规则如果没有[.c]文件,但有一个[.y]文件那么Yacc的隐含规则会被调用生成[.c]文件然后再调用C编译的隐含规则最终由[.c]生成[.o]文件,达到目标。
- 在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以"rm -f"删除。
@@ -1580,11 +1580,11 @@ categories: ["dev/ops"]
- 在"隐含规则链"中禁止同一个目标出现两次或两次以上这样一来就可防止在make自动推导时出现无限递归的情况。
- Make 会优化一些特殊的隐含规则,而不生成中间文件。如,从文件"foo.c"生成目标程序"foo"按道理make会编译生成中间文件"foo.o",然后链接成"foo",但在实际情况下,这一动作可以被一条"cc"的命令完成cc o foo foo.c于是优化过的规则就不会生成中间文件。
## 定义模式规则
### 定义模式规则
- 你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符。"%"的意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%",只是依赖目标中的"%"的取值,取决于其目标。
- 有一点需要注意的是,"%"的展开发生在变量和函数的展开之后变量和函数的展开发生在make载入Makefile时而模式规则中的"%"则发生在运行时。
### 模式规则介绍
#### 模式规则介绍
- 模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。例如:"%.c"表示以".c"结尾的文件名文件名的长度至少为3而"s.%.c"则表示以"s."开头,".c"结尾的文件名(文件名的长度至少为 5
- 如果"%"定义在目标中,那么,目标中的"%"的值决定了依赖目标中的"%"的值,也就是说,目标中的模式的"%"决定了依赖目标中"%"的样子。例如有一个模式规则如下:
```makefile
@@ -1593,7 +1593,7 @@ categories: ["dev/ops"]
- 其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是"a.o b.o",那么"%c"就是"a.c b.c"。
- 一旦依赖目标中的"%"模式被确定那么make会被要求去匹配当前目录下所有的文件名一旦找到make就会规则下的命令所以在模式规则中目标可能会是多个的如果有模式匹配出多个目标make就会产生所有的模式目标此时make关心的是依赖的文件名和生成目标的命令这两件事。
### 模式规则示例
#### 模式规则示例
- 下面这个例子表示了,把所有的[.c]文件都编译成[.o]文件.
```makefile
%.o : %.c
@@ -1607,7 +1607,7 @@ categories: ["dev/ops"]
```
- 这条规则告诉make把所有的[.y]文件都以"bison -d <n>.y"执行,然后生成"<n>.tab.c"和"<n>.tab.h"文件。(其中,"<n>" 表示一个任意字符串)。如果我们的执行程序"foo"依赖于文件"parse.tab.o"和"scan.o",并且文件"scan.o"依赖于文件"parse.tab.h",如果"parse.y"文件被更新了,那么根据上述的规则,"bison -d parse.y"就会被执行一次,于是,"parse.tab.o"和"scan.o"的依赖文件就齐了。(假设,"parse.tab.o" 由"parse.tab.c"生成,和"scan.o"由"scan.c"生成,而"foo"由"parse.tab.o"和"scan.o"链接生成而且foo和其[.o]文件的依赖关系也写好,那么,所有的目标都会得到满足)
### 自动化变量
#### 自动化变量
- 在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。
- 自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
- 下面是所有的自动化变量及其说明:
@@ -1636,11 +1636,11 @@ categories: ["dev/ops"]
- 最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(< )"就要比"$<"要好一些。
- 还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。
## 模式的匹配
### 模式的匹配
- 一般来说,一个目标的模式有一个有前缀或是后缀的"%",或是没有前后缀,直接就是一个"%"。因为"%"代表一个或多个字符,所以在定义好了的模式中,我们把"%"所匹配的内容叫做"茎",例如"%.c"所匹配的文件"test.c"中"test"就是"茎"。因为在目标和依赖目标中同时有"%"时,依赖目标的"茎"会传给目标,当做目标中的"茎"。
- 当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行"茎"的传递时,我们需要知道这个步骤。例如有一个模式"e%t",文件"src/eat" 匹配于该模式,于是"src/a"就是其"茎",如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式"c%r",那么,目标就是"src/car"。("茎"被传递)
## 重载内建隐含规则
### 重载内建隐含规则
- 你可以重载内建的隐含规则(或是定义一个全新的),例如你可以重新构造和内建隐含规则不同的命令,如:
```makefile
%.o : %.c
@@ -1652,7 +1652,7 @@ categories: ["dev/ops"]
```
- 同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。
# 老式风格的"后缀规则"
## 老式风格的"后缀规则"
- 后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。为了和老版本的Makefile兼容GNU make同样兼容于这些东西。后缀规则有两种方式"双后缀"和"单后缀"。
- 双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如".c.o"相当于"%o : %c"。单后缀规则只定义一个后缀,也就是源文件的后缀。如".c"相当于"% : %.c"。
- 后缀规则中所定义的后缀应该是make所认识的如果一个后缀是make所认识的那么这个规则就是单后缀规则而如果两个连在一起的后缀都被make所认识那就是双后缀规则。例如".c"和".o"都是make所知道。因而如果你定义了一个规则是".c.o"那么其就是双后缀规则,意义就是".c" 是源文件的后缀,".o"是目标文件的后缀。如下示例:
@@ -1676,7 +1676,7 @@ categories: ["dev/ops"]
- .SUFFIXES: .c .o .h # 定义自己的后缀,先清楚默认后缀,后定义自己的后缀列表。
- make的参数"-r"或"-no-builtin-rules"也会使用得默认的后缀列表为空。而变量"SUFFIXE"被用来定义默认的后缀列表,你可以用".SUFFIXES"来改变后缀列表,但请不要改变变量"SUFFIXE"的值。
# 隐含规则搜索算法
## 隐含规则搜索算法
- 比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意在下面我们没有提到后缀规则原因是所有的后缀规则在Makefile被载入内存时会被转换成模式规则。如果目标是"archive(member)"的函数库文件模式那么这个算法会被运行两次第一次是找目标T如果没有找到的话那么进入第二次第二次会把"member"当作T来搜索。
- 把T的目录部分分离出来。叫D而剩余部分叫N。如果T是"src/foo.o"那么D就是"src/"N就是"foo.o"
- 创建所有匹配于T或是N的模式规则列表。
@@ -1696,10 +1696,10 @@ categories: ["dev/ops"]
- 如果没有隐含规则可以使用,查看".DEFAULT"规则,如果有,采用,把".DEFAULT"的命令给T使用。
- 一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。
# 使用make更新函数库文件
## 使用make更新函数库文件
- 函数库文件也就是对Object文件程序编译的中间文件的打包文件。在Unix下一般是由命令"ar"来完成打包工作。
## 函数库文件的成员
### 函数库文件的成员
- 一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成:
- archive(member) 这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了"ar"命令来服务的。如:
```makefile
@@ -1719,7 +1719,7 @@ categories: ["dev/ops"]
foolib(*.o)
```
## 函数库成员的隐含规则
### 函数库成员的隐含规则
- 当 make搜索一个目标的隐含规则时一个特殊的特性是如果这个目标是"a(m)"形式的,其会把目标变成"(m)"。于是,如果我们的成员是"%.o" 的模式定义,并且如果我们使用"make foo.a(bar.o)"的形式调用Makefile时隐含规则会去找"bar.o"的规则如果没有定义bar.o的规则那么内建隐含规则生效make会去找bar.c文件来生成bar.o如果找得到的话make执行的命令大致如下
```makefile
cc -c bar.c -o bar.o
@@ -1728,7 +1728,7 @@ categories: ["dev/ops"]
```
- 还有一个变量要注意的是"$%",这是专属函数库文件的自动化变量,有关其说明请参见"自动化变量"一节。
## 函数库文件的后缀规则
### 函数库文件的后缀规则
- 你可以使用"后缀规则"和"隐含规则"来生成函数库打包文件,如:
```makefile
.c.a:
@@ -1744,7 +1744,7 @@ categories: ["dev/ops"]
$(RM) $*.o
```
# 注意事项
## 注意事项
- 在进行函数库打包文件生成时请小心使用make的并行机制"-j"参数。如果多个ar命令在同一时间运行在同一个函数库打包文件上就很有可以损坏这个函数库文件。所以在make未来的版本中应该提供一种机制来避免并行操作发生在函数打包文件上。
- 但就目前而言,你还是应该不要尽量不要使用"-j"参数。