[Golang] Go Mod My-Practice

Go在1.11版本后开始支持modules,以go mod来实现包管理,那么该如何使用呢?

UPDATED 2020/04/10:

  • 增加GOPROXY设置,应对可能存在的网络连接问题
  • 增加仓库发布实践操作部分,有效避坑指南
  • 调整了文章结构

Before

本文前提你已经有了如下环境:

  • VS Code
  • Golang v1.11 or later
  • 已正确设置GOROOT GOPATH
  • Git

Quick Start

如果需要,可以直接查看官方文档,体验更佳。


开启Go Mod

不说废话,直接开始,首先需要开启Go mod。

打开你的终端并执行

1
2
3
4
$ go env -w GO111MODULE=on

# 以下指令视自己情况开启(fq)
$ go env -w GOPROXY=https://goproxy.cn,direct

完成。

打开你的终端并执行

1
2
3
4
$ export GO111MODULE=on

# 以下指令视自己情况开启(fq)
$ export GOPROXY=https://goproxy.cn

或者

1
2
3
$ echo "export GO111MODULE=on" >> ~/.profile
$ echo "export GOPROXY=https://goproxy.cn" >> ~/.profile # 该指令视自己情况开启(fq)
$ source ~/.profile
1
2
3
go env -w GO111MODULE=on
// or
export GO111MODULE=auto
  • Windows

打开你的 PowerShell 并执行

1
2
C:\> $env:GO111MODULE = "on"
C:\> $env:GOPROXY = "https://goproxy.cn"

初试Go Mod

开启go mod后,在GOPATH目录之外的地方创建项目文件,使用VCS (可选)

1
2
3
4
$ mkdir -p /tmp/scratchpad/repo
$ cd /tmp/scratchpad/repo
$ git init -q
$ git remote add origin https://github.com/my/repo

初始化module

1
2
3
$ go mod init github.com/my/repo

go: creating new go.mod: module github.com/my/repo

demo:

1
2
3
4
5
6
7
8
9
10
11
12
$ cat <<EOF > hello.go
package main

import (
"fmt"
"rsc.io/quote"
)

func main() {
fmt.Println(quote.Hello())
}
EOF

之后使用第三方库,都不再需要使用go get了,build的同时会自动将需要的包导入:

1
2
3
4
$ go build -o hello
$ ./hello

Hello, world.

go.mod:

1
2
3
4
5
$ cat go.mod

module github.com/my/repo

require rsc.io/quote v1.5.2

日常使用

  • 在你的 .go 文件中加入需要的声明 (import)
  • 使用 go build 或者 go test 自动导入依赖,go.mod也会自动更新,并且包的校验文件 go.sum 也会一起更新
  • 当需要的时候,可以用如 go get foo@v1.1.3 , go get foo@master 来选择合适的依赖
Command Usage
go list -m all 查看直接或间接依赖库的最终版本
go list -u -m all 查看直接或间接依赖库的可用更新(minor和patch)
go get -u ./… or go get -u=path ./… 安装所有直接或间接依赖库的更新(minor和patch)
go build ./… or go test ./… build或test模块中的所有包
go mod tidy go.mod中清除不再使用的依赖
replace or gohack 使用通过fork、本地复制、解压等方法安装的
go mod vendor Optinal step to create a vendor directory

如何使用本地包

使用本地包:在 go.mod 中末行加入:

1
replace example.com/banana => example.com/hugebanana

更新 Go Module

Go Modules 更新:

Command Usage
go get -u 查看直接或间接依赖库的最终版本
go get -u ./… 查看直接或间接依赖库的可用更新(minor和patch)
go get -u -t 安装所有直接或间接依赖库的更新(minor和patch)
go get -u -t ./… build或test模块中的所有包
go get -u all 推荐,更新所有模块
  • 注意:除了 v0v1 外的主版本号必须显式地出现在模块路径的尾部
  • 注意:example.com/foo/barexample.com/foo/bar/v2 被视为两个完全不同的模块
  • go get -u 不会更新主版本号,需要手动指定

发布go module

前文中已经介绍了日常开发中,如何使用go mod,确实它也给我们带来了很多的便利。

但在发布自己包的过程中,确实存在一些小坑,接下来介绍如何“优雅”地发布自己的 go module.

以 github.com 为例,如何将代码发布到该平台上,并在本地调用 github 上自己发布的仓库?

发布第一个版本

构建一个项目,结构如下:

1
2
3
.
└── release-go
  └── hello.go

hello.go :

1
2
3
4
5
6
7
package pkg

import "fmt"

func Hello() {
fmt.Println("Hello go mod")
}

/release-go/路径下执行:go mod init github.com/fusidic/release-go

可以看到生成go.mod文件如下:

1
2
3
module github.com/fusidice/release-go

go 1.14

查看发布的库

如果你还没有修改代理 GOPROXY 的话,不妨打开 https://pkg.go.dev ,这是Go社区官方开源的Go软件包和模块的信息资源中心,在上面搜索自己的包 github.com/fusidic/release-go,你会发现什么都找不到!

其实在 go.dev/about 中已经说明了:

  • Making a request to proxy.golang.org for the module version, to any endpoint specified by the Module proxy protocol. For example:

https://proxy.golang.org/example.com/my/module/@v/v1.0.0.info

  • Downloading the package via the go command. For example:
1
GOPROXY=“https://proxy.golang.org GO111MODULE=on go get   example.com/my/module@v1.0.0

只有从 https://proxy.golang.org/ 中拉取过对应的仓库之后,仓库才会从 Module list 中缓存这个包,当然,这需要一定的时间。

所以当我们碰到无法检索到自己发布的包的时候,一个简单粗暴的方法就是:

1
$ go get -u example.com@package

这条命令会从代理仓库 (pkg.go.devgoproxy.cn ) 中拉取指定的包,如果代理仓库中暂时没有更新你的包,这条命令就会代理仓库更新它的缓存,从而达到“手动更新”的目的。


说来惭愧,笔者在使用过程中碰到一个很怪的问题,由于自己操作不慎,导致使用 go get -u 拉取包时会出现解析错误的情况,在修复这个问题之后。由于使用 go get -u 出现错误,导致 pkg.go.dev 上的包无法更新到包修复之后的状态。

如何解决?

  • 本地:git tag v1.0.0

  • 上传GitHub:git push origin v1.0.0

  • 通过请求更新 pkg.go.dev指定版本go get example.com@package@v1.0.0

通过指定版本,可以很快拉取更新。


语义化控制版本

值得注意的是,go官方使用语义化控制版本 Semantic Import Version ,是官方关于版本控制的最佳实践,go官方提供了两个方案针对大版本升级和 breaking changes

  • Major branch 即通过创建version分支和tag进行版本升级
  • Major subdirectory 即通过创建version子目录来区分不同版本

构建私有Go模块代理

你的代码永远只属于你自己,因此我们向你提供目前世界上最炫酷的自托管 Go 模块代理搭建方案。通过使用 Goproxy 这个极简主义项目,你可以在现有的任意 Web 服务中轻松地加入 Go 模块代理支持,要知道 goproxy.cn 就是基于它搭建的。

创建一个名为 goproxy.go 的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"net/http"
"os"

"github.com/goproxy/goproxy"
)

func main() {
g := goproxy.New()
g.GoBinEnv = append(
os.Environ(),
"GOPROXY=https://goproxy.cn,direct", // 使用 goproxy.cn 作为上游代理
"GOPRIVATE=git.example.com", // 解决私有模块的拉取问题(比如可以配置成公司内部的代码源)
)
http.ListenAndServe("localhost:8080", g)
}

并且运行它

1
$ go run goproxy.go

然后通过把 GOPROXY 设置为 http://localhost:8080 来试用它。另外,我们也建议你把 GO111MODULE 设置为 on

就这么简单,一个功能完备的 Go 模块代理就搭建成功了。事实上,你可以将 Goproxy 结合着你钟爱的 Web 框架一起使用,比如 GinEcho,你所需要做的只是多添加一条路由而已。更高级的用法请查看文档


LSP

如果您并非使用 VS Code 进行开发,那么以下内容对您帮助不大。

LSP (Language Server Protocol) 即语言服务器协议,目的是为了让不同的编辑器或集成开发环境方便使用各种程序语言,支持包括语法检查、自动补全、跳转、引用查询等功能。

将这些功能放入独立的进程中,可以同时对不同编辑器生效,避免了资源的浪费,同时也可以将语言服务器部署在服务器上,释放本地因扫描语言而带来的CPU负担。

LSP 安装

安装gopls:打开VS Code,command+,打开设置,搜索go.useLanguageServer勾选。

默认情况下,这时Go扩展就会自动提示你安装gopls,或者手动安装

1
$ go get golang.org/x/tools/gopls@latest

gopls会安装到GOPATH目录下的bin中,如果存在网络问题,可以将goproxy设置为goproxy.cn,是一个国内的大学生和七牛云合作提供的一个开源CDN,安全性自己判断了:

1
$ export GOPROXY=https://goproxy.cn

导入配置到settings.json中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"go.useLanguageServer": true,
"go.alternateTools": {
"go-langserver": "gopls"
},
"go.languageServerExperimentalFeatures": {
"format": true,
"autoComplete": true
},
"[go]": {
"editor.snippetSuggestions": "none",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
},
"gopls": {
"usePlaceholders": true,
"enhancedHover": true
}
}

Q1: 如何打开 settings.json ?

A1: command + , 右上角图标 open settings(JSON)

Q2: .vscode 是什么?

A2: 通常在你的项目文件下有一个 .vscode 的文件,里面包含了如系统路径、配置信息、调试参数等信息。

项目下的 .vscode 文件的作用域只包含着整个项目目录,所以当一台服务器同时支持多个用户进行远程开发的时候,每个用户都可以通过修改 .vscode/settings.json,来完成一些自定义(如字体、终端、环境配置等)。

更多配置可以参考:https://code.visualstudio.com/docs/getstarted/settings

(Optional) 开启调试信息,在settings中加入:

1
2
3
4
5
"go.languageServerFlags": [
"-rpc.trace", // for more detailed debug logging
"serve",
"--debug=localhost:6060", // to investigate memory usage, see profiles
],

Reference

  1. https://github.com/golang/go/wiki/Modules#how-to-install-and-activate-module-support
  2. https://go.dev/about
  3. https://juejin.im/post/5e4ccabf6fb9a07ca24f49d4 “如何优雅地发布go module模块”
  4. https://github.com/golang/go/wiki/Modules#how-to-prepare-for-a-release
  5. https://code.visualstudio.com/docs/getstarted/settings