- 一、GO111MODULE开启go mod的环境变量
- 二、GOPROXY
- 三、go mod相关命令
- 四、go.mod文件
- 五、go mod高级操作
- 七、如何使用go module导入本地包
- 首先,它好像指定了一个临时工作目录
- 看着样子,它好像是要准备编译hello目录下的包
- 然后创建了一系列临时文件夹
- 进入包的源文件目录
- 调用6g这个编译器编译生成hello.a,存放在$WORK/临时目录下
- 要编译main目录下的包了
- 还是创建一系列的临时文件夹
- 进入main文件夹
- 调用6g编译器,编译生成main.a,存放于$WORK/临时目录下
- 最后它进入了一个“当前目录”,应该就是我们执行go build命令的目录
- 调用连接器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文件,可见原因
- 最后,移动可执行文件并重命名
go module
是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module
将是Go语言默认的依赖管理工具。
一、GO111MODULE开启go mod的环境变量
要启用go module
支持首先要设置环境变量GO111MODULE
,通过它可以开启或关闭模块支持,它有三个可选值:off
、on
、auto
,默认值是auto
。
GO111MODULE=off
禁用模块支持,编译时会从GOPATH
和vendor
文件夹中查找包。GO111MODULE=on
启用模块支持,编译时会忽略GOPATH
和vendor
文件夹,只根据go.mod
下载依赖。GO111MODULE=auto
,当项目在$GOPATH/src
外且项目根目录有go.mod
文件时,开启模块支持。
简单来说,设置GO111MODULE=on
之后就可以使用go module
了,以后就没有必要在GOPATH中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod
和go.sum
。
- 开启:go env -w GO111MODULE=on
- 查看:go env GO111MODULE
注意:
- 没使用go mod时依赖安装在
%GOPATH/src/github.com
- 使用go mod后依赖安装在
%GOPATH/pkg/mod
不管是第三方还是Google相关的包注意:在使用GoLand工具时,不要配置Project GOPATH为当前工程目录,最好不要配置Project GOPATH,而是配置Module GOPATH
- 安装govendor : go get -u -v github.com/kardianos/govendor:
$GOPATH/src/vendor/vendor.json, 是从远端库添加依赖包
二、GOPROXY
Go1.11之后设置GOPROXY命令为:
export GOPROXY=https://goproxy.cn
Go1.13之后GOPROXY
默认值为[https://proxy.golang.org](https://proxy.golang.org)
,在国内是无法访问的,所以十分建议大家设置GOPROXY,这里我推荐使用goproxy.cn。
$ go env -w GOPROXY=https://goproxy.cn,direct
# 或者 https://mirrors.aliyun.com/goproxy/
# 或者 https://goproxy.io
三、go mod相关命令
常用的go mod
命令如下:
$ go mod init [模块名,或者github路径] # 初始化当前文件夹, 创建go.mod文件
$ go mod download # 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
$ go mod edit # 编辑go.mod文件(选项有-json、-require和-exclude)
$ go mod graph # 以文本模式打印模块依赖图
$ go mod tidy # 增加缺少的module,删除无用的module
$ go mod vendor # 在根目录生成vendor目录,将依赖复制到vendor下
$ go mod verify # 校验依赖是否正确
$ go mod why # 查找依赖
四、go.mod文件
go.mod文件记录了项目所有的依赖信息,其结构大致如下:
module github.com/Q1mi/studygo/blogger
go 1.12
require (
github.com/DeanThompson/ginpprof v0.0.0-20190408063150-3be636683586
github.com/gin-gonic/gin v1.4.0
github.com/go-sql-driver/mysql v1.4.1
github.com/jmoiron/sqlx v1.2.0
github.com/satori/go.uuid v1.2.0
google.golang.org/appengine v1.6.1 // indirect
)
其中,
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
。关于依赖的版本支持以下几种格式:
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest
replace
在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。
replace (
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
五、go mod高级操作
go get
在项目中执行go get
命令可以下载依赖包,并且还可以指定下载的版本。
- 运行
go get -u
将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号) - 运行
go get -u=patch
将会升级到最新的修订版本 - 运行
go get package@version
将会升级到指定的版本号version - 运行
go get -v
显示下载详细信息
如果下载所有依赖可以使用go mod download
命令。
整理依赖
我们在代码中删除依赖代码后,相关的依赖库并不会在go.mod
文件中自动移除。这种情况下我们可以使用go mod tidy
命令更新go.mod
中的依赖关系。
go mod edit
格式化
因为我们可以手动修改go.mod文件,所以有些时候需要格式化该文件。Go提供了一下命令:
go mod edit -fmt
添加依赖项
go mod edit -require=golang.org/x/text
移除依赖项
如果只是想修改go.mod
文件中的内容,那么可以运行go mod edit -droprequire=package path
,比如要在go.mod
中移除golang.org/x/text
包,可以使用如下命令:
go mod edit -droprequire=golang.org/x/text
关于go mod edit
的更多用法可以通过go help mod edit
查看。
替代只能翻墙下载的库
go mod edit -replace=golang.org/x/crypto@v0.0.0=github.com/golang/crypto@latest
go mod edit -replace=golang.org/x/sys@v0.0.0=github.com/golang/sys@latest
六、在项目中使用go module
既有项目
如果需要对一个已经存在的项目启用go module
,可以按照以下步骤操作:
- 在项目目录下执行
go mod init
,生成一个go.mod
文件。 执行
go get
,查找并记录当前项目的依赖,同时生成一个go.sum
记录每个依赖库的版本和哈希值。新项目
对于一个新创建的项目,我们可以在项目文件夹下按照以下步骤操作:
执行
go mod init 项目名
命令,在当前项目文件夹下创建一个go.mod
文件。- 手动编辑
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本地包和导入问题-转载
# go clean 清理moudle 缓存
$ go clean -modcache
# go list 查看可下载版本
$ go list -m -versions github.com/gogf/gf
# 编译并安装hello包,这里安装的意思是将生成的*.a文件放到工作目录$GOPATH/pkg目录下
$ go install hello
# 清除上一次install生成的包
$ go clean -i hello
# 生成可执行程序
$ go build main
# 删除可执行程序
$ 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,但是结果是修改后的源码?
```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
总结:连接器在连接时,其实使用的并不是我们工作目录下的hello.a文件,而是以该最新源码编译出的临时文件夹中的hello.a文件。<br />思考:对于有源代码的第三方库,如果没有源代码呢?如果是自带的Go标准库呢?
文件名可以和该包名不一致,但文件中使用的包名必须要和该包名一致。<br />"multi"包在包"cal"下,所以我们要把包名写完整"cal/multi"<br />自定义包名第一个部分必须是域名形式,例如 "github.com/mock"<br />另外一点要说的是,项目必须放在含[域名]格式的文件夹下<br />如果放在 [github.com] 下,go会帮你去拉更新。。。猝不及防
<a name="bLGjR"></a>
### 前提
假设我们现在有`moduledemo`和`mypackage`两个包,其中`moduledemo`包中会导入`mypackage`包并使用它的`New`方法。<br />`mypackage/mypackage.go`内容如下:
package mypackage import “fmt” func New(){ fmt.Println(“mypackage.New”) }
我们现在分两种情况讨论:
<a name="Twy5K"></a>
### 在同一个项目下 - 接着路径向下找
**注意**:在一个项目(project)下我们是可以定义多个包(package)的。
<a name="Xp8NF"></a>
#### 目录结构
现在的情况是,我们在`moduledemo/main.go`中调用了`mypackage`这个包。
moduledemo ├── go.mod ├── main.go └── mypackage └── mypackage.go └── mypackage1 └── mypackage1.go
<a name="44BoI"></a>
#### 导入包
这个时候,我们需要在`moduledemo/go.mod`中按如下定义:
module moduledemo go 1.14
然后在`moduledemo/main.go`中按如下方式导入`mypackage`
package main import ( “fmt” “moduledemo/mypackage” // 导入同一项目下的mypackage包 ) func main() { mypackage.New() fmt.Println(“main”) }
<a name="nyMci"></a>
#### 举个例子
举一反三,假设我们现在有文件目录结构如下:
└── bubble ├── dao │ └── mysql.go ├── go.mod └── main.go
其中`bubble/go.mod`内容如下:
module github.com/q1mi/bubble go 1.14
`bubble/dao/mysql.go`内容如下:
package dao import “fmt” func New(){ fmt.Println(“mypackage.New”) }
`bubble/main.go`内容如下:
```go
package main
import (
"fmt"
"github.com/q1mi/bubble/dao"
)
func main() {
dao.New()
fmt.Println("main")
}
不在同一个项目下 - replace
目录结构
├── moduledemo
│ ├── go.mod
│ └── main.go
└── mypackage
├── go.mod
└── mypackage.go
导入包
这个时候,mypackage
也需要进行module初始化,即拥有一个属于自己的go.mod
文件,内容如下:
module mypackage
go 1.14
然后我们在moduledemo/main.go
中按如下方式导入:
import (
"fmt"
"mypackage"
)
func main() {
mypackage.New()
fmt.Println("main")
}
因为这两个包不在同一个项目路径下,你想要导入本地包,并且这些包也没有发布到远程的github或其他代码仓库地址。这个时候我们就需要在go.mod
文件中使用replace
指令。
在调用方也就是moduledemo/go.mod
中按如下方式指定使用相对路径来寻找mypackage
这个包。
module moduledemo
go 1.14
require "mypackage" v0.0.0
replace "mypackage" => "../mypackage"
举个例子
最后我们再举个例子巩固下上面的内容。
我们现在有文件目录结构如下:
├── p1
│ ├── go.mod
│ └── main.go
└── p2
├── go.mod
└── p2.go
p1/main.go
中想要导入p2.go
中定义的函数。p2/go.mod
内容如下:
module liwenzhou.com/q1mi/p2
go 1.14
p1/main.go
中按如下方式导入
import (
"fmt"
"liwenzhou.com/q1mi/p2"
)
func main() {
p2.New()
fmt.Println("main")
}
因为我并没有把liwenzhou.com/q1mi/p2
这个包上传到liwenzhou.com
这个网站,我们只是想导入本地的包,这个时候就需要用到replace
这个指令了。p1/go.mod
内容如下:
module github.com/q1mi/p1
go 1.14
require "liwenzhou.com/q1mi/p2" v0.0.0
replace "liwenzhou.com/q1mi/p2" => "../p2"
此时,我们就可以正常编译p1
这个项目了。
说再多也没用,自己动手试试吧。