1. 基础概念篇Go Module已经来了,默认Go Module模式将会在1.13版本发布。也就是说半年后,就会全面铺开。鉴于官方提供扫盲文档中的样例过于简单,提供一个更加贴近实际开发过程的例子也许是有必要的。官方文档参考:Go Module Wiki。1.1 准备环境按照官方的说明,Go Module是在 Go 的 1.11版本开始引入,但是默认该选项是关闭的,直到1.13版本将会默认开启。预计1.13将会在2019年8月份发布。所以在这之前,是必须手动开启Go Module支持。必要条件:Go语言版本 >= 1.11设置环境变量 GO111MODULE=on在开启Go Module功能后,官方还提供了环境变量GOPROXY用于设置包镜像服务。此处暂不详细介绍了。1.2 Go Module带来的改变1.2.1 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路径的功能依旧保持。1.2.2 新增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即可。或者方法二:创建foo路径,位置任意进入foo目录,执行go mod init github.com/liujianping/foo即可。生成了go.mod文件后,就该文件的语法简单的学习一下。moduleto define the module path;go to set the expected language version;requireto require a particular module at a given version or later;excludeto exclude a particular module version from use; andreplaceto replace a module version with a different module version.官方提供了一个简单全面的例子:module my/thinggo 1.12require other/thing v1.0.2require new/thing/v2 v2.3.4exclude old/thing v1.2.3replace bad/thing v1.4.5 => good/thing v1.4.51.3 一个完整的例子官方的版本由于过于简单,连一个基础的本地第三方包的引入都没有,仅仅通过引入一个公开的第三方开源包,缺少了常规本地开发说明。所以,笔者特意提供一个完整的例子,分别从:本地仓库远程仓库私有仓库三个维度阐释Go Module的在实际开发中的具体应用。本例子的目录结构如下:$GOPATH/src├── github.com └── liujianping ├── demo │   └── go.mod └── foo └── go.mod创建两个mod模块:demo 与 foo, 其中 foo 作为一个依赖包,提供简单的 Greet 函数供 demo 项目调用。1.3.1 本地仓库本地仓库的意思,就是例子中的两个包: 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.gopackage fooimport “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.gopackage mainimport ( “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.gobuild 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.modmodule 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/demorequire github.com/liujianping/foo v0.0.0replace github.com/liujianping/foo => ../foo再次执行demo程序:$: go run main.gogo: finding github.com/liujianping/foo v0.0.0GoModule, 你好!对于项目中直接引用本地依赖包的官方文档中有段注意事项: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.2Go Module最主要是引入了依赖包的版本控制。所以,我们不妨就本地版本测试一下。对本地版本foo进行相应的git本地版本控制,增加几个版本,代码中相应的增加版本信息。package fooimport “fmt"func Greet(name string) string { return fmt.Sprintf("%s, 你好! Version 1.0.0”, name)}增加了以下三个版本tag。$: git tagv0.1.0v0.2.0v1.0.0在demo项目中,设置foo版本, go.mod修改如下:module github.com/liujianping/demorequire github.com/liujianping/foo v0.1.0replace github.com/liujianping/foo => ../foo执行demo程序,输出如下:go run main.gogo: finding github.com/liujianping/foo v0.1.0GoModule, 你好! Version 1.0.0不难得出结论:go get是不会从本地仓库获取版本信息的,查看go get在module模式下工具链实现代码也可得出这个结论。1.3.2 远程仓库从上节可以大致了解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.modmodule github.com/liujianping/demo重新执行demo项目$: cd $GOPATH/src/github.com/liujianping/demo$: go run main.gogo: finding github.com/liujianping/foo v1.0.0go: downloading github.com/liujianping/foo v1.0.0GoModule, 你好! Version 1.0.0查看变更后的go.mod,如下$: cat go.modmodule github.com/liujianping/demorequire github.com/liujianping/foo v1.0.0 // indirect同时demo根目录下,增加了go.sum文件。cat go.sumgithub.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.modmodule github.com/liujianping/demorequire github.com/liujianping/foo v0.2.0 // indirect重新执行demo项目$: cd $GOPATH/src/github.com/liujianping/demo$: go run main.gogo: finding github.com/liujianping/foo v0.2.0go: downloading github.com/liujianping/foo v0.2.0GoModule, 你好! Version 0.2.0再看看go.sum文件发生的变化:cat go.sumgithub.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.modmodule github.com/liujianping/demorequire github.com/liujianping/foo v1.0.0 // indirect重新执行demo项目$: cd $GOPATH/src/github.com/liujianping/demo$: go run main.goGoModule, 你好! 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 allgo: finding github.com/liujianping/foo v1.0.1github.com/liujianping/demogithub.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.modmodule github.com/liujianping/demorequire github.com/liujianping/foo v1.0.1重新执行程序$: go run main.goGoModule, 你好! Version 1.0.1基于分支GoModule除了支持基于标签tag的版本控制,可以直接利用远程分支名称进行开发。所以本节,笔者就模块foo创建一个新的远程分支develop.具体代码,请直接参考github.com/liujianping/foo项目。修改demo项目的go.mod文件:module github.com/liujianping/demorequire github.com/liujianping/foo develop再次执行demo, 结果如下:$: go run main.gogo: finding github.com/liujianping/foo developgo: downloading github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446GoModule, 你好! Branch develop查看,go.mod文件,发生如下变更:$: cat go.modmodule github.com/liujianping/demorequire github.com/liujianping/foo v1.0.2-0.20190214080857-9c0018d55446按官方文档的说明,使用分支名,可以直接拉取该分支的最后一次提交。从实验来看, Go Module一旦发生编译就会针对分支名的依赖进行版本号固定。1.3.3 私有仓库鉴于整篇内容长度,私有仓库的部分将和Go Module 工程化实践(二):工程实践篇 一起讲解。(未完待续)