前言

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的文件,内容如下:

  1. package main
  2.  
  3. import (
  4. log "github.com/sirupsen/logrus"
  5. )
  6.  
  7. func main() {
  8. // Add this line for logging filename and line number!
  9. log.SetReportCaller(true)
  10.  
  11. log.Println("hello world")
  12. }

执行

  1. go mod init mytest

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

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

  1. $ ls
  2. go.mod go.sum main.go mytest
  3.  
  4. $ cat go.mod
  5. module mytest
  6.  
  7. go 1.12
  8.  
  9. require github.com/sirupsen/logrus v1.4.2
  10.  
  11. $ cat go.sum
  12. github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
  13. github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
  14. github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/=
  15. github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
  16. github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
  17. github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
  18. github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
  19. golang.org/x/sys v0.0.0--953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
  20. golang.org/x/sys v0.0.0--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就会有答案:

  1. $ go mod
  2. Go mod provides access to operations on modules.
  3.  
  4. Note that support for modules is built into all the go commands,
  5. not just 'go mod'. For example, day-to-day adding, removing, upgrading,
  6. and downgrading of dependencies should be done using 'go get'.
  7. See 'go help modules' for an overview of module functionality.
  8.  
  9. Usage:
  10.  
  11. go mod <command> [arguments]
  12.  
  13. The commands are:
  14.  
  15. download download modules to local cache
  16. edit edit go.mod from tools or scripts
  17. graph print module requirement graph
  18. init initialize new module in current directory
  19. tidy add missing and remove unused modules
  20. vendor make vendored copy of dependencies
  21. verify verify dependencies have expected content
  22. why explain why packages or modules are needed
  23.  
  24. 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,操作如下:

  1. $ go mod edit -replace="github.com/sirupsen/logrus=github.com/gogap/logrus@v0.8.2"
  2. $ go build
  3. go: finding github.com/gogap/logrus v0.8.2
  4. go: downloading github.com/gogap/logrus v0.8.2
  5. go: extracting github.com/gogap/logrus v0.8.2
  6.  
  7. $ cat go.mod
  8. module mytest
  9.  
  10. go 1.12
  11.  
  12. require github.com/sirupsen/logrus v1.4.2
  13.  
  14. replace github.com/sirupsen/logrus => github.com/gogap/logrus v0.8.2

graph

显示依赖关系(图)。

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

tidy

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

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

vendor

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

  1. $ go mod vendor
  2. $ ls
  3. go.mod go.sum main.go mytest vendor
  4. $ go build -mod=vendor

verify

校验依赖关系

  1. $ go mod verify
  2. all modules verified

why

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

  1. $ go mod why github.com/konsorten/go-windows-terminal-sequences
  2. # github.com/konsorten/go-windows-terminal-sequences
  3. mytest
  4. github.com/sirupsen/logrus
  5. github.com/konsorten/go-windows-terminal-sequences

同工程下的依赖管理

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

  1. $ tree
  2. .
  3. ├── go.mod
  4. ├── google
  5.    └── google.go
  6. ├── helloworld
  7. ├── server.go
  8. └── userip
  9. └── userip.go
  10.  
  11. directories, files

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

  1. import (
  2. "helloworld/google"
  3. "helloworld/userip"
  4. )

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

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

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

  1. $ go mod init helloworld
  2. 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




go module的更多相关文章

  1. Android Studio 编译单个module

    前期自己要把gradle环境变量配置好 在Terminal中gradle命令行编译apk 输入gradle assembleRelease 会编译全部module编译单个modulecd ./xiru ...

  2. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

  3. nodejs模块中exports和module.exports的区别

    通过Node.js的官方API可以看到Node.js本身提供了很多核心模块 http://nodejs.org/api/ ,这些核心模块被编译成二进制文件,可以require('模块名')去获取:核心 ...

  4. ES6之module

    该博客原文地址:http://www.cnblogs.com/giggle/p/5572118.html 一.module概述 JavaScript一直没有模块体系,但是伴随着ES6的到来,modul ...

  5. [python] CSV read and write using module xlrd and xlwt

    1. get data from csv, skip header of the file. with open('test_data.csv','rb,) as csvfile: readCSV = ...

  6. Yii2.0.7 限制user module登录遇到的问题

    在Yii2.0.6的时候我是在以下文件通过以下方法实现的. frontend/modules/user/Module.php namespace frontend\modules\user; clas ...

  7. Android Studio导入github下载的project和module

    前言:我们以前eclispe时代, 经常都是跑到github浏览第三方开源资源,然后下载下来,运行一下sample之类的,学习没有接触的第三方安卓库,但是到了Android Studio,在githu ...

  8. Android Studio导入Project、Module的正确方法

    Gradle Project项目.Module模块导入 最近看到网上很多人在抱怨,Android Studio很难导入github上下载下来的一些项目,主要包括: 1.导入就在下载Gradle2.根本 ...

  9. ImportError: No module named 'requests'

    补充说明: 当前环境是在windows环境下 python版本是:python 3.4. 刚开始学习python,一边看书一边论坛里阅读感兴趣的代码, http://www.oschina.net/c ...

  10. android studio 中移除module和恢复module

    一.移除Android Studio中module 在Android Studio中想要删除某个module时,在Android Studio中选中module,右键发现没有delete,如图: An ...

随机推荐

  1. 【剑指offer】数组中重复的数字

    题目描述 在一个长度为n的数组里的所有数字都在0到n-1的范围内. 数组中某些数字是重复的,但不知道有几个数字是重复的.也不知道每个数字重复几次.请找出数组中任意一个重复的数字. 例如,如果输入长度为 ...

  2. HDU 5135.Little Zu Chongzhi's Triangles-字符串 (2014ACM/ICPC亚洲区广州站-重现赛)

    Little Zu Chongzhi's Triangles Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 512000/512000 ...

  3. validate插件使用

    validate插件使用 官网:http://jqueryvalidation.org/ 项目实操 引入文件 add.html调用(注意顺序问题) 为form表单定义一个ID,以方便获取该元素 添加验 ...

  4. 洛谷——P1186 玛丽卡

    P1186 玛丽卡 题目描述 麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复. 因为她和他们不住在同一个城市,因此她开始准备她的长途旅行. 在这个国家中每两个城市之间最多只有一条路相通,并且我们知道 ...

  5. 基于VUE开发项目

    前言 最近由于公司需要,需要写一个相对来说比较大型的后台管理系统.为了保证管理系统操作体验较为舒适并且项目后期益于维护,最后决定基于VUE全家桶来开发一个高度组件化的单页SPA应用. 技术选型 vue ...

  6. Xamarin XAML语言教程Xamarin.Forms中程序状态与进度(一)

    Xamarin XAML语言教程Xamarin.Forms中程序状态与进度(一) 在Xamarin.Forms中,提供了两个控件用来指示程序的状态和进度.他们分别为活动指示器和进度条.其中,活动指示器 ...

  7. Linux网络协议栈之数据包处理过程

    http://blog.csdn.net/cheng_fangang/article/details/8966242

  8. Python将JSON格式数据转换为SQL语句以便导入MySQL数据库

    前文中我们把网络爬虫爬取的数据保存为JSON格式,但为了能够更方便地处理数据.我们希望把这些数据导入到MySQL数据库中.phpMyadmin能够把MySQL数据库中的数据导出为JSON格式文件,但却 ...

  9. vs:如何添加.dll文件

    Newtonsoft.Json.dll  一个第三方的json序列化和反序列化dll,转换成json供页面使用,页面json数据转换成对象,供.net使用 Mysql.Data.dll  mysql数 ...

  10. 获取URL的name值 getUrl(url,name) 传入url和key 得到key对应的value

    <body> <script type="text/javascript"> var url = "http://192.168.1.82:802 ...