zoukankan      html  css  js  c++  java
  • 浅析 golang module

    什么是 module?module 解决了什么问题?

    module 代表一个版本管理单元,它包括一个或者多个 packages。

    一般来说,一个版本控制仓库(比如 golang.org/x/text )包含一个 module(也可以包含多个 module,但是通常会带来一些复杂性)。

    module 在 Go1.11 版本发布,它的前身是 vgo。 在 Go1.9.7+ 版本和 1.10.3+ 版本做了对 module 的部分向后兼容。

    module 机制会在项目的根目录中添加 go.mod, 该文件用来记录项目依赖的 modules 的版本。

    module 的出现主要是为了解决以下问题:

    1. 版本依赖管理

    设想一下,如果有 3 个包, 分别为 foo1, foo2, foo3。

    foo1 依赖 foo3 的版本 v1.0.1 (后续简写为 foo3@v1.0.1), foo2 依赖 foo3@v1.0.2。

    现在我们需要实现一个功能,需要同时使用 foo1 和 foo2 两个包, 那我们应该使用什么版本的 foo3 呢?

    2. 解除对 GOPATH 的依赖

    在 Go1.11 版本之前,所有的 go 代码都要放到 $GOPATH/src 目录下面, 以便 import 能找到对应的包。

    而 module 的出现,可以让我们将 go 代码放到任何地方。


    语义导入版本控制

    语义导入版本控制 (Semantic Import Versioning),是使用 module 必须要遵循的一些规定。

    简单说来,就是需要 modules 的不同版本满足一些兼容规则。 比如: v1.5.4 版本需要向前兼容 v1.5.0、v1.4.0 甚至 v1.0.0 版本, 但不用兼容 v0.0.9 版本。

    另外语义导入版本控制还约定了版本不能向前兼容时,modules 下的包的导入路径的变化。

    下面详细介绍具体要满足哪些规则, 以及 golang 工具链是如何选择版本的:

    1. semver 规范

    semver 是一个语义化版本规范,是 modules 需要遵从的。

    sember 的版本格式为:主版本号.次版本号.修订号,版本号递增规则如下:

    • 主版本号:当你做了不兼容的 API 修改
    • 次版本号:当你做了向下兼容的功能性新增,
    • 修订号:当你做了向下兼容的问题修正。

    例如: 现在最新的版本号如果是 v1.4.9。 在此基础上,

    • 如果要对接口作出参数或返回值调整,导致依赖这个项目的代码需要修改它们的代码。那么下一个版本号应该是 v2.0.0
    • 如果是增加新的功能,不影响旧接口。那么下一个版本号应该是 v1.5.0
    • 如果是修改了一些 bug,而且可以向前兼容。那么下一个版本号应该是 v1.4.10

    具体规则可以参考 https://semver.org/

    2. Go 官方的 导入兼容规则

    如果新 package 和旧 package 拥有相同的导入路径, 那么新的 package 要兼容旧的 package。

    举个例子,比如你开发了一个 module (github.com/you/foo) 提供给用户使用,最初的时候你给这个 module 打了一个版本为 v1.0.0。并且直到 v1.5.9 为止没有出现过不能向前兼容的情况。

    但现在,你要发布一个全新的版本,从而不能向前兼容。所以 semver 规则,你需要将版本号定义成 v2.0.0。

    然而, 导入兼容规则 又给你加了一个新的限制,你的新版本不能向老版本兼容,所以你必须修改包路径为 github.com/you/foo/v2 (后文会详细介绍怎么修改包路径)。

    3. 版本选择算法

    在介绍版本选择算法之前, 让我们先了解一下 module 是怎么存储版本信息的:

    如果你在自己的 module 中 import 了一个公共 moduel (github.com/other/bar),那么你第一次执行 go build或者 go test 的时候,go 会帮你自动找出并且下载 github.com/other/bar 的最新版本。并且在 go.mod 中记录当前依赖的版本, 如 require github.com/other/bar v1.4.9。 如果你事先手动在 go.mod 中增加了 require github.com/other/bar v1.4.8, 那么此时你执行 go build 或者 go test 时, go 会使用 v1.4.8 版本的 module 来编译。

    那版本选择算法是什么呢?让我们先回到之前提出的那个问题:

    “ 如果有 3 个包, 分别为 foo1, foo2, foo3。 foo1 依赖 foo3@v1.0.1, foo2 依赖 foo3@v1.0.2。 现在我们需要实现一个功能,需要同时使用 foo1 和 foo2 两个包, 那我们应该使用什么版本的 foo3 呢?

    这里我们假设 foo1,foo2,foo3 都使用了 module,并且我们实现的这个功能也使用了 module (假设我们的 module 名字叫做 bar )

    对于这种情况,在 foo1 的根目录下, 有一个 go.mod 文件, 包括一行依赖信息; require foo3 v1.0.1。 在 foo2 的根目录下, 有一个 go.mod 文件, 包括一行依赖信息; require foo3 v1.0.2

    那么在编译我们自己的 module bar 时, 会使用哪个版本的 foo3 呢? 答案是 v1.0.2。

    将 golang 选择 foo3 的版本的算法叫做 最小版本选择算法

    它选出来的版本是所有 go.mod 文件(在这里包括 foo1, foo2 和 bar 下的 go.mod 文件) 中明确指定的最大版本。

    这里的最小的意思是 foo1 和 foo2 给出的依赖的版本都是最小化了的, 比如 foo1 依赖 foo3@v1.0.1, 那么根据 semver 规则, foo1 在 foo3@v1.0.2 下也可以正常工作, 因为 foo3@v1.0.2 是向前兼容了 foo3@v1.0.1 的。

    那么如果 foo2 依赖的是 foo3@v2.1.1, 我们编译 bar 时,会使用哪个版本的 foo3 呢? 答案是:v1.0.1 和 v2.1.1 。

    注意: 根据 导入兼容规则, v1.0.1 和 v2.1.1 使用的是不同的路径,一个是 v1.0.1 使用的是 foo3,而 v2.1.1 使用的是 foo3/v2。 所以可以同时存在于一次编译中。 而且 v2.1.1 是不能兼容 v1.0.1 的,所以 foo1 没法使用 v2.1.1 版本,因此也必须同时使用 foo3 的两个版本。

    关于 最小版本选择算法 的详细信息,参考: https://research.swtch.com/vgo-mvs

    4. “伪”版本

    如果一个 module 没有有效的 semver 版本,那么 go.mod 将通过一个叫做 “伪版本“ 的东西来记录版本。

    ”伪版本“ 的通常形式是 vX.0.0-yyyymmddhhmmss-abcdefabcdef。 比如 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

    其中 v0.0.0 表示 semver 版本号, 20170915032832 表示这个版本的时间。 14c0d48ead0c 表示这次提交的 hash。


    怎么使用?中国用户会遇到哪些问题?如何解决这些问题?

    这一节主要介绍怎么使用 go module,以及墙内用户怎么解决墙外的下载问题。

    先看一下官方给的一个例子:

    # 在 $GOPATH 外部创建一个目录
    $ mkdir -p /tmp/scratchpad/hello
    $ cd /tmp/scratchpad/hello
    
    # 初始化 module
    $ go mod init github.com/you/hello
    
    go: creating new go.mod: module github.com/you/hello
    
    # 依赖 module 写一段代码
    $ cat <<EOF > hello.go
    package main
    
    import (
        "fmt"
        "rsc.io/quote"
    )
    
    func main() {
        fmt.Println(quote.Hello())
    }
    EOF
    
    # 编译执行 
    $ go build 
    $ ./hello
    
    Hello, world.
    

    1. 命令介绍

    • go mod init github.com/my/mod 用来初始化一个 module 并且生成一个 go.mod 文件。
    $ go mod init github.com/my/hello
    go: creating new go.mod: module github.com/my/hello
    
    $ cat go.mod
    module github.com/my/hello
    
    go 1.12
    
    • go get github.com/some/pkg 下载最新版本的 module 以及它的所有依赖,并且在 go.mod 中增加对应的 require。 go get 不需要被显示执行,在执行 go build 和 go test 的时候,它会根据依赖自动执行。
    $ go get github.com/sirupsen/logrus
    go: finding github.com/sirupsen/logrus v1.3.0
    go: finding github.com/davecgh/go-spew v1.1.1
    go: finding golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33
    go: finding github.com/stretchr/objx v0.1.1
    go: finding golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
    go: finding github.com/konsorten/go-windows-terminal-sequences v1.0.1
    go: finding github.com/pmezard/go-difflib v1.0.0
    go: finding github.com/stretchr/testify v1.2.2
    go: downloading github.com/sirupsen/logrus v1.3.0
    go: extracting github.com/sirupsen/logrus v1.3.0
    go: downloading golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
    go: extracting golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
    go: downloading golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33
    go: extracting golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33
    

    执行完之后, modules 的文件被下载到 $GOPATH/pkg/mod 下,并且按照 pkg@v1.0.1 的方式命名。

    $ ls ~/go/pkg/mod/github.com/sirupsen
    logrus@v1.3.0
    
    ls ~/go/pkg/mod/golang.org/x/
    crypto@v0.0.0-20180904163835-0709b304e793	sys@v0.0.0-20180905080454-ebe1bf3edb33		text@v0.0.0-20170915032832-14c0d48ead0c
    

    go.mod 中增加了对应的 require:

    $ cat go.mod
    module github.com/my/hello
    
    go 1.12
    
    require github.com/sirupsen/logrus v1.2.0 // indirect
    
    • go get github.com/some/pkg@v1.0.1 下载指定版本的 module 以及它的所有依赖。
    $ go get github.com/sirupsen/logrus@v1.2.0
    go: finding github.com/sirupsen/logrus v1.2.0
    go: downloading github.com/sirupsen/logrus v1.2.0
    go: extracting github.com/sirupsen/logrus v1.2.0
    

    此时在 $GOPATH/pkg/mod 中下载了对应的文件,并且 go.mod 的 require 发生了变化:

    $ cat go.mod
    module github.com/my/hello
    
    go 1.12
    
    require github.com/sirupsen/logrus v1.2.0 // indirect
    
    • go get -u github.com/some/pkg 更新次版本号,由于主版本号的不兼容,所以不会更新主版本号。
    • go get -u=patch 更新修订号
    • go list -m all 查看所有依赖的 module 以及版本
    • go list -u -m all 查看可用的次版本号修订号的更新
    • go mod tidy 删除 go.mod 中没用到的 module

    3. goproxy 的使用

    国内用户在用 golang 的时候经常会遇到一个问题,就是下不下来代码。 在以前, 我们下载不了 googlesource.com 上的 go packages,通常都可以到 github 上面去克隆,然后放到 golang.org目录下面就可以了。

    但是 go module 的出现使我们的操作要变得很复杂了 (可以想象一下, 先 git clone, 然后 git checkout v1.1.1, 最后 copy 到 mod/pkg@v1.1.1 下)。

    最简单的方式是 export GOPROXY=https://goproxy.io。 设置 go 代理,一切搞定!这样下载的时候都通过 goproxy 来下载。


    怎么发布不兼容版本?

    根据前文的介绍,如果新版本不能兼容旧版本,那么就要使用新的主版本号和新的导入路径 。

    要提供新的主版本号并不困难,打个 tag 就是。

    那么怎么来提供新的导入路径呢?有两种方式:

    1. 就地修改

    只需要将 go.mod 中的 module github.com/you/mod 修改成 github.com/you/mod/v2 。然后修改本 module 内的所有 import 语句,添加 /v2。如 import "github.com/you/mod/v2/mypkg"。

    注意: 在 module 的 git(或者其他的版本控制) 仓库中,存在所有的提交, 所以其他依赖 v1..版本的 module 会自动使用旧版本。而依赖 v2.. 版本的 module 将会从 github.com/you/mod/ 中下载对应的版本,并且将 github.com/you/mod/ 下的所有包的路径对应成 github.com/you/mod/v2。

    2. 创建子目录

    另外一种方式是在 module 下创建一个 v2 目录, 然后将所有文件移动 v2 中,并且修改 go.mod 。 同时也需要修改所有相关的 import 语句。

  • 相关阅读:
    Sample XPS Documents Download
    触发器中的inserted表和deleted表
    Using freewheel.labs.autodesk.com to auto generate preview images of DWF files on your web site
    解除SQL对组件"Ad Hoc Distributed Queries"的"STATEMENT'OpenRowset OpenDatasource"的访问
    读写xps
    XPS文件,在Windows XP下的打开查看阅读和打印方法。
    Learning to Reference Inserted and Deleted Tables
    Get value from updated, inserted and deleted
    Reinstall Microsoft Helper Viewer
    如何查找文件的IFilter
  • 原文地址:https://www.cnblogs.com/anjiawei/p/10664975.html
Copyright © 2011-2022 走看看