type
status
slug
date
summary
tags
category
password
icon
鉴于之前写的makefile都比较小,基本就是参考着一些小模板来写,很多地方也有一知半解,只知道这样写能过,也不是很清楚原因,所以打算好好学一下makefile的基础,为写项目的makefile打下基础:
开始了:
Why do Makefiles exist?
(不是翻译,写自己的理解)
如图,一个main.cpp需要许多依赖,有它的头文件,也会依赖其它模块(.c/.cpp),还可能需要其他的库,而任何一个依赖改变我们都是需要重新编译的,这个时候产生两个问题:怎么才能最快编译?怎样才能让我们编译的命令简单?最快编译,就是需要去知道这些依赖,而编译命令简单,可以写脚本,但是单纯的脚本只是重复每次“完整”的编译工作,并不能解决第一个问题,所以makefile这个文件诞生了,它除了具有代替编译的脚本命令这个优势以外,还能保留记录工程的依赖关系,从而能够让每次改动,能尽可能的地位到最简单能加入变动发生的位置开始编译链接。
What alternatives are there to Make?
对C/C++来说,有不少工具:SCons, CMake, Bazel, and Ninja.其它语言的可以不用了解太多,但是makefile并不是只为C/C++用的,java,go同样也可以用makefile,makefile只是用于编译型语言来描述项目的依赖关系,并且代替脚本的功能,配合make工具进行编译过程的文件。
Running the Examples
还是从最开始的hello world开始,我就不写了。
Makefile Syntax
A Makefile consists of a set of rules. A rule generally looks like this:
targets: prerequisites
command
command
command
targets 是一组文件名,由空格分隔,不过通常只有一个文件名就是了
command就是一系列的步骤,通常是用来make (产生)我们的targets,它们是需要以tab开始而不是空格。
prerequisites就是依赖了(dependencies),同样是文件名,空格分隔,这些文件就必须在command为targets运行之前就存在。
同样有简单到复杂,看个简单的例子:
- We have one target called
hello
- This target has two commands
- This target has no prerequisites
(简单的英文解释就直接贴了,长的写理解)
We'll then run
make hello
. As long as the hello
file does not exist, the commands will run. If hello
does exist, no commands will run.(这里就知道,如果目标存在,依赖又没有,那makefile就能知道不需要重新编译)两次对比(第二次手动加入了hello文件),这里我们就知道makefile检查了目标“hello”已经up to date.(注意后缀名也会区别开,target是hello,那hello.out hello.exe就不会是target)It's important to realize that I'm talking abouthello
as both a target and a file. That's because the two are directly tied together. Typically, when a target is run (aka when the commands of a target are run), the commands will create a file with the same name as the target. In this case, thehello
target does not create thehello
file.
这个例子里面hello是target也是文件,通常我们一个target 的 commands 会创建一个文件名字和target一样,这样才是一个合理的make过程,不过我们这个简单例子没有创建hello文件,所以你重复make都会把commands全部运行一遍。
想起一个老梗,冒牌货cc:(tutorial里面用到了cc)
两个连着的示例来说明prerequisite的作用:
这个时候你改动了blah.c的内容,再编译,它也会显示没有问题,up to date。为什么?因为你这里的target没有依赖,那么只要目标文件存在,它就会认为没有变动,而你的blah.c根本没有被监视,所以稍微改改就行:
ok.done.
When we run
make
again, the following set of steps happens:- The first target is selected, because the first target is the default target
- This has a prerequisite of
blah.c
- Make decides if it should run the
blah
target. It will only run ifblah
doesn't exist, orblah.c
is newer thanblah
注意第一个target是默认的target,而make命令会重新跑commands只会在target文件blah不存在或者blah.c文件比blah(target文件)创建的时期更新(细节:不是比blah.c的更改时间,而是你target的最新创建时间)
This last step is critical, and is the essence of make. What it's attempting to do is decide if the prerequisites of
blah
have changed since blah
was last compiled.了解下原理捏:
To make this happen, it uses the filesystem timestamps as a proxy to determine if something has changed. This is a reasonable heuristic, because file timestamps typically will only change if the files are modified. But it's important to realize that this isn't always the case. You could, for example, modify a file, and then change the modified timestamp of that file to something old. If you did, Make would incorrectly guess that the file hadn't changed and thus could be ignored.
用的文件系统的时间戳,所以要是你使坏,可以修改依赖,但是把时间戳手动的改成更老的时间,这样make还是会认为没有更改。
这个essence要记得,Make sure that you understand this. It's the crux(/essence 关键) of Makefiles, and might take you a few minutes to properly understand。
More quick examples
你敲下make,会经历什么:
- make选择第一个target(blah)作为目标,因为默认是第一个target作为项目target
- 发现他需要一个依赖blah.o,首先是看这个依赖存不存在,不存在就需要在makefile里面查找target去创建,即便存在,需要看它是否在makefile里面作为一个target,是否有依赖,然后看它的依赖是否有最新的改动,如果依赖有改动的话也需要重新编译
- 找到blah.o这个target,发现它需要blah.c文件依赖(target),同样的按照第二步操作,先确定依赖情况
- 走到blah.c这个target,这里我们是把内容放在了这个commands的操作里面,blah.c这个target没有依赖,所以不需要检查依赖,然后看是否存在这个target文件blah.c即可,第一次make的时候是没有个文件的,所以会执行commands
- 执行blah.c target的commands过后,产生了最新的blah.c文件,blah.o的依赖存在后,需要进行更新,执行它的command 产生blah.o文件,这里cc -c 命令是执行编译操作,将blah.c文件编译产生可链接的.o文件
- 最后执行blah target的commands
然后我们做一些变动:
你看到,这里原来的第一个command就没有执行了,这是因为那个规则,target blah.c已经存在了,并且也没有依赖(或者说依赖没有改动)。所以不需要重新执行对应的commands命令,但是第二个commands执行,是因为它的以依赖blah.c文件虽然存在,但是发生了改变,所以blah.o这个target就需要重新生成,从而导致blah这个target的依赖更改,然后blah也需要重新使用commands命令。而如果只有blah.o文件发送改变,则连第二条命令也不会触发,因为target存在,并且依赖没有更改,所以只会执行第三个commands,生成blah。
If you deleteblah.c
, all three targets will be rerun. If you edit it (and thus change the timestamp to newer thanblah.o
), the first two targets will run. If you runtouch blah.o
(and thus change the timestamp to newer thanblah
), then only the first target will run. If you change nothing, none of the targets will run.
这个例子就不错,它永远都会跑,因为他的依赖永远都不会存在(other_file).
来小总结一下核心:
- 第一个target是默认target,就像main函数一样,总是从它开始(可能后面可以指定,不过目前的例子都是采用默认第一个),不过你make target 就可以指定target而不是make默认的选择第一个
- target对应的commands是否会执行取决几个点:
- target文件是否存在,如果不存在,那肯定需要执行commands,但是这也是要在prerequisite部分已经处理好后才做(都已经产生)
- prerequisite的 文件/target 的时间戳是否比target文件更新,如果更新,那就说明原来的已经失效,需要重新用commands处理,所以会执行
- 而target的commands在执行之前,一定是先经过了prerequisite的commands执行或者是判定prerequisite存在且不需要重新生成,这个流程是递归的,也就是对于prerequisite也是找它是否是target,如果是,按照第二点的target进行判定,如果makefile里面没有target,那就直接找它的文件,看文件的时间戳。
Make clean
clean
is often used as a target that removes the output of other targets, but it is not a special word in Make. You can run make
and make clean
on this to create and delete some_file
.名字不一定要是clean,这是自己定的,不过惯例来搞,还是选clean最合理。
Note that
clean
is doing two new things here:- It's a target that is not first (the default), and not a prerequisite. That means it'll never run unless you explicitly call
make clean
- It's not intended to be a filename. If you happen to have a file named
clean
, this target won't run, which is not what we want. See.PHONY
later in this tutorial on how to fix this
这里第二点比较细节:clean虽然是一个target,但是他并不是对应一个文件名(和前面的target不一样),并且如果你碰巧有个文件名字是clean,那么这个clean的target就不会运行起来(达不到我们的预期),后面的.PHONY章节会讲解怎么修复这个问题。
来个例子:
加入了clean文件后,就“寄了”。
Variables
Variables can only be strings. You'll typically want to use
:=
, but =
also works. See Variables Pt 2.e.g.
makefile的变量因为只能是字符串,所以单引号双引号就没什么意义了,但是它是一个字符,并且调用commands不如printf的时候,就需要加上单引号或者双引号
这里’$a’和$b是一样的效果
Reference variables using either
${}
or $()
Targets
The all target
Making multiple targets and you want all of them to run? Make an
all
target. Since this is the first rule listed, it will run by default if make
is called without specifying a target.Multiple targets
When there are multiple targets for a rule, the commands will be run for each target.
$@
is an automatic variable that contains the target name.细节是:
- $@是一个automatic variable包含了target的名字。
- 命令会给每一个target都运行一次,所以targets里面(空格分隔)有几个target就会运行几次对应的commands
上例运行结果:
Automatic Variables and Wildcards(通配符)
* Wildcard
Both
*
and %
are called wildcards in Make, but they mean entirely different things. *
searches your filesystem for matching filenames. I suggest that you always wrap it in the wildcard
function, because otherwise you may fall into a common pitfall described below.(下面第一个例子是正确的,但是第二个例子会有错误示范)注意点:
- *和%是不同的,*是搜索你的文件系统来查找匹配的文件名,%的含义后面章节会说
- 最好是把 *通配符都放在wildcard函数里面,向上面例子这样。
运行结果:
这里面$?的含义后面会说。
*
may be used in the target, prerequisites, or in the wildcard
function.两个注意的:
Danger:
*
may not be directly used in a variable definitionsDanger: When
*
matches no files, it is left as it is (unless run in the wildcard
function)e . g
这个例子至少需要改成这样才能跑:
% Wildcard
%
is really useful, but is somewhat confusing because of the variety of situations it can be used in.- When used in "matching" mode, it matches one or more characters in a string. This match is called the stem.
- When used in "replacing" mode, it takes the stem that was matched and replaces that in a string.
%
is most often used in rule definitions and in some specific functions.
后续会深入讲到它,给个总体链接说明它被用在的位置:
See these sections on examples of it being used:
Automatic Variables
There are many automatic variables (GNU mannual 完整的automatic variable), but often only a few show up:
例子里面就是常用的:
第一次make输出:
修改one文件再make一次
删除one再make一次:
看的出来$?的作用
总结:
- $@是当前rule所有的targets,空格分隔
- $^是当前rule所有的prerequisites,空格分隔
- $?是所有比target新的prerequisites,空格分隔,也就是每次make对于这个target需要重新考虑的prerequisites的集合,这些prerequisites都可能已经被改变了。
文档少写了点,我补充一些,后面会用到的automatic variable
$<
The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by the implicit rule (see Using Implicit Rules).
Fancy Rules
Implicit Rules
这句话挺有意思:Make loves c compilation. And every time it expresses its love, things get confusing.(虽然make是能给其他编译语言用的,但是它更为c的编译做了很多小技巧)
Perhaps the most confusing part of Make is the magic/automatic rules that are made. Make calls these "implicit" rules.
让我们看看都是哪些:
- Compiling a C program:
n.o
is made automatically fromn.c
with a command of the form$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
- Compiling a C++ program:
n.o
is made automatically fromn.cc
orn.cpp
with a command of the form$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
- Linking a single object file:
n
is made automatically fromn.o
by running the command$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
The important variables used by implicit rules are:
CC
: Program for compiling C programs; defaultcc
CXX
: Program for compiling C++ programs; defaultg++
CFLAGS
: Extra flags to give to the C compiler
CXXFLAGS
: Extra flags to give to the C++ compiler
CPPFLAGS
: Extra flags to give to the C preprocessor
LDFLAGS
: Extra flags to give to compilers when they are supposed to invoke the linker(当编译器应该调用链接器时,给编译器的额外标志)
Let's see how we can now build a C program without ever explicitly telling Make how to do the compililation :
老实说这种写起来虽然简单,但是少了commands,并且后续不只是写c,还会加上汇编,这个对于性能也没什么提升,只是隐式表达,的确十分鸡肋,自己写还是完整的描述吧。
等价于:
这是上下两个例子通过make命令的结果,完全是一样的,参数顺序有点微小区别。
Static Pattern Rules
Static pattern rules are another way to write less in a Makefile. 不过理解了还是简单的,并且常用
The essence is that the given
target
is matched by the target-pattern
(via a %
wildcard). Whatever was matched is called the stem. The stem is then substituted into the prereq-pattern
, to generate the target's prereqs.A typical use case is to compile
.c
files into .o
files. Here's the manual way:手工方式把.c文件编译成.o:
Here's the more efficient way, using a static pattern rule:
这里留一个疑问,我看到这里都没有发现本tutorial说这个%.c的意思,它作为一个target是怎么去匹配的,看看后面有没有说。
回来看的时候理解了,%和*使用的场景是完全不一样的,原文这几句:
Both*
and%
are called wildcards in Make, but they mean entirely different things.*
searches your filesystem for matching filenames.*
may be used in the target, prerequisites, or in thewildcard
function.%
is really useful, but is somewhat confusing because of the variety of situations it can be used in.
- When used in "matching" mode, it matches one or more characters in a string. This match is called the stem.
- When used in "replacing" mode, it takes the stem that was matched and replaces that in a string.
%
is most often used in rule definitions and in some specific functions. 主要是你看:*是检索文件系统里面符合规则的文件,主要是拿到文件系统特定的以什么为后缀的文件名,它可作为target、prerequisite、以及赋值给一个变量后续使用,然而%就是用在规则定义部分,通常是查找依赖项的时候,%出现在target位置进行匹配,比如放在target的位置,是当寻找依赖的时候,去找符合的字符串,比如找foo.c的时候,找到%.c,那么也符合要求。不过这里如果有指定的完整的target的rule,命中了格式的话,就不会采用通用匹配符的rule。后面我会给例子说明。
这样对于上面的例子,我做一些改变:
输出结果就是:,注意我这里两个echo是避免前面对于c的隐式操作导致启动了编译过程,
再改动一点,你看看当既有通配符%匹配上,也有特定target匹配上的时候,它会选择哪一个:
看到并没有了touch all.c,说明匹配了特定的all.c target并且没有再重复去匹配通配符了,而且先后顺序没有关系。
Static Pattern Rules and Filter
The
filter
function can be used in Static pattern rules to match the correct files. In this example, I made up the .raw
and .result
extensions.$<
The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by the implicit rule (see Using Implicit Rules).
Pattern Rules
Pattern rules are often used but quite confusing. You can look at them as two ways:
- A way to define your own implicit rules
- A simpler form of static pattern rules
Let's start with an example first:
Pattern rules contain a '%' in the target. This '%' matches any nonempty string, and the other characters match themselves. ‘%’ in a prerequisite of a pattern rule stands for the same stem that was matched by the ‘%’ in the target.(prerequisite就是替换规则了,前面target部分就是matching规则)
Double-Colon(双冒号规则) Rules
这个就是运行两个rule,对同一个target,如果不是双冒号,那就会有个warning并且只会执行第二个:
下面是运行结果,第一次是原例子,第二个是去掉一个冒号:
Commands and execution
Command Echoing(回响)/Silencing
Add an
@
before a command to stop it from being printedYou can also run make with
-s
to add an @
before each line就是在之前你的command命令前面如果加入一个@的话,就能不把命令打印出来:
Command Execution
Each command is run in a new shell (or at least the effect is as such)
每个命令都是跑在一个新的shell,至少效果上是。
注意这里的第二组:cd ..;echo `pwd`,这个cd命令就会影响下一个命令,因为是同行运行的,当然第三组加入了折行符号,也是一样的效果,只要这个分隔符:分号。
Default Shell(默认的shell,可以更改)
前面也说了,makefile有部分功能就是替代脚本写编译命令,不过是多加了对于依赖关系的理解,所以对于脚本命令的部分,它有一个shell来执行,默认的shell是 /bin/sh,不过我们可以改,而且我觉得肯定是改了才更好:
使用命令:echo $shell可以看到自己用的是什么shell,我自己配的zsh,(准确说是用的oh-my-zsh)然后自己配置了一些主题和插件。
Double dollar sign(双美元符号)
因为美元符号$是个关键字,所以想要使用的话,需要转义,这里makefile里面就是两个美元符号进行转义:
这里回忆一下shell变量的声明和使用:
(1)变量声明及赋值格式(变量=值,注意:等号两侧不能有空格)
(2)变量的引用($变量名,或者${变量名})
其实在这里都能看出来一点原理,make里面的变量在command处引用的时候,在执行command之前就已经被替换成了对应的字符串了。
如果只有一个美元符号,那就会算成在引用make的变量,看看例子:
结果:
Error handling with -k
, -i
, and -
Add -k when running make to continue running even in the face of errors. Helpful if you want to see all the errors of Make at once.
Add a - before a command to suppress the error
Add -i to make to have this happen for every command.
下面省略一些目前感觉用不上的章节,后面用到了再回来补充。
Export, environments, and recursive make
When Make starts, it automatically creates Make variables out of all the environment variables that are set when it’s executed.(也就是make的变量是包含了所有的环境变量的,make命令开始时会导入当前的所有环境变量到make的变量,并且,虽然每次command的执行都是在新的shell里面,这个新的shell也被加入了执行make指令的时候的那个shell所包含的环境变量)
这个是我的例子,注意到GCC_PATH是写在zshrc文件里面的环境变量。
The
export
directive takes a variable and sets it the environment for all shell commands in all the recipes:在解析这个例子之前,我们先看看没有export的情形是什么情况:
可以看到,两个echo,第一个是make的变量,顺理成章的就是echo Shell env var, created inside of Make 那么执行结果显而易见,但是第二个是在一个新的shell里面执行的,可以看到这里make把自己的变量传入了新的shell里面,所以在新的shell里面执行$shell_env_var的时候也会产生同样的效果,而且当我们退出这个新的shell,在外面再来查看shell_env_var的时候,可以看到环境变量没有受到任何影响。
但是,上面的情况是一个偶然,这是很有意思的,我稍微改改你会看到完全不一样的效果:
发现shell_env_var1在command命令的新shell里面是没有定义的。所以其实这里是make的一点trick,command里面的新的shell和原来的shell有一个关系是继承了原来shell的环境变量,我们前面shell_env_var有值是因为原来的shell里面有这个环境变量,然后在makefile里面修改了,这样也会把修改的结果传给command执行的时候新建的shell,然后对于原来不存在的,如果不进行显示的export的话,就会导致新的shell里面是没有这个环境变量的。
加上export就可以了,建议需要在新shell里面使用的环境变量还是都加上export:
再来看看export,我们再接着看tutorial的例子:
这里需要回忆一下mkdir 的参数含义:
• -p 确保目录名称存在,不存在的就建一个。
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile这个命令稍微讲解一下:
首先是解释new_contents的双引号,是因为printf这个shell命令规范的书写是要加双引号/单引号的,给个示例:
|是shell的一个管道符号,用于将一个命令的输出作为另一个命令的输入。本命令中,printf的输出将会作为sed命令的输入。
而sed命令,是英语对文本进行替换操作,-e选项表示后面跟着一个替换表达式。这里我们的替换表达式是s/^ //,它表将每行开头替换为空。也就是删除了每行开头的空格。
格式是:
sed 's/要被取代的字串/新的字串/g'
s是指替换操作,/分隔,^是正则表达式的元字符,表示行的开头位置,后面跟空格,说明匹配以空格开头的行的开头的空格。//说明了新的字串是空,也就是将^匹配到的字串替换为空。
sed完整命令:
所以tutorial的这里不是很完美,应该要去掉字符串里面开头的所有空格,而不是一个,我改为了:
printf $(new_contents) | sed -e 's/^ \+//' > subdir/makefile,也就是匹配一个或多个
一个细节是这里用正则的时候+需要用转义符号转义一下。
还有一个细节,你看new_contents里面,为什么是写的\$$(cooly),我们去掉\看看是什么:
看到了吗,shell会把$(cooly)看做一个变量引用,这个是在shell里面的,根据shell的语法需要进行一次转义的操作,所以两个美元符号(doble dollar sign)规则下会增加一个\进行在shell中命令的转义。
如果去掉显示的export cooly,就能发现在新的make环境没有拿到这个环境变量
You need to export variables to have them run in the shell as well.
这里就是command里面也是一个新的shell,正常情况都是要export的,不过有特殊,前面已经提到了。
觉得写起来很麻烦,要是想每个变量都export的话,就用.EXPORT_ALL_VARIABLES吧
.EXPORT_ALL_VARIABLES
exports all variables for you.Arguments to make(make的一些参数)
You can have multiple targets to make, i.e.
make clean run test
runs the clean
goal, then run
, and then test
.Variables Pt. 2
Flavors(种类) and modification(变种)
There are two flavors of variables:
- recursive (use
=
) - only looks for the variables when the command is used, not when it's defined.(使用的时候才会形成定义)
- simply expanded (use
:=
) - like normal imperative(命令) programming -- only those defined so far get expanded(只有到这个位置已经有定义的变量才会展开,并且这个变量在这里就定义完成了,而不是使用时)
?expanded是什么意思这里—
很有意思吧,这里one是用的=,所以在使用的时候才会形成它的定义,这个时候later_variable已经定义了,所以打印one later,但是two就是在定义的时候完成定义,这个时候later_avriable还没有完成,所以是空的。
这里给了一个例子,表明Simply expanded(using :=)的方式可以支持递归式的增加,但是recursive(using =)就不能做到这一点,会出现infinite loop error:
因为这里one := 的时候就会使用one,这个时候找到one的上层定义(不管是:=定义的还是=定义的)
然后得到one的新定义,我变形一下:
但是对于=来说,是不可以递归引用的,这是语法的规定:
?=
only sets variables if they have not yet been set就是避免二次定义的覆盖:
Spaces at the end of a line are not stripped, but those at the start are. To make a variable with a single space, use
$(nullstring)
就是说变量后面的space空格不会被省略,但是前面的空格会,如果想要变量名前面有当个空格的话,使用$(nullstring)这个语法。
注释掉nullstring也是一样的效果。
注意一个坑space = $(nullstring) # Make a variable with a single space.
这个nullstring本身没有用,只是让他占了第一位置,这样后面跟的空格就不会被认为是开始,不会忽略。
An undefined variable is actually an empty string!
make也是支持+=的语法的:
Use
+=
to appendCommand line arguments and override
You can override variables that come from the command line by using
override
. Here we ran make with make option_one=hi
我改了一个更好的例子:
有override
去掉override
可以看到命令行传递的变量的优先级是比较高的,如果不使用override参数,那么makefile里面的定义的同名变量很难生效。
来看看之前clean出现的问题,(同名clean文件出现的话,clean target会失效)。
List of commands and define
define
/endef
simply creates a variable that is set to a list of commands. Note here that it's a bit different than having a semi-colon between commands, because each is run in a separate shell, as expected.并不是很常用,就是创建了一个变量是一个command的list,但是list里面的每一个元素都是会独立跑在不同的shell里面的,这和拿;(分号)来组合是不一样的。
Target-specific variables(定目标的变量)
变量是可以指定生效在哪个target中的(Variables can be set for specific targets)
上面例子不太好,扩展一下:
于是乎,延伸一下,也可以按照taget的模式来指定:
Pattern-specific variables
You can set variables for specific target patterns
只是相当于有了模糊的匹配,效果和上面一样,不再赘述。
Conditional part of Makefiles
也就是条件分支语句部分,其实是简单的,主要用在command的部分,拓展了一点执行shell的环节。根据例子就能看出用法。
Conditional if/else
Check if a variable is empty
我这里追加了一个部分,用来说明前面提到的,想要一个字符前面有空格,可以使用nullstirng的方式,它本身就和nullstring不一样。
这个例子里面的strip是一个function,和之前的wildcard是一类,这里的作用就是去除空格。
$(MAKEFLAGS)
This example shows you how to test make flags with
findstring
and MAKEFLAGS
. Run this example with make -i
to see it print out the echo statement.Functions
Functions are mainly just for text processing.
makefile里面的函数部分主要是用来处理文本的,功能不会太多。不过现在make里面有很多function,给个链接:https://www.gnu.org/software/make/manual/html_node/Functions.html
格式:
Call functions with
$(fn, arguments)
or ${fn, arguments}
First Functions
给例子:
$(subst src,dst,srcStr)就是把srcStr里面对应为src的地方换成dst,而且是所有都会替换
If you want to replace spaces or commas, use variables
space := $(empty) $(empty)注意这里,第二个$(empty)没有任何用,空格来自于第一个empty后面的空格,去掉第二个empty都是可以的。
Do NOT include spaces in the arguments after the first. That will be seen as part of the string.
也就是你第一个参数之后的部分,如果前面有空格,不会像变量声明一样给你去掉,还是会保留着。
不仔细看电话,确实会怀疑第一个,是怎么来的,你看看$(foo)替换部分,是不是它前面有个空格,除了第一个参数外,后面两个参数前后的空格都不会省略。(确实需要仔细看了)
String Substitution
$(patsubst pattern,replacement,text)
does the following:"Finds whitespace-separated words in text that match pattern and replaces them with replacement. Here pattern may contain a ‘%’ which acts as a wildcard, matching any number of any characters within a word. If replacement also contains a ‘%’, the ‘%’ is replaced by the text that matched the ‘%’ in pattern. Only the first ‘%’ in the pattern and replacement is treated this way; any subsequent ‘%’ is unchanged.”
这里对于replacement里面带%的情况有点怪:它是说这个%会被换成第一个匹配pattern的串对应在pattern的%中的字串,并且这次替换了replacement以后,后面都不会改replacement 了。尽管pattern还是按照%的方式灵活匹配。
是我理解错了淦,这个话里面Only the first ‘%’ in the pattern and replacement is treated this way; any subsequent ‘%’ is unchanged,是说只有模式串和替换串里面的第一个%会按照那个方式处理,但是模式串里面还可以有%,同样替换串也是,但是后面的%就不会这样来特殊看待了。
有几种简单写法:
The substitution reference
$(text:pattern=replacement)
is a shorthand for this.如果只是替换尾缀的话,还有一种,并且这种方式不用
%
wildcardThere's another shorthand that replaces only suffixes:
$(text:suffix=replacement)
. No %
wildcard is used here.Note: don't add extra spaces for this shorthand. It will be seen as a search or replacement term.
上例子:
The foreach function
The foreach function looks like this:
$(foreach var,list,text)
. It converts one list of words (separated by spaces) to another. var
is set to each word in list, and text
is expanded for each word.This appends an exclamation after each word:
The if function
if
checks if the first argument is nonempty. If so, runs the second argument, otherwise runs the third.这里的if和前面条件分支的语法不一样,这里是判定第一个参数是否非空,如果非空执行第二个参数,否则执行第三个参数
The call function
Make supports creating basic functions. You "define" the function just by creating a variable, but use the parameters
$(0)
, $(1)
, etc. You then call the function with the special call
builtin function. The syntax is $(call variable,param,param)
. $(0)
is the variable, while $(1)
, $(2)
, etc. are the params.也就是make支持你简单的自定义函数,参数就写成$(0),$(1),这种形式。然后调用方式就使用call函数执行,貌似看起来没有返回值这个说法。返回值就是你这个函数本身,因为你定义的变量就是这个函数,这个函数执行就是把参数填充进整个字符串。
The shell function
shell - This calls the shell, but it replaces newlines with spaces!
shell执行结果作为返回,可以赋给一个make的变量,不过这个结果比较难看,因为换行符号被替换成了空格。
Other Features
Include Makefiles
The include directive tells make to read one or more other makefiles. It's a line in the makefile that looks like this:
This is particularly useful when you use compiler flags like
-M
that create Makefiles based on the source. For example, if some c files includes a header, that header will be added to a Makefile that's written by gcc.这里算是进阶内容了,没有给一个合适的例子。
The vpath Directive(vpath 指令)
Use vpath to specify where some set of prerequisites exist. The format is
vpath <pattern> <directories, space/colon separated>
, <pattern>
can have a %
, which matches any zero or more characters. You can also do this globallyish with the variable VPATH使用vpath这个东东,按照
vpath <pattern> <directories, space/colon separated>
的格式,pattern里面就可以包含%来进行模式匹配,这里%就是可以匹配0个或多个字符。vapth就是用来指明prerequisites是在什么位置的,比如指明.h文件这种依赖是在什么位置。
这里,我在第一次和第三次make的时候注释了,
blah.h:
touch ../headers/blah.h
部分,第二次make的时候恢复了注释,但是第四次make的时候并没有
所以才能看到更清晰的效果
如果注释了
vpath %.h ../headers
同样就会报错,说找不到blah.h targetMultiline
The backslash ("\") character gives us the ability to use multiple lines when the commands are too long
makefile也是允许折行的,方式同样是\
.phony
Adding
.PHONY
to a target will prevent Make from confusing the phony(假的) target with a file name. In this example, if the file clean
is created, make clean will still be run. Technically, I should have used it in every example with all
or clean
, but I didn't to keep the examples clean. Additionally, "phony" targets typically have names that are rarely file names, and in practice many people skip this.给一个target加上.PHONY的标签,来改改我们之前的clean的例子吧:
如果没有用PHONY,那么结果就是:
.delete_on_error
The make tool will stop running a rule (and will propagate back to(传播回,ps:原文打错字了) prerequisites) if a command returns a nonzero exit status.
DELETE_ON_ERROR
will delete the target of a rule if the rule fails in this manner. This will happen for all targets, not just the one it is before like PHONY. It's a good idea to always use this, even though make does not for historical reasons.最好这个是都加上,当rule产生错误的时候,把对应的target删掉,并且最好是加在所有的target上。而不是像.PHONY一样只加给clean(当然.PHONY肯定是只应该去一个个加,并且最常用的就是加给clean),只是历史原因make没有把这个都加上。
基础的语法部分到这里就差不多了,后面就是在实战中学习,多写写,多看看。
给出这个tutorial最后的一个编译C、CPP的实战内容较多的模板,可以借鉴学习:
Makefile Cookbook
Let's go through a really juicy Make example that works well for medium sized projects.
The neat thing about this makefile is it automatically determines dependencies for you. All you have to do is put your C/C++ files in the
src/
folder.完结撒花😻
我把后续的补充放在一篇新的文章:
makefile后续
- 作者:liamY
- 链接:https://liamy.clovy.top/article/makefile/base
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。