阅读 1088

Go语言基本环境变量与依赖管理 | 🏆 技术专题第二期征文

前提

最近开始系统学习一下Golang这么新语言,记录一下它的基本环境变量配置以及依赖管理方式。编写本文的时候使用的Golang版本为go1.14.2 windows/amd64,其他版本不一定保证适合本文的内容,开发工具使用JetBrains Goland。因为习惯,可能有时候把Go语言称为Go,有时候称为Golang

理解一下Golang的环境变量

安装完Golang之后,可以通过go env命令查看环境变量配置,下面是笔者执行此命令的结果:

λ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\doge\AppData\Local\go-build
set GOENV=C:\Users\doge\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\doge\go
set GOPRIVATE=
set GOPROXY=https://goproxy.cn,direct
set GOROOT=I:\Environment\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=I:\Environment\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\doge\AppData\Local\Temp\go-build779409438=/tmp/go-build -gno-record-gcc-switches
复制代码

在日常开发中,我们需要重点关注GOPROXYGOROOTGOPATHGOBIN,其他配置项可以在需要的时候再查询文档进行配置。

GOPROXY

GOPROXY就是设置Golang的全局代理。在下载依赖包的时候,一般是访问github的仓库,国内的环境很容易被墙,所以最好设置一个网速快的代理Go在此版本中GOPROXY的默认值为https://proxy.golang.org,国内是无法访问的。因此,这里推荐使用七牛云的代理goproxy.cn进行网络加速:

go env -w GOPROXY=https://goproxy.cn,direct
复制代码

设置完此代理之后,下载依赖的速度就能相对正常。

GOROOT

GOROOT其实就是Golang安装的绝对路径,例如笔者把他安装在I:\Environment\Go目录下,所以:

λ go env
...
set GOROOT=I:\Environment\Go
...
复制代码

GOROOT需要加入到系统变量Path里面,添加成功后才能在命令行使用go [Command]

GOPATH和GOBIN

GOPATH可以简单理解为工作目录,如果用过Eclipse,那么GOPATH可以类比为Eclipse中的WorkSpace的概念。GOPATH目录约定由三个子目录:

$GOPATH
  - src  ---  存放源代码,go run、go install等命令就是在当前的工作路径中执行(也就是这些命令执行的目标文件夹就是这个src文件夹)
  - pkg  ---  存放编译时生成的中间文件
  - bin  ---  存放编译后生成的可执行文件
复制代码

GOPATH变量可以设置多个值,多个值之间使用特定的分隔符隔开,例如在Windows系统,分隔符是英文的分号;

λ go env
...
set GOPATH=C:\Users\doge\go;I:G-Projects
...
复制代码

值得注意的是:go get命令下载的依赖包会下载在GOPATH指定的第一个值对应的目录中,也就是$Users/$User/go目录下。

GOBIN用于指定go install目标保存路径,目的是避免将所有工作空间的bin路径添加到PATH环境变量中(因此在使用版本控制时,尽量忽略binpkg,建议直接在src,或者具体的子包下创建代码仓库)。于此相反的做法,就是在Linux或者Unix系统中,可以在PATH中添加export PATH=$PATH:${GOPATH//://bin:}/bin下把每个GOPATH下的bin都加入到PATH中。

重点来了:Module的出现,就是为了弱化GOPATH的概念,使用Module去管理项目的依赖,那么可以基本忽略GOPATH的原有的功能。

Golang提供的命令

可以通过命令行go help查看Go提供的命令行工具:

这里可以关注一下前面一个栏目的基础命令即可:

命令功能使用例子
go bug报告一个BUG,会调用系统默认浏览器打开提交BUG报告的页面go bug
go build编译所有的包和依赖go build [-o output] [-i] [build flags] [packages]
go clean清理执行其它命令时产生的一些文件和目录go clean [clean flags] [build flags] [packages]
go doc打印附于Go语言程序实体上的文档go doc pkg.app
go env展示Go的环境变量配置go env
go fix把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码,这里的版本是指Go的版本-
go fmt格式化指定代码为统一的风格-
go generate扫描与当前包相关的源代码文件,找出所有包含"//go:generate"的特殊注释,提取并执行该特殊注释后面的命令,命令为可执行程序`go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go...
go get根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装go get github.com/x/y
go install编译并安装指定的代码包及它们的依赖包go install
go list列出指定的代码包的信息-
go modModule相关命令见下文分析
go run编译并运行命令源码文件-
go testGo语言编写的程序进行测试,简单来说就是运行测试代码go test mine_test.go
go tool运行特定的Go提供的工具go tool fix
go version打印Golang的版本go version
go vet检查Go语言源码中静态错误go vet

如果想了解每个命令的详细使用教程可以通过go help 命令Topic得到对应的结果,更多或更详细的用法后面在探究Golang编码和基础知识的时候再深入展开。

Golang依赖管理

之前跟一个前辈讨论对比JavaGolang的生态的时候,笔者指出了Golang在工程化方面对比Java感觉偏弱,最常见的例子就是Java有全球通用的依赖中央仓库,国内也有阿里的Maven仓库做加速,开发者可以很轻易通过GAVGroupIdArtifactIdVersion)去拉取不同版本的依赖包,这一点在Golang中展现出了弱势。回想起来时间已经过去一年了Golang也在进步,依赖管理也开始完善,笔者的过去狭隘的思维也改变了(其实不能总用Java的角度去学习其他编程语言,否则很难体会到其他语言的精髓,甚至有时候会衍生一些奇怪的想法,例如:总是想Golang中是否存在类似Java中的Spring或者Mybatis这类框架)。

Golang1.11版本之后引入了Module作为依赖管理工具,从1.13或者之后的版本,Module作为官方默认的依赖管理工具,对应的命令是go mod [Command]Module功能的启用与否由环境变量中的GO111MODULE决定,而GO111MODULE有三个可选值:

  • GO111MODULE=off,禁用Module功能,则编译的时候会从GOPATHvendor文件夹中查找依赖包。
  • GO111MODULE=on,启用Module功能,则编译的时候会忽略GOPATHvendor文件夹,编译所需的依赖由go.mod文件描述,从本地缓存$GOPATH/pkg/mod目录中加载。
  • GO111MODULE=auto,自动判断是否启用Module功能,此选项是默认值,当项目在$GOPATH/src外且项目根目录有go.mod文件时,则启用Module功能。

Module存在的意义是:没有必要硬性规定在GOPATH中创建项目,管理项目依赖的第三方包信息可以独立管理go mod支持的所有命令如下:

注意前面的描述:go mod提供对模块操作的访问。注意,所有go命令都内置了对模块的支持,不仅仅是'go mod'。例如,依赖的添加、删除、升级或者降级应该使用'go get'来完成。有关模块功能的概述,请参阅'go help modules'。

go mod命令功能
go mod download下载依赖的模块到本地缓存中,本地缓存的默认路径是$GOPATH/pkg/mod目录
go mod edit编辑go.mod文件
go mod graph打印模块依赖图
go mod init基于当前文件夹初始化一个新模块,,创建go.mod文件
go mod tidy添加缺失的模块,移除无用的模块
go mod vendor把所有依赖拷贝到vendor文件夹中
go mod verify校验依赖,检查依赖内容是否和预期一致
go mod why解释为什么需要引入包(packages)和模块(modules

一定要注意:go module不能和GOPATH共存,如果使用了go module一定要把项目移出GOPATH

使用Module进行依赖管理

先使用JetBrains Goland创建一个新项目,项目里面创建binpkgsrc三个目录:

项目创建完成后,根目录中已经存在了一个go.mod文件,也就是JetBrains Goland已经帮我们在当前目录执行过go mod init,文件内容是:

module "module-sample"

go 1.14
复制代码

src目录下引入一个app.go文件:

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main() {
	connection, err := redis.Dial("tcp", "127.0.0.1:6379", redis.DialDatabase(0))
	if nil != err {
		fmt.Println("连接到Redis异常", err)
		return
	}
	defer connection.Close()
	n, err := connection.Do("SET", "name", "throwable")
	if nil != err {
		fmt.Println(err)
	}
	if n == "OK" {
		fmt.Println("执行Set命令成功")
	}
	value, err := redis.String(connection.Do("GET", "name"))
	if nil != err {
		fmt.Println(err)
	}
	fmt.Println("Get命令执行结果:", value)
}
复制代码

会发现,整个app.go文件的依赖都是标红的,也就是依赖包没有下载和导入:

此时使用go mod tidy进行依赖整理,执行完毕之后,发现根目录生成了一个新的go.sum,它用于记录锁定的依赖记录(依赖的包、路径、版本以及哈希值)。go.sum文件如下:

github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
复制代码

go.mod文件也被修改:

module module-sample

go 1.14

require github.com/garyburd/redigo v1.6.2
复制代码

然后使用go mod download下载依赖到本地缓存中,下载完成后,使用go mod vendor把依赖拷贝到当前模块的vendor目录下,那么原来标红的文件就能正常编译了(这里其实只要使用了go mod tidy后就会自动下载依赖包到缓存中,IDE就可以自动识别了)。

go mod管理依赖支持语义化版本号,例如:

  • 依赖版本:foo@v1.2.3
  • Git分支的Tagfoo@master
  • Git提交点的SHA-1值:foo@e3702bed2
  • 其他值:如<=v1.1.1latest等等。

go.mod文件中可以使用如下关键字:

  • module:模块名。
  • require:引入所需依赖,注意包名和版本,例如:
require github.com/garyburd/redigo v1.2.0
复制代码
  • replace:替换依赖,例如:
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
)
复制代码
  • indirect:指明是间接引用,例如:
require (
    github.com/garyburd/redigo v1.6.0
    github.com/garyburd/xx v1.0.0  // indirect
)    
复制代码

另外,可以单独使用go get命令下载对应的依赖,而go mod download会下载所有用到的依赖。最后附上一些小技巧:

命令功能
go mod edit -fmt格式化go.mod文件
go mod edit -require=需要的依赖添加依赖到go.mod文件,或者切换依赖版本
go mod edit -droprequire=指定的依赖go.mod文件移除对应的依赖
go mod tidygo mod downloadgo mod vendor三个命令组合使用,相当于全局更新一次所需的依赖,依赖包会更新到项目中的vendor文件夹

如果熟练使用这些命令,那么依赖管理就会变得相对容易。最后提几点:

  • 使用go mod vendor会把mod缓存中的依赖包复制到项目的vendor文件夹,当前项目就可以随意拷贝分发,避免因网络问题造成接收者安装依赖包的麻烦。
  • 如果开启了模块支持,编译的时候会直接从mod缓存中提取依赖,不会用到vendor目录和GOPATH目录下的文件。
  • 如果关闭了模块支持,编译的时候会完整地使用vendor目录下的文件或者完整地使用GOPATH目录下的文件。
  • 前面的依赖搜索都失败之后,会从$GOROOT/src下搜索依赖包。

小结

本文简单介绍了GolangModule依赖管理功能,这里简单记录几个要点:

  • go mod download下载的依赖包会存放在Go本地缓存中,具体位置是$GOPATH/pkg/mod(这里的GOPATH一般是就是全局的那个GOPATH,值为$Users/$User/go),这些缓存的依赖包可以共享给多个项目。
  • 启用Module功能后,模块根目录生成一个go.mod用于记录当前模块的依赖关系。
  • 启用Module功能后,一旦下载了新的依赖,就会在模块根目录生成一个go.sum用于记录被锁定的依赖记录。

go.modgo.sum最终决定了一棵锁定好的依赖树,最终编译以及安装都是通过这两个描述文件,关联到本地缓存下载好的依赖包完成后续的编译和打包工作。

(本文完 c-2-d e-a-20200812 文章比较菜,见谅,我是Go语言菜鸡一个,封面来源于互联网)

🏆 技术专题第二期 | 我与 Go 的那些事......