Go 学习笔记(69)— Go Modules 使用详解(设置GO111MODULE、设置代理、初始化项目、创建依赖项、回退依赖项版本、删除未使用的依赖项)

1. 历史背景

1.1 GOPATH

Go 1.5 版本之前,所有的依赖包都是存放在 GOPATH 下,没有版本控制。这种方式的最大的弊端就是无法实现包的多版本控制,比如项目 A 和项目 B 依赖于不同版本的 package,如果 package 没有做到完全的向前兼容,往往会导致一些问题。

GOPATH
除此之外还会有下面的问题:

  • 如果依赖包持续演进,可能会导致不同开发者在不同时间获取和编译同一个 Go 包时,得到不同的结果,也就是不能保证可重现的构建。
  • 如果依赖包引入了不兼容代码,程序将无法通过编译。
  • 如果依赖包因引入新代码而无法正常通过编译,并且该依赖包的作者又没用及时修复这个问题,这种错误也会传导到你的程序,导致你的程序无法通过编译。

1.2 go vender

Go 1.5 版本推出了 vendor 机制,所谓 vendor 机制,就是每个项目的根目录下可以有一个 vendor 目录,里面存放了该项目的依赖的 packagego build 的时候会先去 vendor 目录查找依赖,如果没有找到会再去 GOPATH 目录下查找。

GOVender
Go 编译器会优先感知和使用 vendor 目录下缓存的第三方包版本,而不是 GOPATH 环境变量所配置的路径下的第三方包版本。这样,无论第三方依赖包自己如何变化,无论 GOPATH 环境变量所配置的路径下的第三方包是否存在、版本是什么,都不会影响到 Go 程序的构建。

如果你将 vendor 目录和项目源码一样提交到代码仓库,那么其他开发者下载你的项目后,就可以实现可重现的构建。因此,如果使用 vendor 机制管理第三方依赖包,最佳实践就是将 vendor 一并提交到代码仓库中。

要想开启 vendor 机制,你的 Go 项目必须位于 GOPATH 环境变量配置的某个路径的 src 目录下面。如果不满足这一路径要求,那么 Go 编译器是不会理会 Go 项目目录下的 vendor 目录的。

不过 vendor 机制虽然一定程度解决了 Go 程序可重现构建的问题,但对开发者来说,它的体验却不那么好。一方面,Go 项目必须放在 GOPATH 环境变量配置的路径下,庞大的 vendor 目录需要提交到代码仓库,不仅占用代码仓库空间,减慢仓库下载和更新的速度,而且还会干扰代码评审,对实施代码统计等开发者效能工具也有比较大影响。另外,你还需要手工管理 vendor 下面的 Go 依赖包,包括项目依赖包的分析、版本的记录、依赖包获取和存放等等。

Go 1.9 版本推出了实验性质的包管理工具 dep,这里把 dep 归结为 Golang 官方的包管理方式可能有一些不太准确。关于 dep 的争议颇多。

1.3 go mod

Go 1.11 版本推出 modules 机制,简称 mod,更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。
go module

Go 1.11 开始,Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中,为了兼容性,Go 命令仍然在旧的 GOPATH 模式下运行。从 Go 1.13 开始,go.mod模式将成为默认模式。

2. 设置 GO111MODULE

Go ModulesGo 1.11Go 1.12 中有三个模式,根据环境变量 GO111MODULE 定义:

  • 默认模式(未设置该环境变量或 GO111MODULE=auto

Go 命令行工具在同时满足以下两个条件时使用 Go Modules

  1. 当前目录不在 GOPATH/src/ 下;
  2. 在当前目录或上层目录中存在 go.mod 文件;
  • GOPATH 模式(GO111MODULE=off

Go 命令行工具从不使用 Go Modules。相反,它查找 vendor 目录和 GOPATH 以查找依赖项。

  • Go Modules 模式( GO111MODULE=on

Go 命令行工具只使用 Go ModulesGOPATH不再作为导入目录,但它还是会把下载的依赖储存在 GOPATH/pkg/mod 中,也会把 goinstall的结果放在 GOPATH/bin 中,只移除了 GOPATH/src/

如果 GO111MODULE 没有显式设置,那么默认为 on。如果 go env 查出来的环境变量 GO111MODULE 为空,那么 go 编译器默认 GO111moduleon

Go 1.13 默认使用 Go Modules 模式,所以以上内容在 Go 1.13 发布并在生产环境中使用后都可以忽略。

本文以 Go 1.13.6 为基础详细说明 Go modules 的使用。

# 临时开启 Go modules 功能
export GO111MODULE=on
# 永久开启 Go modules 功能
go env -w GO111MODULE=on

# 设置 Go 的国内代理,方便下载第三方包
go env -w GOPROXY=https://goproxy.cn,direct

逗号后面可以增加多个 proxy,最后的 direct 则是在所有 proxy 都找不到的时候,直接访问,代理访问不到的私有仓库就可以正常使用了。

其它代理请参考:
https://www.cnblogs.com/feiquan/p/13357971.html
https://studygolang.com/articles/23599?fr=sidebar

设置后通过 go env 查看如下所示:

wohu@wohu-dev:~$ go env
GO111MODULE="on"
.....
GOPROXY="https://goproxy.cn,direct"
.....
wohu@wohu-dev:~$

主要参数值含义如下:

GOMODCACHE 可以指定安装的第三方包的存放路径。如果不指定,默认值为 ~/go/pkg/mod 下。
env参数

3. Go module 常用操作

3.1 初始化项目

我们在 $GOPATH 以外的目录创建一个任意目录,然后初始化 go mod init project_name,成功之后会发现目录下会生成一个 go.mod 文件。

wohu@wohu-dev:~$ mkdir goProject
wohu@wohu-dev:~$ cd goProject/
wohu@wohu-dev:~/goProject$ mkdir apiserver
wohu@wohu-dev:~/goProject$ cd apiserver/
wohu@wohu-dev:~/goProject/apiserver$ go mod init apiserver
go: creating new go.mod: module apiserver
wohu@wohu-dev:~/goProject/apiserver$ ls
go.mod
wohu@wohu-dev:~/goProject/apiserver$  

查看内容

wohu@wohu-dev:~/goProject/apiserver$ cat go.mod 
module apiserver

go 1.13
wohu@wohu-dev:~/goProject/apiserver$

go.mod 文件只存在于模块的根目录中。模块子目录的代码包的导入路径等于模块根目录的导入路径(就是前面说的 module path)加上子目录的相对路径。

比如,我们如果创建了一个子目录叫 common,我们不需要(也不会想要)在子目录里面再运行一次 go mod init 了,这个代码包会被认为就是 apiserver 模块的一部分,而这个代码包的导入路径就是 apiserver/common

3.2 添加依赖项

apiserver 文件夹下创建 main.go 并添加以下内容

package main
 
import "github.com/gin-gonic/gin"


func ping(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "pong",
	})
}

func main() {
	r := gin.Default()
	r.GET("/ping", ping)
	r.Run()	// listen and serve on 0.0.0.0:8080
}

执行 go build main.go 之后会自动下载三方包到默认的目录 $GOPATH/pkg/mod,也就是 Mod Cache 路径。

wohu@wohu-dev:~/goProject/apiserver$ go build main.go 
go: finding github.com/gin-gonic/gin v1.7.6
go: downloading github.com/gin-gonic/gin v1.7.6
...
wohu@wohu-dev:~/goProject/apiserver$ ls
go.mod  go.sum  main  main.go
wohu@wohu-dev:~/goProject/apiserver$ 

进入 $GOPATH/pkg/mod 目录查看

wohu@wohu-dev:~/goProject/apiserver$ ls $GOPATH/pkg/mod/cache/download/github.com/gin-gonic/gin/@v/
list         v1.7.4.mod      v1.7.5.lock     v1.7.6.info  v1.7.6.ziphash
list.lock    v1.7.4.zip      v1.7.5.mod      v1.7.6.lock
v1.7.4.info  v1.7.4.ziphash  v1.7.5.zip      v1.7.6.mod
v1.7.4.lock  v1.7.5.info     v1.7.5.ziphash  v1.7.6.zip
wohu@wohu-dev:~/goProject/apiserver$ 

​此时再次查看 go.mod文件内容

module apiserver

go 1.13

require github.com/gin-gonic/gin v1.7.6 // indirect

其中

  • module 表示模块名称
  • require 依赖包列表以及版本

一般来说,require () 是不需要自己手动去修改的,当运行代码的时候,会根据代码中用到的包自动去下载导入。

  • exclude 禁止依赖包列表,不下载和引用哪些包(仅在当前模块为主模块时生效)
  • replace 替换依赖包列表和引用路径(仅在当前模块为主模块时生效)

replace 对于国内开发来说是个神功能,他可以将代码中使用,但国内被墙的代码替换成 github上的下载路径,例如:golang.org/x/ 下的包,全都替换成 github地址上的包,版本使用 latest 即可。

replace 指令可以将依赖的模块替换为另一个模块,例如由公共库替换为内部私有仓库。

replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5

replace (
	golang.org/x/net => github.com/golang/net latest
	golang.org/x/tools => github.com/golang/tools latest
	golang.org/x/crypto => github.com/golang/crypto latest
	golang.org/x/sys => github.com/golang/sys latest
	golang.org/x/text => github.com/golang/text latest
	golang.org/x/sync => github.com/golang/sync latest
)

indirect 表示这个库是间接引用进来的。

使用 go list -m all 可以查看到所有依赖列表,也可以使用 go list -json -m all 输出 json格式的打印结果。

wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/sse v0.1.0
...
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
gopkg.in/yaml.v2 v2.2.8
wohu@wohu-dev:~/goProject/apiserver$ 

除了 go.mod 之外,go 命令行工具还维护了一个 go.sum 文件,它包含了指定的模块的版本内容的哈希值作为校验参考:

wohu@wohu-dev:~/goProject/apiserver$ cat go.sum 
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=

go 命令行工具使用 go.sum 文件来确保你的项目依赖的模块不会发生变化——无论是恶意的,还是意外的,或者是其它的什么原因。go.mod 文件和 go.sum 文件都应该保存到你的代码版本控制系统里面去。

go.sum 这个文件记录了源码的直接依赖和间接依赖包的相关版本的 hash 值,用来校验本地包的真实性。在构建的时候,如果本地依赖包的 hash 值与 go.sum 文件中记录的不一致,就会被拒绝构建,这样可以确保你的项目所依赖的 module 内容,不会被恶意或意外篡改。

3.3 回退版本

GIN 框架的版本回退到上个版本。这里需要使用一个命令查看依赖的版本历史。

wohu@wohu-dev:~/goProject/apiserver$ go list -m -versions github.com/gin-gonic/gin
github.com/gin-gonic/gin v1.1.1 v1.1.2 v1.1.3 v1.1.4 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.6.1 v1.6.2 v1.6.3 v1.7.0 v1.7.1 v1.7.2 v1.7.3 v1.7.4 v1.7.5 v1.7.6
wohu@wohu-dev:~/goProject/apiserver$ 

将版本回退到指定版本有两种方法:

  1. 使用 go get 命令
# 只需要在依赖后面加上 @version 就可以了
wohu@wohu-dev:~/goProject/apiserver$ go get github.com/gin-gonic/gin@v1.7.5	
go: finding github.com/gin-gonic/gin v1.7.5
go: downloading github.com/gin-gonic/gin v1.7.5
go: extracting github.com/gin-gonic/gin v1.7.5
go: downloading github.com/json-iterator/go v1.1.9
go: extracting github.com/json-iterator/go v1.1.9
......

请注意我们给 go get 命令的参数后面显式地指定了 @v1.7.5 ,事实上每个传递给 go get 的参数都能在后面显式地指定一个版本号,默认情况下这个版本号是 @latest,这代表 Go 命令行工具会尝试下载最新的版本。

查看回退之后的版本

wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
github.com/gin-gonic/gin v1.7.5
  1. 使用 go mod 命令
wohu@wohu-dev:~/goProject/apiserver$ go mod edit -require="github.com/gin-gonic/gin@v1.7.4"
wohu@wohu-dev:~/goProject/apiserver$ go get
go: downloading github.com/gin-gonic/gin v1.7.4
go: extracting github.com/gin-gonic/gin v1.7.4
go: finding github.com/gin-gonic/gin v1.7.4
wohu@wohu-dev:~/goProject/apiserver$ 

wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
....
github.com/gin-gonic/gin v1.7.4

Go Module 构建模式下,当依赖的主版本号为 0 或 1 的时候,我们在 Go 源码中导入依赖包,不需要在包的导入路径上增加版本号,也就是:

import github.com/user/repo/v0 等价于 import github.com/user/repo
import github.com/user/repo/v1 等价于 import github.com/user/repo
  1. 手动修改 go.mod
    go.mod 文件中修改版本号为:
require github.com/gin-gonic/gin [版本号] 

或者将指定 commit Id 复制到末尾:

require github.com/gin-gonic/gin c9a7ffa8112626ba6c85619d7fd98122dd49f850。

使用上面任一方式保存文件后,再次运行 go mod tidy,版本即会进行更新。这个时候如果我们再打开 go.sum 文件会发现,go.sum 中不仅存储了直接和间接的依赖,还存储了过去的版本信息。

3.4 升级版本

升级版本和回退版本使用的命令一样,只是后面的版本号不同,不再额外说明

3.5 删除未使用的依赖项

我们在构建一个代码包的时候(比如说 go build 或者 go test),可以轻易的知道哪些依赖缺失,从而将它自动添加进来,但很难知道哪些依赖可以被安全的移除掉。移除一个依赖项需要在检查完模块中所有代码包和这些代码包的所有可能的编译标签的组合。一个普通的 build 命令不会获得这么多的信息,所以它不能保证安全地移除掉没用的依赖项。

可以用 go mod tidy 命令来清除这些没用到的依赖项:

go mod tidy

查看 go mod 命令选项

wohu@wohu-dev:~/goProject/apiserver$ go mod
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		# 下载依赖的module到本地cache
	edit        edit go.mod from tools or scripts	# 编辑go.mod文件
	graph       print module requirement graph		# 打印模块依赖图
	init        initialize new module in current directory	# 在当前文件夹下初始化一个新的module, 创建go.mod文件
	tidy        add missing and remove unused modules	# 增加丢失的module,去掉未用的module
	vendor      make vendored copy of dependencies	# 将依赖复制到vendor下
	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 tidy 命令会扫描 Go 源码,并自动找出项目依赖的外部 Go Module 以及版本,下载这些依赖并更新本地的 go.mod 文件。

go mod tidy 下载的依赖 module 会被放置在本地的 module 缓存路径下,默认值为 $GOPATH/pkg/modGo 1.15 及以后版本可以通过 GOMODCACHE 环境变量,自定义本地 module 的缓存路径。如果没有设置 GOPATH 环境变量,其默认值为你的 home 路径下的 go 文件夹。这样第三方包就在 go 文件夹的 pkg/mod 下面。

3.6 引入主版本号大于 1 的三方库

语义导入版本机制有一个原则:如果新旧版本的包使用相同的导入路径,那么新包与旧包是兼容的。也就是说,如果新旧两个包不兼容,那么我们就应该采用不同的导入路径

按照语义版本规范,如果我们要为项目引入主版本号大于 1 的依赖,比如 v2.0.0,那么由于这个版本与 v1v0 开头的包版本都不兼容,我们在导入 v2.0.0 包时,就要使用像下面代码中那样不同的包导入路径:

import github.com/user/repo/v2/xxx

也就是说,如果我们要为 Go 项目添加主版本号大于 1 的依赖,我们就需要使用“语义导入版本”机制,在声明它的导入路径的基础上,加上版本号信息。首先,我们在源码中,以空导入的方式导入 v7 版本的 github.com/go-redis/redis 包:


package main

import (
  _ "github.com/go-redis/redis/v7" // “_”为空导入
  "github.com/google/uuid"
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.Println("hello, go module mode")
  logrus.Println(uuid.NewString())
}

我们通过 go get 获取 redisv7 版本:

$go get github.com/go-redis/redis/v7
go: downloading github.com/go-redis/redis/v7 v7.4.1
go: downloading github.com/go-redis/redis v6.15.9+incompatible
go get: added github.com/go-redis/redis/v7 v7.4.1

3.7 特殊情况:使用 vendor

vendor 机制虽然诞生于 GOPATH 构建模式主导的年代,但在 Go Module 构建模式下,它依旧被保留了下来,并且成为了 Go Module 构建机制的一个很好的补充。特别是在一些不方便访问外部网络,并且对 Go 应用构建性能敏感的环境。

GOPATH 构建模式不同,Go Module 构建模式下,我们再也无需手动维护 vendor 目录下的依赖包了,Go 提供了可以快速建立和更新 vendor 的命令,我们还是以前面的 module-mode 项目为例,通过下面命令为该项目建立 vendor

$go mod vendor
$tree -LF 2 vendor
vendor
├── github.com/
│   ├── google/
│   ├── magefile/
│   └── sirupsen/
├── golang.org/
│   └── x/
└── modules.txt

我们看到,go mod vendor 命令在 vendor 目录下,创建了一份这个项目的依赖包的副本,并且通过 vendor/modules.txt 记录了 vendor 下的 module 以及版本。

如果我们要基于 vendor 构建,而不是基于本地缓存的 Go Module 构建,我们需要在 go build 后面加上 -mod=vendor 参数。在 Go 1.14 及以后版本中,如果 Go 项目的顶层目录下存在 vendor 目录,那么 go build 默认也会优先基于 vendor 构建,除非你给 go build 传入 -mod=mod 的参数。

通常我们直接使用 go module (非vendor) 模式即可满足大部分需求。如果是那种开发环境受限,因无法访问外部代理而无法通过 go 命令自动解决依赖和下载依赖的环境下,我们通过 vendor 来辅助解决。

4. go mod 工作机制

4.1 go mod 版本表达方式

go mod 有两种版本表达方式,分别为语义化版本和基于某一个 commit 的伪版本。
go mod 版本

4.2 管理 go mod 命令

go mod命令
注意:

  • go get 默认不会将语义化版本修改为伪版本
  • @latest 表示最新的语义化版本,而不是伪版本
  • -u 表示更新该依赖并同时更新该依赖中所有参与编译的依赖到 minor 版本

4.3 版本选择算法 Minimal Version Selection(MVS)

那么什么是最小版本选择原理呢?

Go 最小版本选择指的是,在选择依赖的版本时,优先选择项目中最合适的最低版本。当然,并不是说 MVS 不能选择最新的版本,而是说如果项目中任何依赖都用不到最新的版本,那么我们本质上不需要它。

注意:最小版本选择的前提条件是要 go 包版本要满足语义版本规则。

下面问题的答案选择 B
版本
版本选择算法
最终,我们选择的版本是项目导入的可以使用的最小版本,即 A 1.2、B 1.2、C 1.4、D 1.2 。在这个例子中,虽然 C1.3、C1.4 分别被 A、B 两个包导入了,但是现在 Go Modules 认为最好的版本是这两个版本中最大的版本 C1.4,因为 C1.4 相对于 C1.3 增加了接口等操作,如果选择 C1.3 版本,可能出现编译都不通过的情况。而从语义版本控制的角度,默认 C1.4 版本是向后兼容的。

myproject 有两个直接依赖 ABAB 有一个共同的依赖包 C,但 A 依赖 Cv1.1.0 版本,而 B 依赖的是 Cv1.3.0 版本,并且此时 C 包的最新发布版为 v1.7.0。这个时候,Go 命令是如何为 myproject 选出间接依赖包 C 的版本呢?选出的究竟是 v1.7.0v1.1.0 还是 v1.3.0 呢?

版本依赖

Go 会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。这个例子中,C v1.3.0 是符合项目整体要求的版本集合中的版本最小的那个,于是 Go 命令选择了 C v1.3.0,而不是最新最大的 C v1.7.0

如果现在 B 依赖的不是 v1.3.0 而是 v2.3.0 , 那最终 go 会选择 C v1.1.0C v2.3.0,这两个都会下载到本地并链接到最终程序中。因为根据 go 的语义导入版本的规则,不同 major 号的 module 就是不同的 module

go module 采用的语义版本导入机制,v1.0.0v2.0.0major 号不同,是两个完全不同的版本,是可以共同被某个包同时导入的,语法如下;

import (
    "c"
    "c/v2"
)

4.3.1 replace 指令与最小版本选择

当项目中使用了replace指令,如下图所示,B1.2 依赖的 C1.4 replace 为了 R 模块,R 依赖 D1.3,最终最小版本算法选择的版本为 R、D 1.3 版本。
replace

4.3.2 Exclusion 指令与最小版本选择

当项目中使用了 Exclusion 指令时,如下图所示,当 C 1.3 被排除,A1.2 将表现为直接依赖比 C1.3 版本更高的 C1.4。
exclude

5. 常见问题

5.1 导入本地创建的 module

如何 import 自己在本地创建的 module ,在这个 module 还没有发布到 GitHub 的情况下?

假如你的 module aimportmodule b 将发布到 github.com/user/repo 中,那么你可以手动在module ago.mod 中的 require 块中手工加上一条:

require github.com/user/repo v1.0.0

注意 v1.0.0 这个版本号是一个临时的版本号,目的是满足 go.modrequire 块的语法要求。

然后在 module ago.mod 中使用 replace 将上面对 module brequire 替换为本地的 module b :

replace github.com/user/repo v1.0.0 => module b本地路径

这样 go 命令就会使用你本地正在开发、尚未提交 githubmodule b 了。

常用工具和方法
间接依赖
在这里插入图片描述

  • 在使用 go get 更新时,如果不需要更新依赖的依赖则不要使用 -u 参数,如果使用该参数会使得依赖的依赖更新,有可能导致由于依赖的依赖版本不兼容而编译失败。
  • 有些工程不使用 go mod 原因有两个: 一是在 go mod 推广前其版本号已经大于 1;二是便于使用 go get 直接拉取到 V2 以上的版本。

mod1
mod2

5.2 exclude指令

有时我们希望排除某一模块特定的版本,这时就需要用到 exclude 指令了。如果当前项目中,exclude指令与 require 指令对应的版本相同,那么 go getgo mod tidy 指令将查找高一级的版本。

exclude golang.org/x/net v1.2.3

exclude (
    golang.org/x/crypto v1.4.5
    golang.org/x/text v1.6.7
)

5.3 retract指令

retract 撤回指令表示不依赖指定模块的版本或版本范围。当版本发布得太早,或者版本发布之后发现严重问题时,撤回指令就很有用了。例如,对于模块 example.com/m ,假设我们错误地发布了 v1.0.0 版本后想要撤销。这时,我们就需要发布一个新的版本,tag 为v1.0.1 。

retract (
    v1.0.0 
    v1.0.1 
)

然后,我们要执行 go get example.com/m@latest,这样,依赖管理工具读到最新的版本 v1.0.1 是撤回指令,而且发现 v1.0.0 和 v1.0.1 都被撤回了,go 命令就会降级到下一个最合适的版本,比如 v0.9.5 之类的。除此之外,retract 指令还可以指定范围,更灵活地撤回版本。


retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
    v1.0.0
    [v1.0.0, v1.9.9]
)

5.4 重置依赖关系

如果不满意所选的模块和版本,我们可以通过删除 go.modgo.sum 中的依赖关系并再次运行 go mod tidy 来重置版本。当项目还不太成熟时,这是一种选择。


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

6. 总结

1、开启go mod ,go env -w GO111MODULE=on
2、使用go mod,可以在任意位置进行源代码开发。不用在GOPATH下创建bin,pkg,src目录
3、go mod init modulePath ,其中modulePath可以理解为命名空间,之后导入的本地包为:moduleName/本地包路径 。
4、生成的go.mod文件中包含module path,go version ,require 第三方包
5、go mod tidy 会下载第三方依赖包,默认下载到GOPATH/pkg/mod下 (不同项目可以共用第三方依赖包)
6、go mod tidy 还会生成一个go.sum 文件,这个文件记录了 module 的直接依赖和间接依赖包的相关版本的 hash 值,用来校验本地包的真实性

参考:
https://www.njphper.com/posts/8b58ea6d.html
https://blog.golang.org/using-go-modules
https://blog.csdn.net/alisystemsoftware/article/details/104299918

https://pkg.go.dev/cmd/go/internal/modget@go1.15.11
https://juejin.cn/video/7031152677996363779/section/7031152678222856195

  • 17
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
《Go语言学习笔记.pdf》是一本关于Go语言学习学习笔记,内容丰富且简洁明了。本书从基础知识开始,逐步介绍了Go语言的语法、特性和常用库函数等。在学习笔记中,作者通过实际的示例和练习帮助读者理解Go语言的概念和用法。 第一章介绍了Go语言的起源和发展,为读者提供了对Go语言背景的整体了解。第二章讲解了Go语言的基本语法,例如变量声明、循环和条件语句等。通过大量的代码示例,读者能够更好地理解Go语言的语法和结构。 接下来的章节重点介绍了Go语言的并发编程和高级特性。第三章详细介绍了Go语言中的goroutine和channel,这是Go语言并发编程的核心机制。作者通过生动的示例代码和实际应用案例,向读者展示了如何使用goroutine和channel实现并发编程。 第四章和第五章分别介绍了Go语言中的面向对象编程和函数式编程。通过深入讲解Go语言中的结构体、接口和函数,读者能够更好地应用这些特性进行代码设计和开发。 最后几章则介绍了Go语言中常用的库函数和工具。例如,第六章介绍了Go语言中用于网络编程的net包和http包。读者可以学习到如何使用这些库函数构建基于网络的应用程序。 总的来说,《Go语言学习笔记.pdf》是一本非常实用的Go语言学习资料。通过阅读这本书,读者能够系统地学习和理解Go语言的基本概念和高级特性,为之后的Go语言开发打下坚实的基础。无论是初学者还是有一定编程经验的开发者,都能从中获得丰富的知识和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值