Go Path
在 Golang 1.11 之前,Go 采用的是手动依赖管理,也就是使用 Go Path 的方式管理依赖。
使用 Go Path 会有以下问题:
- 代码开发必须在 Go Path 的
src
目录下。 - 依赖手动管理。
- 依赖包没有版本可言。
Go Modules
Go Modules 是 Golang 1.11 新加的特性。Go Modules 的特点如下:
- 模块是相关 Go 包的集合。
- Modules 是源代码交换和版本控制的单元。
- Go 命令直接支持使用 Modules,包括记录和解析对其他模块的依赖性。
- Modules 替换了旧的 Go Path 方法。
在使用 Go Modules 之前需要对环境变量进行一些设置:
- 设置
GO111MODULE
为on
; - 设置
GOPROXY
为https://goproxy.cn
(七牛云的 Go 代理,)。
根据不同平台有不同的设置方式:
-
Golang 1.13 及以上:
$ go env -w GO111MODULE=on $ go env -w GOPROXY=https://goproxy.cn,direct
-
MacOS 或 Linux
$ export GO111MODULE=on $ export GOPROXY=https://goproxy.cn
或者
$ echo "export GO111MODULE=on" >> ~/.profile $ echo "export GOPROXY=https://goproxy.cn" >> ~/.profile $ source ~/.profile
-
Windows:
C:\> $env:GO111MODULE = "on" C:\> $env:GOPROXY = "https://goproxy.cn"
或者,直接在“高级系统设置”中的“环境变量”中添加对应的环境变量。
创建模块
创建 greetings
模块:
-
创建一个模块目录(在任意位置都可以,此时不用指定在 Go Path 下),并切换到目录中。
假设工作目录为
example.com
,在工作目录下创建greetings
模块:$ cd example.com $ mkdir greetings $ cd greetings
-
使用
go mod init <module-name>
初始化模块。$ go mod init example.com/greetings
go mod init
会在greetings
目录下创建一个go.mod
文件。go.mod
用于记录当前模块的名称、Golang SDK 版本以及项目依赖等信息。go.mod
的基本内容如下:// 模块名 module example.com/greetings // Golang SDK 版本 go 1.22.4
在后续的使用中,
go.mod
还可能包含以下内容:// 项目所需依赖 require ( // 依赖的格式如下: dependencyName vision // ... ) // 项目中排除的依赖 exclude ( // 格式同上 dependencyName vision // ... ) // 替换第三方依赖 replace ( sourceName vision => targetName vision // ... ) // 撤回当前项目中有问题的版本 retract ( version // ... )
-
在
greetings
创建对应的greetings.go
。package grettings import "fmt" func Hello(name string) (message string) { message = fmt.Sprintf("Hi, %v. Welcome!", name) return message }
创建 hello
模块:
-
在
example.com
创建hello
目录并初始化:$ mkdir hello $ cd hello $ go mod init example.com/hello
-
在
hello
目录下创建hello.go
并编写main()
来调用greetings.Hello(name string)
。package main import ( "fmt" "example.com/greetings" ) func main() { message := greetings.Hello("Linner") fmt.Println(message) }
-
由于
hello.go
使用到了example.com/greetings
,所以需要对其进行导入。由于example.com/greetings
是当前example.com
中的子模块,example.com/greetings
并未使用任何版本管理工具,所以需要使用mod replace
为其指定路径。在
hello
目录下:# 指定模块路径(一般实在开发环境下使用) $ go mod edit -replace example.com/greetings=../greetings # 导入 example.com/greetings $ go get example.com/greetings
执行以上命令后,在
hello/go.mod
中会添加如下内容:replace example.com/greetings => ../greetings require example.com/greetings v0.0.0-00010101000000-000000000000 // indirect
由于没用使用版本工具发布版本(如使用 Git Tag),所以导入之后,Go Get 会使用一个伪版本号(pseudo-version number)暂替。
完成创建后,可以在 hello
目录中 run
或 build
:
go run .
$ go build
$ ./hello.exe
Go 中的伪版本号的格式遵循语义化版本控制的原则,并在此基础上增加了一段额外的信息来唯一标识Git提交。通常伪版本号使用如下格式生成:
v0.0.0-YYYYMMDDHHMMSS-CommitHash
v0.0.0
表示模块尚未定义正式版本,或依赖的是一个无标签的提交;YYYYMMDDHHMMSS
是提交日期的时间戳,精确到秒,确保了时间上的唯一性;CommitHash
是 Git 提交的前几位哈希值,进一步确保了每个提交的唯一性。由于
example.com/greetings
模块并未使用 Git,所以伪版本号是固定的v0.0.0-00010101000000-000000000000
。
go mod 命令
使用 go help mod
可以查看 go mod
命令的信息:
$ go 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.
命令 | 说明 |
---|---|
download |
下载依赖包到本地缓存 |
edit |
编辑 go.mod |
graph |
打印模块依赖图 |
verify |
在当前目录初始化新的模块 |
tidy |
拉取缺少的模块,移除不用的模块 |
vendor |
将依赖复制到 vendor 下 |
verify |
验证依赖是否正确 |
why |
解释为什么包或模块依赖被依赖 |
代码仓库
通常情况下 Go Modules 是和 Git 一同使用的,在 Go 中创建一个模块的标准流程实际是:
-
初始化。
在模块目录中进行如下操作:
$ go mod init <module-name> $ git init $ git commit -am "init" $ git remote add origin <remote-url> $ git push -u origin main
-
开发模块。
模块开发完成后,需要进行发布。Go Modules 中的发布实际就是使用 Git 提交代码。
git push
-
在任意机器上运行如下命令即可自动安装依赖:
go get <module-name>[@version]
在发布模块的时候需要注意。模块对应的远程仓库需要设置为 Public。如果要导入的模块存放在你的私有仓库中,需要将环境变量 GOPRIVATE
设置为你的远程仓库的用户目录。例如我的 Github 主页是 https://github.com/Linna-cy/ 那么我需要设置 GOPRIVATE=github.com/Linna-cy/*
。再次运行 go get
命令,在通过用户名密码验证后就能正常从我的私有仓库下载依赖到本地。
一般情况下,在创建模块和远程仓库的时候,会将模块名称设置为 远程仓库地址/用户名/仓库名
的形式。
版本管理
Go Modules 中并没有与版本相关的配置项,而是依靠于 Git 进行版本管理。
如上例,假设模块名称为 github.com/Linna-cy/go-utils
,运行 go get github.com/Linna-cy/go-utils
时,在 go.mod
中的结果可能如下:
require github.com/Linna-cy/go-utils v0.0.0-20240608124125-a86730578714
可以观察到,当没有使用 Git 给模块指定版本时,默认的版本号(伪版本号)是通过时间戳等信息生成的。
go get github.com/Linna-cy/go-utils
在没有指定版本时,会自动获取github.com/Linna-cy/go-utils
的最新版本。而模块github.com/Linna-cy/go-utils
恰巧没有使用 Gti 设置任何版本,所以获取到的最新的版本就是 Go 自动生成的。
设置版本号
要设置版本,需要通过 Git Tag 进行设置。例如给 github.com/Linna-cy/go-utils
设置一个 v1.0.0
的版本号并进行发布:
$ git tag v1.0.0
$ git push --tags
Git Tag 创建标签时,也可以在标签中包含一些信息(这种标签称为附注标签):
git tag -a <tagname> -m "Tag message describing the version."
导入依赖
然后就可以导入指定版本的依赖:
$ go get github.com/Linna-cy/go-utils@v1.0.0
go: downloading github.com/Linna-cy/go-utils v1.0.0
go: upgraded github.com/Linna-cy/go-utils v0.0.0-20240608124125-a86730578714 => v1.0.0
go.mod
中 github.com/Linna-cy/go-utils
将更新为:
require github.com/Linna-cy/go-utils v1.0.0
在 go.mod
更新的同时,还生成了 go.sum
,其中除了软件包名和版本号,还包含了软件包的哈希值,以确保具有正确的版本和文件。
依赖导入成功后,就可以在代码中使用 import
进行导入:
import "github.com/Linna-cy/go-utils"
通常情况下,版本号使用语义化版本号(Semantic Versioning,SemVer)。SemVer 的格式通常为 MAJOR.MINOR.PATCH
,每个部分都是一个非负整数,并且在数值上递增。其具体含义如下:
MAJOR
(主版本号):当做了不兼容的 API 修改时,主版本号应该递增。这表明新版本无法向后兼容旧版本,使用者可能需要修改代码才能适配新版本。MINOR
(次版本号):当新增了向后兼容的功能时,次版本号应该递增。这意味着新版本添加了功能,但所有公共接口保持与旧版本兼容,用户无需修改代码即可安全升级。PATCH
(修订号):当进行了向后兼容的错误修正时,修订号应该递增。这类更新修复了问题,但不对公开的 API 做任何改变,因此对用户而言是透明的升级。
分支
在使用 Git Tag 给提交标记上版本号后,假设当前标记的版本号是当前主版本下的第一个版本,一般情况下会给当前主版本创建一个新的分支,用于当前主版本后续的修复推送。例如 github.com/Linna-cy/go-utils
,在推送版本号之后,还可以:
$ git checkout -b v1 v1.0.0
$ git push -u origin v1
其中,checkout
的含义如下(tag-name
是可选的):
git checkout -b <new-branch> [tag-name]
迭代和修复
一般版本后续的迭代和修复,不会直接在主分支(main
、master
)上进行修改,而是创建新的分支进行修改。当修改后的内容通过测试,再将其合并到主分支上。
例如 github.com/Linna-cy/go-utils v1.0.0
有 Bug。当我们修复完成后,可以:
# 提交示例(根据实际情况进行修改)
$ git commit -am "fix: 修复了 xxx 问题"
$ git tag v1.0.1
$ git push --tags origin v1
# 如果已经 git push -u origin v1,可以直接 git push --tags
$ git push origin v1:v1
# 如果已经 git push -u origin v1,可以直接 git push
当修复完成后,在使用了 github.com/Linna-cy/go-utils v1.0.0
的模块中,需要对 github.com/Linna-cy/go-utils
进行更新。更新通常有以下方式:
# 对所有依赖进行更新升级
$ go get -u
$ go get -u=patch
# 指定包和版本进行更新升级
$ go get github.com/Linna-cy/go-utils@v1.0.1
如果要对主版本进行迭代,即发布新的主版本,一般步骤如下:
-
修改
go.mod
。由于主要版本可能会破坏向后兼容性,所以可以通过修改
go.mod
的module
项的方式,告知两个版本并不兼容。module <module-name>/<major> go <version>
例如在
github.com/Linna-cy/go-utils
中添加了一系列新的接口,或对原有的接口进行修改,且修改后的接口不兼容旧版本。此时就需要对主版本进行迭代。假设将主版本迭代到v2
,并且发布了新的版本v2.0.0
,此时对应的go.mod
:module github.com/Linna-cy/go-utils/v2 go 1.22.4 // 省略其它内容...
-
发布新版本
假设要发布
github.com/Linna-cy/go-utils v2.0.0
:# 提交示例(根据实际情况进行修改) $ git commit -am "feat: 发布新版本 v2.0.0,xxx" $ git tag -a v2.0.0 -m "添加了 xxx,修改了 xxx,新版支持 xxx" # 签出分支 $ git checkout -b v2 v2.0.0 $ git push --tags origin v2
-
使用新版本。
原先使用了 github.com/Linna-cy/go-utils
的模块并不会受到影响。如果需要升级模块的主版本,或在其它模块中使用 github.com/Linna-cy/go-utils/v2
需要将 import "github.com/Linna-cy/go-utils"
修改为 import "github.com/Linna-cy/go-utils/v2"
。
评论