前文

题记

  • 《庄子》‘逍遥游’有云:且夫水之积也不厚,则其负大舟也无力。覆杯水于坳堂之上,则芥为之舟;置杯焉则胶,水浅而舟大也。
  • 我们今天要介绍的就是北冥神功—go module绝技,以吸收他人内力为己所用。而且别人的内力愈强 吸力愈大。极天下代码于一身,好不快活。

前言

gopathgo module


`go module`诞生的背景 & 为什么需要`go module`

go module
gopathgo modulesgo module

解决`import` 路径问题

gopath
import "github.com/gobuffalo/buffalo"
$GOPATH/src/github.com/gobuffalo/buffalo
gopathimportgithub.com/gobuffalo/buffalo/users/gobuffalo/buffalogo modulego.mod
## go.mod
01 module github.com/gobuffalo/buffalo
02
...
06
package$gopathimport

解决代码捆绑和版本控制

  • 对于任何版本控制(VCS)工具,我们都能在任何代码提交点打上"tag"标记,如下所示:
  • 使用VCS工具,开发人员可以通过引用特定标签将软件包的任何特定版本克隆到本地。
  • 当我们引用一个第三方包时,可能并不总是希望应用项目最新的代码,而是某一个特定与当前项目兼容的代码。对于某一个项目来说,可能并没有意识到有人在使用他们的代码,或者某种原因进行了巨大的不兼容更新。
  • 我们希望能够指明需要使用的第三方包的版本,并且go工具能够方便下载、管理
  • 更棘手的是,一个第三方包A可能引用了其他的第三方包B,因此还必须把第三方包A的全部依赖下载
    • 如何查找并把所有的依赖包下载下来?
    • 某一个包下载失败应该怎么办?
    • 所有项目之间如何进行依赖的传导?
    • 如何选择一个最兼容的包?
    • 如何解决包的冲突?
    • 如果希望在项目中同时引用第三方包的二个不同版本,需要如何处理?


gopathgo moudle

go moudle 使用

Module缓存

$GOPATH/pkg
go/
├── bin
├── pkg
     ├── darwin_amd64
     └── mod
└── src

在mod目录下,我们能够看到模块名路径中的第一部分用作了模块缓存中的顶级文件夹

~/go/pkg/mod » ls -l                                                                                                                                                                                jackson@192
drwxr-xr-x    6 jackson  staff    192  1 15 20:50 cache
drwxr-xr-x    7 jackson  staff    224  2 20 17:50 cloud.google.com
drwxr-xr-x    3 jackson  staff     96  2 18 12:03 git.apache.org
drwxr-xr-x  327 jackson  staff  10464  2 28 00:02 github.com
drwxr-xr-x    8 jackson  staff    256  2 20 17:27 gitlab.followme.com
drwxr-xr-x    6 jackson  staff    192  2 19 22:05 go.etcd.io
...
github.com/nats-io
~/go/pkg/mod » ls -l github.com/nats-io                                                                                                                                                             jackson@192
total 0
dr-x------  24 jackson  staff   768  1 17 10:27 gnatsd@v1.4.1
dr-x------  15 jackson  staff   480  2 17 22:22 go-nats-streaming@v0.4.0
dr-x------  26 jackson  staff   832  2 19 22:05 go-nats@v1.7.0
dr-x------  26 jackson  staff   832  1 17 10:27 go-nats@v1.7.2
...

为了拥有一个干净的工作环境,我们可以用如下代码清空缓存区。但是请注意,在正常的工作流程中,是不需要执行如下代码的。

$ go clean -modcache

开始一个新的项目

GOPATHmain
$ cd $HOME
$ mkdir mathlib
$ cd mathlib                                                                                                                                                                                 jackson@192
$ touch main.go
  • 接着在当前目录中,执行如下指令初始化moudle。
~/mathlib » go mod init github.com/dreamerjackson/mathlib
go mod initgo.modhttps://github.com/dreamerjackson/mathlibgo.mod
module github.com/ardanlabs/service

#### 引入第三方模块
go 1.13
  • 接下来我们将书写初始化的代码片段
package main

import "github.com/dreamerjackson/mydiv"

func main(){

}
go moudlegithub.com/dreamerjackson/mydivgithub.com/pkg/errors
  • 如下图所示,在goland中我们可以看到导入的package 是红色的,因为此时在go module的缓存并不能找到此package。

下载第三方模块

go mod tidy
$ go mod tidy
go: finding github.com/dreamerjackson/mydiv latest
go: downloading github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
go: extracting github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
go.mod
module github.com/dreamerjackson/mathlib

go 1.13

require github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
github.com/dreamerjackson/mydivgithub.com/pkg/errorsgo.modgo.sum
## go.sum
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161 h1:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161/go.mod h1:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

使用第三方模块

  • 接着就可以愉快的调用我们的代码了
package main

import (
    "fmt"
    "github.com/dreamerjackson/mydiv"
)

func main(){
    res,_ :=mydiv.Div(4,2)
    fmt.Println(res)
}
go run2

手动更新第三方模块

go.mod
require github.com/dreamerjackson/mydiv latest

或者

require github.com/dreamerjackson/mydiv master

获取复制commitId 到最后

require github.com/dreamerjackson/mydiv c9a7ffa8112626ba6c85619d7fd98122dd49f850
go get
go get github.com/dreamerjackson/mydiv
go mod tidy
go.sumgo.sum
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161 h1:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161/go.mod h1:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305090126-c9a7ffa81126/go.mod h1:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

依赖移除

当我们不想在使用此第三方包时,可以直接在代码中删除无用的代码,接着执行

$ go mod tidy
go.modgo.sum

go module 最小版本选择原理

什么是最小版本选择原理

github.com/dreamerjackson/mydivv1.0.2
> go list -m -versions github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mydiv v1.0.0 v1.0.1 v1.0.2 v1.0.3

假设现在有两个模块A、B,都依赖模块D。其中

A -> D v1.0.1,
B -> D v1.0.2
go modulego module

验证最小版本选择原理

github.com/dreamerjackson/mydiv模块Dv1.0.1v1.0.2
## v1.0.1
package mydiv
import "github.com/pkg/errors"
func Div(a int,b int) (int,error){
    if b==0{
        return 0,errors.Errorf("new error b can't = 0")
    }
    return a/b,nil
}

## v1.0.2
package mydiv
import "github.com/pkg/errors"
func Div(a int,b int) (int,error){
    if b==0{
        return 0,errors.Errorf("new error b can't = 0")
    }
    return a/b,nil
}
模块Bgithub.com/dreamerjackson/minidivgithub.com/dreamerjackson/mydiv
## 模块B
package div

import (
    "github.com/dreamerjackson/mydiv"
)

func Div(a int,b int) (int,error){
    return mydiv.Div(a,b)
}
模块Now
package main

import (
    "fmt"
    div "github.com/dreamerjackson/minidiv"
    "github.com/dreamerjackson/mydiv"
)

func main(){
    _,err1:= mydiv.Div(4,0)
    _,err2 := div.Div(4,0)
    fmt.Println(err1,err2)
}

当前的依赖关系如下:

当前模块 --> 模块D v1.0.2
当前模块 --> 模块B --> 模块D v1.0.1
  • 因此我们将验证,是否和我们所料,当前项目选择了模块D v1.0.2 呢?
  • 验证方式有两种:第一种为直接运行,查看项目采用了哪一个版本的代码
$ go run main.go
v1.0.2 b can't = 0 v1.0.2 b can't = 0
go list
~/mathlib » go list -m all | grep mydiv
github.com/dreamerjackson/mydiv v1.0.2
go mod mhygithub.com/dreamerjackson/mydiv
~/mathlib » go mod why github.com/dreamerjackson/mydiv
# github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/mydiv

查看直接和间接模块的当前和最新版本

go list -m -u all
~/mathlib » go list -m -u all | column -t                                                                                                                                                           jackson@192
go: finding github.com/dreamerjackson/minidiv latest
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/minidiv  v0.0.0-20200305104752-fcd15cf402bb
github.com/dreamerjackson/mydiv    v1.0.2                              [v1.0.3]
github.com/pkg/errors              v0.9.1
github.com/dreamerjackson/mydivv1.0.2v1.0.3

更新直接和间接模块

go get最小版本原则
go get -t -d -v  ./...
-t-d-v./…-u最大最新版本
~/mathlib » go get -u -t -d -v ./...                                                                                                                                                                jackson@192
go: finding github.com/dreamerjackson/minidiv latest
go: downloading github.com/dreamerjackson/mydiv v1.0.3
go: extracting github.com/dreamerjackson/mydiv v1.0.3
github.com/dreamerjackson/mydiv
~/mathlib » go list -m all | grep mydiv                                                                                                                                                             jackson@192
github.com/dreamerjackson/mydiv v1.0.3

重置依赖关系

go.mod go.sum
$ rm go.*
$ go mod init <module name>
$ go mod tidy

语义版本控制(semantic version)

  • Go模块引入了一种新的导入路径语法,即语义导入版本控制。每个语义版本均采用vMAJOR.MINOR.PATCH的形式。
    • MAJOR 主版本号,如果有大的版本更新,导致 API 和之前版本不兼容。我们遇到的就是这个问题。
    • MINOR 次版本号,当你做了向下兼容的新 feature。
    • PATCH 修订版本号,当你做了向下兼容的修复 bug fix。
    • v 所有版本号都是 v 开头。


1v2.0.0
my/thing/v22my/thing
A --> 模块B --> 模块D v1.0.0
A --> 模块C --> 模块D v2.0.0
v2.0.0 b can't = 0
package mydiv
import "github.com/pkg/errors"

func Div(a int,b int) (int,error){
    if b==0{
        return 0,errors.Errorf("v2.0.0 b can't = 0")
    }
    return a/b,nil
}
  • 同时需要修改v2模块路径名为:
module github.com/dreamerjackson/mydiv/v2
  • 接着在mathlib中,代码如下:
package main

import (
    "fmt"
    div "github.com/dreamerjackson/minidiv"
    mydiv "github.com/dreamerjackson/mydiv/v2"
)

func main(){
    _,err1:= mydiv.Div(4,0)
    _,err2 := div.Div(4,0)
    fmt.Println(err1,err2)
}
  • 现在的依赖路径可以表示为为:
mathlib --> 直接引用mydiv v2
mathlib --> 直接引用minidiv --> 间接引用mydiv v1

当我们运行代码之后,会发现两段代码是共存的

v2.0.0 b can't = 0 :: v1.0.1 b can't = 0
go list
~/mathlib(master*) » go list -m all | grep mydiv
github.com/dreamerjackson/mydiv v1.0.1
github.com/dreamerjackson/mydiv/v2 v2.0.1

模块镜像(Module Mirror)

模块镜像于2019年八月推出,是go官方1.13版本的默认系统。模块镜像是一个代理服务器,以帮助加快构建本地应用程序所需的模块的获取。代理服务器实现了基于REST的API,并根据Go工具的需求进行了设计。
模块镜像将会缓存已请求的模块及其特定版本,从而可以更快地检索将来的请求。一旦代码被获取并缓存在模块镜像中,就可以将其快速提供给世界各地的用户。

checksum数据库

checksum数据库也于2019八月推出,是可以用来防止模块完整性、有效性的手段。它验证特定版本的任何给定模块代码的正确性,而不管何人何时何地以及是如何获取的。Google拥有唯一的校验和数据库,但是可以通过私有模块镜像对其进行缓存。

go module 环境变量

有几个环境变量可以控制与模块镜像和checksum数据库有关的行为

directoffgo env
$ go env
GONOPROXY=""
GONOSUMDB=""
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOSUMDB="sum.golang.org"
gitlabexport GOPRIVATE=gitlab.XXX.com,gitlab.XXX-XX.com,XXX.io

Athens搭建私有模块镜像

Docker HubDocker
docker run -p '3000:3000' gomods/athens:latest

接下来,启动一个新的终端会话以运行Athens,为大家演示其用法。启动Athens服务并通过额外的参数调试日志(请确保系统已经安装并启动了docker)并有科学*上网的环境

$ docker run -p '3000:3000' -e ATHENS_LOG_LEVEL=debug -e GO_ENV=development gomods/athens:latest
INFO[7:11AM]: Exporter not specified. Traces won't be exported
2020-03-06 07:11:30.671249 I | Starting application at port :3000
3000go mod tidy
$ export GOPROXY="http://localhost:3000,direct"
$ rm go.*
$ go mod init github.com/dreamerjackson/mathlib
$ go mod tidy

在Athens日志中即可查看对应信息

INFO[7:39AM]: incoming request    http-method=GET http-path=/github.com/dreamerjackson/mydiv/@v/list http-status=200
INFO[7:39AM]: incoming request    http-method=GET http-path=/github.com/dreamerjackson/minidiv/@v/list http-status=200
INFO[7:39AM]: incoming request    http-method=GET http-path=/github.com/dreamerjackson/minidiv/@latest http-status=200
  • 详细信息,查看参考资料中Athens的官方网站

go module 优势

gopath

总结

go module
go modulego modulego modulego modulego modulego modulemodule

参考资料

喜欢本文的朋友欢迎点赞分享~

唯识相链启用微信交流群(Go与区块链技术)