01_初识golang
1、Go 的环境变量
1. 查看环境变量
查看全部的go环境变量: go env
查看指定的go 环境变量: go env 环境变量名称
重点的环境变量如上 GOROOT、GOPATH、GOBIN、GOOS、GOARCH,下面详细介绍。
2. GOROOT
GOROOT是 go 语言的安装目录,它的作用就是提供 go 安装目录下的资源的索引。go 安装目录下的 bin 中包含一些工具以及一些常用 go 命令,在配置系统环境变量时会将该路径拼接到 PATH 中,就可以全局的使用 go 提供的一些命令了。
3. GOPATH
GOPATH 是我们的开发区,按照规范一般内部包含 bin(存放编译后的可执行文件)、pkg(存放编译后的包文件)、src(存放项目源文件)等等。当我们使用 go get 命令去远程获取依赖库时,如果有多个 GOPATH 会优先选择最前边的那个工作区进行安装。
4. GOBIN
GOBIN 用来存放项目编译后生成的二进制文件。当使用 go install 命令编译打包我们的项目时,会将编译后生成的二进制文件放入到 GOBIN 指定的目录下,默认是 GOPATH/bin
go install 与 go build 都是编译文件的命令,二者有什么区别呢?
go build: 用来测试编译包,在项目目录下生成可执行的文件(前提是有main包);
go install: 主要用来生成库和工具。一是编译包文件(无main包的),将编译后的包文件(xxx.a) 放入到 $GOPATH/pkg 目录下;二是编译生成可执行文件(有main包的),将生成的可执行文件放入到 $GOPATH/bin 目录下;
因此 go build 不能生成包文件, go install 可以生成包文件; go build 生成的可执行文件放在当前目录下,go install 生成的可执行文件放在 $GOPATH/bin 目录下。
5. GOOS、GOARCH
GOOS 的值代表为其编译代码的操作系统,比如 linux、darwin、windows 等等。
GOARCH 代表为其编译代码的CPU架构或者处理器,比如 amd64、arm 等等。
如果想在一个平台生成其它平台上运行的程序,就需要用到这两个变量。比如我在 32位windows系统上的程序,想能够在 64位Linux系统上运行,需要如下设置:
GOOS = linux、 GOARCH = amd64
,然后再编译目标源代码 go build xxx.go
6. GO MODULES
go modules 为 go 语言的包管理机制,为什么需要这种机制呢?
在 go 1.11之前,go 的包管理弊端如下:
- 在不使用额外的工具情况下,GO 的依赖包需要手工下载;
- 第三方依赖包没有版本的概念,如果该包进行了升级,程序代码会有很大的变动;
- 协作开发时,需要统一每个成员本地 $GOPATH/src 下的依赖包;
- 第三方包和自己的包都在 $GOPATH/src 目录下,非常混乱;
go modules 模式解决了这些问题:
- 自动下载包;
- 项目不必放在 GOPATH/src 目录下;
- 项目会生成一个 go.mod 文件,来列举出额外的包依赖;
- 第三方包会准确指定版本号;
- 对于已经转移的包,可以使用 replace 进行替换,不需要修改代码。
举个例子,比如说在 $GOPATH/src 外部创建了一个 hello.go 项目,首先通过 go mod init hello
来初始化模块,初始化完毕后会在当前目录下生成一个 go.mod 文件,同时还会维护一个 go.sum 文件,该文件包含了特定版本内容的预期加密hash,该hash 保证了这些模块的未来下载检索与第一次下载相同的位,以确保项目依赖的模块不会被恶意更改。注意子目录是不需要进行 go init 的,所有子目录所的依赖都会组织在根目录的 go.mod 文件里。
go.mod 的文件内容?
- module:定义当前项目的模块路径;
- go :标识当前模块的 Go 语言的版本;
- require:设置一个特定的版本;
- exclude:从使用中排除一个特定的版本;
- replace:用于将一个模块版本替换为另外一个模块版本;
go.sum 文件内容?
详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。
依赖私有模块? 如果项目依赖了由 GOPROXY 指定的模块代理或者 GOSUMDB 指定的 Go checksum database 都无法访问到的模块,就需要设置 go env -w GOPRIVATE="git.example.com,github.com/edsfda/mquote"
,比如公司私有的 git 仓库、github中的私有仓库都属于私有模块。
依赖下载的包在哪里? 使用了 go modules 模式所下载的依赖包在 $GOPATH/pkg/mod
目录下。
依赖包如何进行版本控制? go.mod 文件内会通过 require 引用到具体版本的包,也就是说在 $GOPATH/pkg/mod 目录下可以存在不同版本的包。
当项目放在 $GOPATH/src 下时包的引用流程? 根据 GO111MODULE 的值采取不同策略,默认情况下该值是 auto。
- auto(自动模式),项目中包含了 go.mod 文件就采用 GO MODULES。
- on(开启模式),1.12 后无论项目位置在哪,都会使用 require 的包。
- off(关闭模式),先去 $GOROOT 下找,再去 $GOPATH/src 下找。
依赖包中地址失效怎么办?比如说 golang.org/x/.... 下的包 采用 replace 进行包替换,比如 replace golang.org/x/text => github.com/golang/text latest
。 这样 go 就会用 github.com/golang/text 来替代 golang.org/x/text ,下载最新版本(latest) 到该位置下。
老项目如何采用新的包管理机制? 切换 GO111MODULE 为 auto/on ,并将项目移动到 $GOPATH/src 外部;然后进入项目运行 go mod init + 模块名称
;最后进行 go build / go run xxx
REF:
https://blog.csdn.net/Sihang_Xie/article/details/124851399
2、Go 依赖讲解
2.1 背景
Go 依赖管理的演进经历了以下 3 个阶段:
目前被广泛应用的是 Go Module,整个演进路线主要围绕实现两个目标来迭代发展:
- 不同环境 (项目) 依赖的版本不同;
- 控制依赖库的版本。
2.2 Go 依赖管理的演进
2.2.1 GOPATH
GOPATH 是 Go 语言支持的一个环境变量,是 Go 项目的工作区。其目录有以下 3 个结构 (需要手动创建文件夹):
文件夹 | 作用 |
---|---|
bin | 项目编译的二进制文件 |
pkg | 项目编译的中间产物 |
src | 项目源码 |
- 项目代码直接依赖
src
下的代码; go get
下载最新版本的包到src
目录下。
弊端:
下面的场景就体现了 GOPATH 的弊端:项目A 和项B 依赖于某一 package 的不同版本 (分别为 Pkg V1 和 Pkg V2 ) 。而 src 下只能允许一个版本存在,那项目A 和项B 就无法保证都能编译通过。
在 GOPATH 管理模式下,如果多个项目依赖同一个库,则依赖该库是同一份代码,无法做到不同项目依赖同一个库的不同版本。这显然无法满足实际开发中的项目依赖需求,为了解决这个问题,Go Vendor 出现了。
2.2.2 Go Vendor
- 与 GOPATH 不同之处在于项目目录下增加了 vendor 文件,所有依赖包以副本形式放在 $ProjectRoot/vendor 下。
- 在 Vendor 机制下,如果当前项目存在 Vendor 目录,会优先使用该目录下的依赖;如果依赖不存在,则会从 GOPATH 中寻找。这样,通过每个项目引入一份依赖的副本,解决了多个项目需要同一个 package 依赖的冲突问题。
弊端:
但 Vendor 无法很好解决依赖包版本变动问题和一个项目依赖同一个包的不同版本的问题。
如图项目A 依赖 Package B 和 Package C,而 Package B 和 Package C 又依赖了 Package D 的不同版本。通过 Vendor 的管理模式不能很好地控制对于 Package D 的依赖版本。一旦更新项目,有可能出现依赖冲突,导致编译出错。归根到底: Vendor 不能很清晰地标识依赖的版本概念。
2.2.3 Go Module
- Go Module 是 Go 语言官方推出的依赖管理系统,解决了之前依赖管理系统存在的诸如无法依赖同一个库的多个版本等问题。
- Go Module 自 Go1.11 开始引入,Go 1.16 默认开启。可以在项目目录下看到
go.mod
文件:
名称 | 作用 |
---|---|
go.mod | 文件,管理依赖包版本 |
go get / go mod |
指令,管理依赖包 |
【终极目标】定义版本规则和管理项目依赖关系。和 Java 中的 Maven 作用是一样的。
2.3 Go Module实践
2.3.1 依赖管理三要素
要素 | 对于工具 |
---|---|
配置文件,描述依赖 | go.mod |
中心仓库管理依赖库 | Proxy |
本地工具 | go get / go mod |
2.3.2 依赖配置-go.mod
打开项目目录下的 go.mod 文件,其文件结构主要分为三部分:
【module 路径 (上图的“依赖管理基本单元”)】用来标识一个 module,从 module 路径可以看出从哪里找到该 module 。例如,如果以 github 为前缀开头,表示可以从 Github 仓库找到该 module 。依赖包的源代码由 Github 托管,如果项目的子包想被单独引用,则需要通过单独的 init go.mod 文件进行管理。
【原生库】依赖的原生 Go SDK 版本。
【单元依赖】每个依赖单元用 module路径 + 版本号 来唯一标识。
2.3.3 依赖配置-version
- GOPATH 和 Go Vendor 都是源码副本方式依赖,没有版本规则概念。
- 而 go.mod 为了方便管理,定义了版本规则。分为语义化版本和基于 commit 伪版本两个版本。
1.语义化版本
${MAJOR}.${MINOR}.${PATCH}
1
如:V1.18.1、V1.8.0
名称 | 含义 |
---|---|
MAJOR | 不同的MAJOR版本表示是不兼容的API。因此即使是同一个库,MAJOR版本不同也会被认为是不同的模块 |
MINOR | 通常是新增函数或功能,向后兼容 |
PATCH | 一般是修复bug |
2.基于 commit 伪版本
每次提交 commit 后,Go 都会默认生成一个伪版本号: v0.0.0-yyyymmddhhmmss-abcdefgh1234
名称 | 含义 |
---|---|
v0.0.0 | 版本前缀和语义化版本是一样的 |
yyyymmddhhmmss | 时间戳,提交Commit的时间 |
abcdefgh1234 | 校验码,包含12位的哈希前缀 |
2.3.4 依赖配置-indirect
2.3.2 节的 go.mod 文件图中,细心观察可以发现有些单元依赖带有 // indirect
的后缀,这是一个特殊标识符,表示 go.mod 对应的当前 module 没有直接导入的包,也就是非直接依赖 (即间接依赖) 。
例如,一个依赖关系链为:A->B->C 。其中,A->B 是直接依赖;而 A->C 是间接依赖。
2.3.5 依赖配置-incompatible
2.3.2 节的 go.mod 文件图中,细心观察可以发现有些单元依赖带有 +incompatible 的后缀,这也是一个特殊标识符。对于 MAJOR 主版本在 V2 及以上的模块,go.mod 会在模块路径增加 /vN 后缀 (如下图中 example/lib5/v3 v3.0.2 )。这能让 Go Module 按照不同的模块来处理同一个项目不同 MAJOR 主版本的依赖。
由于 Go Module 是在 Go 1.11 才实验性地引入,所以在这个更新提出之前,已经有一些仓库打上了 V2 或者更高版本的 tag 了。
为了兼容这部分仓库,对于没有 go.mod 文件并且 MAJOR 主版本在 V2 及以上的依赖,会在版本号后加上 +incompatible 后缀。表示可能会存在不兼容的源代码。
2.3.6 依赖配置-依赖图
如下图所示,Main 项目依赖项目A 和项目B ,且项目A 和项目B 分别依赖项目C 的 v1.3 和 v1.4 版本。最终编译时,Go 所使用的项目C 的版本为:v1.4 。【总结】Go 选择最低的兼容版本。
2.3.7 依赖分发-回源
- 依赖分发,即依赖从何处下载、如何下载的问题。
- Go 的依赖绝大部分托管在 GitHub 上。Go Module 系统中定义的依赖,最终都可以对应到 GitHub 中某一项目的特定提交 (commit) 或版本。
- 对于 go.mod 中定义的依赖,则直接可以从对应仓库中下载指定依赖,从而完成依赖分发。
弊端
直接使用 GitHub 仓库下载依赖存在一些问题:
- 首先,无法保证构建稳定性。代码作者可以直接在 GitHub 上增加/修改/删除软件版本。
- 无法保证依赖可用性。代码作者可以直接在 GitHub 上删除代码仓库,导致依赖不可用。
- 第三,如果所有人都直接从 GitHub 上获取依赖,会导致 GitHub 平台负载压力。
解决方案-Proxy
- Go Proxy 就是解决上述问题的方案。Go Proxy 是一个服务站点,它会缓存 GitHub 中的代码内容,缓存的代码版本不会改变,并在 GitHub 作者删除了代码之后也依然可用,从而实现了 “immutability” (不变性) 和 “available” (可用的) 的依赖分发。
- 使用 Go Proxy 后,构建时会直接从 Go Proxy 站点拉取依赖。如下图所示。
2.3.8 GOPROXY的使用
-
Go Module 通过 GOPROXY 环境变量控制如何使用 Go Proxy 。
-
GOPROXY 是一个 Go Proxy 站点 URL 列表。
GOPROXY = "https://proxy1.cn, https://proxy2.cn, direct"
-
上述代码中,direct 表示源站 (如 GitHub) ,proxy1 proxy2 是两个URL 站点。依赖寻址路径为:优先从 proxy1 下载依赖,如果 proxy1 不存在,再从 proxy2 寻找,如果 proxy2 不存在,则会回源到源站直接下载依赖,并缓存到 Go Proxy 站点中 (这种设计思路和 Redis 缓存与 MySQL 数据库一模一样)。
2.3.9 工具-go get
go get example.org/pkg +...
后面跟不同的指令能实现不同的功能:
指令 | 功能 |
---|---|
@update | 默认 |
@none | 删除依赖 |
@v1.1.2 | 下载指定tag版本,语义版本 |
@23dfdd5 | 下载特定的commit版本 |
@master | 下载分支的最新commit |
2.3.10 工具-go mod
指令 | 功能 |
---|---|
init | 初始化,创建go.mod文件 |
download | 下载模块到本地缓存 |
tidy | 增加需要的依赖,删除不需要的依赖 |
在实际开发中,尽量提交之前执行下 go tidy
,减少构建时无效依赖包的拉取。
2.4 go mod使用
2.4.1 设置GO111MODULE
GO111MODULE 有三个值:off、on 和 auto (默认值)
- GO111MODULE=off :go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
- GO111MODULE=on :go命令行会使用modules,而一点也不会去GOPATH目录下查找。
- GO111MODULE=auto :默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
- 当前目录在GOPATH/src之外且该目录包含go.mod文件
- 当前文件在包含go.mod文件的目录下面。
注意:
- 在使用go module时,GOPATH是无意义的。不过它仍然会把下载的依赖存储在 $GOPATH/pkg/mod 中,也会把 go install 的结果放在 $GOPATH/bin 中。
- 当module功能启用时,依赖包的存放位置变更为 $GOPATH/pkg/mod 。允许同一个package多个版本并存,且多个项目可以共享缓存的module。
设置命令如下:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
2.4.2 在新项目中创建go mod
注意:如果你的项目根目录下已经有 go.mod
文件,可以不需要创建 go.mod
文件。
为了演示如何管理依赖,我创建了 hello.go
文件和 hello_test.go
单元测试代码:
单元测试的目录结构如下图所示:
//hello.go 代码
package hello
import "rsc.io/quote"// 引入第三方依赖模块 rsc.io/quote
func Hello() string {
return quote.Hello()
}
//hello_test.go 代码
package hello
import "testing"
func TestHello(t *testing.T) {
want := "Hello, world."
if got := Hello(); got != want {
t.Errorf("Hello() = %q, want %q", got, want)
}
}
- 打开 cmd,cd 到新项目的文件夹目录,输入
go mod init xxx(文件夹名称)
成功创建了 go.mod
文件,如下图所示:
- 【重点!】从 Go 1.16 开始,创建完
go.mod
文件还必须执行指令:go mod tidy
来增加项目需要的最小依赖。否则,运行 go test 指令时会报 no Go files in G:\hello 和 no required module provides package rsc.io/quote; to add it: go get rsc.io/quote 的错误。
Environment
处填写的 GOPROXY 网址要与cmd命令行输入go env
中的 GOPROXY 保持一致。设置好后重启Goland
2.4.3 go get更新依赖
go get -u
:更新到最新的次要版本或者修订版本(x.y.z)go get -u=patch
:更新到最新的修订版本go get package@version
:更新到指定的版本号version- 运行 go get 如果有版本的更改,那么 go.mod 文件也会作出相应的更改。