go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。

一、GO111MODULE开启go mod的环境变量

要启用go module支持首先要设置环境变量GO111MODULE,通过它可以开启或关闭模块支持,它有三个可选值:offonauto,默认值是auto

  1. GO111MODULE=off禁用模块支持,编译时会从GOPATHvendor文件夹中查找包。
  2. GO111MODULE=on启用模块支持,编译时会忽略GOPATHvendor文件夹,只根据 go.mod下载依赖。
  3. GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。

简单来说,设置GO111MODULE=on之后就可以使用go module了,以后就没有必要在GOPATH中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.modgo.sum

  • 开启:go env -w GO111MODULE=on
  • 查看:go env GO111MODULE

注意:

  1. 没使用go mod时依赖安装在 %GOPATH/src/github.com
  2. 使用go mod后依赖安装在 %GOPATH/pkg/mod 不管是第三方还是Google相关的包

注意:在使用GoLand工具时,不要配置Project GOPATH为当前工程目录,最好不要配置Project GOPATH,而是配置Module GOPATH

  1. 安装govendor : go get -u -v github.com/kardianos/govendor:

$GOPATH/src/vendor/vendor.json, 是从远端库添加依赖包

二、GOPROXY

Go1.11之后设置GOPROXY命令为:

  1. export GOPROXY=https://goproxy.cn

Go1.13之后GOPROXY默认值为[https://proxy.golang.org](https://proxy.golang.org),在国内是无法访问的,所以十分建议大家设置GOPROXY,这里我推荐使用goproxy.cn

  1. $ go env -w GOPROXY=https://goproxy.cn,direct
  2. # 或者 https://mirrors.aliyun.com/goproxy/
  3. # 或者 https://goproxy.io

三、go mod相关命令

常用的go mod命令如下:

  1. $ go mod init [模块名,或者github路径] # 初始化当前文件夹, 创建go.mod文件
  2. $ go mod download # 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
  3. $ go mod edit # 编辑go.mod文件(选项有-json、-require和-exclude)
  4. $ go mod graph # 以文本模式打印模块依赖图
  5. $ go mod tidy # 增加缺少的module,删除无用的module
  6. $ go mod vendor # 在根目录生成vendor目录,将依赖复制到vendor下
  7. $ go mod verify # 校验依赖是否正确
  8. $ go mod why # 查找依赖

四、go.mod文件

go.mod文件记录了项目所有的依赖信息,其结构大致如下:

  1. module github.com/Q1mi/studygo/blogger
  2. go 1.12
  3. require (
  4. github.com/DeanThompson/ginpprof v0.0.0-20190408063150-3be636683586
  5. github.com/gin-gonic/gin v1.4.0
  6. github.com/go-sql-driver/mysql v1.4.1
  7. github.com/jmoiron/sqlx v1.2.0
  8. github.com/satori/go.uuid v1.2.0
  9. google.golang.org/appengine v1.6.1 // indirect
  10. )

其中,

  • module用来定义包名
  • require用来定义依赖包及版本
  • indirect表示间接引用
  • replace替换成本地库(如自己的库,或者修改源代码的库)或者github对应的库(如翻墙失败)

依赖的版本

如果没有指明 version 的情况下,则默认先下载打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果没有 release 版本,则下载最新的 pre release 版本,比如 v0.0.1-pre1。如果还没有则下载最新的 commit

go mod支持语义化版本号,比如go get foo@v1.2.3,也可以跟git的分支或tag,比如go get foo@master,当然也可以跟git提交哈希,比如go get foo@e3702bed2。关于依赖的版本支持以下几种格式:

  1. gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
  2. gopkg.in/vmihailenco/msgpack.v2 v2.9.1
  3. gopkg.in/yaml.v2 <=v2.2.1
  4. github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
  5. latest

replace

在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。

  1. replace (
  2. golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
  3. golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
  4. golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
  5. )

五、go mod高级操作

go get

在项目中执行go get命令可以下载依赖包,并且还可以指定下载的版本。

  1. 运行go get -u将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
  2. 运行go get -u=patch将会升级到最新的修订版本
  3. 运行go get package@version将会升级到指定的版本号version
  4. 运行go get -v 显示下载详细信息

如果下载所有依赖可以使用go mod download命令。

整理依赖

我们在代码中删除依赖代码后,相关的依赖库并不会在go.mod文件中自动移除。这种情况下我们可以使用go mod tidy命令更新go.mod中的依赖关系。

go mod edit

格式化

因为我们可以手动修改go.mod文件,所以有些时候需要格式化该文件。Go提供了一下命令:

  1. go mod edit -fmt

添加依赖项

  1. go mod edit -require=golang.org/x/text

移除依赖项

如果只是想修改go.mod文件中的内容,那么可以运行go mod edit -droprequire=package path,比如要在go.mod中移除golang.org/x/text包,可以使用如下命令:

  1. go mod edit -droprequire=golang.org/x/text

关于go mod edit的更多用法可以通过go help mod edit查看。

替代只能翻墙下载的库

  1. go mod edit -replace=golang.org/x/crypto@v0.0.0=github.com/golang/crypto@latest
  2. go mod edit -replace=golang.org/x/sys@v0.0.0=github.com/golang/sys@latest

六、在项目中使用go module

既有项目

如果需要对一个已经存在的项目启用go module,可以按照以下步骤操作:

  1. 在项目目录下执行go mod init,生成一个go.mod文件。
  2. 执行go get,查找并记录当前项目的依赖,同时生成一个go.sum记录每个依赖库的版本和哈希值。

    新项目

    对于一个新创建的项目,我们可以在项目文件夹下按照以下步骤操作:

  3. 执行go mod init 项目名命令,在当前项目文件夹下创建一个go.mod文件。

  4. 手动编辑go.mod中的require依赖项或执行go get自动发现、维护依赖。

七、如何使用go module导入本地包

go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。到今天Go1.14版本推出之后Go modules 功能已经被正式推荐在生产环境下使用了。
这几天已经有很多教程讲解如何使用go module,以及如何使用go module导入gitlab私有仓库,我这里就不再啰嗦了。但是最近我发现很多小伙伴在群里问如何使用go module导入本地包,作为初学者大家刚开始接触package的时候肯定都是先在本地创建一个包,然后本地调用一下,然后就被卡住了。。。
这里就详细介绍下如何使用go module导入本地包。

import本地包和导入问题-转载

  1. # go clean 清理moudle 缓存
  2. $ go clean -modcache
  3. # go list 查看可下载版本
  4. $ go list -m -versions github.com/gogf/gf
  5. # 编译并安装hello包,这里安装的意思是将生成的*.a文件放到工作目录$GOPATH/pkg目录下
  6. $ go install hello
  7. # 清除上一次install生成的包
  8. $ go clean -i hello
  9. # 生成可执行程序
  10. $ go build main
  11. # 删除可执行程序
  12. $ go clean -x main

总结:

(1) 一个包确实可以由多个源文件组成,只要它们开头的包声明一样!
(2)一个包对应生成一个*.a文件,生成的文件名并不是包名+.a!— pkg下
(3) go install ××× 这里对应的并不是包名,而是路径名!!
(4) import ××× 这里使用的也不是包名,也是路径名!
(5) ×××××.SayHello() 这里使用的才是包名!
(6)installs the packages named by the import paths=>一个目录下就只能有一个包吧,因为都是指定路径,没有办法指定路径下的某个具体的包

导入包的多种方式:

  • 正常模式:直接根据$GOPATH/src目录导入import “test/lib”(路径其实是$GOPATH/src/test/lib) - 基于$GOPATH
  • 别名模式:别名导入 import alias_name "test/lib" ,这样使用的时候,可以直接使用别名
  • 简便模式:使用点号导入 import . "test/lib" ,作用是使用的时候直接 省略包名
  • 使用下划线(即空白标识符)导入 `improt “test/lib”` ,该操作其实只是引入该包。当导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执行而已。这个时候就可以使用操作引用该包。即使用操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。往往这些init函数里面是注册自己包里面的引擎,让外部可以方便的使用,例如实现database/sql的包,在init函数里面都是调用了sql.Register(name string, driver driver.Driver)注册自己,然后外部就可以使用了。
  • 相对路径导入 import "./model" — 当前文件同一目录的model目录,但是不建议这种方式import

    本地包:

    instal生成pkg后,修改源代码,发现未重新编译pkg,但是结果是修改后的源码?
    依赖管理go module - 图1 ```bash $ rm main $ go build -x -v main

首先,它好像指定了一个临时工作目录

WORK=/tmp/go-build658882358

看着样子,它好像是要准备编译hello目录下的包

hello

然后创建了一系列临时文件夹

mkdir -p $WORK/hello/_obj/
mkdir -p $WORK/

进入包的源文件目录

cd /home/yuxuan/GoProjects/import/src/hello

调用6g这个编译器编译生成hello.a,存放在$WORK/临时目录下

/opt/go/pkg/tool/linuxamd64/6g -o $WORK/hello.a -trimpath $WORK -p hello -complete -D /home/yuxuan/GoProjects/import/src/hello -I $WORK -pack ./hello.go

要编译main目录下的包了

main

还是创建一系列的临时文件夹

mkdir -p $WORK/main/_obj/
mkdir -p $WORK/main/_obj/exe/

进入main文件夹

cd /home/yuxuan/GoProjects/import/src/main

调用6g编译器,编译生成main.a,存放于$WORK/临时目录下

/opt/go/pkg/tool/linuxamd64/6g -o $WORK/main.a -trimpath $WORK -p main -complete -D /home/yuxuan/GoProjects/import/src/main -I $WORK -I /home/yuxuan/GoProjects/import/pkg/linux_amd64 -pack ./main.go

最后它进入了一个“当前目录”,应该就是我们执行go build命令的目录

cd .

调用连接器6l 然后它链接生成a.out,存放与临时目录下的$WORK/main/_obj/exe/文件夹中,但是在链接选项中并未直接发现hello.a

从链接选项:-L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64中可以看出,连接器首先搜索了$WORK临时目录下的所有.a文件,然后再去搜索/home/yuxuan/GoProjects/import/pkg/linux_amd64目录下的.a文件,可见原因

/opt/go/pkg/tool/linux_amd64/6l -o $WORK/main/_obj/exe/a.out -L $WORK -L /home/yuxuan/GoProjects/import/pkg/linux_amd64 -extld=gcc $WORK/main.a

最后,移动可执行文件并重命名

mv $WORK/main/_obj/exe/a.out main

  1. 总结:连接器在连接时,其实使用的并不是我们工作目录下的hello.a文件,而是以该最新源码编译出的临时文件夹中的hello.a文件。<br />思考:对于有源代码的第三方库,如果没有源代码呢?如果是自带的Go标准库呢?
  2. 文件名可以和该包名不一致,但文件中使用的包名必须要和该包名一致。<br />"multi"包在包"cal"下,所以我们要把包名写完整"cal/multi"<br />自定义包名第一个部分必须是域名形式,例如 "github.com/mock"<br />另外一点要说的是,项目必须放在含[域名]格式的文件夹下<br />如果放在 [github.com] 下,go会帮你去拉更新。。。猝不及防
  3. <a name="bLGjR"></a>
  4. ### 前提
  5. 假设我们现在有`moduledemo``mypackage`两个包,其中`moduledemo`包中会导入`mypackage`包并使用它的`New`方法。<br />`mypackage/mypackage.go`内容如下:

package mypackage import “fmt” func New(){ fmt.Println(“mypackage.New”) }

  1. 我们现在分两种情况讨论:
  2. <a name="Twy5K"></a>
  3. ### 在同一个项目下 - 接着路径向下找
  4. **注意**:在一个项目(project)下我们是可以定义多个包(package)的。
  5. <a name="Xp8NF"></a>
  6. #### 目录结构
  7. 现在的情况是,我们在`moduledemo/main.go`中调用了`mypackage`这个包。

moduledemo ├── go.mod ├── main.go └── mypackage └── mypackage.go └── mypackage1 └── mypackage1.go

  1. <a name="44BoI"></a>
  2. #### 导入包
  3. 这个时候,我们需要在`moduledemo/go.mod`中按如下定义:

module moduledemo go 1.14

  1. 然后在`moduledemo/main.go`中按如下方式导入`mypackage`

package main import ( “fmt” “moduledemo/mypackage” // 导入同一项目下的mypackage包 ) func main() { mypackage.New() fmt.Println(“main”) }

  1. <a name="nyMci"></a>
  2. #### 举个例子
  3. 举一反三,假设我们现在有文件目录结构如下:

└── bubble ├── dao │ └── mysql.go ├── go.mod └── main.go

  1. 其中`bubble/go.mod`内容如下:

module github.com/q1mi/bubble go 1.14

  1. `bubble/dao/mysql.go`内容如下:

package dao import “fmt” func New(){ fmt.Println(“mypackage.New”) }

  1. `bubble/main.go`内容如下:
  2. ```go
  3. package main
  4. import (
  5. "fmt"
  6. "github.com/q1mi/bubble/dao"
  7. )
  8. func main() {
  9. dao.New()
  10. fmt.Println("main")
  11. }

不在同一个项目下 - replace

目录结构

  1. ├── moduledemo
  2. ├── go.mod
  3. └── main.go
  4. └── mypackage
  5. ├── go.mod
  6. └── mypackage.go

导入包

这个时候,mypackage也需要进行module初始化,即拥有一个属于自己的go.mod文件,内容如下:

  1. module mypackage
  2. go 1.14

然后我们在moduledemo/main.go中按如下方式导入:

  1. import (
  2. "fmt"
  3. "mypackage"
  4. )
  5. func main() {
  6. mypackage.New()
  7. fmt.Println("main")
  8. }

因为这两个包不在同一个项目路径下,你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。这个时候我们就需要在go.mod文件中使用replace指令。
在调用方也就是moduledemo/go.mod中按如下方式指定使用相对路径来寻找mypackage这个包。

  1. module moduledemo
  2. go 1.14
  3. require "mypackage" v0.0.0
  4. replace "mypackage" => "../mypackage"

举个例子

最后我们再举个例子巩固下上面的内容。
我们现在有文件目录结构如下:

  1. ├── p1
  2. ├── go.mod
  3. └── main.go
  4. └── p2
  5. ├── go.mod
  6. └── p2.go

p1/main.go中想要导入p2.go中定义的函数。
p2/go.mod内容如下:

  1. module liwenzhou.com/q1mi/p2
  2. go 1.14

p1/main.go中按如下方式导入

  1. import (
  2. "fmt"
  3. "liwenzhou.com/q1mi/p2"
  4. )
  5. func main() {
  6. p2.New()
  7. fmt.Println("main")
  8. }

因为我并没有把liwenzhou.com/q1mi/p2这个包上传到liwenzhou.com这个网站,我们只是想导入本地的包,这个时候就需要用到replace这个指令了。
p1/go.mod内容如下:

  1. module github.com/q1mi/p1
  2. go 1.14
  3. require "liwenzhou.com/q1mi/p2" v0.0.0
  4. replace "liwenzhou.com/q1mi/p2" => "../p2"

此时,我们就可以正常编译p1这个项目了。
说再多也没用,自己动手试试吧。