Go 依赖管理

之go module
编程语言包管理工具
Node有npm
PHP有composer
Java有Maven和Gradle
Go 有 go mod

Go Workspaces和GOPATH

go1.11 前不使用第三方包管理工具 直接使用go get第三方包
Workspaces 是工作空间 Go项目的根目录
GOPATH     是GO项目必备 环境变量 存放Go代码和第三方包 按照目录安排
$ echo $HOME
C:\Users\Administrator
$ export GOPATH=$HOME/goworkspace
$ go env |grep GOPATH                    
set GOPATH=F:\Administrator\go

Go 开启module特性

使用 go module 需先设置 GO111MODULE 环境变量
开启 GO111MODULE
设置GO111MODULE=on  没设置 执行命令的时候会有提示

Go创建模块实例

模块名 github.com/starter/mytest 是 GitHub 仓库的路径
按照原 GOPATH 开发模式 创建新包 放置在 $GOPATH/src/github.com
创建 mytest 目录   命令
$ mkdir -p $GOPATH/src/github.com/starter/mytest
$ cd $GOPATH/src/github.com/starter/mytest
使用 go mod init 初始化模块
$go mod init mytest                                                                                            
go: modules disabled inside GOPATH/src by GO111MODULE=auto;
see 'go help modules'
有提示错误是因为 默认设置 GO111MODULE=auto  导致 modules 默认在 GOPATH/src 路径下 不启用
需要把 GO111MODULE 环境变量设置为 on
$ export GO111MODULE=on  
$ go mod init github.com/starter/mytest   
go: creating new go.mod: module github.com/starter/mytest
go模块可以创建在任意位置
再创建一个 调用go modules
在 GOPATH 以外 创建 helloworld 目录 调用刚创建的 mytest 模块
$ mkdir helloworld && cd helloworld
$ vi main.go
package main
import "fmt"
//import "github.com/starter/mytest"
func main() {
    fmt.Println("Hello  World!");
    //mytest.ShowTest1()
}
初始化该模块 引入github.com/starter/mytest模块 指定版本为latest
$ go mod init helloworld
$ go mod edit -require github.com/starter/mytest@latest
初始化 生成了 go.mod 文件
module helloworld
require github.com/starter/mytest v0.0.0
$ go test
build helloworld: cannot find module for path github.com/starter/mytest
直接执行 go test 或者 go run main.go 会报错了,虽然 github.com/starter/mytest 模块在GOPATH路径里有 但GO去 Github 找这个模块
不是GOPATH 里找 所以找不到
解决办法
第一个办法 将starter/mytest 模块推送 GitHub 上  修改starter/mytest 代码 都得先推送到GitHub 才能生效  麻烦
第二个办法 使用 go replace
修改go.mod 新增replace一行
module helloworld
require github.com/starter/mytest v0.0.0
replace github.com/starter/mytest => F:/Administrator/go/src/github.com/starter/mytest
版本号必须填写 v0.0.0或 latest
重新运行 $ go test                                                    
go: finding github.com/starter/mytest v0.0.0
?       helloworld    [no test files]
因为本地没有*_test.go测试文件 所以会有提示"no test files". 运行go test 只是让go module去拉取最新的模块

Go调用第三方模块

项目用到比较流行的路由模块 gorilla/mux
修改go.mod 新增 require 不指定版本 直接写latest获取最新版本
require github.com/gorilla/mux latest
运行 go build 或 go test 从GitHub下载模块 修改 go.mod 文件
运行后 把 latest  修改成目前最新的版本 v1.6.2
module helloworld
require github.com/starter/mytest v0.0.0
require github.com/gorilla/mux v1.6.2
replace github.com/starter/mytest => F:/Administrator/go/src/github.com/starter/mytest
模块gorilla/mux代码会下载到 $GOPATH/pkg/mod/ 模块下

Go 包管理GoModule

GoModule 官方包管理 通过 GoModule 工程 放在GOPATH之外的位置 比 dep vendor GoModule 管理灵活
PS F:\> go mod help
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download    download modules to local cache
edit        edit go.mod from tools or scripts
graph       print module requirement graph
init        initialize new module in current directory
tidy        add missing and remove unused modules
vendor      make vendored copy of dependencies
verify      verify dependencies have expected content
why         explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.
go mod init [module] 初始化.mod 包管理文件到当前工程
go mod vendor 解决方案vendor版本 将依赖复制到vendor下面
go mod tidy   移除未用的模块 以及添加缺失的模块
go mod verify 验证所有模块是否正确

go mod基本使用

golang 1.11 开启 GO11MODULE=on  实验目录不在 GOPATH 中  执行[linux命令,windows用set GO11MODULE=on]
export GO11MODULE=on
export GO111MODULE=on
golang 1.12 确保实验目录不在 GOPATH 中
加速下载 命令行里执行
export GOPROXY=https://goproxy.io
初始化go module环境
带git的项目
从github上 clone 项目
执行 go mod init  生成带git地址的packagename
starter git:(master) go mod init
go: creating new go.mod: module github.com/goodlucktou/starter
//如果有错误
go: cannot determine module path for source directory F:\ATester\gomodtest (outs
ide GOPATH, no import comments)
// 加上module名--example解决
$go mod init example
//

不带git的项目
执行 go mod init packagename
$ go mod init gomodtest
go: creating new go.mod: module gomodtest
当前目录生成 go.mod 文件 内容
module gomodtest
go 1.12

Go下载依赖包

只下载依赖包
$go mod download
拉取必须模块 移除不用的模块
$go mod tidy
查看 go/pkg/mod/github.com/ 可见下载的依赖包
如果tag对应内容有更新 需要删除pkg中的缓存内容
$cd $GOPATH/pkg/mod
$rm -rf *
还有
$go get
$go run
$go build
都会下载依赖

Go 添加新依赖包

方法一  修改 go.mod 文件 执行 go mod download
方法二 用 go get packagename@v1.2.3 会自动更新 go.mod 文件的
方法三  $go run
$go build 会自动下载依赖
将依赖包下载到vendor目录
$go mod vendor
只下载对应版本的包 不下载所有版本 跟之前1.11使用方式一致

Go使用replace本地包替换

国外软件被墙 用这个功能
main.go
package main
import "my/example/pkg"
func main() {
pkg.Hello()
}
go.mod
module my-mod
require my/example/pkg v0.0.0
replace my/example/pkg => ./pkg
replace 可以文件夹 也可以是另外一个package
module my-mod
require my/example/pkg v0.0.0
replace my/example/pkg v0.0.0 => github.com/example/pkg v0.0.0
顶层依赖可替换但间接依赖不可替换

Go 语意化版本

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/goodlucktou/starter"
v2 "github.com/goodlucktou/starter/v2"
)
func main(){
v2.NewIntCollection("hello","sex")
v1.NewIntCollection("hello")
fmt.Println("hello");
}

go.mod
module new_module_test
require (
github.com/goodlucktou/starter v1.0.1
github.com/goodlucktou/starter/v2 v2.0.0
)

Go 依赖包冲突问题

情况1 直接 引用的包 和 间接引用的包是同一个包 但版本不同时 依赖关系
gomodtest_test|--> gomodtest_dep         |--> starter@v1.0.0
      |--> starter@v1.0.1
$go mod tidy 时 gomodtest_test 自动更新到与依赖包关联的第三方包相同版本号 写入go.mod  解决版本冲突问题

情况2  间接引用的两个包是同一个包 但版本不同时
场景 gomodtest_test|--> gomodtest_dep  |--> starter@v1.0.0
      |--> gomodtest_dep2 |--> starter@v1.0.1
go mod tidy 时 默认使用第一个包引用版本号 starter@v1.0.1 并写入到go.mod 两个版本是否功能完全兼容

Go 自动查找包依赖

go mod 遵循之前go get 自动下载依赖特性 依赖包会自动全部下载
未启用 go mod 功能的包会自动下载最高 tag 版本或最高 master commit版本
$go mod download
总结
大部分场景 go mod init 和 go mod tidy 两个命令 够用了
查看 $GOPATH/pkg/mod 文件就知道了 mod做了一件类似maven的事把所有包都打上了版本号
semver 将版本信息绑定进包名对于习惯了传统包管理器方案的用户来说显得有些怪异 可能需要花上一些额外时间适应