网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
targets…: target-pattern: prereq-patterns …
commands
本质是给定的目标与目标模式匹配(通过 `%` 通配符)。 匹配的任何内容都称为词干。 然后将词干替换到先决条件模式中,以生成目标的先决条件。
一个典型的用例是将 .c 文件编译成 .o 文件。 这是手动方式:
objects = foo.o bar.o all.o
all: $(objects)
These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c
all.c:
echo “int main() { return 0; }” > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
这是更有效的方法,使用静态模式规则:
objects = foo.o bar.o all.o
all: $(objects)
These files compile via implicit rules
Syntax - targets …: target-pattern: prereq-patterns …
In the case of the first target, foo.o, the target-pattern matches foo.o and sets the “stem” to be “foo”.
It then replaces the ‘%’ in prereq-patterns with that stem
$(objects): %.o: %.c
all.c:
echo “int main() { return 0; }” > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
#### 静态模式规则和过滤器
当我稍后介绍函数时,我将预示您可以使用它们做什么。 过滤器功能可用于静态模式规则以匹配正确的文件。 在这个例子中,我制作了 .raw 和 .result 扩展名。
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
all: $(obj_files)
Note: PHONY is important here. Without it, implicit rules will try to build the executable “all”, since the prereqs are “.o” files.
.PHONY: all
Ex 1: .o files depend on .c files. Though we don’t actually make the .o file.
(
f
i
l
t
e
r
(filter %.o,
(filter(obj_files)): %.o: %.c
echo “target: $@ prereq: $<”
Ex 2: .result files depend on .raw files. Though we don’t actually make the .result file.
(
f
i
l
t
e
r
(filter %.result,
(filter(obj_files)): %.result: %.raw
echo “target: $@ prereq: $<”
%.c %.raw:
touch $@
clean:
rm -f $(src_files)
#### 模式规则
模式规则经常被使用但是很混乱。 您可以通过两种方式查看它们:
* 一种定义自己的隐式规则的方法
* 一种更简单的静态模式规则
让我们先从一个例子开始:
Define a pattern rule that compiles every .c file into a .o file
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
模式规则在目标中包含`“%”`。 这个 `'%'` 匹配任何非空字符串,其他字符匹配它们自己。 模式规则先决条件中的`“%”`代表与目标中的`“%”`匹配的相同词干。
这是另一个例子:
Define a pattern rule that has no pattern in the prerequisites.
This just creates empty .c files when needed.
%.c:
touch $@
#### 双冒号规则
双冒号规则很少使用,但允许为同一目标定义多个规则。 如果这些是单个冒号,则会打印一条警告,并且只会运行第二组命令。
all: blah
blah::
echo “hello”
blah::
echo “hello again”
### 命令和执行
#### 命令回显/静音
在命令前添加 `@` 以阻止它被打印
您还可以使用 `-s` 运行 `make` 以在每行之前添加一个 `@`
all:
@echo “This make line will not be printed”
echo “But this will”
#### 命令执行
每个命令都在一个新的 `shell` 中运行(或者至少效果是这样的)
all:
cd …
# The cd above does not affect this line, because each command is effectively run in a new shell
echo pwd
# This cd command affects the next because they are on the same line
cd ..;echo `pwd`
# Same as above
cd ..; \
echo `pwd`
#### 默认shell
默认 shell 是 `/bin/sh`。 您可以通过更改变量 `SHELL` 来更改此设置:
SHELL=/bin/bash
cool:
echo “Hello from bash”
#### 双美元符号
如果你想让一个字符串有一个美元符号,你可以使用 `$$`。 这是在 `bash` 或 `sh` 中使用 `shell` 变量的方法。
请注意下一个示例中 `Makefile` 变量和 `Shell` 变量之间的区别。
make_var = I am a make variable
all:
# Same as running “sh_var=‘I am a shell variable’; echo $sh_var” in the shell
sh_var=‘I am a shell variable’; echo $$sh_var
# Same as running "echo I am a amke variable" in the shell
echo $(make_var)
#### 使用 -k、-i 和 - 进行错误处理
在运行 make 时添加 `-k` 以在遇到错误时继续运行。 如果您想立即查看 Make 的所有错误,这将很有帮助。
在命令前添加 `-` 以抑制错误
添加 `-i` 以使每个命令都发生这种情况。
one:
# This error will be printed but ignored, and make will continue to run
-false
touch one
#### 中断或杀死 make
仅注意:如果您按 ctrl+c make,它将删除刚刚创建的较新目标。
#### make的递归使用
要递归调用 makefile,请使用特殊的 `$(MAKE)` 而不是 `make`,因为它会为您传递 `make` 标志,并且本身不会受到它们的影响。
new_contents = “hello:\n\ttouch inside_file”
all:
mkdir -p subdir
printf $(new_contents) | sed -e ‘s/^ //’ > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
#### 导出、环境和递归 make
当 Make 启动时,它会根据执行时设置的所有环境变量自动创建 Make 变量
Run this with “export shell_env_var=‘I am an environment variable’; make”
all:
# Print out the Shell variable
echo $$shell_env_var
# Print out the Make variable
echo $(shell_env_var)
导出指令采用一个变量并将其设置为所有配方中所有 `shell` 命令的环境:
shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
echo $(shell_env_var)
echo $$shell_env_var
因此,当您在 `make` 内部运行 `make` 命令时,您可以使用 `export` 指令使其可供子 `make` 命令访问。 在此示例中,导出了 `cooly`,以便 `subdir` 中的 `makefile` 可以使用它。
new_contents = “hello:\n\techo $$(cooly)”
all:
mkdir -p subdir
printf $(new_contents) | sed -e ‘s/^ //’ > subdir/makefile
@echo “—MAKEFILE CONTENTS—”
@cd subdir && cat makefile
@echo “—END MAKEFILE CONTENTS—”
cd subdir && $(MAKE)
Note that variables and exports. They are set/affected globally.
cooly = “The subdirectory can see me!”
export cooly
This would nullify the line above: unexport cooly
clean:
rm -rf subdir
您还需要导出变量才能让它们在 shell 中运行。
one=this will only work locally
export two=we can run subcommands with this
all:
@echo $(one)
@echo KaTeX parse error: Can't use function '$' in math mode at position 12: one @echo $̲(two) @echo two
`.EXPORT_ALL_VARIABLES` 为您导出所有变量。
.EXPORT_ALL_VARIABLES:
new_contents = “hello:\n\techo $$(cooly)”
cooly = “The subdirectory can see me!”
This would nullify the line above: unexport cooly
all:
mkdir -p subdir
printf $(new_contents) | sed -e ‘s/^ //’ > subdir/makefile
@echo “—MAKEFILE CONTENTS—”
@cd subdir && cat makefile
@echo “—END MAKEFILE CONTENTS—”
cd subdir && $(MAKE)
clean:
rm -rf subdir
#### Make参数
有一个很好的[选项列表](https://bbs.csdn.net/topics/618545628)可以从 make 运行。 查看 `--dry-run`、`--touch`、`--old-file`。
您可以制定多个目标,即 make clean run test 运行 clean 目标,然后运行,然后测试。
### 变量2
#### 风格和修改
有两种类型的变量:
* 递归(使用 =) - 仅在使用命令时查找变量,而不是在定义时查找。
* 简单地扩展(使用 :=)——就像普通的命令式编程一样——只有到目前为止定义的那些才会被扩展
Recursive variable. This will print “later” below
one = one ${later_variable}
Simply expanded variable. This will not print “later” below
two := two ${later_variable}
later_variable = later
all:
echo $(one)
echo $(two)
简单扩展(使用 `:=`)允许您附加到变量。 递归定义会产生无限循环错误。
one = hello
one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there
all:
echo $(one)
`?=` 仅在尚未设置变量时设置变量
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
一行末尾的空格不会被删除,但开头的空格会被删除。 要使用单个空格创建变量,请使用 $(nullstring)
with_spaces = hello # with_spaces has many spaces after “hello”
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "
(
a
f
t
e
r
)
"
e
c
h
o
s
t
a
r
t
"
(after)" echo start"
(after)"echostart"(space)"end
一个未定义的变量实际上是一个空字符串!
all:
# Undefined variables are just empty strings!
echo $(nowhere)
使用 `+=` 追加
foo := start
foo += more
all:
echo $(foo)
字符串替换也是一种非常常见且有用的修改变量的方法。
#### 命令行参数和覆盖
您可以使用 override 覆盖来自命令行的变量。 在这里我们用 `make option_one=hi` 运行 make
Overrides command line arguments
override option_one = did_override
Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
#### 命令列表和定义
`define` 指令不是一个函数,尽管它可能看起来是这样。 我看到它很少使用,所以我不会详细介绍。
`define/endef` 只是创建一个分配给命令列表的变量。 请注意,这与在命令之间使用分号有点不同,因为正如预期的那样,每个命令都在单独的 `shell` 中运行。
one = export blah=“I was set!”; echo $$blah
define two
export blah=“I was set!”
echo $$blah
endef
all:
@echo “This prints ‘I was set’”
@
(
o
n
e
)
@
e
c
h
o
"
T
h
i
s
d
o
e
s
n
o
t
p
r
i
n
t
′
I
w
a
s
s
e
t
′
b
e
c
a
u
s
e
e
a
c
h
c
o
m
m
a
n
d
r
u
n
s
i
n
a
s
e
p
a
r
a
t
e
s
h
e
l
l
"
@
(one) @echo "This does not print 'I was set' because each command runs in a separate shell" @
(one)@echo"Thisdoesnotprint′Iwasset′becauseeachcommandrunsinaseparateshell"@(two)
#### 特定于目标的变量
可以为特定目标分配变量
all: one = cool
all:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
#### 特定于模式的变量
您可以为特定的目标模式分配变量
%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)
### Makefile 的条件部分
#### 条件if/else
foo = ok
all:
ifeq ($(foo), ok)
echo “foo equals ok”
else
echo “nope”
endif
#### 检查变量是否为空
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip
(
f
o
o
)
)
,
)
e
c
h
o
"
f
o
o
i
s
e
m
p
t
y
a
f
t
e
r
b
e
i
n
g
s
t
r
i
p
p
e
d
"
e
n
d
i
f
i
f
e
q
(
(foo)),) echo "foo is empty after being stripped" endif ifeq (
(foo)),)echo"fooisemptyafterbeingstripped"endififeq((nullstring),)
echo “nullstring doesn’t even have spaces”
endif
#### 检查变量是否定义
ifdef 不扩展变量引用; 它只是查看是否定义了某些内容
bar =
foo = $(bar)
all:
ifdef foo
echo “foo is defined”
endif
ifndef bar
echo “but bar is not”
endif
#### `$(makeflags)`
此示例向您展示如何使用 `findstring` 和 `MAKEFLAGS` 测试 make 标志。 使用 `make -i` 运行此示例以查看它打印出 `echo` 语句。
bar =
foo = $(bar)
all:
Search for the “-i” flag. MAKEFLAGS is just a list of single characters, one per flag. So look for “i” in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo “i was passed to MAKEFLAGS”
endif
### 函数
#### 第一个函数
函数主要用于文本处理。 使用 `$(fn, arguments)` 或 `${fn, arguments}` 调用函数。 您可以使用 `call` 内置函数创建自己的。 `Make` 有相当数量的内置函数。
bar := ${subst not, totally, “I am not superman”}
all:
@echo $(bar)
如果要替换空格或逗号,请使用变量
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst
(
s
p
a
c
e
)
,
(space),
(space),(comma),$(foo))
all:
@echo $(bar)
不要在第一个之后的参数中包含空格。 这将被视为字符串的一部分。
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
all:
# Output is “, a , b , c”. Notice the spaces introduced
@echo $(bar)
#### 字符串替换
`$(patsubst pattern,replacement,text)` 执行以下操作:
“在匹配模式的文本中找到以空格分隔的词,并用替换替换它们。这里的模式可能包含一个`‘%’`,它充当通配符,匹配一个词中任意数量的任意字符。如果替换也包含`‘%’`, `'%'` 替换为与模式中的 `'%'` 匹配的文本。只有模式中的第一个 `'%'` 和替换以这种方式处理;任何后续的 `'%'` 都保持不变。” (GNU 文档)
替换引用 `$(text:pattern=replacement)` 是对此的简写。
还有另一种仅替换后缀的简写形式:`$(text:suffix=replacement)`。 这里没有使用 `%` 通配符。
**注意**:不要为此速记添加额外的空格。 它将被视为搜索或替换词。
foo := a.o b.o l.a c.o
one :=
(
p
a
t
s
u
b
s
t
(patsubst %.o,%.c,
(patsubst(foo))
This is a shorthand for the above
two := $(foo:%.o=%.c)
This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)
all:
echo $(one)
echo $(two)
echo $(three)
#### foreach函数
foreach 函数如下所示:`$(foreach var,list,text)`。 它将一个单词列表(以空格分隔)转换为另一个单词列表。 `var` 设置为列表中的每个单词,并为每个单词扩展文本。
这会在每个单词后附加一个感叹号:
foo := who are you
For each “word” in foo, output that same word with an exclamation after
bar := ( f o r e a c h w r d , (foreach wrd, (foreachwrd,(foo),$(wrd)!)
all:
# Output is “who! are! you!”
@echo $(bar)
#### if函数
if 检查第一个参数是否为非空。 如果是,则运行第二个参数,否则运行第三个。
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
#### 调用函数
Make 支持创建基本函数。 您仅通过创建一个变量来“定义”该函数,但使用参数 `$(0)`、`$(1)` 等。然后您使用特殊调用函数调用该函数。 语法是 `$(call variable,param,param)`。 `$(0)` 是变量,而 `$(1)、$(2)` 等是参数。
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
# Outputs “Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:”
@echo $(call sweet_new_fn, go, tigers)
#### shell函数
shell - 这会调用 shell,但它会用空格替换换行符!
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
### 其他特性
#### include Makefile
include 指令告诉 make 读取一个或多个其他 makefile。 它是 makefile 中的一行,如下所示:
include filenames…
当您使用编译器标志(如 `-M`)基于源代码创建 `Makefile` 时,这尤其有用。 例如,如果某些 `c` 文件包含头文件,则该头文件将添加到由 `gcc` 编写的 `Makefile` 中。
#### vpath 指令
使用 `vpath` 指定存在某些先决条件集的位置。 格式为 `vpath <pattern> <directories, space/colon separated> <pattern>` 可以有一个 `%`,它匹配任何零个或多个字符。
您也可以使用变量 `VPATH` 全局执行此操作
vpath %.h …/headers …/other-directory
some_binary: …/headers blah.h
touch some_binary
…/headers:
mkdir …/headers
blah.h:
touch …/headers/blah.h
clean:
rm -rf …/headers
rm -f some_binary
#### 多行
反斜杠(`“\”`)字符使我们能够在命令太长时使用多行
some_file:
echo This line is too long, so
it is broken up into multiple lines
#### .PHONY
将 .PHONY 添加到目标将防止 `Make` 将虚假目标与文件名混淆。 在此示例中,如果创建了文件 `clean`,`make clean` 仍将运行。 从技术上讲,我应该在每个带有 `all` 或 `clean` 的示例中都使用它,但我没有保持示例干净。 此外,“phony”目标的名称通常很少是文件名,实际上许多人会跳过这一点。
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
#### .delete\_on\_error
如果命令返回非零退出状态,make 工具将停止运行规则(并将传播回先决条件)。
如果规则以这种方式失败,则 `DELETE_ON_ERROR` 将删除规则的目标。 这将发生在所有目标上,而不仅仅是像 `PHONY` 之前的目标。 始终使用它是个好主意,即使由于历史原因 make 没有这样做。
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false
### 生成文件
让我们来看一个非常有趣的 Make 示例,它适用于中型项目。
这个 `makefile` 的巧妙之处在于它会自动为您确定依赖项。 您所要做的就是将 `C/C++` 文件放在 `src/` 文件夹中。
TARGET_EXEC := final_program
BUILD_DIR := ./build
SRC_DIRS := ./src
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
h some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
#### .delete\_on\_error
如果命令返回非零退出状态,make 工具将停止运行规则(并将传播回先决条件)。
如果规则以这种方式失败,则 `DELETE_ON_ERROR` 将删除规则的目标。 这将发生在所有目标上,而不仅仅是像 `PHONY` 之前的目标。 始终使用它是个好主意,即使由于历史原因 make 没有这样做。
.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false
### 生成文件
让我们来看一个非常有趣的 Make 示例,它适用于中型项目。
这个 `makefile` 的巧妙之处在于它会自动为您确定依赖项。 您所要做的就是将 `C/C++` 文件放在 `src/` 文件夹中。
TARGET_EXEC := final_program
BUILD_DIR := ./build
SRC_DIRS := ./src
[外链图片转存中…(img-ABEy1KR6-1715750334548)]
[外链图片转存中…(img-MhMB3mOQ-1715750334548)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!