前言

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/=
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--953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
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就会有答案:

$ 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--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 directories, 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




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. poj 2299(离散化+树状数组)

    Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 53777   Accepted: 19766 ...

  2. AC日记——琪露诺 洛谷 P1725

    琪露诺 思路: 单调队列+dp: 然而劳资不会单调队列,所以,线段树水过; 来,上代码: #include <cstdio> #include <cstring> #inclu ...

  3. ACM信息汇总

    一.ACM算法总结及刷题参考 (摘自:http://www.cnblogs.com/flipped/p/5005693.html) 初期: 一.基本算法: (1)枚举. (poj1753,poj296 ...

  4. hdu254 DFS+BFS

    这个题目需要注意以下几点: 1)注意界线问题,箱子和人不可以越界. 2)需要判断人是否可以到达人推箱子的指定位置. 3)不可以用箱子作为标记,因为箱子可以走原来走过的地方,我们用箱子和人推箱子的方向来 ...

  5. 为什么要点两下才能删除一个li节点 原来是空白节点作怪

    奇怪吧,下面的代码居然要点两次button才能删除一个li节点: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional// ...

  6. 本地搭建Hadoop伪分布式环境之四:开启搭建Hadoop2.4.0之旅

    1.准备软件  64位下载包下载:     hadoop-2.4.0-64bit.tar.gz 百度网盘: 链接: http://pan.baidu.com/s/1hqEDe2S password: ...

  7. &lt;LeetCode OJ&gt; 257. Binary Tree Paths

    257. Binary Tree Paths Total Accepted: 29282 Total Submissions: 113527 Difficulty: Easy Given a bina ...

  8. distinct 与order by 一起用

    distinct 后面不要直接跟Order by , 如果要用在子查询用用order by .

  9. 自定义序列化4 (MFC调用C#的.dll)

    CLR:CLR常用简写词语,CLR是公共语言运行时,Common Language Runtime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系 ...

  10. shell循环,判断介绍,以及实例

    shell的循环主要有3种,for,while,until shell的分支判断主要有2种,if,case 一,for循环 #!/bin/bash for file in $(ls /tmp/test ...