Golang入门-Golang包管理

CecilliaBernie 发布于19天前
0 条问题

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也要配置

Golang入门-Golang包管理

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

参考: golang.org/cmd/go/#hdr…

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

复制代码

查看原文: Golang入门-Golang包管理

  • yellowmeercat
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。