随着 Go 语言的深入使用,其依赖管理机制也一直是各位 Gopher 热衷于探讨的话题。Go 语言的源码依赖可通过
go get
命令来获取,但自动化程度不高,于是官方提供了 Dep 这样的自动化批量管理依赖的工具。虽然 Go 语言的依赖管理在很多方面还是不如人意,但整个体系正在日趋完善,本篇就将从最基本的依赖管理场景出发,一同探讨 Go 语言依赖管理的一些最佳实践。
在 Go 语言中,我们通过 go get
命令将 GitHub 或者 Google Code 上的代码下载到本地指定目录,然后在开发代码中通过 import
的形式引用本地的代码。
import "github.com/spf13/cobra"
Go 语言可以通过直接分析代码中的 import 语句来查询依赖关系。go get 命令在执行时,就会自动解析 import 来安装所有的依赖。那么下载的依赖在本地是如何存储的呢?
这里就涉及到 Go 语言的 WORKSPACE 概念,简单来说就是通过 GOPATH 环境变量来设置 Go 代码的位置。一般来说,GOPATH 目录下会包含 pkg、src 和 bin 三个子目录,这三个目录各有用处。
bin 目录用来放置编译好的可执行文件,为了使得这里的可执行文件可以方便的运行,在 shell 中设置PATH变量。
src 目录用来放置代码源文件,在进行 import 时,是使用这个位置作为根目录的。自己编写的代码也应该放在这下面,不同的项目放在不同的目录下进行管理。
pkg 用来放置安装的包的链接对象(Object)的。这个概念有点类似于链接库,Go 会将编译出的可连接库放在这里,方便编译时链接。不同的系统和处理器架构的对象会在 pkg 存放在不同的文件夹中。
当项目在 src 目录下管理时,多个项目可能都会使用相同的依赖,如果每个项目都存一份依赖显然会带来大量的冗余,这里我们推荐一个设置 GOPATH 环境变量时的小技巧。
export GOPATH="/usr/local/share/go:$HOME/codes/go"
这样第三方包就会默认放置在第一个路径中,而你可以在第二个路径下编写自己的代码,多个项目共享一份依赖。
dep 是 Go 语言官方提供的依赖管理工具,跟其他依赖管理工具类似,都是通过一个文件描述依赖的坐标信息,然后批量管理(下载、升级等)依赖包(源码)。dep 是一个开源项目, 大家可以在 https://github.com/golang/dep 了解详细信息,其安装方式大家可以参考官方说明,这里我们主要介绍其使用。
通过 dep init
命令来初始化,会创建Gopkg.lock,Gopkg.toml文件和一个空的vendor目录。
我们在代码中通过 import
命令添加依赖后,通过 dep ensure
就可以下载依赖到本地 $GOPATH/src
目录下。
main.go
...
import (
"github.com/golang/glog"
)
...
Gopkg.lock
[[projects]]
branch = "master"
digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467"
name = "github.com/golang/glog"
packages = ["."]
pruneopts = "UT"
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/golang/glog"
]
solver-name = "gps-cdcl"
solver-version = 1
通过 dep status
我们可以查看当前依赖引用的情况
$ dep status
PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED
github.com/golang/glog branch master branch master 23def4e 23def4e 1
另外有一个 dep check
命令来检查是否存在依赖被引用,但是代码中并没有使用的情况,Go 语言对于依赖的引用比较严格,不允许引用了但是没使用的情况。从软件安全的角度考虑,这是一个很好的实践,避免引入一些安全风险。
$ dep check
# Gopkg.lock is out of sync:
github.com/golang/glog: in Gopkg.lock's input-imports, but neither imported nor required
当然,这种时候我们就需要移除本地依赖,最好不要手动删除vendor中的内容,而是通过 dep ensure -update
命令来移除。
从 dep 的目录结构,我们可以分析出 dep 的基本工作思路:
这里面有两个关键的步骤:
解析依赖
从当前项目的 import
文件中解析出整个工程的依赖情况,并结合 Gopkg.toml 定义的规则,然后将依赖关系输出给 Gopkg.lock,注意这个 lock 文件最好不要手动修改。
获取依赖
通过 Gopkg.lock 了解整个依赖关系之后,将依赖的具体内容拉取下来放到 vendor 目录中,然后执行 Go build 时从本地的 vendor 读取依赖并完成构建。
这一些都是在 dep ensure
时完成的,其实在执行这个命令时还可以传参数,最主要的是 -no-vendor
和 -vendor-only
这两个参数。
-no-vendor
参数只会导致运行 resolve 函数,结果是创建一个新的Gopkg.lock 文件,不会更新 vendor;而 -vendor-only
参数将跳过 resolve 并仅运行 vendoring 函数,导致 vendor/
从已存在的Gopkg.lock 重新更新。
关于 dep 更多深入内容,可以参考 https://golang.github.io/dep/docs/introduction.html
Go dep 目前是一款比较好用的依赖管理工具,很多比较大型的项目都在使用,从中可以学习到依赖管理的一些基本思路,对于理解其他语言,比如NPM的依赖管理模型也是比较有好处的。