Go 语言包管理机制深入分析

随着 Go 语言的深入使用,其依赖管理机制也一直是各位 Gopher 热衷于探讨的话题。Go 语言的源码依赖可通过 go get 命令来获取,但自动化程度不高,于是官方提供了 Dep 这样的自动化批量管理依赖的工具。虽然 Go 语言的依赖管理在很多方面还是不如人意,但整个体系正在日趋完善,本篇就将从最基本的依赖管理场景出发,一同探讨 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 是 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 的基本工作思路:

Go 语言包管理机制深入分析_第1张图片

这里面有两个关键的步骤:

  • 解析依赖
    从当前项目的 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的依赖管理模型也是比较有好处的。

你可能感兴趣的:(Golang,DevOps)