用go语言开发任何项目的时候,一般都会用到第三方的开源项目。也就是说我们开发的项目会依赖于一些第三方的项目,这样自然就需要管理这些依赖项。而目前针对go语言最流行的依赖管理工具就是dep。dep是一个开源的项目,地址如下:
https://github.com/golang/dep
首先要明白为什么需要依赖管理。依赖管理的目的就是要锁定依赖项目的特定版本,从而使我们自己的项目每次build时具有确定性。否则,如果不对依赖项目进行控制,每次build都基于依赖项目master分支的最新代码,那么就有可能出现兼容性问题,或者将依赖项目的最新BUG引入到我们自己的项目中。
Gopkg.toml和Gopkg.lock是dep产生的两个重要的配置文件。当首次在项目的根目录执行命令“dep init”的时候,就会自动生成这两个文件。Gopkg.toml里面定义了我们自己的项目的依赖需求,例如下面两项分别定义了依赖golang/protobuf的1.0.0版本,以及gorilla/muxde的1.6.1版本。
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.0.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.1"
而Gopkg.lock里面则精确定义了依赖项目的信息。上面两项在Gopkg.lock中对应的定义如下。不仅定义了具体的commit ID,还列出了具体依赖的每个package。
[[projects]]
name = "github.com/golang/protobuf"
packages = [
"proto",
"protoc-gen-go/descriptor",
"ptypes/any",
"ptypes/timestamp"
]
version = "v1.0.0"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
version = "v1.6.1"
一般情况下Gopkg.toml里面只定义直接依赖项,而Gopkg.lock里面除了包含Gopkg.toml中的所有项之外,还包含传递依赖项。比如我们的项目依赖项目A, 而项目A又依赖B、C,那么只有A会包含在Gopkg.toml中,而A、B、C都会定义在Gopkg.lock中。所以Gopkg.lock定义了所有依赖的项目的详细信息(commit ID和packages),使得每次build我们自己的项目时,始终基于确定不变的依赖项。
日常工作中使用dep时,其实主要就是和命令“dep ensure”打交道。理解了dep的工作机制,也就明白了“dep ensure”的执行流程。一张图就可以解释清楚:
该图片来自如下网址:
https://golang.github.io/dep/docs/ensure-mechanics.html
根据dep官网的说法,dep主要包含两个功能,分别是solving功能和vendoring功能。solving就是根据我们项目源代码中的import指令和Gopkg.toml定义的规则,来生成Gopkg.lock。通常,当我们需要升级某个依赖项目的版本时,只需要修改Gopkg.toml,然后执行"dep ensure -update xxxx",就可以自动更新Gopkg.lock。一般情况下,都不要试图手动修改Gopkg.lock!
vendoring功能则是根据Gopkg.lock中的信息,将每一个依赖项目的特定版本的源代码拷贝到vendor子目录下面。在执行"go build .."时会自动优先从vendor目录中寻找依赖项的源代码。
有些人倾向于将vendor目录也提交到repository中。这么做的优缺点很明显,优点是使我们的项目完全独立,不受上游项目的任何变更的影响,而且大多数情况下也不用执行"dep ensure"来根据Gopkg.lock来同步vendor。缺点就是使我们的项目的repository变得更加庞大,而且每次Gopkg.lock更新之后,也必须同步更新vendor目录。哪种是best practice?官方的回答是“It's up to you”,就是由你自己决定:)。
上面给出的Gopkg.toml的例子中,制定了依赖项目的版本。其实在Gopkg.toml同样可以通过revision指定具体的commit ID,例如:
[[constraint]]
name = "github.com/prometheus/client_golang"
revision = "c51dc758d4bb30acacbef9eaa2b774969a135086"
通常在依赖的项目很少打tag或者压根就没有tag,而我们又需要使用它的新版本时,就可以直接在Gopkg.toml中指定commit ID。
还有一个值得探讨的问题是,如何控制传递依赖项目的版本?首次执行“dep init"命令时,传递依赖项只会自动生成在Gopkg.lock中。那么如何升级这些传递依赖的项目呢?原则上来说,传递依赖的问题应该不是我们应该操心的,而应该由我们直接依赖的项目来解决。例如我们的项目依赖A,而A依赖B。那么对B的版本控制应该由A解决,而不是我们来解决。但如果A压根就没有管理依赖呢?这时我们只有两种方式来升级B的版本:
1、将B通过override方式定义在Gopkg.toml中。以后每次只须修改Gopkg.toml,然后执行“dep ensure -update xxx"即可。但是官网明确警告要谨慎使用override,因为它还具有解决冲突的作用,可能会屏蔽掉本该引起我们注意的版本冲突问题。
注:override使用方法和constraint完全一样。但dep对二者的处理则有一定的区别。请参考:
https://golang.github.io/dep/docs/Gopkg.toml.html
2、直接修改Gopkg.lock文件。这种方式虽然简单,但显然违反了前面所说的“尽量不要手动修改Gopkg.lock”的规则。
到底该怎么办?It's up to you:)。
最后,分享一个零君在实际工作中遇到的一个坑。最近零君在Gopkg.toml中更新了一个依赖项目的版本。因为该项目虽然一直有更新,但却很久没有发布新的version了,所以零君要在Gopkg.toml中对该项目指定一个比较新的commit ID。但是在执行"dep ensure -update xxx"时,提示“reference isn't a tree”。原因就是$/pkg/dep中该项目对应的repository比较陈旧,没有同步最新的修改,而我在Gopkg.toml中指定的commit ID没有包含在repository中,自然就会报错。当手动同步repository之后,问题就解决了。其实这应该算dep的一个BUG,执行“dep ensure -update xxx”时,应该在找不到commit ID时自动尝试同步repository。
总的来说,dep简单易用,很容易上手。而且官网提供的文档也不多,花个半天、最多一天就能阅读完。所以有兴趣的同学,可以在线阅读官方文档:
https://golang.github.io/dep/docs/introduction.html
或者
https://github.com/golang/dep/tree/master/docs
--END--
领取专属 10元无门槛券
私享最新 技术干货