你必须非常努力,才能看起来毫不费力!
微信搜索公众号[ 漫漫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 环境下运行
规则
Makefile 的规则很简单,分为三个部分:
target : prerequisites
command
...
target:目标,可以是一个文件,或者是一个标签 label
prerequisites:targets 依赖的东西, 可以是文件,也可以依赖其他 target
command:shell 命令(开头是 Tab 键)
- 如果 prerequisites 比 target 要新,就会 执行 command 命令 (已存在的文件比不存在的文件新)
- 如果 prerequisites 中存在其他 target,对该 target 重复第一条
举个例子:
hello: hello.go
go build hello.go
如果 hello 文件不存在,hello.go 肯定比不存在的 hello 新,执行命令 go build hello.go
如果 hello 文件存在,但是 hello.go 又有了新的修改,hello.go 也比 hello 新,执行 go build hello.go
如果 hello 文件存在,且 hello.go 没有改动,不执行任何操作
再举个例子:
hello: hello.go
go build hello.go
world: hello world.go
go build world.go
hello 是个 target ,world 也是个 target,且 world 依赖 hello。
如果执行 world 这个 target,会有如下两种情形:
world 不存在,遍历 prerequisites
hello 是个 target,走一遍例一描述的流程
如果 hello 文件不存在,执行命令
go build hello.go
,此时生成了 hello,比world新如果 hello 文件存在,但是 hello.go 又有了新的修改,执行
go build hello.go
,,此时生成了 hello,比 world 新如果 hello 文件存在,且 hello.go 没有改动,继续判断 world.go
- world.go 是个普通文件,比不存在的 world 新
- world 的依赖中,存在有比 world 新的,因此执行 command
world 存在,遍历 prerequisites
hello 是个 target,走一遍例一描述的流程
如果 hello 文件不存在,那么就会执行命令
go build hello.go
,此时 hello 比 world 新了如果 hello 文件存在,但是 hello.go 又有了新的修改,也会执行
go build hello.go
,此时 hello 比 world 新如果 hello 文件存在,且 hello.go 没有改动,不执行任何操作
- world.go 是个普通文件,如果修改过,那么比 world 新,否则不如 world 新
- 判断 prerequisites 中的 hello 或者 world.go 是否存在比 world 新的,如果有,执行command;没有的话不执行任何操作
多目标
有可能我们会有多个 target 规则类似,我们可以将它们合并起来:
.PHONY: target1 target2
target1 target2: prerequisites
command
等同于
.PHONY: target1 target2
target1: prerequisites
command
target2: prerequisites
command
伪目标
上面说过,target 不一定是一个文件,也可能是一个标签,因为有些操作,我们不一定会生成文件,比如清理一些中间文件,运行单测等,这种 target 我们把它叫做伪目标。
hello: hello.go
go build hello.go
world: hello world.go
go build world.go
clean:
rm hello world
clean 就是一个伪目标,只是一个标签,运行这个伪目标,需要显式地指定这个目标,即 make clean
➜ make clean
rm hello world
但是有可能会有文件和我们的伪目标名称重名,这样我们运行 make clean 的时候就达不到我们的目的了。
# 如果有重名文件存在,make clean 就不会运行
➜ touch clean
➜ make clean
make: `clean' is up to date.
为了防止这样的情况,我们可以使用 ".PHONY" 显式地声明我们的 target 是个 伪目标,这样即使有重名文件在,也不会影响 target 的运行。
hello: hello.go
go build hello.go
world: hello world.go
go build world.go
# 显式声明 clean 是个伪目标
.PHONY: clean
clean:
rm hello world
➜ make world
go build hello.go
go build world.go
➜ touch clean
➜ make clean
rm hello world
运行
上面我们其实已经运行过 makefile 了,这里我们再来简单看下:
hello: hello.go
go build hello.go
world: hello world.go
go build world.go
# 显式声明 clean 是个伪目标
.PHONY: clean
clean:
rm hello world
在默认的方式下,也就是我们只输入 make
命令。那么,
- make 会在当前目录下找名字叫 “Makefile” 或 “makefile” 的文件。
- 如果找到,它会找文件中的第一个 target,运行该 target,并打印相应的日志。
或者我们可以输入 make + target ,强制运行某个 target,比如输入 make world
。
➜ make
go build hello.go
➜ makefile_study make world
go build world.go
➜ make clean
rm hello world
引用 Makefile
在 Makefile 中使用 include
关键字可以把别的 Makefile 包含进来,这很像C语言的 #include
,被包含的文件会放在当前文件的包含位置。 include
的语法是:
include filenames…
- filename 可以是绝对路径或者当前目录下的文件;如果 filename 是空,不会进行引用也不会报错。
- include 前面可以有多个空格,但是
一定不能
是Tab
- include 和 filename 直接使用空格分隔;多个 filename 之间也使用空格分隔
- filename 中可以使用变量
举个例子,如果你当前文件夹下有三个 makefile 文件,分别叫做 a.mk、b.mk、c.mk,那么如下引用
include foo *.mk
相当于
include foo a.mk b.mk c.mk
Makefile 在处理 include 时,遇到一个 include 文件就读取该文件内容,读取完后再处理下一个 include 文件。
如果一个文件没有指定路径,会在当前路径下寻找,如果没找到,还会按照如下顺序寻找:
- 运行 Make 命令时使用 '-I' 或 '--include-dir' 参数指定的路径
- 如果如下目录存在的话,会挨个寻找。prefix/include(一般是/usr/local/include) 、/usr/gnu/include、/usr/local/include、 /usr/include
在处理 include 时,如果一个 include 的文件没有找到,程序不会终止,只会打印一条警告信息,然后继续处理。当读取完整个 Makefile 文件后,程序再去重试处理这些没有找到的文件,如果还是处理失败,会产生一个致命错误。如果想忽略文件不存在带来的报错,可以在 include 前面加上一个减号 '-',表示忽略引用错误。
-include filenames…
我们一般都是有一个主文件 Makefile 对外暴露,其他的引用文件被主文件 Makefile 调用,类似函数调用。一个 target 调用 另一个 target,可以直接使用 $(MAKE)+target
。
a.mk
.PHONY: a_func
a_func:
echo "this is a_func"
Makefile
include a.mk
.PHONY: func
func:
$(MKAKE) a_func # 调用 a.mk 中的 a_func target
总结
本文主要介绍了 Makefile 的运行机制,分为以下几个方面:
- 规则:依据 prerequisites 是否比 target新,决定是否执行 command
- 多目标:多个 target 的 prerequisites 和 command类似时,使用多目标
- 伪目标:target 不是一个目标文件
- 运行:如何运行 Make
- 引用:引用其他 Makefile 文件
更多
个人博客: https://lifelmy.github.io/
微信公众号:漫漫Coding路