使用 Glide 管理你 Golang 项目依赖

依赖管理

项目工程化的第一步,就是要优雅的管理项目依赖。听说过依赖管理和构建工具的鼻祖 Maven/Ant 这类 Java 平台经典的项目吗?
Python 的 pip、Ruby 的 Bundler、NodeJs 的 npm/yarn 等,都是诸如此类的工具。有的是官方的,有的不是官方但却是基于官方所创造的条件而诞生的。

今天要介绍的 Glide 就是 Go 语言平台的一个十分优秀的依赖管理工具,或者叫“包管理工具”。听起来跟 Linux 的软件仓库包管理有点像对吧,因为确实很像啊。

Go 语言原生包管理的缺陷

在 Go 还未像今天这么成熟之前,go get 被许多人痛批过,被骂成”脑残想出来的包管理机制”、“懒到连个软件仓库都不愿意维护”等。
不过那时候也的确如此,go 的 get 子命令管理依赖有很多大缺陷:

  • 能拉取源码的平台很有限,绝大多数依赖的是 github.com
  • 不能区分版本,以至于令开发者以最后一项包名作为版本划分
  • 依赖 列表/关系 无法持久化到本地,需要找出所有依赖包然后一个个 go get
  • 只能依赖本地全局仓库(GOPATH/GOROOT),无法将库放置于局部仓库($PROJECT_HOME/vendor)
  • 等等等等

槽点喷点太多,难怪遭到嘲讽和谩骂。不过显然,在 Go 语言已经足够成熟的今天,那些曾经瞧不起 Go、痛批 Go 的人,大错特错了。

Go 的第三方包管理

在 Go 的包管理和依赖机制不完善的时候已经涌现一批包管理的最佳实践出来,在 Go 1.5 新增了实验特性 GO15VENDOREXPERIMENT 以后更是如雨后春笋一般一个个冒出来。此时,Go 官方的意图展现出来,才生生的打脸了那些瞧不起 Go 团队的人。

Go 提供了原始的 go get ,让第三方包管理可以基于 go get 做扩展。GO15VENDOREXPERIMENT 特性让局部依赖成为现实。Go 官方在给第三方包管理营造条件以及引导开发者用户至所推荐的方向,促进社区的繁荣。证明了一个语言技术的生态不仅仅只能靠官方或者取决于官方的完善程度。(要知道 Java 所做更是少得可怜,但是不缺乏出现 Maven 那样庞大的平台和 Gradle 这样强大的工具)

解释:GO15VENDOREXPERIMENT 是 Go 1.5 版本新增的一个环境变量,如果将值改为 1 则表示启用。它可以将项目根目录名为 vendor 的目录添加到 Go 的库搜寻路径中,实现一个局部依赖的效果。
这个特性在 1.5 版本作为实验特性被添加,1.6 中默认被启用,1.7 移除变量加入标准中。

Glide

Glide 是众多实现 GO15VENDOREXPERIMENT 特性的包管理工具之一,但它是本文最为推荐的,具体为什么推荐它,原因很简单,因为它目前最受关注。

几大主要功能:

  1. 持久化依赖列表至配置文件中,包括依赖版本(支持范围限定)以及私人仓库等
  2. 持久化关系树至 lock 文件中(类似于 yarn 和 cargo),以重复拉取相同版本依赖
  3. 兼容 go get 所支持的版本控制系统:Git, Bzr, HG, and SVN
  4. 支持 GO15VENDOREXPERIMENT 特性,使得不同项目可以依赖相同项目的不同版本
  5. 可以导入其他工具配置,例如: Godep, GPM, Gom, and GB

安装 Glide:

Linux:

curl https://glide.sh/get | sh
          

go get (Windows 可用):

go get -u github.com/Masterminds/glide
          

使用 Glide

假若已存在项目(rest 的 dome 代码):

package main
          
          
          import (
          	"github.com/ant0ine/go-json-rest/rest"
          	"log"
          	"net/http"
          )
          
          func main() {
          	api := rest.NewApi()
          	api.Use(rest.DefaultDevStack...)
          	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
          		w.WriteJson(map[string]string{"Body": "Hello World!"})
          	}))
          	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
          }
          

根目录执行:

glide install
          

Glide 会以交互的方式构建配置文件,并且自动寻找项目的所有依赖下载至 vendor 目录并注册进 glide.yaml 中:

.
          ├── glide.lock
          ├── glide.yaml
          ├── main.go
          └── vendor
          

glide.yaml:

package: golang.bluerain.io/learn-golang/glide
          import:
          - package: github.com/ant0ine/go-json-rest
            version: v3.3.2
            subpackages:
            - rest
          

如果我们想手动添加一个依赖:

glide get github.com/go-resty/resty#~v0.11
          

glide get 类似于 go get,但是它支持仓库标签(后面的 v0.11 表示版本即标签),不仅如此,它甚至可以控制版本到某个 commit 。注意:~v0.11 是一个范围限定。

此时的 glide.yaml:

package: golang.bluerain.io/learn-golang/glide
          import:
          - package: github.com/ant0ine/go-json-rest
            version: v3.3.2
            subpackages:
            - rest
          - package: github.com/go-resty/resty
            version: ~v0.11
          

目录树:

.
          ├── glide.lock
          ├── glide.yaml
          ├── main.go
          └── vendor
              └── github.com
                  ├── ant0ine
                  │   └── go-json-rest
                  └── go-resty
                      └── resty
          

如果你想初始化一个为零的项目:

glide init
          

以交互的方式定义完配置文件以后,跟上面的使用方法一致。

如果想更新依赖版本:

glide up
          

up == update,up 子命令也可以用来初始化已存在非 Glide 项目,跟 install 一样的效果。

注意:如果你想移除一个依赖,包括 vendor 目录中的源码。可以在 yaml 中将相应的 -package 节点删除,然后执行 up 命令。

查看依赖列表:

glide list
          

输出:

[INFO]  Package github.com/ant0ine/go-json-rest/rest/trie found in vendor/ folder
          [WARN]  Version not set for package github.com/ant0ine/go-json-rest/rest/trie
          INSTALLED packages:
                  github.com/ant0ine/go-json-rest/rest
                  github.com/ant0ine/go-json-rest/rest/trie
          
          

list 命令会将 vendor 中的依赖已完整包名列出来。注意:上面那个警告的意思是某个子依赖没定义版本信息。

glide.lock

lock 文件是一个比较通用的依赖关系持久化机制。在 NodeJs 的 yarn 、Rust 的 Cargo 以及 Ruby 的 Bundler 等工具上同样被使用。它会记录所有的依赖关系树,并且记录初始化时候拉取的具体版本。

举个例子:即使两个人都只有一个相同内容的 glide.yaml 文件,但是他们未必拉取的版本是相同的。原因在于版本是可以取范围的:例如上面的: ~v0.11 。

而 lock 中则会这么记录:

imports:
          - name: github.com/go-resty/resty
            version: c45c7bcc0000d1a9ac1b119b2e6043c6540eedea
          

可以看到,它记录的是某个 commit 的 ID,一个绝对具体的版本。所以在团队开发时,为了确保不出现依赖版本问题,lock 机制是必须的。所以 glide.yaml 和 glide.lock 都需要添加到版本控制当中,被所有成员共享。

其他功能

Glide 作为一个依赖管理工具是绝对合格的,它对项目的帮助不言而喻。如果你想更加深入了解 Gilde,可以阅读更多的文档细节:https://glide.readthedocs.io/en/latest

Gogradle

目前我还比较关注的一个项目是 Gogradle 。如果你接触过 Java 的 Gradle 就知道了,作为一个依赖管理工具,还带有强大的执行任务能力和丰富的构建插件。准确的说是一个:带有依赖管理功能的构建工具。

显然无论是 Go 的 Glide 还是其他语言列举出来的工具都不具备这样的能力,而 Gogradle 则是模仿 Gradle 的一个 Go 语言项目。如果存在单纯的依赖管理已经应对不住负责的项目结构和构建过程的话,也许可以试一试。

最后

Go 良好的发展无疑让人对 Go 产生了更大的信心,盲目跟从那些批判过 Go 语言的所谓大 V ,带着有色眼光去看待这门语言简直有些无知。要知道仇视谷歌的很多都是些利益相关的人:毕竟是 M$ 啊。