Go Modules 初探

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

内容简介:这篇文章是对 Go 官方依赖管理工具这里引用

这篇文章是对 Go 官方依赖管理工具 Go Modules 机制的一次初探,教会你如何使用 Go modules

Go Modules 初探

一、准备

1. Go Modules 定义

这里引用 TonyBai老师 初窥Go module 一文的总结:

通常我们会在一个repo(仓库)中创建一组Go package,repo的路径比如: github.com/bigwhite/gocmpp , 会作为go package的导入路径(import path),Go 1.11给这样的一组在同一repo下面的packages赋予了一个新的抽象概念: module,并启用一个新的文件go.mod记录module的元信息。

并且一个repo可以拥有多个module,如下:

Go Modules 初探

图:single repo,single module

Go Modules 初探

图:single monorepo,multiple modules

2. 准备 Go 语言环境

需要安装 Go 1.11 以上的版本,当前最新版 1.12.5

3. Go Modules 配置

Go modules 机制作为 1.11 才正式引入的特性,当前还有一个特性开关: GO111MODULE ,对应的值有三个: auto/on/off ,这会影响 Go 所有命令 依赖管理 的模式选择(即 GOPATH mode/module-aware mode ),这三个值的作用简要介绍如下:

1. off: GOPATH mode,和之前版本一样,默认查找vendor和GOPATH目录

2. on:module-aware mode,使用 go modules,将会忽略GOPATH目录,使用是go mod命令的缓存目录($GOPATH/pkg/mod)

3. auto:如果当前目录不在 $GOPATH 并且 当前目录(或者父目录)下有go.mod文件,则使用 GO111MODULE, 否则仍旧使用 GOPATH mode。

注意:当前版本的默认值是 auto ,从 Go 1.13 以后,将会默认设置为 on ,即 module-aware mode

二、实战

学习最好的方式就是实战!这里采用默认设置( GO111MODULE=auto ),将在 GOPATH 目录外创建一个新的工程( single Module ),使用 Go modules 机制,一步步教会你如何使用 Go modules。

  1. 创建 Module

    Windows 为例( LinuxMac 同理),在我的系统中 GOPATH=D:\go ,接下来就在 GOPATH 之外的目录(比如 D:\gotest )创建工程:

$ mkdir testgomod
$ cd testgomod

继而在当前目录写一个简单的 Go 程序 hello.go

package testgomod

import "fmt" 

// Say hello
func Hello(who string) string {  
   return fmt.Sprintf("Hello, %s", who)
}

程序创建完成,但这还不是一个 module ,需要使用 go mod 命令初始化成一个 module

$ go mod init github.com/bingohuang/testgomod
go: creating new go.mod: module github.com/bingohuang/testgomod

在该目录下,会生成了一个新的文件 go.mod

module github.com/bingohuang/testgomod

go 1.12

到此,我们的工程就是一个 go module 了,区别就在于是否有这个 go.mod 文件。

接下来可以将工程推送到远程仓库:

$ git init
$ git add .
$ git commit -m "init commit"
$ git push -u origin master

到这步,其他人就可以使用这个 go package ,比如使用最常用的 go get 方式: go get -v github.com/bingohuang/testgomod

这会拉取 master 分支的最新代码,但实际上这么做是有隐患的,因为我们不知道代码作者是否会修改代码,造成使用上的不兼容,而且也不能很好的管理代码版本。

2. 使用 Module

有了 Go modules 机制,就可以很方便的解决以上该问题。

Go modules 的主要设计理念包括: Semantic Import VersioningMinimal Version Selection 等,所以我们最好也对我们的 module 打上版本。

使用 git tag 的方式打版本,发布 1.0.0

$ git tag v1.0.0
$ git push --tags

这个将会在当前 commit 上,创建一个 tag v1.0.0 ,并推送至远程仓库。

接下来,我们创建一个 Go 程序,使用上面我们推送的新包: github.com/bingohuang/testgomod

$ mkdir usegomod
$ cd usegomod
package main

import (  
    "fmt"
    "github.com/bingohuang/testgomod"
)

func main() {  
    fmt.Println(testgomod.Hello("bingohuang"))
}

如果是以前,你通常需要通过 go get github.com/bingohuang/testgomod 来下载依赖包,不过有了 go modules ,将无需这么做,而且更加方便。

首先,同样使用 go mod 命令初始化代码工程,使其成为一个 Go Module:

go mod init github.com/bingohuang/usegomod

这样就生成一个我们熟知的文件 go.mod ,内容如下:

module github.com/bingohuang/usegomod

go 1.12

接下来就是见证奇迹的时刻,使用原生 go build 命令编译该工程:

$ go build
go: finding github.com/bingohuang/testgomod v1.0.0  
go: downloading github.com/bingohuang/testgomod v1.0.0  
go: extracting github.com/bingohuang/testgomod v1.0.0

可以看到,go 命令会自动的获取和下载远程三方包,此时再看 go.mod 文件,发现有了新的变化:

module github.com/bingohuang/usegomod

go 1.12

require github.com/bingohuang/testgomod v1.0.0

并且还生成了一个新的文件: go.sum ,包含了所引用包的 hash 值,保证我们获取的是正确的版本和文件。

github.com/bingohuang/testgomod v1.0.0 h1:JdNLPaJoAvogFRBWAyyr5jrLAsKFv7axKYDOOeFUbOo=  
github.com/bingohuang/testgomod v1.0.0/go.mod h1:dSAc0893lV3VXfM/YX6n2s+lW3CxIXytDP//OgpmKFo=

同时,还可以使用 go list -m all 命令来列出当前的 module 信息和它的依赖包:

$ go list -m all
github.com/bingohuang/usegomod  
github.com/bingohuang/testgomod v1.0.0

3. 更新 Module

A、小更新

假设我们需要修复点小 bug,更新这个库: github.com/bingohuang/testgomodgit diff 如下:

+// Say hello
 func Hello(who string) string {
-       return fmt.Sprintf("Hello, %s", who)
+       return fmt.Sprintf("Hello, %s!", who)
 }

并发布新版 v1.0.1

$ git commit -m "update package" hello.go
$ git tag v1.0.1
$ git push --tags origin master

接下来,我们该如何在使用该包的地方( github.com/bingohuang/usegomod )做更新呢?

在此之前,先补充一个知识点:默认情况下,Go modules 是不会自动更新的,需要我们主动更新包依赖,同样还是使用 go get ,有三种更新方式:

go get -u
go get -u=patch
go get package@version

对我们要从 v1.0.0 更新到 v1.0.1 来说,以下几种更新方式都可行:

$ go get -u
$ go get -u=patch
$ go get github.com/bingohuang/testgomod@v1.0.1

运行之后, go.modgo.sum 都会更新,其中 go.mod 关键变动如下:

require github.com/bingohuang/testgomod v1.0.1

B、大变动

再回到我们引用的包: github.com/bingohuang/testgomod ,此时,我们需要做一个大的变更,甚至会影响到函数签名,比如给函数添加参数和返回值:

// Say hello
func Hello(who, lang string) (string, error) {  
    switch lang {
    case "en":
        return fmt.Sprintf("Hello, %s!", who), nil
    cse "cn":
        return fmt.Sprintf("你好, %s!", who), nil
    default:
        return "", errors.New("unknown language")
    }
}

这样的改动,将导致新的 API 不兼容之前版本,所以包的版本需要升级到 v2.0.0

为了给使用我们 package 的程序做好兼容,避免直接出现不兼容错误,需要在我们的 module 名称上加上一个版本路径,比如 V2

github.com/bingohuang/testgomod/v2

接下来还是和之前一样,提交、打tag 并 push:

$ git commit hello.go -m"change func Hello"
$ git commit go.mod -m "Bump version to v2"
$ git tag v2.0.0
$ git push --tags origin master

此时,对于其它使用该包的程序,比如: github.com/bingohuang/usegomod ,还是能正常运行,因为我们一直会使用 v1.0.1 ,即使使用 go get -u 也不会更新到 v2.0.0

如果,我们就是想将 gotestmod 库升级到最新的 v2.0.0 版本,该如何做呢?

很简单,修改引入的 package函数 即可:

package main

import (  
    "fmt"
    "github.com/bingohuang/testgomod/v2"
)

func main() {  
    hi, err := testgomod.Hello("bingohuang", "cn")
    if err != nil {
        panic(err)
    }
    fmt.Println(hi)
}

注意: github.com/bingohuang/testgomod/v2 虽然是以 v2 结尾,但是 Go 引用的包名还是 testgomod ,非常方便。

这时,我们再运行 go build 或者 go run . ,将会自动为我们拉去 v2.0.0 版本:

$ go run .
go: downloading github.com/bingohuang/testgomod/v2 v2.0.0  
go: extracting github.com/bingohuang/testgomod/v2 v2.0.0  
你好, bingohuang!

并且 go.modgo.sum 都有所更新,其中 go.mod 关键变动如下:

require (  
    github.com/bingohuang/testgomod v1.0.1
    github.com/bingohuang/testgomod/v2 v2.0.0
)

甚至,我们还可以同时使用这两个不兼容版本,如下:

package main

import (  
    "fmt"
    "github.com/bingohuang/testgomod"
    testgomodV2 "github.com/bingohuang/testgomod/v2"
)

func main() {  
    fmt.Println(testgomod.Hello("bingohuang"))
    hi, err := testgomodV2.Hello("bingohuang", "cn")
    if err != nil {
        panic(err)
    }
    fmt.Println(hi)
}

这个方式可以用来做升级过渡或调试。

4. 清理 Module

回到上文,我们修改了引用 package 路径,仅使用 v2.0.0 版本的情况,其中 go.mod 的关键变动如下:

require (  
    github.com/bingohuang/testgomod v1.0.1
    github.com/bingohuang/testgomod/v2 v2.0.0
)

可以看到,这里未被使用的 v1.0.1 版本并未自动移除,Go 给我们提供了一个命令来主动移除不用的依赖,如下:

$ go mod tidy

5. Vender 机制

Go modules 机制会忽略 vendor 目录,甚至在最初的设计中, Go team 有想彻底废除 vendor ,但 vendor 毕竟使用多年,影响很大,在社区的反馈下,当前得以保留,并且 Go modules 支持将该 module 下所有的依赖都 copy 一份到 module 根目录vendor目录 下,命令同样很简单:

$ go mod vendor

这样,如果你不在 module-aware 模式下,就可以使用 vendor 目录来编译:

$ go build

即使在 module-aware 模式下,也可以通过如下命令来使用 vendor 目录构建:

$ go build -mod vendor

3. 总结:

Go Modules 机制作为官方正式推出的包依赖管理机制,必定会步入大众的舞台,成为包管理 工具 的主导工具,并且会越来越完善,在此极力推荐大家学习使用。

最后,总结几条好的工程实践:

  • 通常我们在 GOPATH 目录下开发居多,在当前版本下想使用 module-aware 模式,只需打开特性开关: export GO111MODULE=auto
  • 配合 GOPROXY ,可以达到更好的效果,比如无需配置个人代理了,就可以方便下载任何 Go 第三方代码。比如可以采用开源工具 athens 搭建了一个 Go Proxy 服务 ,简要使用方法如下:
# 开启 Go Modules 机制
$ export GO111MODULE=on
# 配置 Go Proxy
$ export GOPROXY=http://go_proxy_server
# 进入工程目录
$ cd 工程目录
# 初始化 Go Module
$ go mod init 你的module名
# 使用 go 相关命令即可,包括 go build/go run/go test/go get 等
$ go build
  • JetBrain 推出的 Go IDE Goland 也有对 Go modules 的集成支持,可以参考这篇文章: Working with Go Modules ,可能会获得一些有趣的 tips

参考资料

  1. Using Go Modules
  2. Introduction to Go Modules
  3. 初窥Go module

以上所述就是小编给大家介绍的《Go Modules 初探》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

这就是OKR

这就是OKR

【美】约翰·杜尔(John Doerr) / 曹仰锋、王永贵 / 中信出版社 / 2018-12 / 68.00元

这本书是传奇风险投资人约翰·杜尔的作品,揭示了OKR这一目标设定系统如何促使英特尔、谷歌等科技巨头实现爆炸性增长,以及怎样促进所有组织的蓬勃发展。 20世纪70年代,在英特尔担任工程师时,杜尔首次接触到OKR。之后,作为一个风险投资人,杜尔不遗余力地将这一管理智慧,分享给50多家公司和机构,包括谷歌、亚马逊、领英、脸书、比尔及梅琳达·盖茨基金会,甚至摇滚歌手波诺的公益项目。在杜尔的帮助下,任......一起来看看 《这就是OKR》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码