Go包管理工具 go mod

go module 基本概念介绍 go 1.11+ 支持 go 1.13+ 默认


go module


参考  »»> 跳出Go module的泥潭


Introduction to Go Modules – Roberto Selbach


环境变量GO111MODULE设置

要使用go module,首先要设置GO111MODULE=on

go env -w GO111MODULE=on
启或关闭模块支持

GO111MODULE=off禁用模块支持 
        
        编译时会从 GOPATH 和 vendor 文件夹中查找包。

GO111MODULE=on启用模块支持 
        
        编译时会忽略GOPATH和vendor文件夹 
        
        只根据 go.mod下载依赖
        
        将依赖下载至 %GOPATH%/pkg/mod/ 目录下 

GO111MODULE=auto  
    
    当项目在 $GOPATH/src外且项目根目录有go.mod文件时  开启模块支持

设置GO111MODULE=on之后就可以使用go module了,以后就没有必要在GOPATH中创建项目了

能够很好的管理项目依赖的第三方包信息

使用 go module 管理依赖后会在项目根目录下生成两个文件go.modgo.sum


代理GOPROXY

Go1.13之后GOPROXY默认值为https://proxy.golang.org (国内无法访问)

设置代理

go env -w GOPROXY=https://goproxy.cn,direct


GO mod命令

常用命令

go mod download    下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit        编辑go.mod文件
go mod graph       打印模块依赖图
go mod init        初始化当前文件夹, 创建go.mod文件
go mod tidy        增加缺少的module,删除无用的module
go mod vendor      将依赖复制到vendor下
go mod verify      校验依赖
go mod why         解释为什么需要依赖


go.mod文件结构

项目所有的依赖信息

// 包名
module github.com/xxx    

go 1.12

// 依赖包及版本
require (
    github.com/DeanThompson/ginpprof v0.0.0-20190408063150-3be636683586
    github.com/gin-gonic/gin v1.4.0
    github.com/go-sql-driver/mysql v1.4.1
    github.com/jmoiron/sqlx v1.2.0
    github.com/satori/go.uuid v1.2.0
    google.golang.org/appengine v1.6.1 // indirect  表示间接引用
)

go mod支持语义化版本号,比如go get foo@v1.2.3

也可以跟git的分支或tag,比如go get foo@master

当然也可以跟git提交哈希,比如go get foo@e3702bed2

  • replace 替换 github=>golang.org (需要翻墙访问)

      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
      )
    


go mod vendor

go mod vendor 会复制modules下载到vendor中

貌似只会下载你代码中引用的库,而不是go.mod中定义全部的module。


go get 下载依赖包(指定版本)

 go get -u   升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)

 go get -u=patch   升级到最新的修订版本

 go get package@version   升级到指定的版本号version

下载所有依赖可以使用 go mod download 命令


go mod tidy 重新整理依赖

删除依赖代码后,相关的依赖库并不会在go.mod文件中自动移除
这种情况下可以使用go mod tidy命令更新go.mod中的依赖关系


go mod edit 修改go.mod文件

go mod edit -fmt    格式化


go mod edit -require=golang.org/x/text  添加依赖项


go mod edit -droprequire=golang.org/x/text   移除依赖项


使用 go mod

已有项目

在 项目目录下 执行 go mod init,生成一个go.mod文件
    
    例: $GOPATH/github.com/smallnest/rpcx
         
         1) go mod init github.com/smallnest/rpcx
         
         2) go get ./... (查找依赖,并记录在go.mod) (指定 -tags,这样可以把tags的依赖都查找到)
         
         3) go mod tidy也可以用来为go.mod增加丢失的依赖
        

执行上面的命令会把go.mod的latest版本换成实际的最新的版本,并且会生成一个go.sum记录每个依赖库的版本和哈希值

新项目

在GOPATH之外创建新的项目

1) go mod init packagename可以创建一个空的go.mod 

2) 可以在其中增加require github.com/smallnest/rpcx latest依赖,或者像上面一样让go自动发现和维护

3) go mod download可以下载所需要的依赖,但是依赖并不是下载到$GOPATH中,而是$GOPATH/pkg/mod中,多个项目可以共享缓存的module

go mod 基本使用


创建一个module

注意: 此目录应位于$GOPATH之外,因为默认情况下,$GOPATH中的模块支持已禁用

this directory should be outside your $GOPATH because by default, 
the modules support is disabled inside it

1) 创建module

mkdir  testmod
cd testmod 

package testmod

import "fmt" 

// Hi returns a friendly greeting
func Hi(name string) string {
   return fmt.Sprintf("Hi, %s", name)
}

2) 增加go.mod文件

go mod init github.com/username/testmod
# go: creating new go.mod: module github.com/username/testmod
go.mod

    module github.com/username/gomodtest
    
    go 1.12

3) 上传模块到github

$ git init 
$ git add * 
$ git commit -am "First commit" 
# github新建仓库 testmod
$ git remote add origin https://github.com/username/testmod.git
$ git push -u origin master

4) go get获取模块

go get github.com/username/testmod


注意: 直接 go get <module> 获取的是master的模块 (我们需要制定版本)

模块的版本

Go模块是版本化的,并且某些版本具有某些特殊性

  • 语义版本控制 X.Y.Z 语义化版本

      版本格式:主版本号.次版本号.修订号,版本号递增规则如下:
        
      主版本号:当你做了不兼容的 API 修改,
      次版本号:当你做了向下兼容的功能性新增,
      修订号:当你做了向下兼容的问题修正。
      先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸
    

更重要的是,Go在查找版本时会使用仓库标签tag,而某些版本则与其他版本有所不同

例如 版本2和更高版本的导入路径应与版本0和版本1不同(我们将介绍该路径)

此外,默认情况下,Go会获取存储库中可用的最新标记版本 tag

这是一个重要的陷阱,因为您可能习惯于使用master分支。 

要发布我们的软件包,我们需要使用版本tag标记我们的仓库

制作第一个 release 版本

# 在Github仓库上创建了一个标记,将当前提交标记为1.0.0版
git tag v1.0.0
git push --tags

创建一个新分支 v1

git checkout -b v1
git push -u origin v1

使用模块

1) 新建 go module

(pycharm file->new->project->go module ) 注意 proxy: direct 需要删除


2) 新建 main.go

package main

import (
 	"fmt"

 	"github.com/landybird/testmod"
)

func main() {
 	fmt.Println(testmod.Hi("roberto"))
}


3) mod 初始化

go mod init mod

go build

修改模块版本

修改testmod模块代码

// Hi returns a friendly greeting
func Hi(name string) string {
-       return fmt.Sprintf("Hi, %s", name)
+       return fmt.Sprintf("Hi, %s!", name)
}

版本升级

git commit -m "Emphasize our friendliness" testmod.go
git tag v1.0.1
git push --tags origin v1


go get 下载依赖包(指定版本)

 go get -u   升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
                
           原来1.0.0    1.0.1 或者 1.1.0

 go get -u=patch   升级到最新的修订版本
        
           1.0.1 而不是 1.1.0

 go get package@version   升级到指定的版本号version
            
             github.com/landybird/testmod@v1.0.1


主版本号 Major versions 升级

主要版本可能会破坏向后兼容性

从Go模块的角度来看,主要版本是完全不同的软件包

修改 testmod.go

package testmod

import (
 	"errors"
 	"fmt" 
) 

// Hi returns a friendly greeting in language lang
func Hi(name, lang string) (string, error) {
 	switch lang {
 	case "en":
 		return fmt.Sprintf("Hi, %s!", name), nil
 	case "pt":
 		return fmt.Sprintf("Oi, %s!", name), nil
 	case "es":
 		return fmt.Sprintf("¡Hola, %s!", name), nil
 	case "fr":
 		return fmt.Sprintf("Bonjour, %s!", name), nil
 	default:
 		return "", errors.New("unknown language")
 	}
}

修改go.mod 更改导入路径

module github.com/landybird/testmod  =>  module github.com/landybird/testmod/v2

可以认为  已经是不同的module了

新建 v2 分支, 升级版本

git commit testmod.go -m "Change Hi to allow multilang"
git checkout -b v2 # optional but recommended
echo "module github.com/landybird/testmod/v2" > go.mod  # 修改moudle路径
git commit go.mod -m "Bump version to v2"
git tag v2.0.0
git push --tags origin v2 # or master if we don't have a branch

运行 go build 获取最新的版本包

代码中导入新的包


package main

import (
	"fmt"
	"github.com/landybird/testmod"
	testmodML "github.com/landybird/testmod/v2" // 路径以“ v2”结尾,Go仍将使用其专有名称(“ testmod”)来引用模块
)

func main() {
	fmt.Println(testmod.Hi("roberto"))
	g, err := testmodML.Hi("jimmy", "cn")
	if err != nil {
		panic(err)
	}
	fmt.Println(g)
}

go mod tidy

升级之后go.mod

module mod
go 1.15
require github.com/landybird/testmod v1.0.1
require github.com/robteix/testmod/v2 v2.0.0

这时候如果代码中 只引用 github.com/landybird/testmod 去掉 github.com/landybird/testmod/v2

需要重新生成 go.mod 的依赖路径

go mod tidy   # 调整路径
module mod
go 1.15
require github.com/landybird/testmod v1.0.1

支持vendors

Go模块默认情况下会忽略vendor/目录

但是,如果我们仍然想将vendored dependencies添加到我们的版本控制中,我们仍然可以这样做:

go mod vendor  #  这将在项目的根目录下创建一个 vendor/目录,其中包含所有依赖项的源代码

go build -mod vendor # 
Buy me a 肥仔水!