在这个工业化合作的时代,一个中等规模的程序可能包含一万个函数,显而易见,这不是凭借一个人可以完成的工程规模,多人如何在同一个项目中进行协作是现代编程语言设计中必须考虑的问题。 GoLang 中,通过包来管理不同的模块,从而实现代码的共享、重用与改进,同时,我们已经看到,包具有定义是否导出变量、函数的功能,从而实现代码的封装能力,保护维护者所编写的代码,同时提供变量、函数的命名空间功能。 Go 语言自带了 100 多个包,可以通过下面链接查看:
http://godoc.org
那么,我们如何创建我们自己的包,如何从网络中下载、构建已有的包,如何进行包版本的管理呢?本文我们就来详细解读。
每一个包都通过唯一的字符串进行标识,这个字符串被称为“导入路径”。 一个包需要使用其他包时,需要在 package 关键字生命的所在包行下面使用 import 关键字声明导入的包。
Go 语言本身没有对包的命名做任何限制,如何查找到包取决于 go 工具对包名的理解,但为了保证包导入路径的唯一性,除标准库中的包,其他包的导入路径应该以互联网域名(组织机构拥有的域名或用于存放包的域名)作为路径开始,从而保证唯一性的同时方便在互联网中查找到相应的包。 包的导入过程是很快的,主要是因为下面的三个原因:
- 导入块中包含的包名不存在无用包
- 编译器读取到文件头就可以确定文件的包依赖关系而不需要读取整个文件
- GoLang 包编译输出的目标文件不仅纪录他的导出信息,还纪录它所依赖包的导出信息
- 包依赖形成有向无环图,所以包可以独立并行编译
正如上面的例子中那样,包需要在 import 语句中进行导入,但导入后的包在代码中只需要使用完整包名的最后一段。 关于最后一段,有三个例外情况:
- 如果当前包定义一条用于执行的命令,那么他的包名最后一段只能为 main
- 文件名以 _test.go 结尾,包名最后一段为 test 说明他是一个普通包的外部测试包,当使用 go test 命令时,两个包会同时被构建
- 有些包管理工具(如 godep)会在 import 语句引入的包尾部增加版本号声明,如 "gopkg.in/yaml.v2",他的包名仍然是 "gopkg.in/yaml",不包含版本号
3.1. 重命名导入
既然包在实际的程序中只使用最后一段,那么很可能因为最后一段过长造成代码编写的不便,更为常见的,可能会出现两个不同的包具备相同的最后一段的可能,例如:
此时,就需要重命名导入,声明代码中通过重命名声明来使用相应的包而不是最后一段:
这样,在接下来的代码中,如果使用 rand 则使用的是包 "crypto/rand",而 mrand 则使用的是 "math/rand"
3.2. 空导入
GoLang 中,所有导入的包都必须在当前程序文件中引用,否则就会产生编译错误,但有时,我们仅希望导入一个包并执行其初始化操作,而不进行任何引用,这就需要进行“空导入”。 空导入指的是在 import 语句中通过在包名前添加下划线,声明这个包只执行其初始化操作,而不引用到程序中:
4.1. go 相关的环境变量
此前,我们介绍了 go 环境的搭建,go 安装成功后需要配置两个环境变量:
- $GOROOT — go 发行版所在根目录
- $GOPATH — go 包源码存放地址
通过 go env 命令可以查看所有 go 所依赖的环境变量及取值:
4.2. 包编译 — go build 与 go install
GoLang 拥有两个编译命令 — go build 与 go install,他们的区别常常让初学者迷惑。 他们的区别其实很简单:
- go install 生成包编译后用于依赖的包文件,并放到 $GOPATH/pkg 下,go build 不会
- go build 生成可执行文件在当前目录下, go install 生成可执行文件在 $GOPATH/bin 目录下
4.3. 远程 go 源码包的获取
通过 go get 命令,可以从网络上获取指定的包:
go get github.com/urfave/cli
go 工具会将获取到的包源码放置到 GOPATH/src 目录下,但问题在于,这样的方式没有维护包的版本,这就意味着如果远程的代码出现了更新,例如修复了隐含的 bug,或者你需要引用只有旧版本才提供的函数或字段,那你就只能到 GOPATH/src 目录下手动删除相应的包,然后手动进行拉取,这是极不方便的。
由于早期原生的 go get 工具的限制,很多项目使用了第三方的包管理工具 — godep 进行包管理。 godep 将项目中使用到的第三方库复制到项目的 Godeps 目录下,因此 go build、go install 等工具显然因为无法找到包而不能工作了,所以 godep 提供了一系列工具来代替原生的 go 工具。
5.1. 安装 godep
go get github.com/tools/godep
5.2. godep 的包管理
5.2.1. godep save
godep save 命令将项目中使用到的第三方库复制到项目的 Godeps 目录下。 在 Godeps/Godeps.json 文件中维护了各个依赖包的版本信息。 同时,执行命令后,包源码会被拷贝到 Godeps/_workspace/src 目录下用于后续进行的 godep go build 等命令的执行。
5.2.2. godep restore
godep restore 命令执行后会按照 Godeps/Godeps.json 列表,依次执行 go get -d -v 来下载对应依赖包到GOPATH路径下。
5.3. godep 的打包命令
godep go run main.go godep go build godep go install godep go test
可以看到,虽然 godep 实现了一套自己的打包工具,但从用法上,仍然与 go 原有工具十分接近。
govendor 是另一个 GoLang 常用的第三方包管理工具。 在 Golang1.5 版本后,Go 提供了 GO15VENDOREXPERIMENT 环境变量,并从 GoLang1.6 版本开始默认开启该环境变量。 一旦开启 GO15VENDOREXPERIMENT,go build 等工具除了搜索 $GOPATH/src 路径外,还会搜索当前项目的 vendor 路径,govendor 正是利用了这一特性来实现的。
6.1. govendor 的安装
go get -u -v github.com/kardianos/govendor
6.2. govendor 的使用
6.2.1. 初始化
进入项目根目录,执行:
govendor init
初始化执行完成后,会在项目根目录生成 vendor 目录和 vendor.json 文件,在 vendor.json 中,维护了项目所依赖的包和版本信息。
6.2.2. 从 $GOPATH 中复制依赖包到 vendor 目录
下面的命令一次性将 $GOPATH 中所有依赖包都同步到 vendor 目录中:
govendor add +external
如果你只想添加某个包,执行:
govendor add gopkg.in/yaml.v2
6.2.3. 列出代码引用的包及状态
govendor list
6.2.4. 从远程仓库添加或更新某个包
govendor fetch golang.org/x/net/context
以下方式指定了要拉取的包版本:
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55 govendor fetch golang.org/x/net/context@v1 govendor fetch golang.org/x/net/context@=v1
本文详细介绍了 GoLang 中依赖包的使用和管理,我们看到了原生包管理工具与两个常见的第三方包管理工具的使用。 但从 GoLang 1.11 版本开始,官方推出了一个崭新的包管理工具 — go module,随着 GoLang 1.13 版本的发布,go module 默认开启,官方开始强推 go module,使用 go module 作为包管理工具已经成为了官方倡导的趋势。 那么,go module 是什么,应该如何使用,他又是如何解决了包管理中的诸多问题的呢?敬请期待主页君下一篇文章。
《Go 语言程序设计》。 https://studygolang.com/articles/4385。 http://github.com/tools/godep。 github.com/kardianos/govendor。