⚠ 转载请注明出处:作者:ZobinHuang,更新日期:May 10 2021
本作品由 ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。
1. Go Package 和 Go Module 的概念
(1) 何为 Go Package?
Go Package 其实就是函数、类型、变量和常量的集合。一个 Go Package 中可能有好几个 Go 文件,通常组织在同一个文件夹下,文件夹名即为 PACKAGE_NAME。这些 Go 文件通过在文件头声明: "package PACKAGE_NAME" 来声明它们同属于 Package PACKAGE_NAME。别处的 Go 文件使用正确的路径 (何为正确的路径?下面会介绍) import PACKAGE_NAME 后,就能够正常使用在这个 Package 中定义的函数、类型、变量和常量等内容。
(2) go tools (Go 的命令行工具)
Go 也提供了一些命令行工具来辅助我们管理工程和编译文件。我们在这里先介绍他们,因为在后面的内容中我们将时不时会用到它们。下面对一些重要的 go tools 进行介绍。
(a) go build [-o output] [build flags] [packages]:
用于编译 Packages。go build会编译传入参数 [packages] 文件本体和所有的在 import 声明中导入的 Packages。go build 在编译后不会将生成的可执行文件进行安装 (放置到 /bin 目录)。如果编译的 Packages 中包含 main Package (见下文),则会生成一个可执行文件在当前目录;如果没有 main Package,则只会执行编译,检查代码是否有错误,并不会生成任何可执行文件。当传入的 [packages] 是一个装着多个 .go 文件的文件夹时,go build 会把他们当作属于同一个 Package 的一串 .go 文件来处理。
(b) go install [build flags] [packages]:
go install 会编译 + 安装指定的 Packages。其与 go build 的区别就是在编译成功后会把可执行文件安装到系统的指定路径中。具体的安装目录是:
(i) 如果 GOBIN 环境变量有被设置的话,则会被安装到 GOBIN 所指示的路径。GOBIN 变量默认是空的。
(ii) 如果 GOBIN 环境变量没有被设置的话,则会被安装至 $GOPATH/bin 目录下。
* 我们下文中会对 Go 常用的一些环境变量做一些介绍。
(3) 特殊的 Go Package: main
1 | ROOTPATH |
在一个 Go 程序中,有一个特殊的必须得存在的 Package: main。Go 编译器在编译 Go 程序时,会找到 Package main 中的的func main(),这也就是整个程序的入口。通常来说,我们会把 Package 源代码文件分别放置在根目录下的对应名称的文件夹下,然后把 属于 Package main 的源文件放在根目录下,然后在根目录下的源文件去 import 相应的 Packges,如上图所示,下面的论述会给出具体例子。
(4) 何为 Go Module
1 | ROOTPATH |
Go 在 v1.11 之后就引入了 Go Module 作为其 Package 的管理模式。首先我们解释一下 Module 的一些概念。Go 的 Module 实际上就是若干 Packages 的集合,通常我们会用一个 repo 来放置一个 Go Module。一个 Go Module 的目录树基本上如上图所示,其根目录下有一个 go.mod 文件,这个文件有两个功能:
(i) 表明该 Module 的 Module Path,这个 Module Path 用于标识这个 Module,相当于名字。如果这个 Module 不是用于发布的 Module,则这个 Module Path 可能就是简简单单的用户自定义名字;如果这个 Module 适用于发布到各种 repo 托管网站上给别人使用的,那这个 Module Path 就会像是:github.com/user_name/repo_name 之类的样子。Go 的 Module 有一个很强大的功能:当它发现用户 import 了一个公开发布的 Module 时,它自动就会顺着这个 Module Path 把这个 Module 下载到本地来。因此对于公开发布的 Module,必须命名为这种可以被解析并下载的格式。
(ii) 表明该 Module 依赖的其它 Module 的 Module Path。
我们来看看两个例子。如上图所示,左边的是一个名为 Bob 的 Module,其下面有一个名为 Alex 的 Package,这个例子很简单地告诉了我们 Module 和 Package 的关系。来看右边的带有一点迷惑性的真实例子,这是用于 Web 开发的 Module gin 的例子,我们可以看到这个 Module 叫作 gin,其下面有一个 Package 也叫作 gin,值得读者进行注意区分,我们在下一小节会着重讲述 Import Packages 的原理,所以理解 Module 和 Package 是两种不同的东西很重要,即使它们可能重名。
(5) 看透 Go Package 的 Import
此时,基于上面的理解,我们来看一下 Go Package 在 Import 时候的注意事项。总结起来就一句话:import 的是目录,调用的是包名。如上面的例子所示,我们在 $ROOTPATH/src/dir_1 和 $ROOTPATH/src/dir_2,下面分别有属于 Package test_module 的文件 (实际上这么安排是不合理的,属于同一个 Package 的文件理应位于同一个文件夹下,此处仅是为了说明)。我们分别在根目录下的属于 Package main 的文件中将 Package test_module Import 进来,我们以 test_1.go 为例。注意到我们是 Import 进来了包含 属于 Package test_module 的 go 源码文件的目录,而不是源码文件。而且值得注意的是,我们 Import 的是 "test/src/dir_1",其中 "test" 是我们 Module 的名字 (根目录下有一个 go.mod,我们的根目录本身就是一个 Module),"/src/dir_1" 用于定位在该 Module 下的某一个 Package (定位目录里是不会体现 Package 的名字的),这就是我们定位一个 Module 中的某一个 Package 时的写法。假如我们要定位在 Github 上发布的 Module gin 的 Package gin,我们的写法就会是 "import ("github.com/gin-gonic/gin")",再强调一次,结尾的 gin 是 Module 名,而不是 Packge 名。在这个仓库中,Package gin 的 Go 源码文件本身就分布在根目录下,因此定位到根目录就足够了。
另外值得注意的是,一个 import 路径下只能有一个 package,否则 import 将会报错。当我们 Import 成功后,我们就能够使用对应 Package 中的内容,在使用时,我们得在对应的 函数/变量/常量/类型 前加上 Package 名,如上图的 test_module.PrintTest1()。并且还有一个很重要的点是,在一个 Package 中,只有 函数名/变量名/常量名/类型名 的首字母是大写的,这个名称才能在 Import 这个 Package 的代码里被使用。
而且,在上面的 test_1.go 的例子中,假如说我们在 "test/src/dir_1" 后面继续添加 "test/src/dir_2",这个时候编译器是会报错的,提示我们 Package test_module 已经被导入过了,不能重复导入。这也就是为什么属于同一个 Package 的 go 源码文件一定尽量保证放置于同一个文件夹下的原因。
2. 相关环境变量
(1) GOPATH
GOPATH 环境变量用于告诉 go 编译器在编译的时候的检索目录,go 编译器将会检索 GOPATH 下所有目录的 go 源文件来找到所使用到的 Package。GOPATH 的默认值一般是在 用户目录下的 go 文件夹 ($HOME/go on Unix, %USERPROFILE%\go on Windows)。回忆我们上文讲到,当 go 发现用户 import 了一个公开发布的 Module 时,它自动就会顺着这个 Module Path 把这个 Module 下载到本地来。如上图所示,就是一个例子。我们在 GOPATH 下找到了被自动下载到本地的 Module mux 的源码。也就是说,go 在真正编译用户的代码之前,会把本地没有的但是被用户 Import 的远程 Module 先通过命令 go get xxx 下载到本地的 GOPATH 里的对应位置中,然后开始编译操作。
(2) GOBIN
GOBIN 环境变量用于告诉 go 编译器在生成可执行文件时 (对应 go install xxx.go 命令),存放可执行文件的位置。默认情况下,GOBIN 是空的,go 编译器生成的可执行文件默认存放在 $GOPATH/bin 下面。当 GOPATH 里有大于等于两条检索目录时,GOBIN 就必须被设置,否则编译器将不知道该把可执行文件放在哪里。 ```
附录:参考源
- golang.org, GOPATH environment variable
- Github, SettingGOPATH
- Stack Overflow What should be the values of GOPATH and GOROOT?
- li24yang75, 初探 Go 的编译命令执行过程
- golang.org, How to Write Go Code
- golang.org, Command go
- CSDN harryhare, 一个例子明白go 的package
