Go 在 包依赖管理 和 缺少泛型 两个方面饱受诟病,它们也是 Go 粉们最希望 Go 核心 Team 重点完善的两个方面。
Go modules 是 go team 在解决包依赖管理方面的一次勇敢尝试,庆幸自己不用去学习 dep,vgo 之类的了。
2021.07.24 更新关于 Go 1.16 的内容
可以直接看 Release Note 或者这篇 Go 1.16 中关于 go get 和 go install 你需要注意的地方。
go build
和 go test
默认情况下不再修改 go.mod
和 go.sum
。可通过 go mod tidy
,go get
或者手动完成;
go install
命令可以接受一个版本后缀了,例如 go install sigs.k8s.io/kind@v0.9.0
,并且它是在模块感知的模式下运行,可忽略当前目录或上层目录的 go.mod
文件。这对于在不影响主模块依赖的情况下,安装二进制很方便;
- 在将来,
go install
被设计为用于构建和安装二进制文件,go get
则被设计为用于编辑 go.mod 变更依赖,并且使用时应该与 -d
参数共用,在将来版本中 -d
可能会默认启用;
- 基本上
go install <package>@<version>
是用于命令的全局安装:例如:go install sigs.k8s.io/kind@v0.9.0
go get
安装二进制的功能,后续版本将会删除;
go get
主要被设计为修改 go.mod
追加依赖之类的,但还存在类似 go mod tidy
之类的命令,所以使用频率可能不会很高;
GOPATH
先来厘清一下思路。$GOPATH
是所谓的工作目录,它下面有 3 个文件夹 bin, pkg, src
。
- bin: 如果你安装的包有 main 入口的话,
go install
之后放这里,是 2 进制文件。
- pkg: 如果你安装的包没有 main 入口的话,
go install
之后放这里。
- src: 基本上你的开发工作都在这里进行,用来放置源码。
所以,当安装完 Go 之后,所有教材都会推荐你先设置一下 $GOPATH
。
# A typical Go workspace
bin/
myapp # Executable binary
hello # Executable binary
pkg/
github.com/callicoder/example/
numbers.a # Package archive
strings.a # Package archive
github.com/gorilla/
mux.a # Package archive
go.uber.org/
zap.a # Package archive
src/
github.com/callicoder/example/ # Project repository
.git/
myapp/
app.go # Executable program containing main package and function
numbers/ # Go Package (contains utility functions for working with numbers)
prime.go
prime_test.go
strings/ # Go Package (contains utility functions for working with strings)
reverse.go
trim.go
github.com/gorilla/mux/ # 3rd Party package
#... package contents
go.uber.org/zap/ # 3rd Party package
#... package contents
hello/ # Local package (not published anywhere)
hello.go
# ... (more repositories and packages omitted) ...
Building / Installing
For commands which have package main
go build
builds the command and leaves the result in the current working directory.
go install
builds the command in a temporary directory then moves it to $GOPATH/bin.
For packages
go build
builds your package then discards the results.
go install
builds then installs the package in your $GOPATH/pkg directory.
go get
这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行 go install。
当你执行 go get github.com/boyter/scc/
后,会从 Github 克隆代码到你本地的 $GOPATH/src/github/boyter/scc/
目录。
参数介绍:
- -d 只下载不安装
- -u 强制使用网络去更新包和它的依赖包
- -v 显示操作流程的日志及信息,方便检查错误
- -f 只有在你包含了 -u 参数的时候才有效,不让 -u 去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用
- -fix 在获取源码之后先运行 fix,然后再去做其他的事情
- -t 同时也下载需要为运行测试所需要的包
go get 和 go install 的区别
go get
执行:download、compile、install
go install
执行:compile、install
推荐使用 go get -d
和 go install
为什么要存在 go install 呢,使用 go get 不是够用了么?
因为 go get
第一步就是 download 远程的库,如果就就想要使用本地的版本,go get
是办不到的,所以如果你不需要 download 你需要就可以使用 go install
。
更多相关可以阅读 https://stackoverflow.com/questions/24878737/what-is-the-difference-between-go-get-and-go-install
VSCode 和 GOPATH
- 首次安装 vscode-go 插件后,会自动使用 go env 环境变量 GOPATH 设置的路径。
- 在 VSCode 的 User settings 中,设置
go.gopath
会覆盖步骤 1 的设置。
- 在 VSCode 的 Workspace settings 中,设置
go.gopath
会覆盖步骤 2 的设置。
go.inferGopath
如何设为 true,会覆盖 go.gopath
设置,VSCode 会根据当前打开的目录作为工作区去推断 GOPATH
- 其方式为:向上搜索打开的文件夹,一旦发现 src 就将其上层目录设置为
GOPATH
- 例如:你打开了目录
/aaa/bbb/ccc/src
,那么 GOPATH
会被自动设置为 GOPATH=/aaa/bbb/ccc
- 通常
go get
会把代码安装在 GOPATH 对应的目录中,也可以通过修改插件的 go.toolsGopath
设置一个专门的目录,不过设置完之后别忘了执行 Go: Install Tools
go.toolsGopath
如果没设置的话,默认还会放根据 1 - 4 步骤中设置的路径来存放文件
最后 debug 工具无法使用上面步骤的设置项,它只认全局 GOPATH,如果你没有设置 GOPATH(不可能),它会按照步骤4的方式去推测。
结论
建议 VSCode 只设置自动回溯,找到合适的路径。
{
"go.inferGopath": true
}
使用 go modules 后,GOPATH 是废弃的。这里这么定义,是为了 VSCode F12 跳转指定代码。VSCode 的一些插件还是需要 GOPATH 的。
Go Modules
好处
- 不用再定义 GOPATH,这里指的是 go build 、 go install 等等 go 命令。IDE 插件目前还是需要 GOPATH。
- 工程目录不再需要放置 src 目录下
- 不再需要 vendor 机制以及其他第 3 方 dep 工具
- 工程内不再有依赖库代码
简单总结:代码可以随意放置,执行 Go 命令,不再需要指定 GOPATH。
GO111MODULE
Go Modules 是否启用目前由环境变量 GO111MODULE 控制:
- off - 则 go 命令从不使用新模块支持。它查找vendor 目录和 GOPATH 以查找依赖关系,也就是继续使用 GOPATH模式。
- on - 则 go 命令需要使用模块,go 会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod 下载依赖。
- auto 或未设置 - 则 go 命令根据当前目录启用或禁用模块支持。仅当当前目录位于
GOPATH/src
之外并且其本身包含 go.mod 文件或位于包含 go.mod 文件的目录下时,才启用模块支持。
基础用法
myproj
├── main.go
└── pkg
└── myapi
└── data
└── api.go
- Creating a new module - 进入项目目录 myproj,执行
go mod init myproj
会在当前目录下创建一个 go.mod 文件。
- Adding a dependency
- 方法1: 有了模块定义,然后执行
go mod tidy
会自动生成依赖,填充 go.mod, go.sum 文件。
- 方法2: 修改源码用 import 引入模块 然后执行
go build, go test
等命令,会更新 go.mod, go.sum 文件。
- Upgrading dependencies -
go list -m all
查看当前项目正在使用的 package 版本,然后执行 go get rsc.io/sampler
来更新指定的 package 再执行 go test
会自动更新 go.mod 文件。
跟 Gemfile 一样,你可以在 go.mod 指定依赖库的版本。如果没有指定,go 命令会自动下载代码中的依赖的最新版本。
如果依赖包地址更换了咋办
在 go.mod 文件里用 replace 替换包
replace golang.org/x/text => github.com/golang/text latest
init 生成的 go.mod 的模块名称有什么用
主要是方便其它根目录的包通过 模块名+路径 的方式来引用。
例如,在项目下新建目录 utils,创建一个 tools.go 文件:
package utils
import "fmt"
func PrintText(text string) {
fmt.Println(text)
}
在根目录下的 hello.go 文件就可以 import "hello/utils"
引用 utils
package main
import (
"hello/utils"
"github.com/astaxie/beego"
)
func main() {
utils.PrintText("Hi")
beego.Run()
}
go.mod 文件一旦创建后,它的内容将会被 go toolchain 全面掌控。go toolchain 会在各类命令执行时,比如 go get、go build、go mod 等修改和维护 go.mod 文件。
升级依赖项
>$ go list -m -versions github.com/gin-gonic/gin
// 将会列出 Gin 版本历史
github.com/gin-gonic/gin v1.1.1 v1.1.2 v1.1.3 v1.1.4 v1.3.0
将版本更新到上个版本,这里只是个演示。
>$ go get github.com/gin-gonic/gin@v1.1.4 // 只需要在依赖后面加上 @version 就可以了
>$ go list -m all
// 看到了版本变化
github.com/gin-gonic/gin v1.1.4
或者可以使用 go mod
来进行版本的切换,这样就需要两个步骤了:
>$ go mod edit -require="github.com/gin-gonic/gin@v1.1.4" // 修改 go.mod 文件
>$ go mod tidy //下载更新依赖
常用命令列表
go mod init
初始化模块,例如 go mod init github.com/wjp2013/hello
go build, go test
和其它构建命令会自动为 go.mod 添加新的依赖
go get
改依赖关系的所需版本(或添加新的依赖关系)
go list -m all
列出当前模块及其所有依赖项
go mod tidy
拉取缺少的模块,移除不用的模块
不常用命令
go mod download
下载依赖包到本地 cache
go mod edit
编辑 go.mod 文件,选项有 -json、-require 和 -exclude,可以使用帮助 go help mod edit
go mod graph
打印模块依赖图
go mod vendor
将依赖复制到 vendor 目录
go mod verify
验证依赖是否正确
go mod why
解释为什么需要依赖
相关文章
GOPATH
module