Go Modules 包管理

Go Package 管理发展史

Go 依赖管理是通过 Git 仓库模式实现,并随着版本的更迭已经逐渐完善

  • GOPATH 模式
    GOPATH 目录是所有工程的公共依赖包目录,所有需要编译的 go 工程的依赖包都放在 GOPATH 目录下。
  • Vendor 特性
    为了解决 GOPATH 模式下,多个工程需要共享 GOPATH 目录,无法适用于各个工程对于不同版本的依赖包的使用,不便于更新某个依赖包。 go 1.6 之后开启了 vendor 目录
  • Go Module 包管理
    从 Go1.11 以后开始支持 Module 依赖管理工具,从而实现了依赖包的进行升级更新,在 Go1.13 版本后默认打开。

GOPATH 环境变量

GOPATH 为 Go 开发环境时所设置的一个环境变量。

历史版本的 go 语言开发时,需要将代码放在 GOPATH 目录的 src 文件夹下。go get 命令获取依赖,也会自动下载到 GOPATH 的 src 下。

go get github.com/foo/bar
会将代码下载到 $GOPATH/src/github.com/foo/bar

GOPATH 具体结构如下,必须包含三个文件夹,具体如下图所示:

GOPATH
 |── bin              // 编译生成的二进制文件
 |── pkg              // 预编译文件,以加快程序的后续编译速度
 |── src              // 所有源代码
    ├── github.com
    ...

Go Modules 介绍

go.mod是Golang1.11版本新引入的官方包管理工具用于解决之前没有地方记录依赖包具体版本的问题,方便依赖包的管理。

go.mod其实就是一个Modules

一个 Go Module 是一个 Go 包的集合。module 是有版本的,所以 module 下的包也就有了版本属性。这个 module 与这些包会组成一个独立的版本单元,它们一起打版本、发布和分发。

  • 在 Go Module 模式下,通常一个代码仓库对应一个 Go Module。一个 Go Module 的顶层目录下会放置一个 go.mod 文件,每个 go.mod 文件会定义唯一一个 module,也就是说 Go Module 与 go.mod 是一一对应的。

  • go.mod 文件所在的顶层目录也被称为 module 的根目录,module 根目录以及它子目录下的所有 Go 包均归属于这个 Go Module,这个 module 也被称为 main module。

Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件。

主要通过 go.modgo.sum 组成,主要包括依赖模块路径定义,以及通过 checksum 方式进行保证包的安全性。并且 可以在 GOPATH 外进行创建和编译项目

初步使用Go Modules

1. 开启 GO MODULE

export GO111MODULE=on

配置环境变量 GO111MODULE:有三个可选值: offonauto,默认值是 auto

  • off:无模块支持,go会从 GOPATH 和 vendor 文件夹寻找包。
  • on:模块支持,go会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod下载依赖。
  • auto:默认值,go命令行将会根据当前目录来决定是否启用module功能。
    1. 当前目录在GOPATH/src之外且该目录包含go.mod文件
    2. 当前文件在包含go.mod文件的目录下面。

2. 初始化项目

// 创建一个目录
$ mkdir hello
// 初始化项目
$ go mod init hello
go: creating new go.mod: module hello

已经成功使用 go mod 创建一个项目

go.mod文件一旦创建后,它的内容将会被go toolchain全面掌控。
go toolchain会在各类命令执行时,比如go get、go build、go mod等修改和维护go.mod文件。

非 GOPATH 目录下创建 Go项目执行 go mod init 报错: cannot determine module path for source directory xxxxxxx (outside GOPATH, module path must be specified) .
解决:go mod init 项目名
总结:非GOPATH目录下必须要加上项目名,GOPATH下可以不加

运行完之后,会在当前目录下生成一个go.mod文件,这是一个关键文件,之后的包的管理都是通过这个文件管理。

查看当前目录结构信息

$ tree
.
|____go.mod

注意:子目录里是不需要init的,所有的子目录里的依赖都会组织在根目录的go.mod文件里

// 输出go.mod内容
$ cat go.mod
module hello

go 1.16

3. 引入外部包

编写 main.go

package main

import log "github.com/sirupsen/logrus"

func main() {
	log.Info("hello")
}

通过 go get github.com/sirupsen/logrus,可以下载或者更新依赖包
也可以指定版本 path@v1.8.1

查看 go.mod 文件

module hello

go 1.16

require github.com/sirupsen/logrus v1.8.1

go.mod 介绍

go.mod 文件中可以使用到的语法关键词以及含义:

module hello //定义当前项目的模块路径

go 1.16 // 标识当前模块的 Go 语言版本,目前来看还只是个标识作用

// 说明 Module 需要什么版本的依赖 path@version
require github.com/sirupsen/logrus v1.8.1

// 用于从使用中排除一个特定的模块版本
exclude github.com/sirupsen/logrus v1.8.0 

// 替换 require 中声明的依赖,使用另外的依赖及其版本号 (用的名字 => 实际包)
replace github.com/sirupsen/logrus => github.com/sirupsen/logrus v1.8.2 

go.sum 介绍

为了解决 Go Modules 的包被篡改安全隐患,Go 开发团队在引入 go.mod 的同时也引入了go.sum 文件,用于记录每个依赖包的哈希值,在构建时,如果本地的依赖包 hash值go.sum 文件中记录得不一致,则会拒绝构建。

  • go.sum 文件中每行记录由 module 名、版本和哈希组成,并由空格分开;
  • 在引入新的依赖时,通常会使用 go get 命令获取该依赖,将会下载该依赖包下载到本地缓存目录 $GOPATH/pkg/mod/cache/download,该依赖包为一个后缀为 .zip 的压缩包,并且把哈希运算同步到 go.sum 文件中;
  • 在构建应用时,会从本地缓存中查找所有 go.mod 中记录的依赖包,并计算本地依赖包的哈希值 ,然后与 go.sum 中的记录进行对比,当校验失败,go 命令将拒绝构建。

注意

go.mod和go.sum都应检入版本控制。
go.sum 不需要手工维护,所以可以不用太关注。

Go Modules 代理

Go 1.13 将 GOPROXY 默认成 https://proxy.golang.org,但是国内无法访问,所以在国内需要配置代理进行使用。

GOPROXY 可以解决一些公司内部的使用问题:

  • 访问公司内网的 git 仓库
  • 防止公网仓库变更或者消失,导致线上编译失败或者紧急回退失败 (可以自己加一层代理,防止包不存在)
  • 公司审计和安全需要
  • 防止公司内部开发人员配置不当造成 import path 泄露 (使用GOPRIVATE防止访问到恶意包文件)
  • cache 热点依赖,降低公司公网出口带宽

GOPROXY

# 配置GOPROXY环境变量
# direct表示可以回源到原始的github
export GOPROXY=https://goproxy.cn,direct

GOPRIVATE

GOPRIVATE 用来控制 go 命令把哪些仓库看做是私有的仓库
这样的话,就可以跳过 proxy server 和校验检查,这个变量的值支持用逗号分隔,可以填写多个值,例如:

# 设置自己的仓库或者组(不走proxy)多个逗号相隔
export GOPRIVATE=*.corp.example.com,github.com/org_name

当配置 GOPRIVATE ,它的值同时也将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以当配置 GOPRIVATE 后将会跳过代理,以及 checksum 效验检查。

通常推荐配置 GOPROXY 和 GOPRIVATE 进行使用,GOPRIVATE 也可以识别 Git SSH KEY 进行权限效验。

补充

go env方式配置

也可以使用 go env 的方式来进行配置

设置env 配置

go env -w 环境变量=值

# 开启gomod
go env -w GO111MODULE=on
# 使用七牛云代理  阿里云 https://mirrors.aliyun.com/goproxy
go env -w GOPROXY=https://goproxy.cn,direct

取消env配置

go env -u 环境变量

go mod cli 模式基本命令

命令解释
go mod init在当前目录初始化mod
go mod download下载依赖包 下载modules到本地cache 包在 GOPATH/pkg/mod 目录中,而且是只读文件
go mod vendor将依赖复制到vendor下 ,会生成vendor文件夹该文件夹下将会放置你go.mod文件描述的依赖包,文件夹下同时还有一个文件modules.txt
go mod verify验证依赖项是否达到预期的目的 如果所有的模块都没有被修改过,那么执行这条命令之后,会打印all modules verified
go mod tidy添加缺失的模块以及移除无用的模块 执行后会生成go.sum文件
go mod tidy -v可以将执行的信息,即删除和添加的包打印到命令行
go mod graph打印模块依赖图
go mod why解释为什么需要依赖
go clean -modcache清除mod下载的包

常见问题

一:依赖的包下载到哪里了?还在GOPATH/src里吗?

不在。
使用Go的包管理方式,依赖的第三方包被下载到了$GOPATH/pkg/mod路径下。

二: 依赖包的版本是怎么控制的?

在上一个问题里,可以看到最终下载在$GOPATH/pkg/mod下的包中最后会有一个版本号 v1.0.5,也就是说,$GOPATH/pkg/mod里可以保存相同包的不同版本。

版本是在go.mod中指定的。如果,在go.mod中没有指定,go命令会自动下载代码中的依赖的最新版本,本例就是自动下载最新的版本。如果,在go.mod用require语句指定包和版本 ,go命令会根据指定的路径和版本下载包,
指定版本时可以用latest,这样它会自动下载指定包的最新版本;

go get github.com/username/repository@vx.x.x下载并设置依赖项和更新go.mod文件的特定版本。

三: 可以把项目放在$GOPATH/src下吗?

可以。但是go会根据GO111MODULE的值而采取不同的处理方式,默认情况下,GO111MODULE=auto 自动模式

  1. auto 自动模式下,项目在 G O P A T H / s r c 里 会 使 用 GOPATH/src里会使用 GOPATH/src使GOPATH/src的依赖包,在$GOPATH/src外,就使用go.mod 里 require的包
  2. on 开启模式,1.12后,无论在$GOPATH/src里还是在外面,都会使用go.mod 里 require的包
  3. off 关闭模式,就是老模式。

四: 依赖包中的地址失效了怎么办?比如 golang.org/x/… 下的包都无法下载怎么办?

在go快速发展的过程中,有一些依赖包地址变更了。以前的做法:

  1. 修改源码,用新路径替换import的地址
  2. git clone 或 go get 新包后,copy到$GOPATH/src里旧的路径下

无论什么方法,都不便于维护,特别是多人协同开发时。

使用go.mod就简单了,在go.mod文件里用 replace 替换包,例如
replace golang.org/x/text => github.com/golang/text latest

这样,go会用 github.com/golang/text 替代golang.org/x/text,原理就是下载github.com/golang/text 的最新版本到 $GOPATH/pkg/mod/golang.org/x/text下。

五: init生成的go.mod的模块名称有什么用?

本例里,用 go mod init hello 生成的go.mod文件里的第一行会申明module hello

因为我们的项目已经不在$GOPATH/src里了,那么引用自己怎么办?就用模块名+路径

例如,在项目下新建目录 utils,创建一个tools.go文件:

package utils
 
import "fmt"
 
func PrintText(text string) {
    fmt.Println(text)
}

在根目录下的main.go文件就可以 import “hello/utils”引用utils

package main
 
import (
	"hello/utils"
	"github.com/astaxie/beego"
)
 
func main() {
	utils.PrintText("Hi")
	beego.Run()
}

六:以前老项目如何用新的包管理

如果用auto模式,把项目移动到$GOPATH/src外
进入目录,运行 go mod init + 模块名称
go build 或者 go run 一次

七:列出软件包的可用版本

go list -m -versions  github.com/sirupsen/logrus
github.com/sirupsen/logrus v0.1.0 v0.1.1 v0.2.0 v0.3.0 v0.4.0 v0.4.1 v0.5.0 v0.5.1 v0.6.0 v0.6.1 v0.6.2 v0.6.3 v0.6.4 v0.6.5 v0.6.6 v0.7.0 v0.7.1 v0.7.2 v0.7.3 v0.8.0 v0.8.1 v0.8.2 v0.8.3 v0.8.4 v0.8.5 v0.8.6 v0.8.7 v0.9.0 v0.10.0 v0.11.0 v0.11.1 v0.11.2 v0.11.3 v0.11.4 v0.11.5 v1.0.0 v1.0.1 v1.0.3 v1.0.4 v1.0.5 v1.0.6 v1.1.0 v1.1.1 v1.2.0 v1.3.0 v1.4.0 v1.4.1 v1.4.2 v1.5.0 v1.6.0 v1.7.0 v1.7.1 v1.8.0 v1.8.1
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值