包和模块
GOPATH
进行golang开发时的工作空间,你编写的go源代码和编译后生成的可执行程序以及下载的第三方包都将存放在GOPATH下。
注意,GOPATH只是一个普通的文件目录并且你所有的编码工作都应该在该目录下完成(golang 1.11版本引入 包依赖管理工具go mod,可以在GOPATH以外编写go程序)。需要创建环境变量指定GOPATH位置。
GOPATH的目录结构:
- bin:存放编译后的可执行文件
- src:存放GO程序源码,每个目录对应一个包
- pkg:包对象
GOPATH可以包含多个目录,但这些目录不能和go的安装目录相同。
第三方包的导入
如果项目中需要引入第三方包,则使用import导入包,例如import "github.com/go-redis/redis"
,然后使用go get github.com/go-redis/redis
命令将该包下载下来到GOPATH/pkg/github.com/go-redis/redis目录下。
下载的过程:go会将包拼接成https协议的URL地址,例如https://github.com/go-redis/redis,go的第三方包是存储在像git或SVN这样的在线版本控制管理系统上的,go目前支持的在线版本管理类型:Bazaar(.bar)/Fossil(.fossil)/Git(.git)/Mercurial(.hg)/Subversion(.svn)
所以在示例中,go首先会解析github.com/go-redis/redis.git。其次根据支持的协议依次尝试clone该包,若该在线版本管理系统支持多种协议,那么go会一次尝试。然后将包clone到GOPATH/pkg下。
缺点:
- 多个项目都保存在GOPATH/src下,所有项目导入的第三方包都在GOPATH/pkg下,造成项目比较混乱。
- 导入的第三方包都会以最master分支的新版本进行下载,且不能指定版本。
Modules
是 Go 1.11 版本之后引入的,Go 1.11 之前使用 $GOPATH 机制,现在官方更提倡使用go modules。go modules 可以算作是较为完善的包管理工具。同时支持代理,国内也能享受高速的第三方包镜像服务。
go modules 在 1.13 版本仍是可选使用的,通过设置环境变量GO111MODULE
来选择是否开启go mod。GO111MODULE
共有三个值:
- auto:默认值,如果当前目录不在
$GOPATH
并且当前目录或者父目录下有go.mod文件,则使用go modules,否则仍使用GOPATH mode。 - on:使用go module,忽略GOPATH目录。
- off:GOPATH mode,只查找GOPATH。
Modules可以满足:
- 开发者可以在任意目录下工作,不仅仅是GOPATH指定的目录下
- 可以安装依赖包的指定版本,而不是只能从master分支安装最新的版本。
- 可以导入同一个依赖包的多个版本
- 有一个罗列当前项目所依赖包的列表。
一个module也是可以像package一样共享的。因此,module也必须是一个git仓库或其它go可支持的代码控制系统,因此,官方给的建议是:
- 一个module必须是一个代码控制系统的仓库,并且一个仓库应该只能包含一个module;
- 一个module应该包含一个或多个package
在使用go modules时,GOPATH是无意义的,不过还是会把下载的依赖存储在GOPATH/src/mod
中,也会把go install
的结果放在GOPATH/bin
。
Package
一般来说,一个文件夹可以作为 package,一个module包含多个package的目录,同一个 package 内部变量、类型、方法等定义可以相互看到。go程序的第一行语句必须是package pkgname
。对于可执行文件的包必须是main
。
比如我们新建一个文件 calc.go
, main.go
平级,分别定义 add 和 main 方法。
// calc.go
package main
func add(num1 int, num2 int) int {
return num1 + num2
}
// main.go
package main
import "fmt"
func main() {
fmt.Println(add(3, 5)) // 8
}
运行 go run main.go
,会报错,add 未定义:
./main.go:6:14: undefined: add
因为 go run main.go
仅编译 main.go 一个文件,所以命令需要换成
$ go run main.go calc.go
8
或
$ go run .
8
Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。
go modules的创建
在一个空文件夹下,初始化一个 Module
$ go mod init github.com/xxx/encodex
go: creating new go.mod: module github.com/xxx/encodex
此时,在当前文件夹下生成了go.mod
,这个文件记录当前模块的模块名、go的版本以及所有依赖包的版本。
module github.com/xxx/encodex
go 1.13
然后在创建一个文件夹(package)hash,然后创建一个hash.go文件,添加代码,这样就可以当作一个module使用了,也可以把这个module发布到github上。
首先需要在github上创建代码仓库,然后将本地代码push上去,在发布之前,需要给module指定语义化版本,一般由字符vMajor.Minor.patch
构成,v
是固定字符,Major
代表主版本,Minor
代表此版本,Patch
代表不定版本。只有在版本不兼容之前版本时,才会改动主版本号,当兼容时只改动次版本号,当对此版本做了问题的修复时会改动patch。对于git的每次提交都可以为其创建一个语义化版本名:
git tag v1.0.0
git push --tag
到这里module就发布成功了。
第三方module的使用
接着,我们在当前目录下新建文件 main/main.go
,添加如下代码:
package main
import (
"fmt"
"github.com/xxx/encodex/hash"
)
func main() {
...
}
运行go get github.com/xxx/encodex/hash@v1.0.0
,可以指定包的具体版本,也可以不指定,不指定就会下载最新版本。
go.mod文件中会增加一条语句require github.com/xxx/encodex v1.0.0
,下载的包会存储在GOPATH/pkg/mod/github.com/xxx/encodex
下。
依赖包版本升级
时间长了项目所依赖的第三方包可能会有新的升级,比如修复了一个bug。如果我们只想升级补丁版本path,则可以使用命令go get -u=patch
;如果是想更新次版本号,直接使用go get -u
,该命令如果次版本有升级会升级次版本,如果没有次版本则升级补丁版本。如果想升级到指定版本可以使用go get module@version
。
如果想升级大版本,因为一个大的版本和之前的版本不兼容,所以go会认为是一个全新的module,因此需要在代码中import对应的新版本,并使用go get
重新下载。
https://blog.csdn.net/RA681t58CJxsgCkJ31/article/details/123911038