18

Golang入门-Golang包管理

 4 years ago
source link: https://studygolang.com/articles/26367
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

eeaARfQ.png!web

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/[email protected]: 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/[email protected]

如果想要使用特定的分支,只需要把版本号换成分支名即可(如果分支名包含特定符号,如"/",可用双引号将分支名括起来):

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

复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK