使用go module引入包
接下来我们将通过一个示例来演示如何在开发项目时使用 go module 拉取和管理项目依赖。
初始化项目 我们在本地新建一个名为holiday项目,按如下方式创建一个名为holiday的文件夹并切换到该目录下:
$ mkdir holiday
$ cd holiday
目前我们位于holiday文件夹下,接下来执行下面的命令初始化项目。
$ go mod init holiday
go: creating new go.mod: module holiday
该命令会自动在项目目录下创建一个go.mod文件,其内容如下。
module holiday
go 1.16
其中:
- module holiday:定义当前项目的导入路径
- go 1.16:标识当前项目使用的 Go 版本
go.mod文件会记录项目使用的第三方依赖包信息,包括包名和版本,由于我们的holiday项目目前还没有使用到第三方依赖包,所以go.mod文件暂时还没有记录任何依赖包信息,只有当前项目的一些信息。
接下来,我们在项目目录下新建一个main.go文件,其内容如下:
// holiday/main.go
package main
import "fmt"
func main() {
fmt.Println("现在是假期时间...")
}
然后,我们的holiday项目现在需要引入一个第三方包github.com/q1mi/hello来实现一些必要的功能。类似这样的场景在我们的日常开发中是很常见的。我们需要先将依赖包下载到本地同时在go.mod中记录依赖信息,然后才能在我们的代码中引入并使用这个包。下载依赖包主要有两种方法。
第一种方法是在项目目录下执行go get命令手动下载依赖的包:
holiday $ go get -u github.com/q1mi/hello
go get: added github.com/q1mi/hello v0.1.1
这样默认会下载最新的发布版本,你也可以指定想要下载指定的版本号的。
holiday $ go get -u github.com/q1mi/hello@v0.1.0
go: downloading github.com/q1mi/hello v0.1.0
go get: downgraded github.com/q1mi/hello v0.1.1 => v0.1.0
如果依赖包没有发布任何版本则会拉取最新的提交,最终go.mod中的依赖信息会变成类似下面这种由默认v0.0.0的版本号和最新一次commit的时间和hash组成的版本格式:
require github.com/q1mi/hello v0.0.0-20210218074646-139b0bcd549d
如果想指定下载某个commit对应的代码,可以直接指定commit hash,不过没有必要写出完整的commit hash,一般前7位即可。例如:
holiday $ go get github.com/q1mi/hello@2ccfadd
go: downloading github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3
go get: added github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3
此时,我们打开go.mod文件就可以看到下载的依赖包及版本信息都已经被记录下来了。
module holiday
go 1.16
require github.com/q1mi/hello v0.1.0 // indirect
行尾的indirect表示该依赖包为间接依赖,说明在当前程序中的所有 import 语句中没有发现引入这个包。
另外在执行go get命令下载一个新的依赖包时一般会额外添加-u参数,强制更新现有依赖。
第二种方式是我们直接编辑go.mod文件,将依赖包和版本信息写入该文件。例如我们修改holiday/go.mod文件内容如下:
module holiday
go 1.16
require github.com/q1mi/hello latest
表示当前项目需要使用github.com/q1mi/hello库的最新版本,然后在项目目录下执行go mod download下载依赖包。
holiday $ go mod download
如果不输出其它提示信息就说明依赖已经下载成功,此时go.mod文件已经变成如下内容。
module holiday
go 1.16
require github.com/q1mi/hello v0.1.1
从中我们可以知道最新的版本号是v0.1.1。如果事先知道依赖包的具体版本号,可以直接在go.mod中指定需要的版本然后再执行go mod download下载。
这种方法同样支持指定想要下载的commit进行下载,例如直接在go.mod文件中按如下方式指定commit hash,这里只写出来了commit hash的前7位。
require github.com/q1mi/hello 2ccfadda
执行go mod download下载完依赖后,go.mod文件中对应的版本信息会自动更新为类似下面的格式。
module holiday
go 1.16
require github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3
下载好要使用的依赖包之后,我们现在就可以在holiday/main.go文件中使用这个包了。
package main
import (
"fmt"
"github.com/q1mi/hello"
)
func main() {
fmt.Println("现在是假期时间...")
hello.SayHi() // 调用hello包的SayHi函数
}
将上述代码编译执行,就能看到执行结果了。
holiday $ go build
holiday $ ./holiday
现在是假期时间...
你好,我是七米。很高兴认识你。
当我们的项目功能越做越多,代码越来越多的时候,通常会选择在项目内部按功能或业务划分成多个不同包。Go语言支持在一个项目(project)下定义多个包(package)。
例如,我们在holiday项目内部创建一个新的package——summer,此时新的项目目录结构如下:
holidy
├── go.mod
├── go.sum
├── main.go
└── summer
└── summer.go
其中holiday/summer/summer.go文件内容如下:
package summer
import "fmt"
// Diving 潜水...
func Diving() {
fmt.Println("夏天去诗巴丹潜水...")
}
此时想要在当前项目目录下的其他包或者main.go中调用这个Diving函数需要如何引入呢?这里以在main.go中演示详细的调用过程为例,在项目内其他包的引入方式类似。
package main
import (
"fmt"
"holiday/summer" // 导入当前项目下的包
"github.com/q1mi/hello" // 导入github上第三方包
)
func main() {
fmt.Println("现在是假期时间...")
hello.SayHi()
summer.Diving()
}
从上面的示例可以看出,项目中定义的包都会以项目的导入路径为前缀。
如果你想要导入本地的一个包,并且这个包也没有发布到到其他任何代码仓库,这时候你可以在go.mod文件中使用replace语句将依赖临时替换为本地的代码包。例如在我的电脑上有另外一个名为liwenzhou.com/overtime的项目,它位于holiday项目同级目录下:
├── holiday
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── summer
│ └── summer.go
└── overtime
├── go.mod
└── overtime.go
由于liwenzhou.com/overtime包只存在于我本地,并不能通过网络获取到这个代码包,这个时候应该如何在holidy项目中引入它呢?
我们可以在holidy/go.mod文件中正常引入liwenzhou.com/overtime包,然后像下面的示例那样使用replace语句将这个依赖替换为使用相对路径表示的本地包。
module holiday
go 1.16
require github.com/q1mi/hello v0.1.1
require liwenzhou.com/overtime v0.0.0
replace liwenzhou.com/overtime => ../overtime
这样,我们就可以在holiday/main.go下正常引入并使用overtime包了。
package main
import (
"fmt"
"holiday/summer" // 导入当前项目下的包
"liwenzhou.com/overtime" // 通过replace导入的本地包
"github.com/q1mi/hello" // 导入github上第三方包
)
func main() {
fmt.Println("现在是假期时间...")
hello.SayHi()
summer.Diving()
overtime.Do()
}
我们也经常使用replace将项目依赖中的某个包,替换为其他版本的代码包或我们自己修改后的代码包。
go.sum文件
使用go module下载了依赖后,项目目录下还会生成一个go.sum文件,这个文件中详细记录了当前项目中引入的依赖包的信息及其hash 值。go.sum文件内容通常是以类似下面的格式出现。
不同于其他语言提供的基于中心的包管理机制,例如 npm 和 pypi等,Go并没有提供一个中央仓库来管理所有依赖包,而是采用分布式的方式来管理包。为了防止依赖包被非法篡改,Go module 引入了go.sum机制来对依赖包进行校验。
依赖保存位置
Go module 会把下载到本地的依赖包会以类似下面的形式保存在 $GOPATH/pkg/mod目录下,每个依赖包都会带有版本号进行区分,这样就允许在本地存在同一个包的多个不同版本。
如果想清除所有本地已缓存的依赖包数据,可以执行 go clean -modcache 命令。