代码改变世界

go mod

2022-09-15 14:12  天心PHP  阅读(669)  评论(0编辑  收藏  举报

Go Module 自 Go1.11 开始引入,Go 1.16 默认开启 

2.4 go mod使用

2.4.1 设置GO111MODULE

Win + R 输入 cmd 打开命令行,输入:

go env

即可看到 GO111MODULE (默认情况是空的):

GO111MODULE 有三个值:off、on 和 auto (默认值)

GO111MODULE=off :go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
GO111MODULE=on :go命令行会使用modules,而一点也不会去GOPATH目录下查找。
GO111MODULE=auto :默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
当前目录在GOPATH/src之外且该目录包含go.mod文件
当前文件在包含go.mod文件的目录下面。
【注】

在使用go module时,GOPATH是无意义的。不过它仍然会把下载的依赖存储在 $GOPATH/pkg/mod 中,也会把 go install 的结果放在 $GOPATH/bin 中。
当module功能启用时,依赖包的存放位置变更为 $GOPATH/pkg 。允许同一个package多个版本并存,且多个项目可以共享缓存的module。
设置的命令如下:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

可在命令行中输入:go env 查看 GO111MODULE=on 。

2.4.2 清空所有GOPATH

开启 go mod 之后,并不能与 GOPATH 共存。必须把项目从 GOPATH 中移除,否则会报 $GOPATH/go.mod exists but should not 的错误。

在 Goland 中,移除项目所有 GOPATH 的操作如下:

 

 清空 GOPATH 之后,在单元测试模式下,同一个包下不同文件函数调用报错为 undefined 的问题也会解决。

创建一个新的模块

让我们来创建一个新的模块吧。

在 $GOPATH/src 之外创建一个新的、空白的目录,进入这个目录,并且新建一个源代码文件,hello.go

 hello.go 代码:

package hello
func Hello() string {
  return "Hello, world."
}

让我们再写点测试,在 hello_test.go 文件中:

package hello
import "testing"
func TestHello(t *testing.T) {
    want := "Hello, world."
    if got := Hello(); got != want {
        t.Errorf("Hello() = %q, want %q", got, want)
    }
}

到目前为止,目录里面包含了一个代码包,但是它还不是一个模块,因为这里面没有 go.mod 文件。如果我们现在的工作目录是 c:/go/hello 并且我们运行 go test,我们会看到如下的输出:

C:\go\hello>go test
PASS
ok      _/C_/go/hello   0.530s

这里先要保证  GO111MODULE=off   因为开启GO111MODULE=on后,go会忽略GOPATH和vendor文件夹,只会根据go.mod加载依赖。

最后一行总结了代码包整体的测试结果,因为我们工作目录在 $GOPATH 之外,且又不属于任何模块,所以 Go 命令行工具并不知道当前目录的导入路径是什么,所以只能用目录的路径作为包的名字:_/home/gopher/hello

让我们把当前的目录设置成模块的根目录吧,为此我们要用到 go mod init 命令然后再尝试运行 go test:(GO111MODULE=on)

恭喜你,你编写并测试了你的第一个模块!

go mod init 命令创建了一个 go.mod 文件:

module example.com/hello

go 1.15

go.mod 文件只存在于模块的根目录中。模块子目录的代码包的导入路径等于模块根目录的导入路径(就是前面说的 module path)加上子目录的相对路径。比如,我们如果创建了一个子目录叫 world,我们不需要(也不会想要)在子目录里面再运行一次 go mod init 了,这个代码包会被认为就是 example.com/hello 模块的一部分,而这个代码包的导入路径就是 example.com/hello/world

添加依赖项

引进 Go 模块系统的主要动机,就是让用户更轻松地使用其他开发者编写的代码(换句话说就是添加一个依赖项)。

让我们修改一下我们的 hello.go ,让它导入 rsc.io/quote 模块,并用这个模块的接口来实现 Hello

package hello
import "rsc.io/quote"
func Hello() string {
    return quote.Hello()
}

现在让我们再运行一遍测试:

C:\go\hello>go test
go: finding module for package rsc.io/quote
go: found rsc.io/quote in rsc.io/quote v1.5.2
PASS
ok      example.com/hello       0.381s

C:\go\hello>

go 命令行工具会根据 go.mod 里面指定好的依赖的模块版本来下载相应的依赖模块。在你的代码中 import 了一个包,但 go.mod 文件里面又没有指定这个包的时候,go 命令行工具会自动寻找包含这个代码包的模块的最新版本,并添加到 go.mod 中(这里的 " 最新 " 指的是:它是最近一次被 tag 的稳定版本(即非预发布版本,non-prerelease),如果没有,则是最近一次被 tag 的预发布版本,如果没有,则是最新的没有被 tag 过的版本)。在我们的例子是,go test 把新导入的 rsc.io/quote 包解析为 rec.io/quote v1.5.2模块。它还会下载 rsc.io/quote 模块依赖的两个依赖项。即 rsc.io/sampler 和 golang.org/x/text。但是只有直接依赖会记录在 go.mod 文件里面:

go.mod 文件内容

module example.com/hello
go 1.15
require rsc.io/quote v1.5.2

第二次运行 go test 命令的时候 Go 命令工具就不再重复上述的工作了,因为 go.mod 已经是更新过了,并且刚才下载下来的模块已经缓存在本地(在 $GOPATH/pkg/mod)目录中:

C:\go\hello>go test
PASS
ok      example.com/hello       0.352s
C:\go\hello>

要注意的是虽然用 Go 命令行工具添加依赖非常的简单快捷,但是它不是没有代价的。你的项目现在有很多关键指标都对新的依赖项有了依赖,比方说代码的正确性、安全性、合适的版权等等,不一而足。更多相关的资讯,可以查看 Russ Cox 的博客文章,"Our Software Dependency Problem"。

正如我们上面所见,添加一个直接依赖往往会带来其它间接的依赖。go list -m all 命令会把当前的模块和它所有的依赖项都列出来:

C:\go\hello>go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
C:\go\hello>

在上述 go list 命令的输出中,当前的模块,又称为主模块 (main module),永远都在第一行,接着是主模块的依赖项,以依赖项的 module path 排序。

golang.org/x/text 的版本 v0.0.0-20170915032832-14c0d48ead0c 是一个典型的伪版本(pseudo-version) 的例子,它其实就是 Go 命令工具自定义的一个命名规则,当你想要依赖一个模块的某个 commit 版本的代码,但是这个 commit 没有被 tag 过的时候,可以这样子来指定。

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

golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

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

更新依赖项

有了 Go 模块机制后,模块的版本通过带有语义化版本号(semantic version)的标签来指定。一个语义化版本号包括三个部分:主版本号(major)、次版本号(minor)、修订号(patch)。举个例子:对于版本 v0.1.2,主版本号是 0,次版本号是 1,修订号是 2。我们先来过一遍更新某个模块的次版本号的流程。在下一节,我们再考虑主版本号的更新。

从 go list -m all 的输出中,我们可以看到我们在使用的 golang.org/x/text 模块还是以前没有被打过版本号标签的版本。让我们来把它更新到最新的有打过版本号标签的的版本,并测试是否能正常使用。

C:\go\hello>go get golang.org/x/text
go: golang.org/x/text upgrade => v0.3.7

C:\go\hello>go test
PASS
ok example.com/hello 0.360s

C:\go\hello>

哇嗷,一切正常!我们再来看看现在 go list -m all 的输出和 go.mod 文件长什么样子:

C:\go\hello>go list -m all
example.com/hello
golang.org/x/text v0.3.7
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

C:\go\hello>

go.mod 文件

module example.com/hello

go 1.15

require (
    golang.org/x/text v0.3.7 // indirect
    rsc.io/quote v1.5.2
)

golang.org/x/text 模块已经被升级到最新的版本(v0.3.7),go.mod 文件里面也把这个模块的版本指定成版本 v0.3.7。注释 indirect 意味着这个依赖项不是直接被当前模块使用的。而是被模块的其它依赖项使用的。详情请见 go help modules

现在让我们来尝试更新 rsc.io/sampler 模块的次版本号。同样操作,先运行 go get 命令,然后跑一遍测试:

C:\go\hello>go get rsc.io/sampler
go: rsc.io/sampler upgrade => v1.99.99

C:\go\hello>go test
--- FAIL: TestHello (0.00s)
    hello_test.go:6: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL    example.com/hello       0.335s

C:\go\hello>

噢,糟糕,测试报错了,这个测试表明 rsc.io/sampler 模块的最新版本跟我们之前的用法不兼容。我们来列举一下这个模块能用的 tag 过的版本:

C:\go\hello>go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99

C:\go\hello>

我们之前用过 v1.3.0,而 v1.99.99 明显不能用了。也许我们能试一下 v1.3.1 版本

C:\go\hello>go get rsc.io/sampler@v1.3.1

C:\go\hello>go test
PASS
ok      example.com/hello       0.291s

C:\go\hello>

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

添加一个拥有更高主版本号的依赖项

我们来为我们的代码包添加一个新的函数,func Proverb 会返回一条关于 Go 并发编程的名言,这可以通过调用 quote.Concurrency 来实现,而这个函数由 rsc.io/quote/v3 模块提供。首先我们要修改我们的 hello.go 来添加新的函数:

package hello
import (
    "rsc.io/quote"
    quoteV3 "rsc.io/quote/v3"
)
func Hello() string {
    return quote.Hello()
}
func Proverb() string {
    return quoteV3.Concurrency()
}

然后我们在 hello_test.go 里面添加一个测试:

func TestProverb(t *testing.T) {
    want := "Concurrency is not parallelism."
    if got := Proverb(); got != want {
        t.Errorf("Proverb() = %q, want %q", got, want)
    }
}

然后我们可以来测试我们的代码了:

C:\go\hello>go test
go: finding module for package rsc.io/quote/v3
go: found rsc.io/quote/v3 in rsc.io/quote/v3 v3.1.0
PASS
ok      example.com/hello       0.277s

C:\go\hello>

请注意我们的模块现在既依赖 rsc.io/quote 也依赖 rsc.io/quote/v3

C:\go\hello>go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0

C:\go\hello>

不同主版本号的同一个 Go 模块,使用了不同的 module path ——从 v2 开始,module path 的结尾一定要跟上主要版本号。在本例中,v3 版本的 rsc.io/quote 已经不是 rsc.io/quote 了,它的 module path 是 rsc.io/quote/v3 。这个规定被称为 semantic import versioning(语义化的导入版本控制),它给予了不兼容的模块一个不同的名字。反之,v1.6.0 版本的 rsc.io/quote 应该做到能够向下兼容 v1.5.2 版本。所以这两个版本可以共用同一个名字 rsc.io/quote。(在上一节中,v1.99.99 版本的 rsc.io/sampler 应该要向下兼容 v1.3.0 版本的 rsc.io/sampler,但是 bug 或者模块使用者对模块的用法不对,这些都有可能会导致错误发生。)

每一次构建项目,go 命令行工具允许每个 module path 最多只有一个,这就意味着每一个主要版本只有一个:最多一个 rsc.io/quote,最多一个 rsc.io/quote/v2,最多只有一个 rsc.io/quote/v3 等等。这给了模块的作者一个明确的信号:对于同一个 module path,可能会存在有多个重复的模块——一个程序有可能同时使用了 v1.5.2 的 rsc.io/quote 和 v1.6.0 的 rsc.io/quote。还有,允许同一个模块的不同主版本号存在(因为它们的 module path 不一样),能够给模块使用者部分更新主版本的能力。拿这个例子来说,我们想要使用 rsc.io/quote/v3 v3.1.0 模块里面的 quote.Concurrency,但是没准备好要整个代码都迁移到 v3 版本的 rsc.io/quote 中,这种部分迁移的能力,在大型程序或者代码库里面尤其重要。

更新当前依赖项到一个新的主版本号

现在让我们把整个项目的 rsc.io/quote 都升级到 rsc.io/quote/v3 吧。因为主版本号改变了,所以我们应该做好心理准备,可能会有些 API 已经被移除、重命名或者被修改成了不兼容的方式。通过阅读文档,我们得知 Hello 已经变成了 HelloV3

C:\go\hello>go doc rsc.io/quote/v3
package quote // import "rsc.io/quote/v3"

Package quote collects pithy sayings.

func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string

(上面的输出有个 已知的 Bug:显示的 import 路径后面漏了 v3

我们可以把 hello.go 中使用 quote.Hello() 的地方改成 quote.HelloV3()

package hello
import quoteV3 "rsc.io/quote/v3"
func Hello() string {
    return quoteV3.HelloV3()
}
func Proverb() string {
    return quoteV3.Concurrency()
}

然后我们再重新运行一下测试确保一切正常:

C:\go\hello>go test
PASS
ok      example.com/hello       0.378s

C:\go\hello>

移除没有用到的依赖

我们代码中已经没有用到 rsc.io/quote 的地方了,但是它还是会存在 go list -m all 的输出和 go.mod 文件中:

C:\go\hello>go test
PASS
ok      example.com/hello       0.378s

C:\go\hello> go list -m all
example.com/hello
golang.org/x/text v0.3.7
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1

go.mod

module example.com/hello

go 1.15

require (
    golang.org/x/text v0.3.7 // indirect
    rsc.io/quote v1.5.2
    rsc.io/quote/v3 v3.1.0
    rsc.io/sampler v1.3.1 // indirect
)

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

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

C:\go\hello>go mod tidy

C:\go\hello>go list -m all
example.com/hello
golang.org/x/text v0.3.7
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1

C:\go\hello>

go.mod 

module example.com/hello

go 1.15

require (
    golang.org/x/text v0.3.7 // indirect
    rsc.io/quote/v3 v3.1.0
    rsc.io/sampler v1.3.1 // indirect
)

结论

Go 的模块功能将会成为未来 Go 的依赖管理系统。在所有支持模块机制的 Go 版本中(即目前的 Go 1.11、Go 1.12),都能正常使用模块系统拥有的所有功能。

本文介绍了使用 Go 模块过程中的几个工作流程:

  • go mod init 创建了一个新的模块,初始化 go.mod 文件并且生成相应的描述
  • go build, go test 和其它构建代码包的命令,会在需要的时候在 go.mod 文件中添加新的依赖项
  • go list -m all 列出了当前模块所有的依赖项
  • go get 修改指定依赖项的版本(或者添加一个新的依赖项)
  • go mod tidy 移除模块中没有用到的依赖项。

我们鼓励你在你的本地开发中使用模块功能,并为你的项目添加 go.mod 和 go.sum 文件.

安装beego和bee工具

1.beego需要在有网络的情况下安装,使用以下命令进行安装,安装的前提是GO的SDK和环境变量都已经配置完成。

命令:go get -u github.com/beego/bee/v2

2.如果网络超时可以设置代理,命令如下:

go env -w GOPROXY=https://goproxy.cn

3.安装完后使用以下命令进行升级,不然项目无法正常启动

go get -u github.com/beego/beego/v2

4.beego需要配合着bee工具的使用,bee工具安装使用如下命令

 # go 1.16 以前的版本
go get -u github.com/beego/bee/v2
 
# go 1.16及以后的版本
go install github.com/beego/bee/v2@latest

5.安装完之后,bee 可执行文件默认存放在 $GOPATH/bin 里面,所以您需要把 $GOPATH/bin 添加到您的环境变量中,才可以进行下一步。

6.我们在命令行输入 bee,就可以看到bee工具的相关信息

 

 

 7.new 命令是新建一个 Web 项目,我们在命令行下执行 bee new  就可以创建一个新的项目。但是注意该命令必须在 $GOPATH/src 下执行。最后在 $GOPATH/src 相应目录下生成如下目录结构的项目:

bee new myproject
[INFO] Creating application...
/gopath/src/myproject/
/gopath/src/myproject/conf/
/gopath/src/myproject/controllers/
/gopath/src/myproject/models/
/gopath/src/myproject/static/
/gopath/src/myproject/static/js/
/gopath/src/myproject/static/css/
/gopath/src/myproject/static/img/
/gopath/src/myproject/views/
/gopath/src/myproject/conf/app.conf
/gopath/src/myproject/controllers/default.go
/gopath/src/myproject/views/index.tpl
/gopath/src/myproject/main.go
13-11-25 09:50:39 [SUCC] New application successfully created!

8.上面的 new 命令是用来新建 Web 项目,不过很多用户使用 beego 来开发 API 应用。所以这个 api 命令就是用来创建 API 应用的,执行命令之后如下所示:

9.我们在开发 Go 项目的时候最大的问题是经常需要自己手动去编译再运行,bee run 命令是监控 beego 的项目,通过 fsnotify监控文件系统。但是注意该命令必须在 $GOPATH/src/appname(以例子来说需要进入到: D:\install\go\src\myapiproject) 下执行。

10.如上图启动有时候可能会报: 0003 Failed to build the application: go: github.com/beego/beego/v2@v2.0.1: missing go.sum entry; to add it:
go mod download github.com/beego/beego/v2 github.com/beego/beego/v2/serve错误,这是因为使用的第三方的包没有下载和或者更新,执行:go mod tidy 命令来处理然后再启动就OK了

14.最后说下环境变量的设置问题,很多问题都是因为环境变量没有设置好: