go.mod文件中通过指令声明module信息,用于控制命令行工具进行版本选择。一共有四个指令可供使用:

  • module: 声明module名称;
  • require: 声明依赖以及其版本号;
  • replace: 替换require中声明的依赖,使用另外的依赖及其版本号;
  • exclude: 禁用指定的依赖;

其中modulerequire我们前面已介绍过,module用于指定module的名字,如module github.com/renhongcai/gomodule,那么其他项目引用该module时其import路径需要指定github.com/renhongcai/gomodulerequire用于指定依赖,如require github.com/google/uuid v1.1.1,该指令相当于告诉go build使用github.com/google/uuidv1.1.1版本进行编译。

本节开始介绍replace的用法,包括其工作机制和常见的使用场景,下一节再对exclude展开介绍。

replace 工作机制

顾名思义,replace指替换,它指示编译工具替换require指定中出现的包,比如,我们在require中指定的依赖如下:

module github.com/renhongcai/gomodule  

go 1.13  

require github.com/google/uuid v1.1.1

此时,我们可以使用go list -m all命令查看最终选定的版本:

[root@ecs-d8b6 gomodule]# go list -m all
github.com/renhongcai/gomodule
github.com/google/uuid v1.1.1

毫无意外,最终选定的uuid版本正是我们在require中指定的版本v1.1.1

如果我们想使用uuid的v1.1.0版本进行构建,可以修改require指定,还可以使用replace来指定。
需要说明的是,正常情况下不需要使用replace来修改版本,最直接的办法是修改require即可,虽然replace也能够做到,但这不是replace的一般使用场景。
下面我们先通过一个简单的例子来说明replace的功能,随即介绍几种常见的使用场景。

比如,我们修改go.mod,添加replace指令:

[root@ecs-d8b6 gomodule]# cat go.mod 
module github.com/renhongcai/gomodule

go 1.13

require github.com/google/uuid v1.1.1

replace github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0

replace github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0指定表示替换uuid v1.1.1版本为 v1.1.0,此时再次使用go list -m all命令查看最终选定的版本:

[root@ecs-d8b6 gomodule]# go list -m all 
github.com/renhongcai/gomodule
github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0

可以看到其最终选择的uuid版本为 v1.1.0。如果你本地没有v1.1.0版本,你或许还会看到一条go: finding github.com/google/uuid v1.1.0信息,它表示在下载uuid v1.1.0包,也从侧面证明最终选择的版本为v1.1.0。

到此,我们可以看出replace的作用了,它用于替换require中出现的包,它正常工作还需要满足两个条件:

第一,replace仅在当前module为main module时有效,比如我们当前在编译github.com/renhongcai/gomodule,此时就是main module,如果其他项目引用了github.com/renhongcai/gomodule,那么其他项目编译时,replace就会被自动忽略。

第二,replace指定中=>前面的包及其版本号必须出现在require中才有效,否则指令无效,也会被忽略。
比如,上面的例子中,我们指定replace github.com/google/uuid => github.com/google/uuid v1.1.0,或者指定replace github.com/google/uuid v1.0.9 => github.com/google/uuid v1.1.0,二者均都无效。

replace 使用场景

前面的例子中,我们使用replace替换require中的依赖,在实际项目中replace在项目中经常被使用,其中不乏一些精彩的用法。
但不管应用在哪种场景,其本质都一样,都是替换require中的依赖。

替换无法下载的包

由于中国大陆网络问题,有些包无法顺利下载,比如golang.org组织下的包,值得庆幸的是这些包在GitHub都有镜像,此时
就可以使用GitHub上的包来替换。

比如,项目中使用了golang.org/x/text包:

package main

import (
    "fmt"

    "github.com/google/uuid"
    "golang.org/x/text/language"
    "golang.org/x/text/message"
)

func main() {
    id := uuid.New().String()
    fmt.Println("UUID: ", id)

    p := message.NewPrinter(language.BritishEnglish)
    p.Printf("Number format: %v.\n", 1500)

    p = message.NewPrinter(language.Greek)
    p.Printf("Number format: %v.\n", 1500)
}

上面的简单例子,使用两种语言language.BritishEnglishlanguage.Greek分别打印数字1500,来查看不同语言对数字格式的处理,一个是1,500,另一个是1.500。此时就会分别引入"golang.org/x/text/language""golang.org/x/text/message"

执行go getgo build命令时会就再次分析依赖情况,并更新go.mod文件。网络正常情况下,go.mod文件将会变成下面的内容:

module github.com/renhongcai/gomodule

go 1.13

require (
    github.com/google/uuid v1.1.1
    golang.org/x/text v0.3.2
)

replace github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0

我们看到,依赖golang.org/x/text被添加到了require中。(多条require语句会自动使用()合并)。此外,我们没有刻意指定golang.org/x/text的版本号,Go命令行工具根据默认的版本计算规则使用了 v0.3.2版本,此处我们暂不关心具体的版本号。

没有合适的网络代理情况下,golang.org/x/text 很可能无法下载。那么此时,就可以使用replace来让我们的项目使用GitHub上相应的镜像包。我们可以添加一条新的replace条目,如下所示:

replace (
    github.com/google/uuid v1.1.1 => github.com/google/uuid v1.1.0
    golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2
)

此时,项目编译时就会从GitHub下载包。我们源代码中import路径 golang.org/x/text/xxx不需要改变。

也许有读者会问,是否可以将import路径由golang.org/x/text/xxx改成github.com/golang/text/xxx?这样一来,就不需要使用replace来替换包了。

遗憾的是,不可以。因为github.com/golang/text只是镜像仓库,其go.mod文件中定义的module还是module golang.org/x/text,这个module名字直接决定了你的import的路径。

调试依赖包

有时我们需要调试依赖包,此时就可以使用replace来修改依赖,如下所示:

replace (
github.com/google/uuid v1.1.1 => ../uuid
golang.org/x/text v0.3.2 => github.com/golang/text v0.3.2
)

语句github.com/google/uuid v1.1.1 => ../uuid使用本地的uuid来替换依赖包,此时,我们可以任意地修改../uuid目录的内容来进行调试。

除了使用相对路径,还可以使用绝对路径,甚至还可以使用自已的fork仓库。

使用fork仓库

有时在使用开源的依赖包时发现了bug,在开源版本还未修改或者没有新的版本发布时,你可以使用fork仓库,在fork仓库中进行bug fix。
你可以在fork仓库上发布新的版本,并相应的修改go.mod来使用fork仓库。

比如,我fork了开源包github.com/google/uuid,fork仓库地址为github.com/RainbowMango/uuid,那我们就可以在fork仓库里修改bug并发布新的版本v1.1.2,此时使用fork仓库的项目中go.mod中replace部分可以相应的做如下修改:

github.com/google/uuid v1.1.1 => github.com/RainbowMango/uuid v1.1.2

需要说明的是,使用fork仓库仅仅是临时的做法,一旦开源版本变得可用,需要尽快切换到开源版本。

禁止被依赖

另一种使用replace的场景是你的module不希望被直接引用,比如开源软件kubernetes,在它的go.modrequire部分有大量的v0.0.0依赖,比如:

module k8s.io/kubernetes

require (
    ...
    k8s.io/api v0.0.0
    k8s.io/apiextensions-apiserver v0.0.0
    k8s.io/apimachinery v0.0.0
    k8s.io/apiserver v0.0.0
    k8s.io/cli-runtime v0.0.0
    k8s.io/client-go v0.0.0
    k8s.io/cloud-provider v0.0.0
    ...
)

由于上面的依赖都不存在v0.0.0版本,所以其他项目直接依赖k8s.io/kubernetes时会因无法找到版本而无法使用。
因为Kubernetes不希望作为module被直接使用,其他项目可以使用kubernetes其他子组件。

kubernetes 对外隐藏了依赖版本号,其真实的依赖通过replace指定:

replace (
    k8s.io/api => ./staging/src/k8s.io/api
    k8s.io/apiextensions-apiserver => ./staging/src/k8s.io/apiextensions-apiserver
    k8s.io/apimachinery => ./staging/src/k8s.io/apimachinery
    k8s.io/apiserver => ./staging/src/k8s.io/apiserver
    k8s.io/cli-runtime => ./staging/src/k8s.io/cli-runtime
    k8s.io/client-go => ./staging/src/k8s.io/client-go
    k8s.io/cloud-provider => ./staging/src/k8s.io/cloud-provider
)

前面我们说过,replace指令在当前模块不是main module时会被自动忽略的,Kubernetes正是利用了这一特性来实现对外隐藏依赖版本号来实现禁止直接引用的目的。

赠人玫瑰手留余香,如果觉得不错请给个赞~

本篇文章已归档到GitHub项目,求星~ 点我即达

文档更新时间: 2020-08-08 22:03   作者:kuteng