化耀 發表於 2019-6-15 17:51:00

go module

<h3>前言</h3>
<p>go 1.5 引进了vendor管理工程依赖包,但是vendor的存放路径是在GOPATH底下,另外每个依赖还可以有自己的vendor,通常会弄得很乱,尽管dep管理工具可以将vendor平级化管理,但是相对GOPATH的路径是逃不掉的。另外,各个包的版本管理也显得原始,甚至有的开发将依赖包从github直接download下来自己放到GOPATH底下的vendor。go的依赖包管理一致是开发者诟病的一个痛点。所以在千呼万唤中,go 1.11 终于引进了go module管理工程的包依赖,去除了项目包管理对GOPATH的依赖,明确了依赖包的版本管理。</p>
<p>&nbsp;</p>
<h3>定义</h3>
<p>一个module是go相关包版本信息的收集单元。记录了精准的必须依赖信息和重新编译依赖。</p>
<p>&nbsp;</p>
<h3>从示例开始</h3>
<p>go module的使用其实十分容易上手,下面我会以一个例子来说明。</p>
<p>示例的go环境信息:</p>
<style>p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }</style>
<blockquote>
<p class="p1"><span class="s1">$ go version</span></p>
<p class="p1"><span class="s1">go version go1.12.4 darwin/amd64</span></p>
</blockquote>
<p class="p1">&nbsp;</p>
<p class="p1"><span style="font-size: 14px; font-family: &quot;Microsoft YaHei&quot;"><span class="s1">下面这个例子是依赖</span><span class="s1">github.com/sirupsen/logrus 输出一行日志。在GOPATH外创建一个mytest的目录,然后创建一个main.go的文件,内容如下:</span></span></p>
<p class="p1">&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">package main

import (
    log </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">github.com/sirupsen/logrus</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
)

func main() {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add this line for logging filename and line number!</span>
    log.SetReportCaller(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)

    log.Println(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello world</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
}</span></pre>
</div>
<p>执行</p>
<div class="cnblogs_code">
<pre>go mod init mytest</pre>
</div>
<p>其实mytest是我指定的module名称,可以是任意的命名,但是一定要指定,否则会报错&nbsp;<span class="s1">go: cannot determine module path for source directory。</span></p>
<p><span class="s1">然后执行go build就会成功编译,并且多了go.mod和go.sum两个module相关的文件:</span></p>
<div class="cnblogs_code">
<pre>$ <span style="color: rgba(0, 0, 255, 1)">ls</span><span style="color: rgba(0, 0, 0, 1)">
go.mod    go.</span><span style="color: rgba(0, 0, 255, 1)">sum</span><span style="color: rgba(0, 0, 0, 1)">    main.go    mytest

$ </span><span style="color: rgba(0, 0, 255, 1)">cat</span><span style="color: rgba(0, 0, 0, 1)"> go.mod
module mytest

go </span><span style="color: rgba(128, 0, 128, 1)">1.12</span><span style="color: rgba(0, 0, 0, 1)">

require github.com</span>/sirupsen/logrus v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span><span style="color: rgba(0, 0, 0, 1)">

$ </span><span style="color: rgba(0, 0, 255, 1)">cat</span> go.<span style="color: rgba(0, 0, 255, 1)">sum</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/davecgh/go-spew v1.<span style="color: rgba(128, 0, 128, 1)">1.1</span>/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/konsorten/go-windows-terminal-sequences v1.<span style="color: rgba(128, 0, 128, 1)">0.1</span>/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/pmezard/go-difflib v1.<span style="color: rgba(128, 0, 128, 1)">0.0</span>/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/<span style="color: rgba(128, 0, 128, 1)">4</span>=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span>/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/stretchr/objx v0.<span style="color: rgba(128, 0, 128, 1)">1.1</span>/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=<span style="color: rgba(0, 0, 0, 1)">
github.com</span>/stretchr/testify v1.<span style="color: rgba(128, 0, 128, 1)">2.2</span>/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=<span style="color: rgba(0, 0, 0, 1)">
golang.org</span>/x/sys v0.<span style="color: rgba(128, 0, 128, 1)">0.0</span>-<span style="color: rgba(128, 0, 128, 1)">20190422165155</span>-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=<span style="color: rgba(0, 0, 0, 1)">
golang.org</span>/x/sys v0.<span style="color: rgba(128, 0, 128, 1)">0.0</span>-<span style="color: rgba(128, 0, 128, 1)">20190422165155</span>-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=</pre>
</div>
<p>从示例中可以看出go.mod文件存放的是工程包依赖信息,而go.sum里面存放的是依赖包的校验信息。主要关注go.mod的信息。可以看到,如果我们不指定依赖包的版本信息,go build默认是会替我们去拉取该依赖包的最新版本。</p>
<p>所以可以总结,go module的使用分为以下几步:</p>
<ul>
<li>go mod init $moduleName 初始化module信息。</li>
<li>go build或者go test等标准命令自动更新工程的依赖包信息。</li>
<li>如果有需要可以使用go get&nbsp; $packageName@$version,例如go get foo@v1.2.3, go get foo@master, go get foo@e3702bed2,也可以直接修改go.mod或者使用go mod edit(文章后面会讲到)获取特定的依赖包版本。</li>
</ul>
<p>以上就是基本的go module工作流程,已经可以满足日常的工作流程要求,下面会详细的讲解go module的其他用法。</p>
<p>&nbsp;</p>
<h3>详细用法</h3>
<p>那么go module一共有多少种玩法呢?直接运行go mod就会有答案:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ go mod
Go mod provides access to operations on modules.

Note that support </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> modules is built into all the go commands,
not just </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">go mod</span><span style="color: rgba(128, 0, 0, 1)">'</span>. For example, day-to-<span style="color: rgba(0, 0, 0, 1)">day adding, removing, upgrading,
and downgrading of dependencies should be </span><span style="color: rgba(0, 0, 255, 1)">done</span> using <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">go get</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">.
See </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">go help modules</span><span style="color: rgba(128, 0, 0, 1)">'</span> <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> an overview of module functionality.

Usage:

    go mod </span>&lt;command&gt;<span style="color: rgba(0, 0, 0, 1)">

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 </span><span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> current directory
    tidy      add missing and remove unused modules
    vendor      </span><span style="color: rgba(0, 0, 255, 1)">make</span><span style="color: rgba(0, 0, 0, 1)"> vendored copy of dependencies
    verify      verify dependencies have expected content
    why         explain why packages or modules are needed

Use </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">go help mod &lt;command&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(0, 0, 255, 1)">for</span> <span style="color: rgba(0, 0, 255, 1)">more</span> information about a command.</pre>
</div>
<p>其中init前面我已经讲过了,这里就不再重复。</p>
<p>&nbsp;</p>
<h4>download&nbsp;</h4>
<p>下载依赖包到缓存目录。</p>
<p>&nbsp;</p>
<h4>edit</h4>
<p>提供命令版本编辑go.mod的功能,例如go mod edit -fmt go.mod 会格式化go.mod。</p>
<p>用法 go mod edit </p>
<p>其中flag选项有:</p>
<ul>
<li>-fmt 格式化go.mod文件</li>
<li>-require=$package:@version添加依赖,会覆盖已存在的相同依赖。添加依赖更推荐使用go get,因为go get会更新相关的go.mod文件,而edit只会更新你指定的go.mod文件。</li>
<li>-droprequire=$package:@version 移除依赖</li>
<li>-replace=$oldPackage=$newPackage 更新已经存在的依赖。通常用于私有仓库代码覆盖共有仓库。  </li>
</ul>
<p>这里我重点说下-replace 选项,因为在生产中经常遇到的一种情况是由于这样那样的原因我们需要fork一个私有仓库去改动第三方开源库,例如有个小哥针对logrus做了二次开发github.com/gogap/logrus,这个时候就需要用github.com/gogap/logrus替换之前的第三方开源库github.com/sirupsen/logrus,操作如下:</p>
<div class="cnblogs_code">
<pre>$ go mod edit -replace=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">github.com/sirupsen/logrus=github.com/gogap/logrus@v0.8.2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
$ go build
go: finding github.com</span>/gogap/logrus v0.<span style="color: rgba(128, 0, 128, 1)">8.2</span><span style="color: rgba(0, 0, 0, 1)">
go: downloading github.com</span>/gogap/logrus v0.<span style="color: rgba(128, 0, 128, 1)">8.2</span><span style="color: rgba(0, 0, 0, 1)">
go: extracting github.com</span>/gogap/logrus v0.<span style="color: rgba(128, 0, 128, 1)">8.2</span><span style="color: rgba(0, 0, 0, 1)">

$ </span><span style="color: rgba(0, 0, 255, 1)">cat</span><span style="color: rgba(0, 0, 0, 1)"> go.mod
module mytest

go </span><span style="color: rgba(128, 0, 128, 1)">1.12</span><span style="color: rgba(0, 0, 0, 1)">

require github.com</span>/sirupsen/logrus v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span><span style="color: rgba(0, 0, 0, 1)">

replace github.com</span>/sirupsen/logrus =&gt; github.com/gogap/logrus v0.<span style="color: rgba(128, 0, 128, 1)">8.2</span></pre>
</div>
<p>&nbsp;</p>
<h4>graph</h4>
<p>显示依赖关系(图)。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ go mod graph
mytest github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> github.com/davecgh/go-spew@v1.<span style="color: rgba(128, 0, 128, 1)">1.1</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> github.com/konsorten/go-windows-terminal-sequences@v1.<span style="color: rgba(128, 0, 128, 1)">0.1</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> github.com/pmezard/go-difflib@v1.<span style="color: rgba(128, 0, 128, 1)">0.0</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> github.com/stretchr/objx@v0.<span style="color: rgba(128, 0, 128, 1)">1.1</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> github.com/stretchr/testify@v1.<span style="color: rgba(128, 0, 128, 1)">2.2</span><span style="color: rgba(0, 0, 0, 1)">
github.com</span>/sirupsen/logrus@v1.<span style="color: rgba(128, 0, 128, 1)">4.2</span> golang.org/x/sys@v0.<span style="color: rgba(128, 0, 128, 1)">0.0</span>-<span style="color: rgba(128, 0, 128, 1)">20190422165155</span>-953cdadca894</pre>
</div>
<p>&nbsp;</p>
<h4>tidy</h4>
<p>增加缺失的包并且移除没有依赖的包。自动去下载依赖包,并且缓存到$GOPATH/pkg/mod目录下。</p>
<p>需要注意的是,tidy会自动更新依赖包的版本,所以如果不是初建的项目还是尽量少用tidy,尽量用go get精准控制新增的依赖包。</p>
<h4>vendor</h4>
<p>把依赖包拷贝到vendor目录底下。前面说了那么多想必你一定有一个疑问:go build的时候需要现场去拉取依赖包,如果我的编译机没有外网(访问不了github)怎么办?vendor就是为了应用这种情况,在本地开发机(有外网)执行 go mod vendor 将依赖包拷贝到vendor底下,然后将代码push到编译机 执行 go build -mod=vendor。示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ go mod vendor
$ </span><span style="color: rgba(0, 0, 255, 1)">ls</span><span style="color: rgba(0, 0, 0, 1)">
go.mod    go.</span><span style="color: rgba(0, 0, 255, 1)">sum</span><span style="color: rgba(0, 0, 0, 1)">    main.go    mytest    vendor
$ go build </span>-mod=vendor</pre>
</div>
<p>&nbsp;</p>
<h4>verify</h4>
<p>校验依赖关系</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ go mod verify
all modules verified</span></pre>
</div>
<h4>&nbsp;</h4>
<h4>why</h4>
<p>指出为什么需要依赖包。与graph的区别是,why只能解释某一个特定的依赖包,而graph则是给出完整的依赖关系图。</p>
<div class="cnblogs_code">
<pre>$ go mod why github.com/konsorten/go-windows-terminal-<span style="color: rgba(0, 0, 0, 1)">sequences
# github.com</span>/konsorten/go-windows-terminal-<span style="color: rgba(0, 0, 0, 1)">sequences
mytest
github.com</span>/sirupsen/<span style="color: rgba(0, 0, 0, 1)">logrus
github.com</span>/konsorten/go-windows-terminal-sequences</pre>
</div>
<p>&nbsp;</p>
<h3>同工程下的依赖管理</h3>
<p>例如建立一个webserver的工程,目录为/Users/saas/src/awesomeProject/webserver,GOPATH设置为/Users/saas, webserver下的目录结构为:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ tree
.
├── go.mod
├── google
│&nbsp;&nbsp; └── google.go
├── helloworld
├── server.go
└── userip
    └── userip.go

</span><span style="color: rgba(128, 0, 128, 1)">2</span> directories, <span style="color: rgba(128, 0, 128, 1)">5</span> files</pre>
</div>
<p>其中google目录为packge google,userip目录为package userip,那么我们要在server.go如何引用google和userip这两个包呢?只需:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">import (
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">helloworld/google</span><span style="color: rgba(128, 0, 0, 1)">"</span>
    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">helloworld/userip</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
)</span></pre>
</div>
<p>helloworld 是go mod init helloworld时指定的module名称,所以helloworld索引到了webserver目录,helloworld/google指的是webserver底下的google包。如果不指定module的名称,默认是GOPATH下的路径,即为awesomeProject/webserver,引用google包时就需要指定awesomeProject/webserver/google。如果GOPATH没有指定,又没有指定module的名字则报错:</p>
<div class="cnblogs_code">
<pre>$ export GOPATH=<span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
$ go mod init
go: cannot determine module path </span><span style="color: rgba(0, 0, 255, 1)">for</span> source directory /Users/saas/src/awesomeProject/webserver (outside GOPATH, no import comments)</pre>
</div>
<p>指定module就可以了,即便没有GOPATH:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">$ go mod init helloworld
go: creating new go.mod: module helloworld</span></pre>
</div>
<p>go build时默认会用module的名字(base name)给程序名称,这里是helloworld。如果module名称为&nbsp;awesomeProject/webserver则是webserver。</p>
<p>&nbsp;</p>
<h3>Goland IDE打开Go Module</h3>
<p>上面例子中发现在Goland IDE中helloworld/google会被标红,说找不到helloworld这个目录,说明IDE的Go Module功能还没有打开,需要如下设置:</p>
<p><img src="https://img2018.cnblogs.com/blog/614799/201907/614799-20190719150219047-1438730485.png"></p>
<p>&nbsp;</p>
<h3>总结</h3>
<p>文章通过一个打印日志的例子演示了所有go module的用法,其中包括日常基本用法和全面的用法介绍。新增依赖包的更新推荐使用go get。依赖包的替换推荐使用go mod edit -replace。在编译机网络有限制的时候提供了vendor的解决方案。</p>
<p>&nbsp;</p>
<h3>参考</h3>
<p>https://github.com/golang/go/wiki/Modules</p>
<p class="p1">&nbsp;</p>
<style>p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }</style>
<style>p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }</style>
<style>p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }</style>
<style>p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1) }
span.s1 { font-variant-ligatures: no-common-ligatures }</style><br><br>
来源:https://www.cnblogs.com/makelu/p/11028329.html
頁: [1]
查看完整版本: go module