【导读】本文总结了 go mod 的使用和开发中的规范与最佳实践。
最近由于换工作,开始交接工作。整理以前的工作内容,由于组内就我一个在做 go 和大数据。所以开发没有规划,当时是怎么快怎么来。go 也是使用最传统的 go path 的方式管理的。都是手动管理依赖的。现在交接给他人,需要多人开发,发现很多问题。比如版本问题,各种依赖的问题等等。
由于工作原因,几乎所有主流语言都写过。所以,对应语言包管理工具也都了解和使用过。我前面有写过 maven 的使用。maven 是使用过的功能最强大的包管理工具了,maven 定位是项目管理工具。pip 和 npm 都是及格的产品。
我个人觉得,一个包管理工具应该有以下功能:基本功能
加分:
对比上面几点:目前做的最好的也就 maven 了,gradle 没有使用过,不知道。
今天主角是 go mod,先来谈谈没有使用 go mod 之前的问题。
从这个看, go path 不算包管理工具
go modules 是 golang 1.11 新加的特性。现在 1.12 已经发布了,是时候用起来了。Modules 官方定义为:
模块是相关 Go 包的集合。modules 是源代码交换和版本控制的单元。go 命令直接支持使用 modules,包括记录和解析对其他模块的依赖性。modules 替换旧的基于 GOPATH 的方法来指定在给定构建中使用哪些源文件。
首先,必须升级 go 到 1.11, 目前版本是 1.14 下面我以我自己升级演示:
### 卸载旧版本,删除对应文件
brew uninstall -f go
### 更新一下 brew
brew update
### 安装 go
brew install go
上面升级完了,使用 go version
看下版本
go version go1.14.1 darwin/amd64
下面设置 go mod 和 go proxy
go env -w GOBIN=/Users/youdi/go/bin
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct // 使用七牛云的
注意:go env -w 会将配置写到 GOENV="/Users/youdi/Library/Application Support/go/env"
下面看下我的配置
GO111MODULE="on"
GOARCH="amd64"
GOBIN="/Users/youdi/go/bin"
GOCACHE="/Users/youdi/Library/Caches/go-build"
GOENV="/Users/youdi/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/youdi/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"
GOROOT="/usr/local/go"
GOSUMDB="off"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/8m/v_1j4dgs7rzgqq4p_4_8k_nr0000gn/T/go-build221113671=/tmp/go-build -gno-record-gcc-switches -fno-common"
我们看一下,我修改的内容
cat /Users/youdi/Library/Application Support/go/env
GO111MODULE=on
GOBIN=/Users/youdi/go/bin
GOPROXY=https://goproxy.cn,direct
GOSUMDB=off
GO111MODULE 有三个值:off, on 和 auto(默认值)。
GO111MODULE=off,go 命令行将不会支持 module 功能,寻找依赖包的方式将会沿用旧版本那种通过 vendor 目录或者 GOPATH 模式来查找。GO111MODULE=on,go 命令行会使用 modules,而一点也不会去 GOPATH 目录下查找。GO111MODULE=auto,默认值,go 命令行将会根据当前目录来决定是否启用 module 功能。这种情况下可以分为两种情形:
当前目录在 GOPATH/src 之外且该目录包含 go.mod 文件
当前文件在包含 go.mod 文件的目录下面。
当 modules 功能启用时,依赖包的存放位置变更为$GOPATH/pkg,允许同一个 package 多个版本并存,且多个项目可以共享缓存的 module
我们看下目录:
cd /Users/youdi/go/pkg
├── darwin_amd64
│ ├── github.com
│ ├── go.etcd.io
│ ├── golang
│ ├── golang.org
│ ├── gopkg.in
│ ├── quickstart
│ └── uc.a
├── mod
│ ├── cache
│ ├── github.com
│ ├── golang.org
│ ├── google.golang.org
│ └── gopkg.in
└── sumdb
└── sum.golang.org
golang 提供了 go mod
命令来管理包。
go help mod
Go mod provides access to operations on modules.
Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.
Usage:
go mod <command> [arguments]
The commands are:
download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed
Use "go help mod <command>" for more information about a command.
go mod 有以下命令:
命令 | 说明 |
---|---|
download | download modules to local cache(下载依赖包) |
edit | edit go.mod from tools or scripts(编辑 go.mod) |
graph | print module requirement graph (打印模块依赖图) |
verify | initialize new module in current directory(在当前目录初始化 mod) |
tidy | add missing and remove unused modules(拉取缺少的模块,移除不用的模块) |
vendor | make vendored copy of dependencies(将依赖复制到 vendor 下) |
verify | verify dependencies have expected content (验证依赖是否正确) |
why | explain why packages or modules are needed(解释为什么需要依赖) |
比较常用的是 init
,tidy
, edit
可以随便找一个目录创建项目,我使用习惯用 IDEA 进行创建
mkdir Gone
cd Gone
go mod init Gone
查看一下 go.mod 文件
module Gone
go 1.14
go.mod 文件一旦创建后,它的内容将会被 go toolchain 全面掌控。go toolchain 会在各类命令执行时,比如 go get、go build、go mod 等修改和维护 go.mod 文件。
go.mod 提供了 module, require、replace 和 exclude 四个命令
module
语句指定包的名字(路径)require
语句指定的依赖项模块replace
语句可以替换依赖项模块exclude
语句可以忽略依赖项模块创建 main.go 文件
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
执行 go run main.go 运行代码会发现 go mod 会自动查找依赖自动下载
再查看 go.mod
module Gone
go 1.14
require github.com/gin-gonic/gin v1.6.3
go module 安装 package 的原則是先拉最新的 release tag,若无 tag 则拉最新的 commit
go 会自动生成一个 go.sum 文件来记录 dependency tree
再次执行脚本 go run main.go 发现跳过了检查并安装依赖的步骤。
可以使用命令 go list -m -u all 来检查可以升级的 package,使用 go get -u need-upgrade-package 升级后会将新的依赖版本更新到 go.mod * 也可以使用 go get -u 升级所有依赖
去 mod 包缓存下看看
/Users/youdi/go/pkg/mod/github.com/gin-gonic/gin@v1.6.3
由于某些已知的原因,并不是所有的 package 都能成功下载,比如:golang.org 下的包。
modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
参考 Roberto Selbach 写的 go mod 入门文章,文末,我给出链接
如果你设置好 go mod 了,那你就可以在任何目录下随便创建
$mkdir gomodone
$cd gomodone
在这个目录下创建一个文件say.go
package gomodone
import "fmt"
// say Hi to someone
func SayHi(name string) string {
return fmt.Sprintf("Hi, %s", name)
}
初始化一个 go.mod
文件
$ go mod init github.com/jacksonyoudi/gomodone
go: creating new go.mod: module github.com/jacksonyoudi/gomodone
查看 go.mod 内容如下:
github.com/jacksonyoudi/gomodone
go 1.14
下面我们要将这个 module 发布到 github 上,然后在另外一个程序使用
$git init
$vim .gitiiignore
$git commit -am "init"
// github 创建对应的 repo
$git remote add origin git@github.com:jacksonyoudi/gomodone.git
$git push -u origin master
执行完,上面我们就相当于发布完了。
如果有人需要使用,就可以使用
go get github.com/jacksonyoudi/gomodone
这个时候没有加 tag,所以,没有版本的控制。默认是 v0.0.0 后面接上时间和 commitid。如下:
gomodone@v0.0.0-20200517004046-ee882713fd1e
官方不建议这样做,没有进行版本控制管理。
使用 tag,进行版本控制
git tag v1.0.0
git push --tags
操作完,我们的 module 就发布了一个 v1.0.0 的版本了。
推荐在这个状态下,再切出一个分支,用于后续 v1.0.0 的修复推送,不要直接在 master 分支修复
$git checkout -b v1
$git push -u origin v1
上面已经发布了一个 v1.0.0 的版本,我们可以在另一个项目中使用,创建一个 go 的项目
$mkdir Gone
$cd Gone
$vim main.go
package main
import (
"fmt"
"github.com/jacksonyoudi/gomodone"
)
func main() {
fmt.Println(gomodone.SayHi("Roberto"))
}
代码写好了,我们生成 go mod 文件
go mod init Gone
上面命令执行完,会生成 go mod 文件 看下 mod 文件:
module Gone
go 1.14
require (
github.com/jacksonyoudi/gomodone v1.0.0
)
$go mod tidy
go: finding module for package github.com/jacksonyoudi/gomodone
go: found github.com/jacksonyoudi/gomodone in github.com/jacksonyoudi/gomodone v1.0.0
同时还生成了 go.sum, 其中包含软件包的哈希值,以确保我们具有正确的版本和文件。
github.com/jacksonyoudi/gomodone v1.0.1 h1:jFd+qZlAB0R3zqrC9kwO8IgPrAdayMUS0rSHMDc/uG8=
github.com/jacksonyoudi/gomodone v1.0.1/go.mod h1:XWi+BLbuiuC2YM8Qz4yQzTSPtHt3T3hrlNN2pNlyA94=
github.com/jacksonyoudi/gomodone/v2 v2.0.0 h1:GpzGeXCx/Xv2ueiZJ8hEhFwLu7xjxLBjkOYSmg8Ya/w=
github.com/jacksonyoudi/gomodone/v2 v2.0.0/go.mod h1:L8uFPSZNHoAhpaePWUfKmGinjufYdw9c2i70xtBorSw=
这个内容是下面的,需要操作执行的结果
go run main.go 就可以运行了
假如 fix 一个 bug, 我们在 v1 版本上进行修复
修改代码如下:
// say Hi to someone
func SayHi(name string) string {
- return fmt.Sprintf("Hi, %s", name)
+ return fmt.Sprintf("Hi, %s!", name)
}
修复好,我们开始 push
$ git commit -m "Emphasize our friendliness" say.go
$ git tag v1.0.1
$ git push --tags origin v1
刚才 fix bug,所以要在我们使用项目中更新
这个需要我们手动执行更新 module 操作
我们通过使用我们的好朋友来做到这一点 go get:
go get -u
以使用最新的 minor 版本或修补程序版本(即它将从 1.0.0 更新到例如 1.0.1,或者,如果可用,则更新为 1.1.0)目前 module 最新的也是 v1.0.1
// 更新最新
$go get -u
$go get -u=patch
//指定包,指定版本
$go get github.com/jacksonyoudi/gomodone@v1.0.1
操作完,go.mod 文件会修改如下:
module Gone
go 1.14
require (
github.com/jacksonyoudi/gomodone v1.0.1
)
根据语义版本语义,主要版本与次要版本 不同。主要版本可能会破坏向后兼容性。从 Go 模块的角度来看,主要版本是 完全不同的软件包。乍一看这听起来很奇怪,但这是有道理的:两个不兼容的库版本是两个不同的库。比如下面修改,完全破坏了兼容性。
package gomodone
import (
"errors"
"fmt"
)
// Hi returns a friendly greeting
// Hi returns a friendly greeting in language lang
func SayHi(name, lang string) (string, error) {
switch lang {
case "en":
return fmt.Sprintf("Hi, %s!", name), nil
case "pt":
return fmt.Sprintf("Oi, %s!", name), nil
case "es":
return fmt.Sprintf("¡Hola, %s!", name), nil
case "fr":
return fmt.Sprintf("Bonjour, %s!", name), nil
default:
return "", errors.New("unknown language")
}
}
如上,我们需要不同的大版本,这种情况下
修改 go.mod 如下
module github.com/jacksonyoudi/gomodone/v2
go 1.14
然后,重新 tag,push
$ git commit say.go -m "Change Hi to allow multilang"
$ git checkout -b v2 # 用于 v2 版本,后续修复 v2
$ git commit go.mod -m "Bump version to v2"
$ git tag v2.0.0
$ git push --tags origin v2
即使发布了库的新不兼容版本,现有软件 也不会中断,因为它将继续使用现有版本 1.0.1。go get -u 将不会获得版本 2.0.0。如果想使用 v2.0.0, 代码改成如下:
package main
import (
"fmt"
"github.com/jacksonyoudi/gomodone/v2"
)
func main() {
g, err := gomodone.SayHi("Roberto", "pt")
if err != nil {
panic(err)
}
fmt.Println(g)
}
执行 go mod tidy
go: finding module for package github.com/jacksonyoudi/gomodone/v2
go: downloading github.com/jacksonyoudi/gomodone/v2 v2.0.0
go: found github.com/jacksonyoudi/gomodone/v2 in github.com/jacksonyoudi/gomodone/v2 v2.0.0
当然,两个版本都可以同时使用,使用别名 如下:
package main
import (
"fmt"
"github.com/jacksonyoudi/gomodone"
mv2 "github.com/jacksonyoudi/gomodone/v2"
)
func main() {
g, err := mv2.SayHi("Roberto", "pt")
if err != nil {
panic(err)
}
fmt.Println(g)
fmt.Println(gomodone.SayHi("Roberto"))
}
执行一下 go mod tidy
默认是忽略 vendor 的,如果想在项目目录下有 vendor 可以执行下面命令
$go vendor
当然,如果构建程序的时候,希望使用 vendor 中的依赖,
$ go build -mod vendor
创建完项目,会自动生成 go mod 文件 如果需要修改,可以手动修改,加入 git 等操作
写业务逻辑代码
解决依赖,更新 go.mod
go build
转自:
jianshu.com/p/760c97ff644c
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!