前言
创新互联在网站设计、网站制作、重庆APP开发公司、网站运营等方面均有出色的表现,凭借多年丰富的经验,我们会仔细了解各客户的需求而做出多方面的分析、设计、整合,为客户设计出具风格及创意性的商业解决方案,我们更提供一系列成都全网营销推广,网站制作和网站推广的服务,以推动各中小企业全面信息化,并利用创新技术帮助各行业提升企业形象和运营效率。在上文中,我们介绍了gopath
的含义、功能、优劣、以及如何通过GOPATH来组织项目
在本文中,我们将介绍go module
的原理和用法以试图能够回答下面的几个问题
是什么?
g
g
g
g
g
在go module
之前,有一些问题长期困扰go语言的开发人员
能否将go工程代码脱离gopath
之外
go1.11开始支持,go1.13全面支持的go modules
正是为了解决上面的问题诞生的,下面我们详细介绍go module
企图解决的问题
在介绍gopath
时,我们介绍了如果导入为
实际引用的是$GOPATH/src/github.com/gobuffalo/buffalo
文件中的代码。
也就是说,在gopath
中 ,导入路径与项目在文件系统中的目录结构和名称必须是匹配的。
那么能否import
路径为github.com/gobuffalo/buffalo
,但是项目实际的路径却是在另一个任意的文件目录中?(例如/users/gobuffalo/buffalo
).答案是肯定的,go module
通过在一个特殊的叫做go.mod
的文件中指定模块名来解决这一问题。
module .//
...
在go.mod文件的第一行指定了模块名,模块名表示开发人员可以用此来引用当前代码仓库中任何package
的路径名,以此来替代$gopath
的路径。从而,代码仓库在任何位置都已经没有关系,因为Go工具可以使用模块文件的位置和模块名来解析代码仓库中的任何内部import
。
对于任何版本控制(VCS)工具,我们都能在任何代码提交点打上"tag"标记,如下所示:
更棘手的是,一个第三方包A可能引用了其他的第三方包B,因此还必须把第三方包A的全部依赖下载
因此,只通过gopath
维护单一的master包的方式是远远不够的,因为依赖包的最新代码不一定与项目兼容。尽管go社区已经针对以上问题提供了一些解决方案(例如dep,godep,glide等)但是go官方的go moudle
提供了一种集成解决方案,通过在文件中维护直接和间接依赖项的版本列表来解决这一问题。通过将一个特定版本的依赖项看做是捆绑的不可变的依赖项,就叫做一个模块(moudle)
为了加快构建程序的速度并快速切换、获取项目中依赖项的更新,Go维护了下载到本地计算机上的所有模块的缓存,缓存目前默认位于$GOPATH/pkg
目录中。有go的提议希望能够自定义缓存的位置。
所在位置看上去如下所示:
go/
├── bin
├── pkg
├── darwin_amd64
└──
└── src
在mod目录下,我们能够看到模块名路径中的第一部分用作了模块缓存中的顶级文件夹
~/go/pkg/ » ls -l jackson@
drwxr-xr-x jackson staff : cache
drwxr-xr-x jackson staff : cloud.google.com
drwxr-xr-x jackson staff : git.apache.org
drwxr-xr-x jackson staff : github.com
drwxr-xr-x jackson staff : gitlab.followme.com
drwxr-xr-x jackson staff : go.etcd.
...
当我们打开一个实际的模块,例如github.com/nats-io
,我们会看到与nats库有关许多模块
~-io jackson@192
total
dr-x------ jackson staff gnatsd@v1.
dr-x------ jackson staff go-nats-streaming@v0.
dr-x------ jackson staff go-nats@v1.
dr-x------ jackson staff go-nats@v1.
...
为了拥有一个干净的工作环境,我们可以用如下代码清空缓存区。但是请注意,在正常的工作流程中,是不需要执行如下代码的。
$ go clean -modcache
我们从GOPATH
外开始一个新的项目讲解,新建一个新建夹以及一个main
文件
cd
cd mathlib jackson@192
~dreamerjackson/mathlib
go mod init
指令的功能很简单,自动生成一个go.mod
文件 后面紧跟的路径即是自定义的模块名。习惯上以托管代码仓库的URL为模块名(代码将会放置在https://github.com/dreamerjackson/mathlib
下)
go.mod
文件 位于项目的根目录下,内容如下所示,第一行即为模块名。
module .//
go
main
func {
}
我们在代码片段中导入了为了讲解go moudle
而特地的引入的packagegithub.com/dreamerjackson/mydiv
,其进行简单的除法操作,同时又引入了另一个包github.com/pkg/errors
。其代码如下图所示:
如下图所示,在goland中我们可以看到导入的package 是红色的,因为此时在go module的缓存并不能找到此package。
为了能够将此package下载到本地,我们可以使用go mod tidy
指令
$ mod tidy
: finding github.com/dreamerjackson/mydiv latest
: downloading github.com/dreamerjackson/mydiv v0-fdd187670161
: extracting github.com/dreamerjackson/mydiv v0-fdd187670161
同时我们在go.mod
中能够看到新增加了一行用于表示我们引用的依赖关系
module .//
go
github.com/dreamerjackson/mydiv v..--fdd187670161
注意在这里间接的依赖(即github.com/dreamerjackson/mydiv
依赖的github.com/pkg/errors
)并没有也没有必要在go.mod
文件展示出来,而是出现在了一个自动生成的新的文件go.sum
中.
## .sum
github.com/dreamerjackson/mydiv v0-fdd187670161 h2:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0-fdd187670161/.mod h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0 h2:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0/.mod h2:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
main
(
)
func {
res,_ :=mydiv.Div(,)
fmt.Println(res)
}
运行go run
命令后,即会为我们输出除法结果2
有多种方式可以实现依赖模块的更新,在go.mod
文件中修改版本号为:
github.com/dreamerjackson/mydiv latest
或者
github.com/dreamerjackson/mydiv master
获取复制commitId 到最后
github.com/dreamerjackson/mydiv c9a7ffa8112626ba6c85619d7fd98122dd49f850
还有一种办法是在终端当前项目中,运行go get
go github.com/dreamerjackson/mydiv
上述几种方式在保存文件后,再次运行go mod tidy
即会进行更新
此时如果我们再次打开go.sum
文件会发现,go.sum
中不仅仅存储了直接和间接的依赖,还会存储过去的版本信息。
github.com/dreamerjackson/mydiv v0-fdd187670161 h2:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0-fdd187670161/go. h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/dreamerjackson/mydiv v0-c9a7ffa81126/go. h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0 h2:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0/go. h2:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
当我们不想在使用此第三方包时,可以直接在代码中删除无用的代码,接着执行
$ go mod tidy
会发现go.mod
与go.sum
一切又都空空如也~
Go决定采用其他方法,Russ Cox花费了大量时间和精力撰写和谈论 Go团队的版本选择方法,即最小版本选择(Minimal Version Selection,MVS)。从本质上讲,Go团队相信MVS可以为Go程序提供最佳的机会,以实现兼容性和可重复性。我建议阅读这篇文章,以了解Go团队为什么相信这一点。
举一个简单的例子,假设现在项目github.com/dreamerjackson/mydiv
的最新版本为v1.0.2
,可通过下面指令查看所有
> go -m -versions github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mydiv v1 v1 v1 v1
假设现在有两个模块A、B,都依赖模块D。其中
> ,
>
如果我们的当前项目只依赖A,这个时候go module
会如何选择呢?像dep这样的依赖工具将选择v1.0.3,即最新的语义版本控制。但是在go module
中,最小版本选择原理将遵循A项目声明的版本,即v1.0.1
为了验证最小版本选择原理,作者呕心沥血设计了一个简单的示例。
以项目github.com/dreamerjackson/mydiv
为例,读者可以将其看做上节中的模块D
,其v1.0.1
与v1.0.2
版本的代码如下,只是简单的改变了错误返回的字符串。
## v1
mydiv
func int,b ) int,error){
b=={
,errors.Errorf()
}
a/b,
}
## v1
mydiv
func int,b ) int,error){
b=={
,errors.Errorf()
}
a/b,
}
接着模块B
即github.com/dreamerjackson/minidiv
引用了模块D即github.com/dreamerjackson/mydiv
v1.0.1版本
## 模块B
div
(
)
func int,b ) int,error){
mydiv.Div(a,b)
}
最后当前的项目,我们将其称为模块Now
直接依赖了模块D v1.0.2,同时依赖了模块B
main
(
div
)
func {
_,err1:= mydiv.Div(,)
_,err2 := div.Div(,)
fmt.Println(err1,err2)
}
当前的依赖关系如下:
当前模块 > 模块
当前模块 > 模块 > 模块
$ run main.
v1 b cant =
第二种方式是使用go list
指令
~/mathlib » go -m all | grep mydiv
github.com/dreamerjackson/mydiv v1
我们还可以通过使用go mod mhy
z指令,查看在哪里引用了包github.com/dreamerjackson/mydiv
~/mathlib » go why github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/mydiv
我们可以使用go list -m -u all
指令查看直接和间接模块的当前和最新版本
~/mathlib » list -m -u all | column -t jackson@
: finding github.com/dreamerjackson/minidiv latest
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/minidiv v0-fcd15cf402bb
github.com/dreamerjackson/mydiv v1 [v1]
github.com/pkg/errors v0
如上所示,我们可以看到github.com/dreamerjackson/mydiv
的当前版本为v1.0.2
,但是最新的版本为v1.0.3
获取直接和间接模块可以使用go get
指令。其中有不少的参数。
下面命令以最小版本原则
更新所有的直接和间接模块
go -t -d -v ./...
-t
考虑构建测试所需的模块
-d
下载每个模块的源代码
-v
提供详细输出
./…
在整个源代码树中执行这些操作,并且仅更新所需的依赖项
注意,除非你了解项目的所有细节,否则慎用全部的大最新版本的更新
如果go get中使用-u
参数会用大最新版本
原则更新所有的直接和间接模块
~/mathlib » get -u -t -d -v ./... jackson@
: finding github.com/dreamerjackson/minidiv latest
: downloading github.com/dreamerjackson/mydiv v1
: extracting github.com/dreamerjackson/mydiv v1
接着我们可以再次查看当前引用的版本,我们会发现模块github.com/dreamerjackson/mydiv
已经强制更新到了最新的v1.0.3
~/mathlib » go -m all | grep mydiv jackson@
github.com/dreamerjackson/mydiv v1
如果您不满意所选的模块和版本,则始终可以通过删除go.mod go.sum
模块文件并再次运行go mod tidy来重置。当项目还不太成熟时这是一种选择。
$ rm go.*
$ go mod init
$ go mod tidy
Go模块引入了一种新的导入路径语法,即语义导入版本控制。每个语义版本均采用vMAJOR.MINOR.PATCH的形式。
因此我们在上面的实例中可以看到,go预料到v1.0.3与v1.0.1是兼容的,因为他们有相同的主版本号1
v2.0.0
如上图实例显示了go对于版本更新的处理。my/thing/v2
标识特定模块的语义主版本2
。版本1是my/thing
,模块路径中没有明确的版本。但是,当您引入主要版本2或更大版本时,必须在模块名称后添加版本,以区别于版本1和其他主要版本,因此版本2为my/thing/v2,版本3为my/thing/v3,依此类推。
> 模块 > 模块
> 模块 > 模块
首先我们给mydiv打一个v2.0.0的tag,其代码如下,简单修改了错误文字v2.0.0 b can't = 0
mydiv
func int,b ) int,error){
b=={
,errors.Errorf()
}
a/b,
}
module .///
main
(
div
mydiv
)
func {
_,err1:= mydiv.Div(,)
_,err2 := div.Div(,)
fmt.Println(err1,err2)
}
mathlib --> 直接引用mydiv v2
mathlib --> 直接引用minidiv --> 间接引用mydiv v1
当我们运行代码之后,会发现两段代码是共存的
v2.0.0 b cant = 0
接着执行go list
,模块共存,验证成功~
~/mathlib(master*) » go -m all | grep mydiv
github.com/dreamerjackson/mydiv v1
github.com/dreamerjackson/mydiv/v2 v2
模块镜像于2019年八月推出,是go官方1.13版本的默认系统。模块镜像是一个代理服务器,以帮助加快构建本地应用程序所需的模块的获取。代理服务器实现了基于REST的API,并根据Go工具的需求进行了设计。
模块镜像将会缓存已请求的模块及其特定版本,从而可以更快地检索将来的请求。一旦代码被获取并缓存在模块镜像中,就可以将其快速提供给世界各地的用户。
checksum数据库也于2019八月推出,是可以用来防止模块完整性、有效性的手段。它验证特定版本的任何给定模块代码的正确性,而不管何人何时何地以及是如何获取的。Google拥有唯一的校验和数据库,但是可以通过私有模块镜像对其进行缓存。
有几个环境变量可以控制与模块镜像和checksum数据库有关的行为
GOPROXY:一组指向模块镜像的URL,用于获取模块。如果您希望Go工具仅直接从VCS地址获取模块,则可以将其设置为direct
。如果将此设置为off
,则将不会下载模块
GOSUMDB:用于验证给定模块/版本的代码的checksum数据库地址。此地址用于形成一个适当的URL,该URL告诉Go工具在哪里执行这些checksum校验和查找。该URL可以指向Google拥有的checksum数据库,也可以指向支持对checksum数据库进行缓存或代理的本地模块镜像。如果您不希望Go工具验证添加到go.sum文件中的给定模块/版本的哈希码,也可以将其设置为off,仅在将任何新module添加到go.sum文件之时,才查询checksum数据库
GOPRIVATE:一个方便变量,用于将GONOPROXY和GONOSUMDB设置为相同的默认值
我们可以通过go env
来查看到这些默认值
$ go env
GONOPROXY=
GONOSUMDB=
GOPRIVATE=
GOPROXY=
GOSUMDB=
例如,如果我们需要访问所有代理服务器,例如需要权限的位于gitlab
等地的代码,我们可以使用export GOPRIVATE=gitlab.XXX.com,gitlab.XXX-XX.com,XXX.io
多个域名用逗号分隔。
Athens是一个私有模块镜像,可以用于搭建私有模块镜像。使用私有模块镜像的一个原因是允许缓存公共模块镜像无法访问的私有模块。Athens项目提供了一个在Docker Hub
上发布的Docker
容器,因此不需要特殊的安装。
run -p gomods/athens:latest
接下来,启动一个新的终端会话以运行Athens,为大家演示其用法。启动Athens服务并通过额外的参数调试日志(请确保系统已经安装并启动了docker)并有科学*上网的环境
$ docker run -p -e ATHENS_LOG_LEVEL=debug -e GO_ENV=development gomods/latest
INFO[AM]: Exporter specified. Traces won2020-03-06 07:11:30.671249 I | Starting application at port :3000
接着我们修改GOPROXY参数,指向本地3000
端口,初始化我们之前的项目,再次执行go mod tidy
export GOPROXY=
在Athens日志中即可查看对应信息
INFO[AM]: incoming request http-method=GET http-path=@v/list http-status=
INFO[AM]: incoming request http-method=GET http-path=@v/list http-status=
INFO[AM]: incoming request http-method=GET http-path=@latest http-status=
提供脱离gopath
管理go代码的优势
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。