Go 模块--开始使用 Go Modules

Go的1.11和1.12版本包括对模块--新的Go依赖管理系统的初步支持,使依赖版本信息变得明确且更易于管理。这篇博客文章介绍了开始使用模块所需的基本操作。

模块是存储在根目录有一个 go.mod文件的文件树中的 Go 包(package)的集合。go.mod文件定义了模块的module path(也是模块根目录的导入路径)以及模块依赖的其他模块的要求,满足了依赖要求模块才能被成功构建起来。每个依赖模块的要求被写为一个模块路径和相应的模块版本。

下面展示了一个简单的go.mod文件

module example.com/hello

go 1.12

require rsc.io/quote v1.5.2

从Go 1.11开始,当当前目录或任何父目录有go.mod时,只要该目录位于$GOPATH/src之外,go命令就可以使用模块。 (在$ GOPATH/src内部,出于兼容性考虑,即使找到了go.mod,go命令仍然在旧的GOPATH模式下运行。)从Go 1.13开始,模块模式将是所有开发的默认模式。

本文介绍了使用模块开发Go代码时出现的一系列常见操作:

创建一个新模块。

添加模块的依赖项。

升级模块的依赖项。

增加依赖项的主版本。

将依赖项升级到新的主版本。

删除未使用的依赖项。

创建一个新模块

在$GOPATH/src之外的某个地方创建一个新的空目录,然后在新目录下创建一个新的源文件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)

    }

}

假设我们新建的目录为/home/gopher/hello,此时该目录包含一个包,而不是模块,因为目录中没有go.mod文件。使用 go 命令运行测试会看到:

$ go test

PASS

ok      _/home/gopher/hello    0.020s

$

输出的最后一行汇总了整个包的测试信息。因为我们工作在$GOPATH和任意模块之外,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 mod init命令编写了一个go.mod文件:

$ cat go.mod

module example.com/hello

go 1.12

$

go.mod仅出现在模块的根目录中。位于子目录中的包的导入路径将由模块路径加上子目录路径组成。比如说如果我们创建了一个子目录world无需(也不希望)在其中运行go mod init。该包将自动被识别为example.com/hello模块的一部分,导入路径为example.com/hello/world。

现在再运行go test其运行结果如下:

$ go test

PASS

ok      example.com/hello    0.020s

$

现在输出中的导入路径变成了example.com/hello,不知不觉中就编写并测试了我们的第一个go模块。

添加模块依赖

Go模块的主要动机是改善管理使用其他开发者编写的代码(代码依赖)的体验。 让我们更新hello.go以导入rsc.io/quote并使用它来实现Hello 函数:

package hello

import "rsc.io/quote"

func Hello() string {

    return quote.Hello()

}

现在再次运行go test:

$ 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中的任何模块提供的包的导入时,go命令将自动查找包含该软件包的模块,使用其最新的稳定版本,并将其添加到go.mod中。 在我们的示例中,go test将新的导入rsc.io/quote解析为rsc.io/quote v1.5.2模块,它还下载了rsc.io/quote使用的两个依赖项,即rsc.io/sampler和golang.org/x/text。但是只有直接依赖项被记录在go.mod文件中:

$ cat go.mod

module example.com/hello

go 1.12

require rsc.io/quote v1.5.2

$

再次运行go test命令不会重复上面的依赖下载工作,因为go.mod现在是最新的,并且下载的模块已本地缓存在$ GOPATH/pkg / mod中了。

正如我们在上面看到的,添加一个直接依赖项通常也会带来其他间接依赖项。命令go list -m all列出当前模块及其所有依赖项:

$ 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的输出中,当前模块也被称为主模块,总是会出现在第一行,后面跟随的是根据模块路径排序后展示的依赖项:

除了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.sum并不是类似package-lock.json的包管理器锁文件**,它是一个构建状态跟踪文件。它会记录当前模块所有的直接和间接依赖,以及这些依赖的校验和,从而提供一个可以100%复现的构建过程并对构建对象提供安全性的保证。所以应该将go.mod和go.sum都添加到版本控制中。go.sum同时还会保留过去使用的包的版本信息,以便日后可能的版本回退,这一点也与普通的锁文件不同。所以go.sum并不是包管理器的锁文件。

更新依赖

对于Go模块,使用语义版本标记引用模块版本。语义版本包括三个部分:主要,次要和补丁。例如,对于v0.1.2,主要版本为0,次要版本为1,补丁版本为2。让我们逐步进行几个次要版本升级。在下一节中,我们将考虑进行主要版本升级

从go list -m all的输出中,我们可以看到我们正在使用未标记版本的golang.org/x/text。让我们升级到最新的标记版本,并测试一切是否正常:

$ 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 all的输出和go.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/text软件包已升级到最新的标记版本(v0.3.0)。 go.mod文件中golang.org/x/text也已更新为指定的v0.3.0。indirect注释指明依赖项不被当前模块直接使用,而是由其依赖的模块所使用的。

现在,让我们尝试升级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

$

我们将 rsc.io/sampler升级到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。通常,传递给get的每个参数都可以采用显式形式。默认值为@latest,它将解析为先前定义的最新版本。

增加依赖的主版本

让我们在包中添加一个新函数:函数Proverb通过调用quote.Concurrency返回Go并发谚语(就是Pike说在某年 Go 开发大会上说的金句:"Concurrency is not parallelism"),这是由rsc.io/quote/v3模块提供的。首先,我们更新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 := Proverb(); got != want {

        t.Errorf("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

$

可以看到 go 命令下载安装了rsc.io/quote/v3模块,现在我们的模块同时依赖了 rsc.io/quote和rsc.io/quote/v3:

$ go list -m rsc.io/q...

rsc.io/quote v1.5.2

rsc.io/quote/v3 v3.1.0

$

Go模块的每个不同的主要版本(v1,v2等)都使用不同的模块路径:从v2开始,该路径必须以主要版本结尾。在示例中,rsc.io/quote的v3版本的模块路径不再是rsc.io/quote,而是rsc.io/quote/v3。此约定称为语义导入版本控制,它为不兼容的程序包(具有不同主要版本的程序包)提供了不同的名称。相反,rsc.io/quote的v1.6.0应该与v1.5.2向后兼容,因此它重用了名称rsc.io/quote。

go命令要求每个主版本模块路径不可重复,每个主要版本的至多:一个rsc.io/quote,一个rsc.io/quote/v2,一个rsc.io/quote/v3,依此类推。这为模块作者提供了关于可能重复单个模块路径的明确规则:程序无法同时使用rsc.io/quote v1.5.2和rsc.io/quote v1.6.0来构建。同时,允许模块的不同主要版本(因为它们具有不同的路径)使模块使用者可以逐步升级到新的主要版本。在此示例中,我们想使用rsc/quote/v3 v3.1.0中的quote.Concurrency,但尚未准备好迁移rsc.io/quote v1.5.2的使用。在大型程序或代码库中,增量迁移的能力尤其重要。

将依赖项升级到新的主版本

让我们完成从使用rsc.io/quote两个版本的包到仅使用rsc.io/quote/v3的转换。由于版本的重大更改,我们应该期望某些API可能已以不兼容的方式被删除,重命名或以其他方式更改。阅读文档,我们可以看到Hello已经变成HelloV3:

$ go doc rsc.io/quote/v3

package quote // import "rsc.io/quote"

Package quote collects pithy sayings.

func Concurrency() string

func GlassV3() string

func GoV3() string

func HelloV3() string

func OptV3() string

$

我们可以把hello.go中对qoute.Hello()的调用更新为使用quoteV3.HelloV3(),现在已经不需要对 v3 版本的导入路径重命名了所以我们撤销包的重命名(注意默认包名不会包含版本后缀)。

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 -m all的输出和go.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 build或go test)可以轻松判断出来缺少某些内容并需要添加,但无法确定某些内容是否可以安全删除。只有在检查模块中的所有软件包以及这些软件包的所有可能的构建标记组合之后,才能删除依赖项。普通的build命令不会加载此信息,因此它不能安全地删除依赖项。

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依赖管理的未来。从 Go1.11都提供模块功能。 这篇文章介绍了使用Go模块的这些工作流程:

go mod init 创建一个新模块,初始化描述它的go.mod文件。

go buil,go test和其他程序包构建命令根据需要向go.mod添加新的依赖项。

go list -m all打印当前模块的依赖关系。

go get更改所需依赖的版本(或添加新的依赖)。

go mod tidy删除未使用的依赖项。

最后:

为了帮助大家少走弯路,我总结出一个Java程序员的工作2-5年成长路线图。

上面都是自己整理好的!我就把资料贡献出来给有需要的人!顺便求一波关注,哈哈~各位小伙伴关注我后私信【Java】就可以免费领取哒

原文作者: kevinyan

原文链接:https://juejin.im/post/5e119b196fb9a048246197d4

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容