人的一切痛苦,本質上都是對本身的無能的憤怒。 -- 王小波
Go Module
已经来了,默认Go Module
模式将会在1.13版本发布。也就是说半年后,就会全面铺开。鉴于官方提供扫盲文档中的样例过于简单,提供一个更加贴近实际开发过程的例子也许是有必要的。git
官方文档参考:Go Module Wiki。github
按照官方的说明,Go Module
是在 Go 的 1.11
版本开始引入,可是默认该选项是关闭的,直到1.13
版本将会默认开启。预计1.13
将会在2019年8月份发布。因此在这以前,是必须手动开启Go Module
支持。golang
必要条件:算法
GO111MODULE=on
在开启Go Module
功能后,官方还提供了环境变量GOPROXY
用于设置包镜像服务。此处暂不详细介绍了。缓存
Go Module
带来的改变GOPATH
做用的改变 引入Go Module
后,环境变量GOPATH
仍是存在的。开启Go Module
功能开关后,环境变量GOPATH
的做用也发生了改变。函数
When using modules, GOPATH is no longer used for resolving imports. However, it is still used to store downloaded source code (in GOPATH/pkg/mod) and compiled commands (in GOPATH/bin).
翻译出来就是:工具
GOPATH
再也不用于解析imports
包路径,即原有的GOPATH/src/
下的包,经过import
是找不到了。Go Module
功能开启后,下载的包将存放与$GOPATH/pkg/mod
路径$GOPATH/bin
路径的功能依旧保持。go.mod
文件配置 开始Go Module
开发以前,首先是初始化一个正确的Go Module
定义,即go.mod
文件。
何为正确的Go Module
定义。就是说mod
包必须符合。学习
方法一:测试
- 在
$GOPATH/src
的目录下,建立合理的路径github.com/liujianping/foo
路径。- 进入
$GOPATH/src/github.com/liujianping/foo
路径,执行go mod init
便可。
或者ui
方法二:
- 建立foo路径,位置任意
- 进入foo目录,执行
go mod init github.com/liujianping/foo
便可。
生成了go.mod
文件后,就该文件的语法简单的学习一下。
官方提供了一个简单全面的例子:
module my/thing go 1.12 require other/thing v1.0.2 require new/thing/v2 v2.3.4 exclude old/thing v1.2.3 replace bad/thing v1.4.5 => good/thing v1.4.5
go get
流程改变 引入Go Module
以后,go get
官方又从新实现了一套。具体实现代码能够参考:
Go Module
功能,go get
代码实现$GOROOT/src/cmd/go/internal/get/get.go
Go Module
功能,go get
代码实现$GOROOT/src/cmd/go/internal/modget/get.go
简单说明一下主要区别,更详细的go get
取包原理放到下篇讲解。最直接的区别是:
- 老的
go get
取包过程相似:git clone
+go install
, 开启Go Module
功能后go get
就只有git clone
或者download
过程了。- 新老实现还有一个不一样是,二者存包的位置不一样。前者,存放在
$GOPATH/src
目录下;后者,存放在$GOPATH/pkg/mod
目录下。- 老的
go get
取完主包后,会对其repo
下的submodule
进行循环拉取。新的go get
再也不支持submodule
子模块拉取。
官方的版本因为过于简单,连一个基础的本地第三方包的引入都没有,仅仅经过引入一个公开的第三方开源包,缺乏了常规本地开发说明。因此,笔者特地提供一个完整的例子,分别从:
三个维度阐释Go Module
的在实际开发中的具体应用。
本例子的目录结构以下:
$GOPATH/src ├── github.com └── liujianping ├── demo │ └── go.mod └── foo └── go.mod
建立两个mod
模块:demo
与 foo
, 其中 foo
做为一个依赖包,提供简单的 Greet
函数供 demo
项目调用。
本地仓库的意思,就是例子中的两个包: github.com/liujianping/demo
与 github.com/liujianping/foo
暂时仅仅存在于本地。没法经过 go get
直接从github.com
上获取。
经过如下命令,简单的建立项目代码:
$: mkdir -p $GOPATH/src/github.com/liujianping/foo $: cd $GOPATH/src/github.com/liujianping/foo $: go mod init $: cat <<EOF > foo.go package foo import "fmt" func Greet(name string) string { return fmt.Sprintf("%s, 你好!", name) } EOF $: mkdir -p $GOPATH/src/github.com/liujianping/demo $: cd $GOPATH/src/github.com/liujianping/demo $: go mod init $: cat <<EOF > main.go package main import ( "fmt" "github.com/liujianping/foo" ) func main(){ fmt.Println(foo.Greet("GoModule")) } EOF
执行完以上命令之后,mod demo
与 foo
的代码部分就完成了。如今来执行如下:
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go build github.com/liujianping/demo: cannot find module for path github.com/liujianping/foo
从输出能够看出,在demo
中调用 foo
的依赖包,在编译过程就失败了。demo
没法找到github.com/liujianping/foo
。为何这样?
按照传统的$GOPATH
引入包原则,只要在$GOPATH/src
存在相应路径的包,就能够完成编译了。从如今的情形就能够解释$GOPATH
在Go Module
功能开启后,对原有引入包的规则发生的改变。
既然,$GOPATH/src
路径再也不支持。那么如何解决这个没法找到包依赖的问题呢?方法有二:
该小节提供本地路径
方法。
$: cat $GOPATH/src/github.com/liujianping/demo/go.mod module github.com/liujianping/demo
目前demo
项目的go.mod
仅仅一句话,由于没法找github.com/liujianping/foo
,因此在go build
过程当中也不会修改go.mod
,增长对包github.com/liujianping/foo
的依赖关系。因此,只能是手动处理了。修改go.mod
文件以下:
module github.com/liujianping/demo require github.com/liujianping/foo v0.0.0 replace github.com/liujianping/foo => ../foo
再次执行demo程序:
$: go run main.go go: finding github.com/liujianping/foo v0.0.0 GoModule, 你好!
对于项目中直接引用本地依赖包的官方文档中有段注意事项:
Note: for direct dependencies, a require directive is needed even when doing a replace. For example, if foo is a direct dependency, you cannot do replace foo => ../foo without a corresponding require for foo. (If you are not sure what version to use in the require directive, you can often use v0.0.0 such as require foo v0.0.0; see #26241).
意思就是,即便是本地依赖包,明确的require
仍然是须要的。至于版本号,其实只要符合SemVer规范就能够。能够是v0.0.0
,也能够是v0.1.2
Go Module
最主要是引入了依赖包的版本控制。因此,咱们不妨就本地版本测试一下。
对本地版本foo
进行相应的git本地版本控制,增长几个版本,代码中相应的增长版本信息。
package foo import "fmt" func Greet(name string) string { return fmt.Sprintf("%s, 你好! Version 1.0.0", name) }
增长了如下三个版本tag。
$: git tag v0.1.0 v0.2.0 v1.0.0
在demo
项目中,设置foo
版本, go.mod
修改以下:
module github.com/liujianping/demo require github.com/liujianping/foo v0.1.0 replace github.com/liujianping/foo => ../foo
执行demo
程序,输出以下:
go run main.go go: finding github.com/liujianping/foo v0.1.0 GoModule, 你好! Version 1.0.0
不可贵出结论:go get
是不会从本地仓库获取版本信息的,查看go get
在module模式下工具链实现代码也可得出这个结论。
从上节能够大体了解Go Module
的原理。如今咱们将foo
依赖包上传到github.com
上,包括相应的版本tag。首先github.com
建立相应的项目foo
.再将本地仓库上传到远程仓库中。
$: git remote add origin git@github.com:liujianping/foo.git $: git push -u origin master
上传版本tag信息:
$: git push origin --tags
如今完成了github.com/liujianping/foo
依赖包的远程部署。看看具体实操demo
项目,首先去掉本地的直接依赖。demo
项目的go.mod
以下
$: cat $GOPATH/src/github.com/liujianping/demo/go.mod module github.com/liujianping/demo
从新执行demo
项目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go go: finding github.com/liujianping/foo v1.0.0 go: downloading github.com/liujianping/foo v1.0.0 GoModule, 你好! Version 1.0.0
查看变动后的go.mod
,以下
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.0 // indirect
同时demo
根目录下,增长了go.sum
文件。
cat go.sum github.com/liujianping/foo v1.0.0 h1:yYoUzvOwC1g+4mXgSEloF187GmEpjKAHEmkApDwvOVQ= github.com/liujianping/foo v1.0.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic=
修改foo版本号到 v0.2.0
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v0.2.0 // indirect
从新执行demo
项目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go go: finding github.com/liujianping/foo v0.2.0 go: downloading github.com/liujianping/foo v0.2.0 GoModule, 你好! Version 0.2.0
再看看go.sum
文件发生的变化:
cat go.sum github.com/liujianping/foo v0.2.0 h1:2JCV7mfUyneSksnWokX0kZoBbtWPoyL8s8iW80WHl/A= github.com/liujianping/foo v0.2.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic= github.com/liujianping/foo v1.0.0 h1:yYoUzvOwC1g+4mXgSEloF187GmEpjKAHEmkApDwvOVQ= github.com/liujianping/foo v1.0.0/go.mod h1:HKRu+NgbfULQV4mqZOnCXpF9IwlhOOIwmns7gpwjZic=
经过以上步骤,粗略能够了解针对Go Module
对于远程仓库的版本选择。简单解释版本的选择过程下:
- 检查远程仓库最新的tag版本,有就取得该版本
- 远程仓库没有tag版本时,直接获取master分支的HEAD版本
- 若是在
go.mod
文件中指定了具体版本,go get
直接获取该指定版本go.mod
中除了能够指定具体版本号之外,还支持分支名
继续对远程版本foo
增长新的版本v1.0.1
。提交相应代码并推送版本标签v1.0.1
到远端。并从新设置demo
项目中的go.mod
中的依赖版本为v1.0.0
.以下:
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.0 // indirect
从新执行demo
项目
$: cd $GOPATH/src/github.com/liujianping/demo $: go run main.go GoModule, 你好! Version 1.0.0
此次执行没有输出go自己的提示信息,而是直接输出告终果。由于github.com/liujianping/foo v1.0.0
已经存在于本地的缓存中了,不妨查看一下。
$: ls $GOPATH/pkg/mod/github.com/liujianping/foo@v1.0.0
虽然就demo
项目而言,依赖项目foo
有两个v1.0.0
与v1.0.1
两个版本可用。按照GoModule
版本选择最小版本的算法,demo
项目依旧选择v1.0.0
版本。
如何更新依赖包版本
更新依赖包的版本,最简单的方式,直接手动编辑go.mod
设置依赖包版本便可。
另一种方式就是经过go get -u
的方式进行自动更新。具体操做步骤以下:
查看依赖包版本更新信息
$: go list -u -m all go: finding github.com/liujianping/foo v1.0.1 github.com/liujianping/demo github.com/liujianping/foo v1.0.0 [v1.0.1]
更新依赖包版本
$: go get -u go: downloading github.com/liujianping/foo v1.0.1
或者,制定更新patch版本
$: go get -u=patch github.com/liujianping/foo go: downloading github.com/liujianping/foo v1.0.1
此时,go.mod
文件即被更新
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.1
从新执行程序
$: go run main.go GoModule, 你好! Version 1.0.1
基于分支
GoModule
除了支持基于标签tag
的版本控制,能够直接利用远程分支名称进行开发。
因此本节,笔者就模块foo
建立一个新的远程分支develop
.具体代码,请直接参考github.com/liujianping/foo
项目。
修改demo
项目的go.mod
文件:
module github.com/liujianping/demo require github.com/liujianping/foo develop
再次执行demo
, 结果以下:
$: go run main.go go: finding github.com/liujianping/foo develop go: downloading github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446 GoModule, 你好! Branch develop
查看,go.mod
文件,发生以下变动:
$: cat go.mod module github.com/liujianping/demo require github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446
按官方文档的说明,使用分支名,能够直接拉取该分支的最后一次提交。从实验来看, Go Module
一旦发生编译就会针对分支名的依赖进行版本号固定。
对于私有仓库而言,其原理与1.3.2中的远程仓库是相似的。惟一不一样之处是,go get
取包的过程可能存在种种障碍,致使没法经过go get
取到私有仓库包。主要缘由多是:
致使按照正常的go get
过程取包失败。若是了解了go get
取包原理,以上问题也就迎刃而解了。
更多文章可直接访问我的BLOG:GitDiG.com