使用go module引入包| 青训营

129 阅读1分钟

使用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 命令。