你必须非常努力,才能看起来毫不费力!
微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero !
前言
在 Go 语言开发中,我们希望能够规范代码风格,每个成员在提交时可以一键格式化,同时检查是否有语法错误;我们希望能够一键运行单测,生成单测报告;我们希望能够一键编译、打包、发布项目,这就需要使用到 Make。Make 有很多种,我们常用的就是 GUN Make,有了 Make,我们将极大的提高项目开发、测试、发布的效率。
Make 最初是为 C、C++项目的编译、构建服务的,因此有很多为C、C++的特性,但是这些特性在 Go 开发中使用不到。毕竟每个人的时间有限,我们就只学习Go 开发中 Make 使用所必需的知识。
Make 的规则都在 Makefile 文件编写上,本篇文章,我们来学习 Makefile 命令和变量相关知识。
make 命令在 Windows 下不支持运行,需要在 Linux 或 Mac 环境下运行
命令
命令显示
运行 target 时,执行命令的同时,默认会输出命令到控制台
.PHONY: echo_test
echo_test:
echo "hello"
echo "world"
➜ make echo_test
echo "hello"
hello
echo "world"
world
如果在命令前加上一个 @
符号,则只执行命令,不显示命令
.PHONY: echo_test
echo_test:
@echo "hello" # 这个命令不会显示
echo "world"
➜ make echo_test
hello
echo "world"
world
@
符号只对单个命令生效,如果想要对所有命令都生效,可以使用 -s
或者 --silent
参数,表示沉默:
➜ make -s echo_test
hello
world
如果想要只显示命令,不执行命令,使用如下参数:make -n 或者 make --just-print
# .PHONY 表示这是一个伪目标
.PHONY: echo_test
echo_test:
echo "hello"
@echo "world"
➜ make -n echo_test
echo "hello"
echo "world"
命令执行
如果想要后一个命令基于前一个命令,需要在命令之间加上分号;而命令写在两行是单独执行的:
exec:
cd /tmp
pwd
➜ make -s exec
/Users/admin/makefile_study
通过上面的例子,可以看到第二个命令执行时,并没有在 /tmp 目录,说明前一个命令并没有影响到后一个命令。
我们把两个命令使用分号分隔
exec:
cd /tmp; pwd
➜ make -s exec
/tmp
命令出错
在我们执行命令时,有可能命令会出错,影响后续命令的执行。对于有些错误,我们可以忽略,让命令继续执行下去。比如我们想新建个文件夹,如果文件夹本来就存在,新建就会出错,但我们需要的就是文件夹存在,这种错误可以忽略。
当前目录 test 文件夹不存在,新建文件夹和文件,并显示文件列表
exec:
@mkdir test; cd test; touch a.txt; ls
第一次运行正常
➜ make exec
a.txt
第二次运行就会提示错误,但是同一行的命令会继续执行下去
➜ make exec
mkdir: test: File exists
a.txt
如果存在多行命令的话,前面一行的命令报错,后面的命令就不会继续执行了
exec:
@mkdir test
@echo "hello world"
➜ make exec
mkdir: test: File exists
make: *** [exec] Error 1
如果我们想忽略这个报错的话,可以有多种方式:
- 在命令前面加上一个减号 '-' ,表示忽略这个错误,继续执行下面的命令
exec:
-@mkdir test
@echo "hello world"
➜ make exec
mkdir: test: File exists
make: [exec] Error 1 (ignored)
hello world
- 对规则 加上 .IGNORE 标记,会忽略该规则中所有命令的错误
.IGNORE: exec
exec:
@mkdir test
@echo "hello world"
➜ make exec
mkdir: test: File exists
make: [exec] Error 1 (ignored)
hello world
- make 中加上
-i
或者--ignore-errors
参数,那么此次运行的所有命令都会忽略错误
exec:
@mkdir test
@echo "hello world"
➜ make -i exec
mkdir: test: File exists
make: [exec] Error 1 (ignored)
hello world
定义命令模板
有时候可能有一些常用的功能,我们可以将其抽出来做成一个模板,供其他command 调用。定义模板的语法为:define
,接模板的名字;中间是命令,最后以 endef
结尾。
define template-name
...commands...
endef
使用命令模板,就像使用普通变量一样:
define my_template
@echo "this is a template"
endef
.PHONY: test
test:
$(my_template)
➜ make test
this is a template
自动变量
使用命令模板,经常需要使用自动常量,这种变量和 shell 中的 位置参数变量 类似,常用的有这几个:
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
.PHONY: test
test: hello.go world.go
@echo $@ # test
@echo $^ # hello.go world.go
@echo $< # hello.go
@echo $? # hello.go world.go
我们就定义一个模板,输出 target 和 所有的 prerequisites,然后再使用这个模板:
define my_template
@echo $@ $^
endef
.PHONY: hello
hello: hello.go
$(my_template)
.PHONY: world
world: world.go
$(my_template)
➜ make hello
hello hello.go
➜ make world
world world.go
变量
变量的声明和使用
和其他编程语言一样,Makefile 也可以定义变量,在 target、prerequisites、command中都可以使用。变量名由字母、数字和下划线组成(可以是数字开头),大小写敏感。
定义变量时,变量名 和 值 之间使用 "=" 连接,"=" 两侧空格不敏感;使用变量时,可以使用 $+变量名
的形式,更推荐的方式还是使用 $(变量名)
将变量包裹起来。
name= tom
age = 18
info:
echo $name
echo $(age)
➜ make info
echo tom
tom
echo 18
18
可以将变量使用在 target 中:
MyTarget = info
$(MyTarget):
go build hello.go
go build world.go
➜ make info
go build hello.go
go build world.go
也可以将变量使用在 prerequisite 中:
files = hello.go
info: $(files)
go build $(files)
➜ make info
go build hello.go
Makefile 中的变量,会在使用的时候 展开,直接使用你定义的值来替代,你完全可以这样使用这样的骚操作,但是并不推荐:
type = go
build:clean
go build hello.$(type)
go build world.$(type)
.PHONY: clean
clean:
-@rm -f hello world
➜ make build
go build hello.go
go build world.go
使用其他变量
我们可以一个变量中使用其他变量
name = tom
age = 18
detail = $(name) $(age)
info:
@echo $(detail)
➜ make info
tom 18
在我们平常编程过程中,被引用的变量,一定要定义在前面,但是在 Makefile 中没有这个限制,你可以引用后面定义的变量(也可以理解为懒加载,只有用到时才会展开,此时整个文件已经都加载完了,因此不在乎使用的变量定义在前或者在后):
detail = $(name) $(age)
name = tom
age = 18
info:
@echo $(detail)
➜ make info
tom 18
如果可以引用之后的变量,就可能出现递归的情况,比如下面的示例,a 引用 b,b 引用 a,当然 makefile 是会检测到这种递归的。
a = $(b)
b = $(a)
info:
@echo $(b)
➜ make info
Makefile:4: *** Recursive variable `b' references itself (eventually). Stop.
懒加载
上面也提到过,Makefile 中的变量是懒加载的,只有在使用的时候,才会将变量的值展开,也就是说后面定义的变量值会将之前的覆盖:
name = tom
age = 18
detail = $(name) $(age)
info:
@echo $(detail)
age = 22
➜ make info
tom 22 # 被后面的值覆盖了
当然,我们可以强制指定当前的变量值,即使用 " := " 来定义变量:
name = tom
age = 18
detail := $(name) $(age)
.PHONY: info
info:
@echo $(detail)
age = 22
➜ make info
tom 18 # 使用的是定义detail 时 age 的值
如果当前没有变量值,展开就是空值:
name = tom
# 此时还没有定义 age 变量
detail := $(name) $(age)
.PHONY: info
info:
@echo $(detail)
age = 22
➜ make info
tom # age 是空值
追加变量值
我们可以对已有的变量进行修改,重新赋值或者追加,我们可以使用 += 来追加:
name = tom
name += john
.PHONY: info
info:
@echo $(name)
➜ make info
tom john
需要注意的是,追加时,变量的赋值方式会延续初次定义的符号,使用 = 或者 := 来保持是否需要懒加载
name = tom
name += john # 等同于 name = tom john
name := tom
name += john # 等同于 name := tom john
因此在引用其他变量时就需要注意:
name = tom
# 等同于 name = tom $(a_name)
name += $(a_name)
.PHONY: info
info:
@echo $(name) # tom john
a_name = john
上述例子保持了懒加载,而下面这个则不会使用懒加载:
name := tom
# 等同于 name := tom $(a_name)
name += $(a_name)
.PHONY: info
info:
@echo $(name) # tom
a_name = john
总结
本篇文章我们一起学习了Makefile 命令和变量相关的知识点,主要有:
- 命令显示:使用
@
符号控制命令是否打印 - 命令执行:使用
;
符号使得后一个命令基于前一个命令执行 - 命令出错:使用
-
符号来忽略命令执行中产生的错误 - 位置变量:可以获取命令中的参数,结合命令模板使用会更加易用
- 变量使用:包括变量的声明、使用以及懒加载机制
更多
个人博客: https://lifelmy.github.io/
微信公众号:漫漫Coding路