首页 > Go语言教程 > Go语言简介 阅读:4,955

Go语言依赖管理

从 Go 1.0 发布那天起,社区做了很多努力,提供各种 Go 工具,以便开发人员的工作更轻松。有很多工具专注在如何管理包的依赖关系。现在最流行的依赖管理工具是 Keith Rarik 写的 godep、Daniel Theophanes 写的 vender 和 Gustavo Niemeyer 开发的 gopkg.in 工具。gopkg.in 能帮助开发人员发布自己的包的多个版本。

作为对社区的回应,Go语言在 1.5 版本开始试验性提供一组新的构建选项和功能,来为依赖管理提供更好的工具支持。尽管我们还需要等一段时间才能确认这些新特性是否能达成目的,但毕竟现在已经有一些工具以可重复使用的方式提供了管理、构建和测试 Go 代码的能力。

第三方依赖

像 godep 和 vender 这种社区工具已经使用第三方(verdoring)导入路径重写这种特性解决了依赖问题。其思想是把所有的依赖包复制到工程代码库中的目录里,然后使用工程内部的依赖包所在目录来重写所有的导入路径。

下面的代码展示了使用 godep 来管理工程里第三方依赖时的一个典型的源代码树。

$GOPATH/src/github.com/ardanstudios/myproject
|-- Godeps
|    |-- Godeps.json
|    |-- Readme
|    |-- _workspace
|        |-- src
|            |-- bitbucket.org
|            |-- ww
|            |    |-- goautoneg
|            |        |-- Makefile
|            |        |-- README.txt
|            |        |-- autoneg.go
|            |        |-- autoneg_test.go
|            |-- github.com
|                |-- beorn7
|                    |-- perks
|                        |-- README.md
|                        |-- quantile
|                            |-- bench_test.go
|                            |-- example_test.go
|                            |-- exampledata.txt
|                            |-- stream.go
|
|-- examples
|-- model
|-- README.md
|-- main.go

可以看到 godep 创建了一个叫作 Godeps 的目录。由这个工具管理的依赖的源代码被放在一个叫作 _workspace/src 的目录里。

接下来,如果看一下在 main.go 里声明这些依赖的 import 语句(如上述代码所示),就能发现需要改动的地方。

下面代码展示了在路径重写之前
package main

import (
    "bitbucket.org/ww/goautoneg"
    "github.com/beorn7/perks"
)
下面代码展示了在路径重写之后
package main

import (
    "github.ardanstudios.com/myproject/Godeps/_workspace/src/bitbucket.org/ww/goautoneg"
    "github.ardanstudios.com/myproject/Godeps/_workspace/src/github.com/beorn7/perks"
)
在路径重写之前,import 语句使用的是包的正常路径。包对应的代码存放在 GOPATH 所指定的磁盘目录里。在依赖管理之后,导入路径需要重写成工程内部依赖包的路径。可以看到这些导入路径非常长,不易于使用。

引入依赖管理将所有构建时依赖的源代码都导入到一个单独的工程代码库里,可以更容易地重构建工程。使用导入路径重写管理依赖包的另外一个好处是这个工程依旧支持通过 go get 获取代码库。当获取这个工程的代码库时,go get 可以找到每个包,并将其保存到工程里正确的目录中。

对 gb 的介绍

gb 是一个由 Go 社区成员开发的全新的构建工具。gb 意识到,不一定要包装 Go 本身的工具,也可以使用其他方法来解决可重复构建的问题。

gb 背后的原理源自理解到Go语言的 import 语句并没有提供可重复构建的能力。import 语句可以驱动 go get,但是 import 本身并没有包含足够的信息来决定到底要获取包的哪个修改的版本。

go get 无法定位待获取代码的问题,导致 Go 工具在解决重复构建时,不得不使用复杂且难看的方法。我们已经看到过使用 godep 时超长的导入路径是多么难看。

gb 的创建源于上述理解。gb 既不包装 Go 工具链,也不使用 GOPATH。gb 基于工程将 Go 工具链工作空间的元信息做替换。这种依赖管理的方法不需要重写工程内代码的导入路径。而且导入路径依旧通过 go get 和 GOPATH 工作空间来管理。

下面的代码展示了如何将工程转换为 gb 工程。

/home/bill/devel/myproject ($PROJECT)
|-- src
|    |-- cmd
|    |    |-- myproject
|    |    |    |-- main.go
|    |-- examples
|    |-- model
|    |-- README.md
|-- vendor
    |-- src
        |-- bitbucket.org
        |    |-- ww
        |        |-- goautoneg
        |        |-- Makefile
        |        |-- README.txt
        |        |-- autoneg.go
        |        |-- autoneg_test.go
        |-- github.com
            |-- beorn7
                |-- perks
                |-- README.md
                |-- quantile
                |-- bench_test.go
        |-- example_test.go
        |-- exampledata.txt
        |-- stream.go

一个 gb 工程就是磁盘上一个包含 src/ 子目录的目录。符号 $PROJECT 导入了工程的根目录中,其下有一个 src/ 的子目录中。这个符号只是一个简写,用来描述工程在磁盘上的位置。$PROJECT 不是必须设置的环境变量。事实上,gb 根本不需要设置任何环境变量。

gb 工程会区分开发人员写的代码和开发人员需要依赖的代码。开发人员的代码所依赖的代码被称作第三方代码(vendored code)。gb 工程会明确区分开发人员的代码和第三方代码,如下面代码所示。

$PROJECT/src/
$PROJECT/vendor/src/

gb 一个最好的特点是,不需要重写导入路径。可以看看这个工程里的 main.go 文件的 import 语句——没有任何需要为导入第三方库而做的修改,如下面代码所示。
package main

import (
    "bitbucket.org/ww/goautoneg"
    "github.com/beorn7/perks"
)
gb 工具首先会在 $PROJECT/src/ 目录中查找代码,如果找不到,会在 $PROJECT/vender/src/ 目录里查找。与工程相关的整个源代码都会在同一个代码库里。自己写的代码在工程目录的 src/ 目录中,第三方依赖代码在工程目录的 vender/src 子目录中。

这样,不需要配合重写导入路径也可以完成整个构建过程,同时可以把整个工程放到磁盘的任意位置。这些特点,让 gb 成为社区里解决可重复构建的流行工具。

还需要提一点:gb 工程与 Go 官方工具链(包括 go get)并不兼容。因为 gb 不需要设置 GOPATH,而 Go 工具链无法理解 gb 工程的目录结构,所以无法用 Go 工具链构建、测试或者获取代码。构建(如下面代码所示)和测试 gb 工程需要先进入 $PROJECT 目录,并使用 gb 工具。

gb build all

很多 Go 工具支持的特性,gb 都提供对应的特性。gb 还提供了插件系统,可以让社区扩展支持的功能。其中一个插件叫作 vender。这个插件可以方便地管理 $PROJECT/vender/src/ 目录里的依赖关系,而这个功能 Go 工具链至今没有提供。