前提
最近开始系统学习一下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
在日常开发中,我们需要重点关注GOPROXY
、GOROOT
、GOPATH
和GOBIN
,其他配置项可以在需要的时候再查询文档进行配置。
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
环境变量中(因此在使用版本控制时,尽量忽略bin
、pkg
,建议直接在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... | packages]` |
go get | 根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装 | go get github.com/x/y | |
go install | 编译并安装指定的代码包及它们的依赖包 | go install | |
go list | 列出指定的代码包的信息 | - | |
go mod | Module 相关命令 | 见下文分析 | |
go run | 编译并运行命令源码文件 | - | |
go test | 对Go 语言编写的程序进行测试,简单来说就是运行测试代码 | 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依赖管理
之前跟一个前辈讨论对比Java
和Golang
的生态的时候,笔者指出了Golang
在工程化方面对比Java
感觉偏弱,最常见的例子就是Java
有全球通用的依赖中央仓库,国内也有阿里的Maven
仓库做加速,开发者可以很轻易通过GAV
(GroupId
、ArtifactId
和Version
)去拉取不同版本的依赖包,这一点在Golang
中展现出了弱势。回想起来时间已经过去一年了,Golang
也在进步,依赖管理也开始完善,笔者的过去狭隘的思维也改变了(其实不能总用Java
的角度去学习其他编程语言,否则很难体会到其他语言的精髓,甚至有时候会衍生一些奇怪的想法,例如:总是想Golang
中是否存在类似Java
中的Spring
或者Mybatis
这类框架)。
Golang
从1.11
版本之后引入了Module
作为依赖管理工具,从1.13
或者之后的版本,Module
作为官方默认的依赖管理工具,对应的命令是go mod [Command]
。Module
功能的启用与否由环境变量中的GO111MODULE
决定,而GO111MODULE
有三个可选值:
GO111MODULE=off
,禁用Module
功能,则编译的时候会从GOPATH
和vendor
文件夹中查找依赖包。GO111MODULE=on
,启用Module
功能,则编译的时候会忽略GOPATH
和vendor
文件夹,编译所需的依赖由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
创建一个新项目,项目里面创建bin
、pkg
和src
三个目录:
项目创建完成后,根目录中已经存在了一个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
分支的Tag
:foo@master
。Git
提交点的SHA-1
值:foo@e3702bed2
。- 其他值:如
<=v1.1.1
、latest
等等。
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 tidy 、go mod download 、go mod vendor | 三个命令组合使用,相当于全局更新一次所需的依赖,依赖包会更新到项目中的vendor 文件夹 |
如果熟练使用这些命令,那么依赖管理就会变得相对容易。最后提几点:
- 使用
go mod vendor
会把mod
缓存中的依赖包复制到项目的vendor
文件夹,当前项目就可以随意拷贝分发,避免因网络问题造成接收者安装依赖包的麻烦。 - 如果开启了模块支持,编译的时候会直接从
mod
缓存中提取依赖,不会用到vendor
目录和GOPATH
目录下的文件。 - 如果关闭了模块支持,编译的时候会完整地使用
vendor
目录下的文件或者完整地使用GOPATH
目录下的文件。 - 前面的依赖搜索都失败之后,会从
$GOROOT/src
下搜索依赖包。
小结
本文简单介绍了Golang
的Module
依赖管理功能,这里简单记录几个要点:
go mod download
下载的依赖包会存放在Go
本地缓存中,具体位置是$GOPATH/pkg/mod
(这里的GOPATH
一般是就是全局的那个GOPATH
,值为$Users/$User/go
),这些缓存的依赖包可以共享给多个项目。- 启用
Module
功能后,模块根目录生成一个go.mod
用于记录当前模块的依赖关系。 - 启用
Module
功能后,一旦下载了新的依赖,就会在模块根目录生成一个go.sum
用于记录被锁定的依赖记录。
go.mod
和go.sum
最终决定了一棵锁定好的依赖树,最终编译以及安装都是通过这两个描述文件,关联到本地缓存下载好的依赖包完成后续的编译和打包工作。
(本文完 c-2-d e-a-20200812 文章比较菜,见谅,我是Go语言菜鸡一个,封面来源于互联网)