【译】Go 模块使用入门

栏目: Go · 发布时间: 7年前

内容简介:Go 1.11 和 1.12 版本初步支持了模块(modules)。它是 Go 中新的依赖管理系统,能更简单的显示、管理依赖的版本。本文介绍将一个Go 1.11 中,go 会在下面情况下使用
  • 原文标题:Using Go Modules
  • 原文作者:Tyler Bui-Palsulich and Eno Compton
  • 原文时间:2019-03-19

Go 1.11 和 1.12 版本初步支持了模块(modules)。它是 Go 中新的依赖管理系统,能更简单的显示、管理依赖的版本。本文介绍将 模块 使用入门所需要的一些基本操作。后续将有文章来涵盖跟多 模块 的使用。

一个 模块 就是一系列 Go packages 的集合,其存储在一个根目录中包含 go.mod 文件的文件夹中。 go.mod 文件定义了模块的 模块路径 ,这也是导入路径中的根路径,还定义了能成功构建本模块所需的依赖模块。每一项依赖表示为一个模块路径和一个特殊的语义化的版本号。

Go 1.11 中,go 会在下面情况下使用 模块 :当前目录或任何上级目录中有 go.mod 文件,且在 $GOPATH/src 目录之外(为了兼容,在 $GOPATH/src 目录中依然使用老的 GOPATH 模式,即使存在 go.mod ,详情见go 命令文档)。从 Go 1.13 开始, 模块模式 将成为默认的依赖管理系统。

本文介绍使用 模块 来开发 Go 项目时一些常用操作:

  • 新建模块
  • 添加依赖
  • 更新依赖
  • 添加新的 major 版本的依赖
  • 更新依赖到新的 major 版本
  • 移除未使用的依赖

新建模块

让我们来新建一个模块。

$GOPATH/src 目录外的任意目录中,新建一个空的文件夹。cd 进入该文件夹,新建一个源文件, hello.go

package hello

func Hello() string {
  return "Hello, World."
}
复制代码

然后再写个测试,名为 hello_test.go

package  hello

import "testing"

func TestHello(t *testing.T) {
  want := "Hello, world."
  if got := Hello(); got != want {
    t.Errorf("Hello() =%q, want %q", got, want)
  }
}
复制代码

现在,这个目录下包含一个 包(package) ,而不是一个 模块(module) ,因为没有 go.mod 文件。如果该目录为 /home/gopher/hello,运行 go test ,我们将看到:

$ go test
PASS
ok   _/home/gopher/hello   0.020s
$
复制代码

最后一行列出了所有被测试的包。因为我们的目录在 $GOPATH 之外,也不属于任何模块,go 知道对于当前目录没有导入路径,所以 go 创建了一个基于当前文件路径的假模块:_/home/gopher/hello。

使用 go mod init 来把当前目录变成一个模块的根路径,然后再试一次 go test

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
$ go test
PASS
ok      example.com/hello    0.020s
$
复制代码

恭喜!你已经完成并测试了你的第一个 模块

go mod init 命令将以下内容写入 go.mod 文件中:

$ cat go.mod
module example.com/hello

go 1.12
$
复制代码

go.mod 文件仅出现在模块的根路径中。子目录下的包的导入路径为模块路径加上子目录路径。例如,我们新建一个 world 子目录,我们不需要在 world 中运行(也不想) go mod init 。这个包将自动成为模块 example.com/hello 的一部分,导入路径为 example.com/hello/world

添加依赖

Go 模块 的首要目标是提升使用别人写的代码(也是添加依赖)的体验。

让我们更新 hello.go ,导入 rsc.io/quote 依赖并使用它来实现 Hello:

package hello

import "rsc.io/quote"

func Hello() string {
  return quote.Hello()
}
复制代码

现在,让我们再次运行测试:

$ go test
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
PASS
ok      example.com/hello    0.023s
$
复制代码

go 使用 go.mod 中的特定版本的依赖模块来解析包导入。当它遇到一个不属于 go.mod 中任何一个模块中的导入包时,它会自动寻找包含该缺失包的模块,并把这个模块的最新版本( 最新版本 的定义为最新的一个被标记为 稳定(stable) 的版本,如果没有则为最新的 预览(prerelease) 版本,如果还是没有,则为最新的没有标记的版本)加入 go.mod 中。在我们的例子中,go test 解析新的 rsc.io/quote 导入为模块 rsc.io/quote v1.5.2 。它同时也下载了 rsc.io/quote 所需要的两个依赖: rsc.io/samplergolang.org/x/test 。只有直接依赖会出现在 go.mod 文件中:

$ cat go.mod
module example.com/hello

go 1.12

require rsc.io/quote v1.5.2
$
复制代码

再次运行 go test 不会重复以上的工作,因为 go.mod 已经被更新,而且能下载的模块也缓存到了本地(在 $GOPATH/pgk/mod 中):

$ go test
PASS
ok      example.com/hello    0.020s
$
复制代码

注意,虽然go 命令能轻松快速的添加一个新依赖,但这并非没有代价。你的模块现在依赖新的模块,需要注意很多特殊的点,例如:正确性、安全性以及合适的许可证,以上仅仅是几个例子。更多思考,见 Russ Cox 的这篇文章, Our Software Dependency Problem

就如我们上面看到的,添加一个直接依赖往往会带来其他的非直接依赖。命令 go list -m 会列出当前模块和所有的依赖模块:

$ go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$
复制代码

go list 输出中第一行永远是当前模块,也叫 主模块 。接下来是按模块路径 排序 的依赖模块。

上面 golang.org/x/text version v0.0.0-20170915032832-14c0d48ead0c 是一个 pseudo-version 的例子,这是 go 对于没有标签的版本语法。

还有一点关于 go.mod ,就是 go 会维护一个名为 go.sum 文件,该文件包含目标依赖特定版本的哈希值:

$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
$
复制代码

go 使用这个 go.sum 来确保将来下载的依赖版本和第一次下载的是一模一样的,以确保你项目中依赖的模块没有改变,无论是恶意的或意外的还是其他原因导致的改变。 go.modgo.sum 都应该加入到版本控制中。

更新依赖

Go 模块中,版本号是语义化的。语义化版本号有三个部分:major、minor、patch。例如,版本号 v0.1.2 中,major 版本是 0,minor 版本是 1,patch 版本是 2。让我们试一试 minor 版本更新。下一节,进行 major 版本更新。

go list -m all 的输出中,我们可以看到,我们使用的是没有版本号的 golang.org/x/test 。让我们来升级到最新的版本,看看是否还能正常工作:

$ go get golang.org/x/text
go: finding golang.org/x/text v0.3.0
go: downloading golang.org/x/text v0.3.0
go: extracting golang.org/x/text v0.3.0
$ go test
PASS
ok      example.com/hello    0.013s
$

复制代码

哇,所有都通过了。让我们看看 go list -m allgo.mod 文件:

$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0
$ cat go.mod
module example.com/hello

go 1.12

require (
    golang.org/x/text v0.3.0 // indirect
    rsc.io/quote v1.5.2
)
$
复制代码

golang.org/x/test 包已经更新到最新的带版本号的版本(v0.3.0)。 go.mod 中也更新到了 v0.3.0。 indirect 注释表示它不是被本模块直接使用,而是被本模块依赖的模块使用。详情见 go modules

现在,让我们来试下更新 rsc.io/sampler 的 minor 版本。和上面一样,运行 go get 然后运行测试:

$ go get rsc.io/sampler
go: finding rsc.io/sampler v1.99.99
go: downloading rsc.io/sampler v1.99.99
go: extracting rsc.io/sampler v1.99.99
$ go test
--- FAIL: TestHello (0.00s)
    hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
FAIL
exit status 1
FAIL    example.com/hello    0.014s
$
复制代码

啊,测试失败。输出表明最新版的 rsc.io/sampler 与我们的用法不兼容。那让我们看看这个模块有哪些可用的版本:

$ go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
$
复制代码

我们之前使用的就是 v1.3.0 ,而 v1.99.99 与我们不兼容。那试一试 v1.3.1

$ go get rsc.io/sampler@v1.3.1
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
$ go test
PASS
ok      example.com/hello    0.022s
$
复制代码

注意, go get 中使用 @v1.3.1 来显示的指明版本,默认是 @latest ,也就是最新的版本。

添加一个新 major 版本依赖

我们来添加一个新函数: func Proverb 返回一个 Go 并发 proverb。这是通过调用 rsc.io/quote/v3 中的 quote 实现的。首先,我们在 hello.go 中添加一个新函数:

package hello

import (
  "rsc.io/quote"
  quoteV3 "rsc.io/quote/v3"
)

func Hello() string {
  return quote.Hello()
}

func Proverb() string {
  return quoteV3.Concurrency()
}
复制代码

然后在 hello_test.go 中添加一个测试:

func TestProverb(t *testing.T) {
  want := "Concurrency is not parallelism."
  if got := Prover(); got != want {
    t.Error("proverb() = %q, want %q", got, want)
  }
}
复制代码

然后,我们运行测试:

$ go test
go: finding rsc.io/quote/v3 v3.1.0
go: downloading rsc.io/quote/v3 v3.1.0
go: extracting rsc.io/quote/v3 v3.1.0
PASS
ok      example.com/hello    0.024s
$
复制代码

注意,我们的模块现在同时依赖 rsc.io/quotersc.io/quote/v3 :

$ go list -m rsc.io/q...
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
$
复制代码

Go 模块的每一个不同的 major 的版本(v1, v2等等)都使用不同的模块路径:从 v2 开始,路径必须以 major 版本号结尾。例如, rsc.io/quote 的 v3 版本不同于 rsc.io/quote ,它们通过模块路径来区别。这种约定惯例称为 语义化导入版本(semantic import versioning) ,它给不兼容的包(不同的 major 版本号)不同的名字。与之对比, rsc.io/quote 的 v1.6.0 版应该向后兼容 v1.5.2 版,所以它能重用 rsc.io/quote 这个名字。(上节中, rsc.io/sampler 的 v1.99.99 版应该向后兼容 v1.3.0 版,但由于 bug 或者客户端错误的使用导致不兼容。)

go 构建的时候某个模块路径最多只能有一个版本,也就是每个 major 版本最多只能有一个:一个 rsc.io/quote ,一个 rsc.io/quote/v2 ,一个 rsc.io/quote/v3 等等。这给模块作者一个模块可能重复使用的清晰规则:同时使用 rsc.io/quote 的 v1.5.2 和 1.6.0 是不允许的。同时,允许使用同一模块的不同 major 版本(因为它们有不同的模块路径),这给模块使用者升级到一个新的 major 版本的能力。例如,我们想使用 rsc.io/quote/v3 中的 quote.Concurrency ,还我们还不准备对 rsc.io/quote v1.5.2 的使用进行升级合并。这种平滑升级合并是非常重要的,尤其是在大型软件或代码库中。

更新一个依赖到最新的 major 版本

现在让我们来完成从 src.io/quotesrc.io/quote/v3 的升级转化。因为 major 版本号变了,那可能存在一些 API 被移除,或重命名,或有不兼容的变化。通过文档,我们发现 Hello 变成了 HelloV3

$ go doc rsc.io/quote/v3
package quote // import "rsc.io/quote/v3"

Package quote collects pithy sayings.

func Concurrency() string
func GlassV3() string
func GoV3() string
func HelloV3() string
func OptV3() string
$
复制代码

我们将 hello.go 中的 quote.Hello() 升级为 quoteV3.HelloV3()

package hello

import quoteV3 "rsc.io/quote/v3"

func Hello() string {
  return quoteV3.HelloV3()
}

func Proverb() string {
  return quoteV3.Concurrency()
}
复制代码

现在,我们也不用重命名导入了,所以去掉重命名:

package hello

import "rsc.io/quote/v3"

func Hello() string {
  return quote.HelloV3()
}

func Proverb() string {
  return quote.Concurrency()
}
复制代码

最后,运行测试看看一切都是否正确:

$ go test
PASS
ok      example.com/hello       0.014s
复制代码

移除未使用的依赖

我们移除了所有关于 rsc.io/quote 的使用,但这个模块依然在 go list -mgo.mod 文件中:

$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello

go 1.12

require (
    golang.org/x/text v0.3.0 // indirect
    rsc.io/quote v1.5.2
    rsc.io/quote/v3 v3.0.0
    rsc.io/sampler v1.3.1 // indirect
)
$
复制代码

这是为什么?因为构建单个包,比如 go buildgo test ,能轻松的发现缺什么包并需要加入到依赖中,却不能发现能安全移除的包。只有在检查一个模块中所有的包之后,才能确定哪些依赖能安全移除。普通的构建命令不会加载所有的包,因而也不能安全的移除没有使用的依赖。

go mod tidy 命令能清理这些没有使用的依赖:

$ go mod tidy
$ go list -m all
example.com/hello
golang.org/x/text v0.3.0
rsc.io/quote/v3 v3.1.0
rsc.io/sampler v1.3.1
$ cat go.mod
module example.com/hello

go 1.12

require (
    golang.org/x/text v0.3.0 // indirect
    rsc.io/quote/v3 v3.1.0
    rsc.io/sampler v1.3.1 // indirect
)

$ go test
PASS
ok      example.com/hello    0.020s
$
复制代码

总结

Go 模块 是 Go 语言未来的依赖管理系统。模块功能在所有支持的 Go 版本中都是可用的(现在为,Go 1.11,Go 1.12)。

本文介绍了使用 Go 模块的工作流:

  • go mod init 创建一个新模块,并初始化 go.mod 文件来描述该模块。
  • go bulidgo test 还有其他的包构建命令来添加一个新的依赖到 go.mod 中。
  • go list -m all 打印当前模块的依赖。
  • go get 改变依赖的版本(或者添加新依赖)。
  • go mod tidy 移除未使用的依赖。

我们鼓励你在个人本机开发中使用 Go 模块,并且把 go.modgo.sum 加入到你的项目中。提供反馈和帮助 Go 中依赖管理完善,请发送bug 报告 或使用报告 给我们。

感谢你的反馈以及对模块功能提升的帮助。

译者总结

本文主要讲 Go 模块的基本用法,关于模块的更多概念和用法见 Github Page


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Four

The Four

Scott Galloway / Portfolio / 2017-10-3 / USD 28.00

NEW YORK TIMES BESTSELLER USA TODAY BESTSELLER Amazon, Apple, Facebook, and Google are the four most influential companies on the planet. Just about everyone thinks they know how they got there.......一起来看看 《The Four》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具