模块管理
模块管理
模块的集合通常叫做库library
,在大部分情况下对于一些已有的工具和轮子,没有必要自己再去编写一个,一个库通常由很多包组成,通过导入对应的包,就能直接使用该包下提供的任何功能。上述说的库通常分为:
- 标准库:标准库是由Go官方开发并维护的,提供了许多的功能强大且实用的包,例如在入门示例中就用到了
fmt
包下的Println
函数进行字符串输出。 - 第三方库:第三方库是由社区开发并维护的,同样也有许多出色的工具,例如著名的web框架
gin
。
而Go Module是官方的一个依赖管理工具,可以前往Go-模块手册,以了解更多细节。
提示
由于Go Path
已经过时,应该被弃用,官方仅仅只是为了兼容性考虑才保留了Go Path
,在依赖管理这部分不再讲解关于它的任何内容。
简介
每一个语言对于依赖管理都独有一套解决方案,而Go在远古时期由于谷歌内部项目结构的原因,或许根本就没认真考虑过依赖管理这个问题,导致项目管理起来十分的杂乱,直到1.11版本官方才终于推出了正式的解决方案:Go mod
。
官方对于模块module
的定义为被版本标记的package
集合组成的一个单元。通常情况下,大致关系如下:
- 一个项目可能有一个或多个模块(大多数都是单模块,多模块项目需要使用工作空间)
- 每个模块拥有一个或多个包
- 每个包拥有一个或多个源文件
版本规范遵循格式:v(major主版本).(minor小版本).(patch补丁版本)
,例如v1.0.0
。模块管理主要依赖于一个名为go.mod
的文件,用于记录项目中的依赖和版本。
将env
中的GO111MODULE
参数值修改为auto
或者on
以开启模块功能,或者执行以下命令
go env -w GO111MODULE=on
代理
有些依赖仓库是在国外,由于不可明说的原因,无法直接访问,因此也无法下载到本地,默认的官方的模块代理大概率国内是没法访问的,这个时候就需要修改Go代理,下面列出国内几家做的不错的代理:
推荐使用第一个,修改也是十分简单。
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
寻找
对于标准库而言,可以直接前往Go的安装目录下的src
文件夹,该目录下即是标准库的源代码,又或者是前往Go-标准库文档查询包名和具体用法。
对于第三方库而言,可以前往Go 依赖直接搜索想要寻找的依赖名,例如搜索gin
,如下图。
可以得知依赖名为github.com/gin-gonic/gin
,就可以使用go get
命令来进行下载,同时也可以指定版本,例如github.com/gin-gonic/gin@v1.8.2
。事实上,只要是Github上开源的Go项目可以获取。
下载
下载依赖的命令总共有两个,go get
与go install
,但两者的使用有点区别。
go get
格式如下
go get [-t] [-u] [-x] [build flags] [packages]
-t
:构建测试所需要的模块,这些模块是在命令行指定的packages
。
-u
:更新指定的模块,当这些模块有新的镜像或者发行补丁版本。
-x
:打印过程中执行的命令,通常用于调试。
先来看几个例子
go get github.com/gin-gonic/gin@latest
go get golang.org/x/text@master
go get golang.org/x/text@v0.3.2
删除一个依赖
go get github.com/gin-gonic/gin@none
go get命令专门用于调整和修改go.mod
文件中的依赖,如果没有特殊需求,用go get就足够了。
go install
格式如下
go install [build flags] [packages]
该命令会下载远程的包并编译成二进制文件放在$GOROOT/bin
或者$GOBIN
目录下,使用的话分几个情况:
如果指定版本时,会自动忽略目录下的go.mod
文件,如果在模块外部执行该命令则必须要指定版本,例如:
go install golang.org/x/tools/gopls@latest
执行后,Go会将远程下载该项目并编译成二进制可执行文件,然后放在$GOBIN
对应的目录下。
在模块内部使用该命令可以不用指定版本,但是只能指定模块内已有的包。
使用
笔者建议,先学会怎么使用依赖管理工具,再去研究到底是怎么一回事,这样会更容易去理解,先来看看大致的流程。
创建项目
项目也就是一个文件夹,创建一个文件夹,命名为learn
。
初始化
在项目内,使用命令go mod init learn
初始化项目模块,learn
也就是模块名,建议不要随便写,最好与项目名相同,这时会项目下会自动生成一个名为go.mod
的文件,这个文件的作用先按下不表,后面会讲,此时文件内容如下。
module learn // 模块名
go 1.19 // 使用Go的版本
下载依赖
使用命令go get github.com/gin-gonic/gin@latest
,下载依赖,成功后项目内会出现一个名为go.sum
的文件,此时go.mod
文件内容如下
module learn
go 1.19
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.8.2 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
go.sum
文件的内容就太多了,只列出一部分,后面会讲有什么用。
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
......
导入并运行
创建main.go
文件,导入gin
,然后编译并运行。
package main
import "github.com/gin-gonic/gin" // 导入包
func main() {
// 运行
gin.Default().Run(":8080")
}
在项目内执行命令
go run learn
看到输出如下说明运行成功
[GIN-debug] Listening and serving HTTP on :8080
以上步骤完成后,大致概括一下就是从远程仓库下载了一个第三方编写的Web框架,然后在本地导入包,并运行了一个监听端口为8080的Http服务器,日后不管下载任何依赖,都是这一个步骤。
go.mod
可以前往Go Modules - go.mod file以了解更多细节。
module learn
go 1.19
require github.com/gin-gonic/gin v1.8.2
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
...
)
一个模块的定义通常是由go.mod
文件中的module
指定,每一行都包含一个指令,该文件被设计成人类能够读懂和编辑的格式,且Go提供了许多子命令来编辑该文件,通常不建议手动编辑。
module
一个module
指令定义了一个主模块的路径,一个go.mod
文件必须只包含一个module
指令,例如
module learn
require
require
指令声明了一个模块依赖项所需要的最小版本。Go命令有时候会自动的加上// indirect
注释,即表示间接依赖,这里是因为gin
直接依赖了这些包,主模块依赖了gin
,所以对于主模块而言,这些模块就是间接依赖,例如
require github.com/gin-gonic/gin v1.8.2
也可以
require golang.org/x/net v1.2.3
require (
golang.org/x/crypto v1.4.5 // indirect
golang.org/x/text v1.6.7
)
exclude
exclude
指令可以防止Go命令加载该版本的依赖,可能会导致类似go get
这样的命令向go.mod
文件中添加更高版本的require
,并且该指令仅在主模块中应用,在子模块中会被忽略,例子
exclude golang.org/x/net v1.2.3
exclude (
golang.org/x/crypto v1.4.5
golang.org/x/text v1.6.7
)
replace
replace
将会替换掉指定版本的依赖,可以使用模块路径和版本替换又或者是其他平台指定的文件路径,例子
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
golang.org/x/net => example.com/fork/net v1.4.5
golang.org/x/net v1.2.3 => ./fork/net
golang.org/x/net => ./fork/net
)
仅=>
左边的版本被替换,其他版本的同一个依赖照样可以正常访问,无论是使用本地路径还是模块路径指定替换,如果替换模块具有 go.mod
文件,则其module
指令必须与所替换的模块路径匹配。
retract
retract
指令表示,不应该依赖go.mod
文件所指定依赖的版本或版本范围。例如在一个新的版本发布后发现了一个重大问题,这个时候就可以使用retract
指令。
撤回一些版本
retract (
v1.0.0 // Published accidentally.
v1.0.1 // Contains retractions only.
)
撤回版本范围
retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
v1.0.0
[v1.0.0, v1.9.9]
)
go.sum
go.sum
文件的存在是为了解决一致性构建的问题。依赖下载到本地后,会缓存到本地,以方便下次构建,下载的依赖和缓存的依赖都会有被纂改的可能,go.sum
文件会记录每一个依赖的哈希值,如果go.sum
文件中的哈希值与本地依赖的哈希值不同,则会拒绝构建,并且一般在下载依赖完成后还会请求环境变量GOSUMDB
所指定的服务器查询一个可信的公共哈希值,如果哈希值不同的话也不会继续执行。可以前往Go Modules - go.sum file以查看更多细节。总的来说,该文件存在的意义就是确保下载的依赖是安全的与远程仓库是内容一致且未被修改的。
提示
大多数情况下,不应该去手动修改go.sum
文件。
常用命令
下列命令是模块模块管理会经常用到的
命令 | 说明 |
---|---|
go mod download | 下载当前项目的依赖包 |
go mod edit | 编辑go.mod文件 |
go mod graph | 输出模块依赖图 |
go mod init | 在当前目录初始化go mod |
go mod tidy | 清理项目模块 |
go mod verify | 验证项目的依赖合法性 |
go mod why | 解释项目哪些地方用到了依赖 |
go clean -modcache | 用于删除项目模块依赖缓存 |
go list -m | 列出模块 |
工作区
工作区(workspace),是Go在1.18引入的关于多模块管理的一个新的解决方案。在以往的时候,如果想要在本地依赖其他模块但又没有上传到远程仓库,一般都需要使用replace
指令,文件结构如下。
learn
-main
|-- main.go
|-- go.mod
-tool
|-- util.go
|-- go.mod
假如main.go
想要导入tool
模块下一个函数,则需要将main
的go.mod
文件修改如下。
module main
go 1.19
require (
tool v0.0.0
)
replace (
tool => "../utils" // 使用replace指令指向本地模块
)
package main
import (
"fmt"
"tool"
)
func main() {
fmt.Println(tool.StringMsg())
}
另一种解决办法是将tool
模块上传至远程仓库然后发布tag,然后main
模块使用go get -u
进行更新,而工作区就是为了解决这样的问题而生的。在目录learn
下使用命令go work init main tool
,就会多出一个名为go.work
的文件,内容如下。
go 1.19
use (
./main
./tool
)
如下操作后,main
模块下的go.mod
文件便可以不用replace
也可以访问tool
模块下的函数。
go.work
文件有三种指令。
go
go.work
文件中必须要有一个有效的Go版本,其指定了要使用的Go工具链版本。
go 1.19
use
use
指令用于将本地的模块加入到主模块集合中,它的参数是包含go.mod
文件目录的相对路径,但不会添加包含在参数中的子模块。
use ./mymod // example.com/mymod
use (
../othermod
./subdir/thirdmod
)
replace
就如同go.mod
文件中的replace
指令一样,用于替换指定版本的依赖,区别在于,它会覆盖use
指令中相同的replace
指令,也就是说replace
指令的优先级以go.work
文件为准,其次才是go.mod
文件。
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
replace (
golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
golang.org/x/net => example.com/fork/net v1.4.5
golang.org/x/net v1.2.3 => ./fork/net
golang.org/x/net => ./fork/net
)
顺便提下,go work
命令有几个子命令
edit 编辑go.work文件
init 初始化工作区
sync 将工作区构建列表同步至模块
use 添加模块到工作区中
若想要禁用工作区模式,可以通过 -workfile=off
指令来指定。
go run -workfile=off main.go