Go Vgomod


原文链接: Go Vgomod

travis 集成

language: go

matrix:
  include:
  - go: "1.10.x"
    script:  go test -v ./...
  - go: "1.11.x"
    script: go test -v -mod=vendor ./...

env:
  - GO111MODULE=on

# 如果不需要安装,即跳过安装阶段,就直接设为true
install: true

# script字段指定要运行的脚本,
#script: true #表示不执行任何脚本,状态直接设为成功。

使用go mod 安装二进制

go install -v github.com/cweill/gotests/...

使用go mod 管理本地package

  1. go mod init "github.com/pytool/usart"
  2. 直接根据模块名进行引用
    import "github.com/pytool/usart/gpio"
    gpio.Open()

I. GOPATH 模式

go mod 的目的是实现 "去GOPATH" 的
Go 1.8版本中,如果开发者没有显式设置GOPATH,Go会赋予GOPATH一个默认值

    在linux上为$HOME/go
    在Windows上为 %USERPROFILE%/go

II. go mod on 模式下

go mod 命令在 $GOPATH 里默认是执行不了的,因为 GO111MODULE 的默认值是 auto 。默认在 $GOPATH 里是不会执行, 如果一定要强制执行,就设置环境变量为 on 。

执行go build 会主动download依赖,并不会从GOPATH中获取.

GO111MODULE有三个值:auto、on和off,默认值为auto
默认GO111MODULE=auto: auto是指如果在gopath下不启用mod

  1. 当GO111MODULE的值为auto时(不显式设置即为auto),也就是我们在上面的例子中所展现的那样:使用GOPATH mode还是module-aware mode,取决于要构建的源码目录所在位置以及是否包含go.mod文件。如果要构建的源码目录不在以GOPATH/src为根的目录体系下,且包含go.mod文件(两个条件缺一不可),那么使用module-aware mode;否则使用传统的GOPATH mode。

  2. 当GO111MODULE的值为on时(export GO111MODULE=on),go modules experiment feature始终开启,与off相反,go compiler会始终使用module-aware mode,即无论要构建的源码目录是否在GOPATH路径下,go compiler都不会在传统的GOPATH和vendor目录下搜索目标程序依赖的go package,而是在go mod命令的缓存目录($GOPATH/src/mod)下搜索对应版本的依赖package;

  3. 当GO111MODULE的值为off时,退化为 GOPATH 模式 go modules experiment feature关闭,go compiler显然会始终使用GOPATH mode,即无论要构建的源码目录是否在GOPATH路径下,go compiler都会在传统的GOPATH和vendor目录(仅支持在gopath目录下的package)下搜索目标程序依赖的go package;

如何使用replace将远程包替换为本地包服务

幸运的是, go module 提供了另外一个方案, replace, 这个replace怎么使用的呢?
我们先看一下一个最基本的mod文件

module GoRoomDemo

go 1.12

require (

github.com/gin-gonic/gin v1.3.0
github.com/gohouse/goroom v0.0.0-20190327052827-9ab674039336
github.com/golang/protobuf v1.3.1 // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/mattn/go-sqlite3 v1.10.0
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 // indirect

)

replace github.com/gohouse/goroom => /path/to/go/src/github.com/gohouse/goroom
这里的 path/to/go/src/github.com/gohouse/goroom 是本地的包路径

III. 使用GOMOD在 GOPATH 之外创建新的项目。

go.mod 文件维护

  1. go mod init packagename可以创建一个空的go.mod,然后你可以在其中增加 require github.com/smallnest/rpcx latest 依赖,或者像上面一样让go自动发现和维护。
  2. go mod tidy 命令会根据需要的依赖自动生成 require 语句。官方建议经常维护这个文件,保持依赖项是干净的
  3. go mod download可以下载所需要的依赖,但是依赖并不是下载到$GOPATH中,而是$GOPATH/pkg/mod中,多个项目可以共享缓存的module。
  4. go mod vendor会复制 modules 下载到vendor中, 貌似只会下载你代码中引用的库,而不是go.mod中定义全部的module。

go build 命令go build会更新go.mod, 自动把require 添加到 go.mod
go build -mod=readonly 防止隐式修改 go.mod,如果遇到有隐式修改的情况会报错,可以用来测试 go.mod 中的依赖是否整洁,但如果明确调用了 go mod、go get 命令则依然会导致 go.mod 文件被修改。
go build -mod=vendor 在开启模块支持的情况下,用这个可以退回到使用 vendor 的时代。

翻墙
在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。

replace (

golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0

)
依赖库中的replace对你的主go.mod不起作用,比如github.com/smallnest/rpcx的go.mod已经增加了replace,但是你的go.mod虽然require了rpcx的库,但是没有设置replace的话, go get还是会访问golang.org/x。

通过 replace 实现本地项目引用

所以如果想编译那个项目,就在哪个项目中增加replace。

module cloudms.cspentry
require cloudms.general v1.0.0
replace cloudms.general v1.0.0 => ../general

使用replace替换package

replace顾名思义,就是用新的package去替换另一个package,他们可以是不同的package,也可以是同一个package的不同版本。看一下基本的语法:

go mod edit -replace=old[@v]=new[@v]
old是要被替换的package,new就是用于替换的package。

这里有几点要注意:

replace应该在引入新的依赖后立即执行,以免go tools自动更新mod文件时使用了old package导致可能的失败

package后面的version不可省略。(edit所有操作都需要版本tag)

version不能是master或者latest,这两者go get可用,但是go mod edit不可识别,会报错。(不知道是不是bug,虽然文档里表示可以这么用,希望go1.12能做点完善措施)

基于以上原因,我们替换一个package的步骤应该是这样的:

首先go get new-package(如果你知道package的版本tag,那么这一步其实可以省略,如果想使用最新的版本而不想确认版本号,则需要这一步)

然后查看go.mod,手动复制new-package的版本号(如果你知道版本号,则跳过,这一步十分得不人性化,也许以后会改进)

接着go mod tidy或者go build或者使用其他的go tools,他们会去获取new-package然后替换掉old-package

最后,在你的代码里直接使用old-package的名字,golang会自动识别出replace,然后实际你的程序将会使用new-package,替换成功

下面我们仍然用chromedp的example做一个示例。

chromedp使用了golang.org/x/image,这个package一般直连是获取不了的,但是它有一个github.com/golang/image的镜像,所以我们要用replace来用镜像替换它。

我们先来看看如果不replace的情况下的依赖情况:

https://img3.mukewang.com/5b95126a0001ed1712270290.jpg

没错,我们使用了原来的包,当然如果你无法获取到它的话是不会被记录进来的。

下面我们go get它的镜像:

master表示获取最新的commit
go get github.com/golang/image@master
然后我们查看版本号:

cat go.mod
https://img3.mukewang.com/5b9512720001225706680090.jpg

有了版本号,我们就能replace了:

go mod edit -replace=golang.org/x/image@v0.0.0-20180708004352-c73c2afc3b81=github.com/golang/image@v0.0.0-20180708004352-c73c2afc3b81
现在我们查看一下go.mod:

https://img4.mukewang.com/5b95128100015fad12770091.jpg

replace信息已经更新了,现在我们只要go mod tidy或者go build,我们的代码就可以使用new-package了。

更新后的go.sum,依赖已经替换成了镜像:

https://img1.mukewang.com/5b9512880001946e12300234.jpg

目前来看,replace做的远不如go get那样方便人性化,不过毕竟还只是测试阶段的功能,期待一下它在go1.12的表现吧。

GO111MODULE=on go build -mod=vendor

# 如果提示:build git.xf.io/drama/mybee: cannot find module for path golang.org/x/crypto/acme/autocert,
# 是因为 go modules 未能添加部分间接依赖,手动添加就好
# 添加 golang.org/x/crypto 依赖,且使用 replace 替换成 github.com/golang/crypto,
# 版本号 v0.0.1 随意,最终版本号以 replace 为准,最终再重新 build
GO111MODULE=on go mod edit -require=golang.org/x/crypto@v0.0.1
GO111MODULE=on go mod edit -replace=golang.org/x/crypto@v0.0.1=github.com/golang/crypto@release-branch.go1.11
GO111MODULE=on go mod vendor
GO111MODULE=on go build -mod=vendor

跳出Go module的泥潭
vgo简明教程(Go语言依赖包管理工具) - 技术分享 - SegmentFault 思否
git tag list
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5.1

初窥Go module | Tony Bai
vgo 即 versioned go的缩写,意即带版本的go,从功能上类比java的maven,rust的cargo,node的npm,golang现有的dep等,但是有所不同。

(2018)年初,Go核心Team的技术leader,也是Go Team最早期成员之一的Russ Cox在个人博客上连续发表了七篇文章,系统阐述了Go team解决“包依赖管理”的技术方案: vgo。vgo的主要思路包括:Semantic Import Versioning、Minimal Version Selection、引入Go module等。这七篇文章的发布引发了Go社区激烈地争论,尤其是MVS(最小版本选择)与目前主流的依赖版本选择方法的相悖让很多传统Go包管理工具的维护者“不满”,尤其是“准官方工具”:dep。vgo方案的提出也意味着dep项目的生命周期即将进入尾声。

5月份,Russ Cox的Proposal “cmd/go: add package version support to Go toolchain”被accepted,这周五早些时候Russ Cox将vgo的代码merge到Go主干,并将这套机制正式命名为“go module”。由于vgo项目本身就是一个实验原型,merge到主干后,vgo这个术语以及vgo项目的使命也就就此结束了。后续Go modules机制将直接在Go主干上继续演化。

Go modules是go team在解决包依赖管理方面的一次勇敢尝试,无论如何,对Go语言来说都是一个好事。在本篇文章中,我们就一起来看看这个新引入的go modules机制。

〇、需要准备的概念

在这个新鲜出炉的vgo工具,我个人认为有一个非常重要也非常值得一提的概念称之为:语义导入版本(英文Semantic Import Versioning )。

先看两个在golang社区非常常见的两个版本概念。

语义版本 (Semantic Versioning):典型代表 dep ( https://github.com/golang/dep ) 。使用版本控制的tag等表明包的版本,正确的写法如 v2.3.4 。其中2是主版本号,3是小版本号,4是补丁版本号。顾名思义,小版本号往往是添加新的特性,补丁版本号是补补修修。优点是精准选择版本,避免坑。导入版本 ( Import Versioning ) : 典型代表 gopkg ( http://gopkg.in ) , 具体使用者如go-redis ( http://gopkg.in/redis.v3 ),即包名中包含版本号,通过go get特性实现,具体参见gopkg官方说明。好处是非常方便更新修复bug。

那么什么是语义导入版本,也就呼之欲出了,即贪心的人两个都要。

即在包路径上写明主版本号,也同时使用语义版本

如何使用?
1、项目目录下的go.mod文件

go.mod文件像Java maven的pom.xml文件,PHP的composer.json文件,就是依赖的配置文件,放在项目的根目录下。

切记 go.mod文件至少要有 module 字段!!!
(提示:为了这个,有些人还在main包使用注释package main // import "xxx"语句来生成,其实你只要在go.mod文件指示这个项目的包路径就可以了,可以不要注释了。)

go.mod文件常用字段:
module 指示这个项目的包路径(别人怎么引用你的包名称)
require 必须依赖
exclude 排除依赖
replace 替换依赖

一、撸起袖子 上手就干

输入命令行安装:

go get golang.org/x/vgo

(假设你本地安装的是最新的golang,且GOPATH什么的都配置好了 @.@ )

在任意目录创建一个名为hello的文件夹,同时创建一个main.go,并写入下面的代码(代码和整个例子都来自源网站 )。

package main // import "github.com/you/hello"

import (

"fmt"
"rsc.io/quote"

)

func main() {

fmt.Println(quote.Hello())

}

创建一个空的go.mod 文件在这个目录下面(PS: 这个文件像maven的pom.xml、npm的package.json,里面会有当前的包下的库依赖,当然现在还是空的)。

touch go.mod

在目录下运行下面的命令,使用vgo构建这个简单的hello程序

vgo build

输出的内容如下所示

vgo: resolving import "rsc.io/quote"
vgo: finding rsc.io/quote (latest)
vgo: adding rsc.io/quote v1.5.2
vgo: finding rsc.io/quote v1.5.2
vgo: finding rsc.io/sampler v1.3.0
vgo: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
vgo: downloading rsc.io/quote v1.5.2
vgo: downloading rsc.io/sampler v1.3.0
vgo: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

这个时候hello就构建完成了,运行./hello 可以输出十分亲切的“你好,世界”。

而此时go.mod里也自动生成了依赖关系,文件内容被修改成了:

module "github.com/you/hello"

require "rsc.io/quote" v1.5.2
module 指示这个模块的包路径。
require 顾名思义,即依赖XX包的v.x.x.x 版本。

(PS: 虽然这个地方感觉比较疑惑,自定义的格式真的好吗,是不是使用比如toml什么的标准的验证过的文件格式更好呢?)

二、vgo的其他用法

vgo list -m 查看所有依赖
vgo list -m -u 查看所有依赖同时检查更新,会打印出最新版本和当前版本
vgo test all 执行所有测试,包括依赖包的测试
vgo test http://rsc.io/sampler 执行指定包测试
vgo get -u 更新所有依赖
vgo list -t http://rsc.io/sampler 检查指定包所有可用的版本 即tag
vgo get http://rsc.io/sampler@v1.3.1 获取指定版本,并修改go.mod 

排除指定版本:指明不使用版本,这个时候需要手改go.mod,添加一行如

exclude "rsc.io/sampler" v1.99.99

替换依赖:替换依赖包,go.mod语法如下

replace "rsc.io/quote" v1.5.2 => "github.com/you/quote" v0.0.0-myfork

也可以替换成本地文件夹

replace "rsc.io/quote" v1.5.2 => "../quote"

这样vgo就会替换这个依赖,vgo list -m 时候也会显出这个箭头表示替换。

MODULE VERSION
github.com/you/hello -
golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54
rsc.io/quote v1.5.2
=> ../quote
rsc.io/sampler v1.3.1

三、兼容 vendor 兼容不使用vgo的用户

有一种场景叫做和谐共处,我想使用vgo,但是构建我项目的人不想使用vgo,想用标准的go build构建,vgo也是可以的。

首先把项目放在gopath下,然后执行

vgo vendor

vgo就会把所有依赖都放在vendor目录下,这样go build直接就可以运行啦!

四、需要注意的几点

在vgo-import文章中(Semantic Import Versioning),提出了避免单例问题,同时依赖多版本的库,如果这个库在初始化时候有单例和相关操作,有可能会冲突,如都向默认的http 路由注册handler。目前vgo的文档似乎还不是很健全,代码也还有一些bug,在生产环境可能还不适合去使用。
`