内容简介:Golang的包管理分为三个阶段,version < 1.11、 1.11 <= version < 1.13、 version >= 1.13。在这个阶段,Golang的包管理存在以下不足:之所以设置GOPATH环境变量有两个原因:
Golang的包管理分为三个阶段,version < 1.11、 1.11 <= version < 1.13、 version >= 1.13。
version < 1.11
在这个阶段,Golang的包管理存在以下不足:
- 必须设置GOPATH环境变量,且源代码必须存放在GOPATH下
- 拉取外部依赖包时,总是拉取最新的版本,无法指定需要的版本
之所以设置GOPATH环境变量有两个原因:
go get
另外,由于无法指定依赖包的版本,因此容易导致“本地测试OK,但线上部署失败”的问题。这样的问题是广大开发者无法忍受的,所以,各种包管理 工具 开始涌现出来,典型的有dep,glide等,这里不再赘述。
1.11 <= version < 1.13
这个阶段默认使用的还是GOPATH的管理方式,但是开始支持 Go Module
的管理方式。
Go Module解决了上述的阶段存在的不足:
1.它不再需要GOPATH,即你的项目代码可以随意存放
2.它通过go.mod + go.sum解决依赖包的版本问题(后面会讲到)
如果需要迁移到Go Module,需要设置以下环境变量
vim ~/.bash_profile export GO111MODULE=on 复制代码
version >= 1.13
从这个阶段开始,Golang的包管理默认使用的是Go Module。
使用GOPATH进行包管理
注:为了完整性,这里尝试使用 go 1.11复现之前使用GOPATH进行包管理的情况。
1.下拉 docker 镜像
$ docker pull ubuntu:16.04 $ docker run -itd --name golang-lab ubuntu:16.04 /bin/bash $ apt-get update && apt-get install wget 复制代码
2.安装go 1.11
$ wget https://dl.google.com/go/go1.11.10.linux-amd64.tar.gz $ tar -zxvf go1.11.10.linux-amd64.tar.gz $ go/bin/go version go version go1.11.10 linux/amd64 复制代码
3.新建项目
3.1 这里我们假定 /home/go-projects
为我们的工作区
3.2 新建bin目录用于存放 可执行文件 ; 新建pkg目录用于存放 静态链接库文件 ; 新建src目录用于存放的我们 源码文件 , 一般我们写的代码都会放到这个目录下。
3.3 git.own.com
名称可自定义,这里只是个人编程习惯,表示这里存放的都是个人项目
$ mkdir /home/go-projects $ cd /home/go-projects && mkdir src && mkdir pkg && mkdir bin $ cd src && mkdir git.own.com && cd git.own.com $ mkdir gopath-lab && cd gopath-lab && touch main.go 复制代码
4.目录树
root@ebca4ae962aa:/home/go-projects# tree -L 4 . |-- bin |-- pkg `-- src `-- git.own.com `-- gopath-lab `-- main.go 复制代码
5.设置环境变量
- GOPATH:工作区路径,存放源代码。
- GOBIN:当使用go install xx.go 时, 生成的可执行文件就会放在此目录
- GOROOT:Go的安装位置,用于寻找标准库,这里是/home/go
$ vim ~/.bashrc export PATH=$PATH:/home/go/bin export GOPATH=/home/go-projects export GOBIN=/home/go-projects/bin export GOROOT=/home/go 复制代码
如果没有设置GOBIN,会报错
$ go install main.go go install: no install location for .go files listed on command line (GOBIN not set) 复制代码
6.main.go 代码如下:
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } 复制代码
可以看到,直接 go run
并不能自动下载依赖
$ go run main.go main.go:3:8: cannot find package "github.com/gin-gonic/gin" in any of: /home/go/src/github.com/gin-gonic/gin (from $GOROOT) /home/go-projects/src/github.com/gin-gonic/gin (from $GOPATH) 复制代码
7.手动下载并测试
# 居然奇迹般下载成功了,一般这个时候需要设置代理 $ go get -v github.com/gin-gonic/gin # 可以看到,源码已经下载到src目录了 $ ls /home/go-projects/src/ git.own.com github.com golang.org gopkg.in # 再次执行,运行成功 $ go run main.go [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /ping --> main.main.func1 (3 handlers) [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default [GIN-debug] Listening and serving HTTP on :8080 复制代码
使用Go Module进行包管理
本节翻译自 《Using Go Modules》
Module 是一系列依赖包的集合,通过 go mod init xxx
可初始化一份空的go.mod和go.sum,这两份文件存放于项目的根路径下。
对于go.mod,它不仅存储了这些依赖包的路径及其版本,同时 也指定了import的根路径 ,对于go.sum,它存放了依赖包内容的预期校验和,保证前一次下载的代码和现在下载的代码是一致的。
配置代理
由于Golang大部分依赖包都在国外,直接下载非常缓慢,在没有Go Module的时候,需要自己配置代理,比如socks;但是有了Go Module,就可通过设置环境变量来配置代理了,具体参考: goproxy.io/zh/。
配置时有几个注意点:
1.如果你有私有仓库和公共仓库,则需要加上 direct
参数,并配置 GOPRIVATE
(针对Go1.13)
# 有了direct,GOPRIVATE指定的仓库不会使用代理 go env -w GOPROXY=https://goproxy.io,direct # 设置不走代理的私有仓库,多个用逗号相隔 go env -w GOPRIVATE=*.corp.example.com 复制代码
2.如果你使用的是Golang IDE,则注意该IDE也要配置
3.如果你的~/.bash_profile或~./bashrc 文件存在GO111MODULE等环境变量,则go env 写入时会冲突
warning: go env -w GOPROXY=... does not override conflicting OS environment variable
初始化项目
1.新建文件夹
mkdir go-module-lab && cd go-module-lab
2.初始化Go Module项目,git.own.com/go-module是自定义的
go mod init git.own.com/go-module
3.查看go.mod
module git.own.com/go-module go 1.13 复制代码
添加代码测试
1.自定义库
mkdir hello && touch hello/hello.go
hello.go 内容
package hello func Hello() string { return "Hello, world." } 复制代码
2.新建main.go测试,内容如下
package main import ( "fmt" // 前面提过,go.mod 指定了import时的根路径 "git.own.com/go-module/hello" ) func main() { fmt.Println(hello.Hello()) } 复制代码
添加外部依赖
1.更新hello.go文件,引入 rsc.io/quote
依赖
package hello import "rsc.io/quote" func Hello() string { return quote.Hello() } 复制代码
2.执行 go run main.go
,会自动下载依赖
➜ go-module-lab go run main.go go: finding rsc.io/quote v1.5.2 go: downloading rsc.io/quote v1.5.2 go: extracting rsc.io/quote v1.5.2 go: downloading rsc.io/sampler v1.3.0 go: extracting rsc.io/sampler v1.3.0 go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: finding rsc.io/sampler v1.3.0 go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c Hello, world. 复制代码
3.查看go.mod
module git.own.com/go-module go 1.13 require rsc.io/quote v1.5.2 复制代码
可以看到,使用Go Module的包管理方式,Golang会自动帮我们处理包的依赖关系,并把缺失的包添加到go.mod,并使用 rsc.io/quote
的最新版本。(这里的最新版本应理解为最新并打了tag的版本,如果没有打tag,则会使用一种 pseudo-version
的方式标识,下文会说到)
4.借助go list命令查看所有依赖
$ go list -m all git.own.com/go-module golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c rsc.io/quote v1.5.2 rsc.io/sampler v1.3.0 复制代码
补充:
pseudo-versions(伪版本)
一般情况下,go.mod使用 语义化版本
来标志依赖包的版本号,比如v1.0.0、v1.0.1。
它包含三个部分:
- 主版本号:当你做了不兼容的 API 修改,比如v1.5.2的1
- 次版本号:当你做了向下兼容的功能性新增,比如v1.5.2的5
- 修订号:当你做了向下兼容的问题修正,比如1.5.2的2
语义化版本规定,同一个主版本号的必须向下兼容,比如v1.5.2必须向下兼容v1.1.0;如果代码不兼容,则必须使用新的版本号。
但是语义化版本是基于项目有打tag的情况下,如果一些项目没有打tag,则Golang会使用一种 pseudo-version
来标识,类似 v0.0.0-yyyymmddhhmmss-abcdefabcdef
的形式。
其中,yyyymmddhhmmss使用的是UTC时间,abcdefabcdef对应的是你这次commit的哈希值(前12位),
对于前缀v0.0.0,则有三种情况:
1.当你的项目一个tag都没有的时候,形式为v0.0.0-yyyymmddhhmmss-abcdefabcdef
2.当你项目最近打的tag的名称为vX.Y.Z-pre的时候,形式为vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef
3.当你的项目最近打的tag的名称是vX.Y.Z的时候,形式为vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef
go.sum
之所以有go.sum文件,是因为单纯地通过语义化版本(v1.5.2)无法确定每次通过v1.5.2标签下载的都是同一份代码。
比如发布者在 GitHub 上给自己的项目打上 v1.5.2 的tag之后,依旧可以删掉这个tag ,提交不同的内容后再重新打个 1.5.2 的 tag。
为了确定是否是同一份代码,go.sum存放了特定模块版本的内容的预期校验和,如果该代码有改动,则预期校验和不匹配,就会导致编译错误。
verifying xxx/base@v1.3.0: checksum mismatch downloaded: h1:T2eK+D0jzzeu4+S+oP9KvGgovPnl4FjxYShqdNSPrjc= go.sum: h1:Crwm2FliMjZ3BABjnydOpoJiFPaKcod/zYNOtcB9Xkw= 复制代码
更新外部依赖
更新次版本号
更新次版本号比较简单,直接使用go get即可,比如更新golang.org/x/text
go get golang.org/x/text
通过查看go.mod的变化,我们可以看到 golang.org/x/text
的版本号由v0.0.0-20170915032832-14c0d48ead0c升级到v0.3.2。(indirect表明该依赖包在源码中没有用到,是间接依赖的)
module git.own.com/go-module go 1.13 require ( golang.org/x/text v0.3.2 // indirect rsc.io/quote v1.5.2 ) 复制代码
除此之外,我们还可以更新到特定版本,在此之前,我们先看看该模块有哪些可用版本(以rsc.io/quote为例)
$ go list -m -versions rsc.io/quote rsc.io/quote v1.0.0 v1.1.0 v1.2.0 v1.2.1 v1.3.0 v1.4.0 v1.5.0 v1.5.1 v1.5.2 v1.5.3-pre1 复制代码
更新到特定版本:
go get rsc.io/quote@v1.4.0
如果想要使用特定的分支,只需要把版本号换成分支名即可(如果分支名包含特定符号,如"/",可用双引号将分支名括起来):
go get rsc.io/quote@dev
更新主版本号
如果需要更新主版本号,需要在代码中手动指定,因为不同主版本号相当于一个新的依赖(库)。
1.添加新函数
package hello import ( "rsc.io/quote" quoteV3 "rsc.io/quote/v3" ) func Hello() string { return quote.Hello() } func Proverb() string { return quoteV3.Concurrency() } 复制代码
2.自动下载依赖
package main import ( "fmt" "git.own.com/go-module/hello" ) func main() { fmt.Println(hello.Hello()) fmt.Println("proverb", hello.Proverb()) } 复制代码
3.查看go.mod
module git.own.com/go-module go 1.13 require ( golang.org/x/text v0.3.2 // indirect rsc.io/quote v1.5.2 rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 // indirect ) 复制代码
从上面可以看出,Go Module每一个主版本号使用不同的路径表示,如v1,v2,v3;另外,Golang允许同时存在多个主版本号,因为路径不同,相当于是一个新的库,这样做的目的是保持增量迁移。
比如我一开始使用 rsc.io/quote
,后面有改动,且与之前不兼容,这是我就可以使用新的主版本号,比如 rsc.io/quote/v3
,但是Hello这个函数暂时还不能迁移到V3版本,这是多版本的作用就凸显出来了
删除多余依赖
当过了一段时间,我们已经把把 rsc.io/quote
的代码全部迁移到新版本 rsc.io/quote/v3
, 类似下面的代码
package hello import ( quoteV3 "rsc.io/quote/v3" ) func Hello() string { return quoteV3.HelloV3() } func Proverb() string { return quoteV3.Concurrency() } 复制代码
这时之前的go.mod里面的 rsc.io/quote
是多余的,我们可以通过 go mod tidy
删除多余的 rsc.io/quote
$ go mod tidy $ cat go.mod module git.own.com/go-module go 1.13 require ( golang.org/x/text v0.3.2 // indirect rsc.io/quote/v3 v3.1.0 rsc.io/sampler v1.3.1 // indirect ) 复制代码
总结
1.go mod init: 初始化一个Go Module项目,同时生成go.mod和go.sum文件
2.go build/go test/go run: 会自动下载依赖,并更新go.mod和go.sum文件
3.go list -m all:打印目前的所有依赖包
4.go get:手动下载依赖包,或者更改依赖包版本
5.go mod tidy:增加缺失的依赖,删除没有用到的依赖
其他命令
go env
配置一些环境变量。
# 环境变量说明文档 go help environment # 环境变量配置文件路径 $ go env GOENV /Users/xxx/Library/Application Support/go/env # 列出所有环境变量 go env # 列出所有环境变量(以json格式) go env -json # 修改某个环境变量 go env -w GOPROXY=https://goproxy.io,direct # 重置某个变量 go env -u GOPROXY 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 数据管理流程,基础入门简介
- Rust包管理器Cargo入门
- 全栈开发入门实战:后台管理系统
- kustomize 入门:Kubernetes 原生配置管理工具
- Akka入门系列(四):akka cluster管理
- Helm 入门介绍:Kubernetes 上的包管理软件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。