go module

前言

go 1.5 引进了vendor管理工程依赖包,但是vendor的存放路径是在GOPATH底下,另外每个依赖还可以有自己的vendor,通常会弄得很乱,尽管dep管理工具可以将vendor平级化管理,但是相对GOPATH的路径是逃不掉的。另外,各个包的版本管理也显得原始,甚至有的开发将依赖包从github直接download下来自己放到GOPATH底下的vendor。go的依赖包管理一致是开发者诟病的一个痛点。所以在千呼万唤中,go 1.11 终于引进了go module管理工程的包依赖,去除了项目包管理对GOPATH的依赖,明确了依赖包的版本管理。

 

定义

一个module是go相关包版本信息的收集单元。记录了精准的必须依赖信息和重新编译依赖。

 

从示例开始

go module的使用其实十分容易上手,下面我会以一个例子来说明。

示例的go环境信息:

$ go version

go version go1.12.4 darwin/amd64

 

下面这个例子是依赖github.com/sirupsen/logrus 输出一行日志。在GOPATH外创建一个mytest的目录,然后创建一个main.go的文件,内容如下:

 

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
        // Add this line for logging filename and line number!
    log.SetReportCaller(true)

    log.Println("hello world")
}

执行

go mod init mytest

其实mytest是我指定的module名称,可以是任意的命名,但是一定要指定,否则会报错 go: cannot determine module path for source directory。

然后执行go build就会成功编译,并且多了go.mod和go.sum两个module相关的文件:

$ ls
go.mod    go.sum    main.go    mytest

$ cat go.mod 
module mytest

go 1.12

require github.com/sirupsen/logrus v1.4.2

$ cat go.sum 
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

从示例中可以看出go.mod文件存放的是工程包依赖信息,而go.sum里面存放的是依赖包的校验信息。主要关注go.mod的信息。可以看到,如果我们不指定依赖包的版本信息,go build默认是会替我们去拉取该依赖包的最新版本。

所以可以总结,go module的使用分为以下几步:

  • go mod init $moduleName 初始化module信息。
  • go build或者go test等标准命令自动更新工程的依赖包信息。
  • 如果有需要可以使用go get  $packageName@$version,例如go get foo@v1.2.3, go get foo@master, go get foo@e3702bed2,也可以直接修改go.mod或者使用go mod edit(文章后面会讲到)获取特定的依赖包版本。

以上就是基本的go module工作流程,已经可以满足日常的工作流程要求,下面会详细的讲解go module的其他用法。

 

详细用法

那么go module一共有多少种玩法呢?直接运行go mod就会有答案:

$ go mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

    go mod <command> [arguments]

The commands are:

    download    download modules to local cache
    edit        edit go.mod from tools or scripts
    graph       print module requirement graph
    init        initialize new module in current directory
    tidy        add missing and remove unused modules
    vendor      make vendored copy of dependencies
    verify      verify dependencies have expected content
    why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

其中init前面我已经讲过了,这里就不再重复。

 

download 

下载依赖包到缓存目录。

 

edit

提供命令版本编辑go.mod的功能,例如go mod edit -fmt go.mod 会格式化go.mod。

用法 go mod edit [flag] [go.mod]

其中flag选项有:

  • -fmt 格式化go.mod文件
  • -require=$package:@version添加依赖,会覆盖已存在的相同依赖。添加依赖更推荐使用go get,因为go get会更新相关的go.mod文件,而edit只会更新你指定的go.mod文件。
  • -droprequire=$package:@version 移除依赖
  • -replace=$oldPackage=$newPackage 更新已经存在的依赖。通常用于私有仓库代码覆盖共有仓库。  

这里我重点说下-replace 选项,因为在生产中经常遇到的一种情况是由于这样那样的原因我们需要fork一个私有仓库去改动第三方开源库,例如有个小哥针对logrus做了二次开发github.com/gogap/logrus,这个时候就需要用github.com/gogap/logrus替换之前的第三方开源库github.com/sirupsen/logrus,操作如下:

$ go mod edit -replace="github.com/sirupsen/logrus=github.com/gogap/logrus@v0.8.2"
$ go build
go: finding github.com/gogap/logrus v0.8.2
go: downloading github.com/gogap/logrus v0.8.2
go: extracting github.com/gogap/logrus v0.8.2

$ cat go.mod 
module mytest

go 1.12

require github.com/sirupsen/logrus v1.4.2

replace github.com/sirupsen/logrus => github.com/gogap/logrus v0.8.2

 

graph

显示依赖关系(图)。

$ go mod graph 
mytest github.com/sirupsen/logrus@v1.4.2
github.com/sirupsen/logrus@v1.4.2 github.com/davecgh/go-spew@v1.1.1
github.com/sirupsen/logrus@v1.4.2 github.com/konsorten/go-windows-terminal-sequences@v1.0.1
github.com/sirupsen/logrus@v1.4.2 github.com/pmezard/go-difflib@v1.0.0
github.com/sirupsen/logrus@v1.4.2 github.com/stretchr/objx@v0.1.1
github.com/sirupsen/logrus@v1.4.2 github.com/stretchr/testify@v1.2.2
github.com/sirupsen/logrus@v1.4.2 golang.org/x/sys@v0.0.0-20190422165155-953cdadca894

 

tidy

增加缺失的包并且移除没有依赖的包。自动去下载依赖包,并且缓存到$GOPATH/pkg/mod目录下。

需要注意的是,tidy会自动更新依赖包的版本,所以如果不是初建的项目还是尽量少用tidy,尽量用go get精准控制新增的依赖包。

vendor

把依赖包拷贝到vendor目录底下。前面说了那么多想必你一定有一个疑问:go build的时候需要现场去拉取依赖包,如果我的编译机没有外网(访问不了github)怎么办?vendor就是为了应用这种情况,在本地开发机(有外网)执行 go mod vendor 将依赖包拷贝到vendor底下,然后将代码push到编译机 执行 go build -mod=vendor。示例:

$ go mod vendor
$ ls
go.mod    go.sum    main.go    mytest    vendor
$ go build -mod=vendor

 

verify

校验依赖关系

$ go mod verify
all modules verified

 

why

指出为什么需要依赖包。与graph的区别是,why只能解释某一个特定的依赖包,而graph则是给出完整的依赖关系图。

$ go mod why github.com/konsorten/go-windows-terminal-sequences
# github.com/konsorten/go-windows-terminal-sequences
mytest
github.com/sirupsen/logrus
github.com/konsorten/go-windows-terminal-sequences

 

同工程下的依赖管理

例如建立一个webserver的工程,目录为/Users/saas/src/awesomeProject/webserver,GOPATH设置为/Users/saas, webserver下的目录结构为:

$ tree
.
├── go.mod
├── google
│   └── google.go
├── helloworld
├── server.go
└── userip
    └── userip.go

2 directories, 5 files

其中google目录为packge google,userip目录为package userip,那么我们要在server.go如何引用google和userip这两个包呢?只需:

import (
    "helloworld/google"
    "helloworld/userip"
)

helloworld 是go mod init helloworld时指定的module名称,所以helloworld索引到了webserver目录,helloworld/google指的是webserver底下的google包。如果不指定module的名称,默认是GOPATH下的路径,即为awesomeProject/webserver,引用google包时就需要指定awesomeProject/webserver/google。如果GOPATH没有指定,又没有指定module的名字则报错:

$ export GOPATH=""
$ go mod init
go: cannot determine module path for source directory /Users/saas/src/awesomeProject/webserver (outside GOPATH, no import comments)

指定module就可以了,即便没有GOPATH:

$ go mod init helloworld
go: creating new go.mod: module helloworld

go build时默认会用module的名字(base name)给程序名称,这里是helloworld。如果module名称为 awesomeProject/webserver则是webserver。

 

Goland IDE打开Go Module

上面例子中发现在Goland IDE中helloworld/google会被标红,说找不到helloworld这个目录,说明IDE的Go Module功能还没有打开,需要如下设置:

 

总结

文章通过一个打印日志的例子演示了所有go module的用法,其中包括日常基本用法和全面的用法介绍。新增依赖包的更新推荐使用go get。依赖包的替换推荐使用go mod edit -replace。在编译机网络有限制的时候提供了vendor的解决方案。

 

参考

https://github.com/golang/go/wiki/Modules

 

posted @ 2019-06-15 17:51  我是码客  阅读(6670)  评论(0编辑  收藏  举报