Golang 包管理机制

1. 历史

在go1.11之前, 并没有官方的包管理机制(Godep算个半官方), 主流的包管理机制有:

  • GoVendor
  • Glide
  • Godep
GoModuleGO111MODULE=on

2. 基础概念

在 Go 语言中,我们通过 go get 命令将 GitHub 或者 Google Code 上的代码下载到本地指定目录,然后在开发代码中通过 import 的形式引用本地的代码。例如:

import "github.com/json-iterator/go"

Go 语言可以通过直接分析代码中的 import 语句来查询依赖关系。go get 命令在执行时,就会自动解析 import 来安装所有的依赖。

workplace

通过 GOPATH 环境变量来设置 Go 代码的位置, 包含三个文件夹: src, pkg, bin:

  • src: 包含工程所有的源码
  • pkg: 包含编译生成的package目标文件
  • bin: 包含最后生成的可执行文件

这种原始的方式存在一些不足之处:

  1. 不能很方便地隔离不同项目的环境
  2. 由于墙的存在, 很多依赖很难下载
  3. 不能很方便地控制某个依赖包的版本
  4. 不能管理 Go 本身的版本
vendordepmodule

3. 使用

这里主要介绍govendor, godep以及gomodule三种方式. 大多数的包管理工具都是通过一个文件描述依赖的坐标信息, 然后批量管理(下载, 升级等)依赖.

3.1. govendor

govendor 是对Golang的包依赖管理的一个插件,该工具将项目依赖的外部包拷贝到项目下的 vendor 目录下,并通过 vendor.json 文件来记录依赖包的版本,方便用户使用相对稳定的依赖。

GO15VENDOREXPERIMENT当前项目目录/vendor
  • govendor的几种状态

    状态 缩写 含义
    +local l 本地包,即项目自身的包组织
    +external e 外部包, 被GOPATH管理
    +vendor v 被govendor 管理
    +std s 标准库
    +unused u 未使用的包
    +missing m 代码引用了依赖包,但是该包找到
    +program p 主程序包,意味着可以编译为执行文件
    +outside 外部包和缺失的包
    +all 所有包

3.1.1. 安装

go get -u -v github.com/kardianos/govendor
govendor

3.1.2. 说明

govendorGOPATHgo getgovendor add +externalvendor

3.1.3. 相关命令

init     创建 vendor 文件夹和 vendor.json 文件
list     列出已经存在的依赖包
add      从 $GOPATH 中添加依赖包,会加到 vendor.json
update   从 $GOPATH 升级依赖包
remove   从 vendor 文件夹删除依赖
status   列出本地丢失的、过期的和修改的package
fetch   从远端库增加新的,或者更新 vendor 文件中的依赖包
sync     Pull packages into vendor folder from remote repository with revisions
migrate  Move packages from a legacy tool to the vendor folder with metadata.
get     类似 go get,但是会把依赖包拷贝到 vendor 目录
license  List discovered licenses for the given status or import paths.
shell    Run a "shell" to make multiple sub-commands more efficient for large projects.

go tool commands that are wrapped:
      `+<status>` package selection may be used with them
    fmt, build, install, clean, test, vet, generate, to

e.g.

# Setup your project.
cd "my project in GOPATH"
# 初始化 vendor 目录, project 下出现 vendor 目录
govendor init
# 从$GOPATH中添加依赖到vendor下
govendor add +external
# 列出已经存在的依赖包
govendor list
# Look at what is using a package
govendor list -v fmt
# 从远程添加, 更新依赖
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
# 格式化本地依赖文件
govendor fmt +local
# Build everything in your repository only
govendor install +local
# Test your repository only
govendor test +local

3.2. godep

Depdepgodep

github地址:

相比非官方的godep, 官方的dep的兼容性更好一些, 是官方出品, 可以有更长久的更新维护.

3.2.1. 安装

curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep version

3.2.2. 说明

当你使用dep管理依赖时, 会在目录下产生以下几个文件:

├── Gopkg.lock
├── Gopkg.toml
└── vendor
Gopkg.lockGopkg.tomlGopkg.tomlGopkg.lockGopkg.tomlGopkg.tomlGopkg.lockGopkg.lockvendornode_module 

Dep关系

3.2.3. 相关命令

# 初始化
dep init
# 依赖管理帮助
dep help ensure
# 添加一条依赖
dep ensure -add github.com/bitly/go-simplejson
# 这里 @= 参数指定的是 某个 tag
dep ensure -add github.com/bitly/go-simplejson@=0.4.3
# 添加后一定记住执行 确保 同步
dep ensure
# 建议使用
dep ensure -v
# 删除没有用到的 package
dep prune -v
# 依赖更新
dep ensure -update -v 

3.3. gomodule

GoModule是官方提供的包管理解决方案. 通过GoModule. 开发者可以把工程放在GOPATH之外的位置. 相比于之前的包管理方案: dep, vendor, GoModule的管理方案更加灵活.

Go1.11版本初步引入的GoModule模块, 1.12版本正式开始支持.

3.3.1. 安装

编辑环境变量, 添加:

export GO111MODULE=on
# 加速下载, 使用代理
export GOPROXY=https://goproxy.io,direct
gomodules

3.3.2. 相关命令

# 初始化
go mod init
# 下载依赖
go mod download
# 移除未用模块, 添加缺失模块
go mod tidy
# 验证模块正确性
go mod verify
# 将依赖复制到项目的vendor目录下
go mod vendor

4. GoModule的优点

4.1. 使用replace进行本地包替换

使用场景: 有时候一些依赖在墙外, 或者并没有进入go的依赖仓库

解决方法:

go.mod
replace (
	golang.org/x/crypto => github.com/golang/crypto latest
	golang.org/x/sys => github.com/golang/sys latest
)

例如log, logfmt这两个依赖在网上无法找到, 在go.mod文件中添加:

replace (
	github.com/qiniu/log => /Users/user/go/src/github.com/yis/test/vendor/github.com/qiniu/log
	github.com/go-logfmt/logfmt => /Users/user/go/src/github.com/yis/test/vendor/github.com/go-logfmt/logfmt
)

注意点: 顶层依赖可替换但间接依赖不可替换

4.2. 语义化版本 semver—Semantic Versioning

semver是官方为了类库升级引入的新规范,即:

“If an old package and a new package have the same import path, the new package must be backwards compatible with the old package.” - go modules wiki "

如果旧软件包和新软件包具有相同的导入路径,则新软件包必须向后兼容旧软件包。"

main.go

package main
import (
    "fmt"
    v1 "github.com/e421083458/gomodtest_base"
    v2 "github.com/e421083458/gomodtest_base/v2"
)
func main(){
    v2.NewIntCollection("hello","sex")
    v1.NewIntCollection("hello")
    fmt.Println("hello");
}

go.mod

module new_module_test

require (
    github.com/e421083458/gomodtest_base v1.0.1
    github.com/e421083458/gomodtest_base/v2 v2.0.0
)

4.3. 依赖包冲突问题

这边存在两种情况:

依赖关系:
gomodtest_test|--> gomodtest_dep         |--> gomodtest_base@v1.0.0
              |--> gomodtest_base@v1.0.1    
比如以下场景:
gomodtest_test|--> gomodtest_dep  |--> gomodtest_base@v1.0.0
              |--> gomodtest_dep2 |--> gomodtest_base@v1.0.1

4.4. 自动查找包依赖

go modgo get
go modgo mod

4.5. 总结

go mod initgo mod tidy

其他

1. 参考文章