一. 简介
依赖管理一直以来都是大型项目开发所面临的一个问题,成熟的编程语言都会有对应的一个甚至多个依赖管理工具。
例如**C++**项目通常会使用Make、Scons等来管理依赖的so,Java项目通常会使用Maven来管理依赖包。Golang项目同样也需要有类似工具来管理对应的依赖包。
dep是Golang官方依赖管理工具,目前只支持Golang 1.9以上的版本。
① go get
Golang最原始的依赖管理是go get ,执行命令后会拉取代码放入src下面,使用go get依赖管理的项目结构如下。假设项目目录 $GOPATH/src/demo
└── bin
└── pkg
└── src
├── demo
│ └── main.go
├── github.com
├── golang.org
└── gopkg.in
go get存在以下几个问题
- go get的代码是作为GOPATH下全局依赖,并且没有版本控制。
- go get管理依赖必须要设置GOPATH=/xx/xx/src,这样才能保证代码可以编译通过。
② dep
dep 依赖管理引入了 vendor 目录作为依赖管理目录,目前 IDE 或 Golang 编辑插件都能很好的支持。
例如Goland依赖包时会优先查找项目根目录下的vendor目录,dep依赖管理的项目结构如下。假设项目目录 $GOPATH/src/projectname
.
├── Gopkg.lock
├── Gopkg.toml
├── main.go
└── vendor
├── github.com
│ ├── gin-contrib
│ ├── gin-gonic
│ ├── golang
│ ├── mattn
│ └── ugorji
├── golang.org
│ └── x
└── gopkg.in
├── go-playground
└── yaml.v2
二. 安装
- 设置环境变量,使用vendor目录
GO15VENDOREXPERIMENT=1 - 安装dep
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
其它安装方式请参考官网 https://golang.github.io/dep/docs/installation.html - 验证安装,安装成功后输入dep命令会有以下输出
$ dep
Dep is a tool for managing dependencies for Go projects
Usage: "dep [command]"
Commands:
init Set up a new Go project, or migrate an existing one
status Report the status of the project's dependencies
ensure Ensure a dependency is safely vendored in the project
version Show the dep version information
check Check if imports, Gopkg.toml, and Gopkg.lock are in sync
Examples:
dep init set up a new project
dep ensure install the project's dependencies
dep ensure -update update the locked versions of all dependencies
dep ensure -add github.com/pkg/errors add a dependency to the project
Use "dep help [command]" for more information about a command.
三. 使用
dep依赖管理非常简单,只有几个子命令,下面我们重点介绍几个常用的命令。
① dep init
一个项目要使用dep管理依赖首先必须使用dep init来初始化,进入项目主目录初始化。
$ dep init -v
Getting direct dependencies...
Checked 1 directories for packages.
Found 0 direct dependencies.
Root project is "sample/demo"
1 transitively valid internal packages
0 external packages imported from 0 projects
(0) ✓ select (root)
✓ found solution with 0 packages from 0 projects
Solver wall times by segment:
select-root: 72.32µs
other: 8.248µs
TOTAL: 80.568µs
dep init之后项目主目录下会多两个文件Gopkg.lock、Gopkg.toml和一个目录vendor。
- Gokpg.lock 自动生成不可自行修改,Gopkg.lock定义了所有依赖项目的详细信息。
- Gopkg.toml 依赖管理的核心文件,可以生成也可以手动修改。Gopkg.toml里面只定义项目直接依赖项,而Gopkg.lock里面除了包含Gopkg.toml中的所有项之外,还包含间接依赖项。例如我们的项目依赖项目A, 而项目A又依赖B、C,那么只有A会包含在Gopkg.toml中,而A、B、C都会定义在Gopkg.lock中。
- vendor 是依赖管理目录,会优先从vendor目录找依赖代码。
它们三者之间的关系如下
② dep status
dep status用来查看项目依赖的状态
$ dep status
PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED
github.com/gorilla/context v1.1.1 v1.1.1 08b5f42 v1.1.1 1
github.com/gorilla/mux v1.6.2 v1.6.2 e3702be v1.6.2 1
---------------------
③ dep ensure
dep ensure用来安装、更新依赖包代码,-v 参数可以输出详细过程。
- dep ensure: 安装项目依赖的代码
- dep ensure -update: 更新项目依赖
- dep ensure -add: 添加项目依赖
注意: 在国内使用dep ensure有个很重要的问题是,当我们使用了第三方开源代码例如Github,由于GFW原因会导致无法下载源码到本地,因此需要保证本地机器是能够*
四. Gopkg.toml
一个完整的Gopkg.toml文件如下所示
required = ["github.com/user/thing/cmd/thing"]
ignored = [
"github.com/user/project/pkgX",
"bitbucket.org/user/project/pkgA/pkgY"
]
noverify = ["github.com/something/odd"]
[metadata]
codename = "foo"
[prune]
non-go = true
[[prune.project]]
name = "github.com/project/name"
go-tests = true
non-go = false
[[constraint]]
name = "github.com/user/project"
version = "1.0.0"
[metadata]
property1 = "value1"
property2 = 10
[[constraint]]
name = "github.com/user/project2"
branch = "dev"
source = "github.com/myfork/project2"
[[override]]
name = "github.com/x/y"
version = "2.4.0"
[metadata]
propertyX = "valueX"
Gopkg.toml文件是由dep init命令生成的, 支持自定义编辑文件内容,主要用来定义dep行为的几种规则。
- 依赖规则: constraint和override定义依赖的包的版本以及搜索来源
- 包引用规则: required和ignored定义哪些包是必须包含或者哪些包必须排除
- prune 定义哪些包是不需要的会自动从vendor中删除
由于toml文件非树形结构,Gopgk.toml文件要求required和ignored必须在constraint和override之前定义
① constraint
constraint定义了项目直接依赖的包相关的信息
[[constraint]]
# Required: the root import path of the project being constrained.
name = "github.com/user/project"
# Recommended: the version constraint to enforce for the project.
# Note that only one of "branch", "version" or "revision" can be specified.
version = "1.0.0"
branch = "master"
revision = "abc123"
# Optional: an alternate location (URL or import path) for the project's source.
source = "https://github.com/myfork/package.git"
# Optional: metadata about the constraint or override that could be used by other independent systems
[metadata]
key1 = "value that convey data to other systems"
system1-data = "value that is used by a system"
system2-data = "value that is used by another system"
- name: 必须字段,定义依赖包的名称
- version: 推荐字段,定义依赖包的特定版本
- branch: 推荐字段,定义依赖包分支名称
- revision: 可选字段,定义修订标识
- source: 可选字段,定义依赖项目代码来源
- metadata: 可选字段,定义项目元信息
正常情况下constraint只需要定义name、version和branch这几个字段即可。
② override
override定义了项目直接依赖的包相关的信息,但它是用于解决依赖冲突。
实际项目开发中我们经常会遇到A依赖B、C,B依赖C这种情况。
假设A项目的Gopkg.toml如下
[[constraint]]
name = "B"
branch = "master"
[[constraint]]
name = "C"
branch = "master"
假设B项目的Gopkg.toml如下
[[constraint]]
name = "C"
branch = "master"
接下来如果A项目依赖了C的某个分支,那此时A项目的Gopkg.toml如下。
[[constraint]]
name = "B"
branch = "master"
[[constraint]]
name = "C"
branch = "branch"
这个时候A项目中执行dep ensure是不会成功的,会提示A和B依赖的C冲突了。
为了解决这个问题只需要A项目Gopkg.toml把constraint改成override,强制让B项目在依赖C项目的时候使用branch而不是使用master。
[[constraint]]
name = "B"
branch = "master"
[[override]]
name = "C"
branch = "branch"
③ prune
prune定义了全局和每个项目的清理机制,这个配置决定了哪些文件会被写入vendor目录,prune配置支持以下3个配置选项。没有设置默认prune选项为禁用
- unused-packages 表示是否需要删除未出现在引用视图里的包
- non-go 表示是否需要删除未被Go代码使用的文件
- go-tests 表示是否需要删除单元测试文件
一个完整的prune配置如下所示
[prune]
# 全局配置
go-tests = true
unused-packages = true
# 子依赖配置
[[prune.project]]
name = "github.com/ethereum/xxxx"
# xxxx只删除go-tests
unused-packages = false
五. Gopkg.lock
Gopkg.lock文件由dep ensure和dep init命令自动生成用户不能做任何编辑修改。文件内容为整个项目的完整依赖,由一序列的**[[project]]**组成。
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:51046bf99bcee01c3ff29ed1866c8e43a2191220406781a26b208f19e2b40c1d"
name = "cloud.google.com/go"
packages = [
"compute/metadata",
"internal/version",
"translate",
"translate/internal/translate/v2",
]
pruneopts = "UT"
revision = "6cb29e61d96723a38dcac44d4c15c36744d96d07"
version = "v0.29.0"
[[projects]]
digest = "1:9f3b30d9f8e0d7040f729b82dcbc8f0dead820a133b3147ce355fc451f32d761"
name = "github.com/BurntSushi/toml"
packages = ["."]
pruneopts = "UT"
revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005"
version = "v0.3.1"
- digest: vendor目录下当前项目内容的哈希签名,使用标准的crypto/sha256算法
- name: 依赖的项目名
- packages: 项目依赖的完整的包列表
- pruneopts: prune选项, N表示non-go,U表示unused-packages,T表示go-tests
- revision: 修订版本唯一标识
- version: 项目版本