zoukankan      html  css  js  c++  java
  • 正在或即将被使用的Go依赖包管理方法:Go Modules,Go 1.13的标准特性

    公众号原文地址:https://mp.weixin.qq.com/s/SGGV3tWEg5AAJ7I_FcK0cg

    目录

    • 目录

    • 说明

    • 初始化

    • 依赖包的默认导入

    • 依赖包的特定版本导入

    • 查看已添加依赖

    • 依赖包的存放管理

    • 依赖包的版本切换

    • 删除未使用依赖包

    • 引用项目中的 package

    • 引用不同版本的父子目录

    • 实例演示

    • 需要注意的坑

    • IDE 与 Go Modules

    • IntelliJ IDEA/Goland

    • vim

    • 参考

    说明

    Go 的依赖包管理一直是个问题,先后出现了 godep、glide、dep 等一系列工具,vendor 机制使依赖包的管理方便了很多,但依然没有统一的管理工具,不同的项目各用各的方法。

    另外使用 vendor 后,每个项目都完整拷贝一份依赖包,既不方便管理又浪费了本地空间。

    此外,Go 项目中的 import 指令后面的 package 路径与项目代码的存放路径相关,项目目录不能随意移动,必须安分守己地趴在 $GOPATH/src 中,否则 import 会找不到项目中的 package,虽然可以通过在容器中编译或者为每个项目准备一套 Go 环境的方式解决,但是麻烦且有额外开销。

    Go1.11 和 Go1.12 引入的 Go Modules 机制,提供了统一的依赖包管理工具 go mod,依赖包统一收集在 $GOPATH/pkg/mod 中进行集中管理,并且将 import 路径与项目代码的实际存放路径解耦,使 package 定义导入更加灵活。

    Go Modules 将成为 Go1.13 默认的依赖包管理方法,在 Go1.11 和 Go1.12 中, Go Modules 只能在 $GOPATH 外部使用,Using Go Modules 中有详细介绍。

    很多开源项目已经改用 Go Modules 了,浏览代码的时候会发现,很多项目的 master 分支中增加了 go.mod 和 go.sum 文件。

    Go Modules 的主要功能就四个:添加依赖、更新依赖、删除依赖,以及多版本依赖。

    初始化

    Go Modules 的初始化命令为 go mod init <ROOTPATH>,ROOTPATH 是项目的 import 路径。

    在 $GOPATH 外部创建一个目录,然后初始化,项目的路径设置为 exampe.com/hello

    $ mkdir go-modules-example$ cd go-modules-example$ go mod init example.com/hello        # 该项目代码的引用路径是 example.com/hellogo: creating new go.mod: module example.com/hello
    

    引用该项目中的 package 时使用前缀 example.com/hello

    项目下生成一个 go.mod 文件,里面记录了 module 路径和 go 的版本,刚创建时这个文件中没有依赖信息:

    $ cat go.modmodule example.com/hellogo 1.12
    

    对于 Go1.11 和 Go1.12,如果在 $GOPATH 中执行 go mod 会遇到下面的错误:

    $ go mod init example.com/hellogo: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'
    

    依赖包的默认导入

    在 go-modules-example 中创建一个 main.go,简单写几行代码,引入 “github.com/lijiaocn/golib/version”:

    // Create: 2019/05/05 16:53:00 Change: 2019/05/05 16:56:53// FileName: main.go// Copyright (C) 2019 lijiaocn <lijiaocn@foxmail.com>//// Distributed under terms of the GPL license.package mainimport (    "github.com/lijiaocn/golib/version")func main() {    version.Show()}
    

    用下面的 Makefile 编译( Makefile 纯粹为了方便,直接用 go build 也可以):

    # Makefile# lijiaocn, 2019-05-05 16:56#VERSION=1.0.0COMPILE=$(shell date -u "+%Y-%m-%d/%H:%M:%S")all: buildbuild:   go build -ldflags "-X github.com/lijiaocn/golib/version.VERSION=${VERSION} -X github.com/lijiaocn/golib/version.COMPILE=${COMPILE}"
    

    编译或者用 go test 运行测试代码时,默认将 import 引入的 package 的最新版本写入 go.mod 和 go.sum:

    $ makego build -ldflags "-X github.com/lijiaocn/golib/version.VERSION=1.0.0 -X github.com/lijiaocn/golib/version.COMPILE=2019-05-05/09:55:04"go: finding github.com/lijiaocn/golib v0.0.1go: downloading github.com/lijiaocn/golib v0.0.1go: extracting github.com/lijiaocn/golib v0.0.1
    

    go.mod 中写入依赖关系:

    $ cat go.modmodule example.com/hellogo 1.12require github.com/lijiaocn/golib v0.0.1
    

    go.sum 中记录的完整依赖:

    $ cat go.sumgithub.com/lijiaocn/golib v0.0.1 h1:bC8xWHei7xTa8x65ShiPBNjVYXoxt6EDmnSUaGgRUW8=github.com/lijiaocn/golib v0.0.1/go.mod h1:BUO0RF2eDlol519GuXLQtlku8pdUim0h+f6wvX/AsNk=
    

    依赖包的特定版本导入

    在使用 go modules 的项目目录中,用 go get 下载的代码包自动作为依赖包添加,例如:

    $ go get github.com/lijiaocn/codes-go/01-02-hellogo: finding github.com/lijiaocn/codes-go/01-02-hello latestgo: finding github.com/lijiaocn/codes-go latestgo: downloading github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7ego: extracting github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7e
    

    go.mod 中增加了一行记录,新增的依赖被标注为 indirect,意思是还没有被使用:

    $ cat go.modmodule example.com/hellogo 1.12require (    github.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7e // indirect github.com/lijiaocn/golib v0.0.1)
    

    在用 go get 添加依赖的时候,可以用 @v1.1 样式的后缀指定依赖的版本,例如:

    $ go get github.com/lijiaocn/glib@v0.0.2
    

    查看已添加依赖

    go list 命令列出当前项目的依赖包以及代码版本:

    $ go list -m allexample.com/hellogithub.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7egithub.com/lijiaocn/golib v0.0.1
    

    依赖包的存放管理

    依赖包既不在 GOPATH/src 目录中,也不在 vendor 目录(Go Moduels 不会创建 vendor 目录),而是在 `GOPATH/pkg/mod` 目录中:

    $ ls $GOPATH/pkg/mod/github.com/lijiaocn/codes-go@v0.0.0-20180220071929-9290fe35de7e golib@v0.0.1$ ls $GOPATH/pkg/mod/github.com/lijiaocn/golib@v0.0.1config container generator terminal version virtio
    

    如上所示,目录名中包含版本信息,例如 golib@v0.0.1。

    $GOPATH/pkg/mod/cache/download/ 中有原始代码的缓存,避免重复下载:

    $ ls $GOPATH/pkg/mod/cache/download/github.com/lijiaocncodes-go golib$ ls $GOPATH/pkg/mod/cache/download/github.com/lijiaocn/golib/@vlist           list.lock      v0.0.1.info    v0.0.1.lock    v0.0.1.mod     v0.0.1.zip     v0.0.1.ziphash
    

    依赖包的版本切换

    依赖代码的版本更新很简单,直接用 go get 获取指定版本的依赖代码即可,例如将 lijiaocn/glib 更新到 v0.0.2:

    $ go get github.com/lijiaocn/glib@v0.0.2go: finding github.com/lijiaocn/golib v0.0.2go: downloading github.com/lijiaocn/golib v0.0.2go: extracting github.com/lijiaocn/golib v0.0.2
    

    可以看到依赖的代码版本发生了变化:

    $ go list -m allexample.com/hellogithub.com/lijiaocn/codes-go v0.0.0-20180220071929-9290fe35de7egithub.com/lijiaocn/golib v0.0.2
    

    删除未使用依赖

    不需要的依赖必须手动清除,执行 go mod tidy,清除所有未使用的依赖:

    $ go mod tidy
    
    $ go list -m allexample.com/hellogithub.com/lijiaocn/golib v0.0.2
    

    引用项目中的 package

    在项目中创建一个名为 display 的 package:

    $ tree displaydisplay└── display.go
    

    导入时使用 go mod 初始化时定义的前缀,example.com/hello/display

    import (    "example.com/hello/display"    "github.com/lijiaocn/golib/version"    )
    

    引用当前项目中的 package 时,import 使用的路径和项目所在的路径彻底解耦,但是要注意,如果提供给外部项目使用,需要确保 go get 能够从 example.com 获得 /hello/display。

    引用不同版本的父子目录

    Using Go Modules 中有一节是 Adding a dependency on a new major version,示例中引入了 v1.5.2 版本的 rsc.io/quote,和 v3.1.0 版本的 rsc.io/quote/v3,这两个 package 是父子目录,版本不相同:

    package helloimport (    "rsc.io/quote"    quoteV3 "rsc.io/quote/v3")func Hello() string {    return quote.Hello()}func Proverb() string {    return quoteV3.Concurrency()}
    
    rsc.io tree quotequote├── LICENSE├── README.md├── buggy│   └── buggy_test.go├── go.mod├── go.sum├── quote.go├── quote_test.go└── v3    ├── go.mod    ├── go.sum    └── quote.go2 directories, 10 files
    

    注意,v3 是一个真实存在的子目录,必须是用 go modules 管理的,rsc.io/quote 和 rsc.io/quote/v3 是父子目录,但它们是完全独立的 package。

    引用 1.5.2 版本的 rsc.io/quote 和 v3.1.0 版本的 rsc.io/quote/v3 :

    $ go get rsc.io/quote@v1.5.2  ...$ go get rsc.io/quote/v3@v3.1.0  ...
    

    可以看到两个版本同时存在:

    $ go list -m rsc.io/q...rsc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0
    

    实例演示

    实现一个用 go modules 管理的 package: github.com/introclass/go_mod_example_pkg

     
    image

    在另一个使用 go modules 的项目中引用 v1.0.1 版本:github.com/introclass/go-mod-example

    $ go get github.com/introclass/go_mod_example_pkg@v1.0.1go: finding github.com/introclass/go_mod_example_pkg v1.0.1go: downloading github.com/introclass/go_mod_example_pkg v1.0.1go: extracting github.com/introclass/go_mod_example_pkg v1.0.1
    

    查看依赖的代码,显示依赖的是 v1.0.1:

    $ go list  -m allexample.com/hellogithub.com/introclass/go_mod_example_pkg v1.0.1github.com/lijiaocn/golib v2.0.1+incompatible
    

    在 main 函数中使用导入的依赖包:

    package mainimport (    "example.com/hello/display"    pkg "github.com/introclass/go_mod_example_pkg"    "github.com/lijiaocn/golib/version")func main() {    version.Show()    display.Display("display print
    ")    pkg.Vesrion()}
    

    编译执行,输出的v1.0.1:

    $ ./helloversion:    compile at:   golib v2display printv1.0.1
    

    将依赖包切换到版本 2.0.1:

    $ go get github.com/introclass/go_mod_example_pkg@v2.0.1go: finding github.com/introclass/go_mod_example_pkg v2.0.1
    

    重新编译执行,输出的版本是 v2.0.1:

    $ ./helloversion:    compile at:   golib v2display printv2.0.1
    

    引用依赖包 v3.0.1 版本的 v3 子目录(事实上是一个独立的 pacakge ):

    $ go get github.com/introclass/go_mod_example_pkg/v3@v3.0.1go: finding github.com/introclass/go_mod_example_pkg/v3 v3.0.1go: downloading github.com/introclass/go_mod_example_pkg/v3 v3.0.1go: extracting github.com/introclass/go_mod_example_pkg/v3 v3.0.1
    

    修改 main 函数,引用 v3:

    package mainimport (    "example.com/hello/display"    pkg "github.com/introclass/go_mod_example_pkg"    pkgv3 "github.com/introclass/go_mod_example_pkg/v3"    "github.com/lijiaocn/golib/version")func main() {    version.Show()    display.Display("display print
    ")    pkg.Vesrion()    pkgv3.Vesrion()}
    

    重新编译执行,分别输出 v2.0.1 和 v3.0.1 in v3:

    $ ./helloversion:    compile at:   golib v2display printv2.0.1v3.0.1 in v3
    

    需要注意的坑

    1、引用不同版本的父子目录,被引用的父子目录必须是用 go mod 管理的 package,非 go mod 管理的代码不行;

    2、go mod 会在本地缓存代码,如果被引用的代码的版本号不变,但是代码变了(在做实验或者代码版本管理比较乱的时候,可能会出现的这种情况),清除本地缓存( GOPATH/pkg/mod/cache 和GOPATH/pkg/mod/ 依赖代码 )后,才能重新拉取最新的代码(可能会有其它的更新缓存的方法);

    3、如果被外部项目引用,go.mod 中设置的 package 路径需要与代码的获取地址相同,项目内部引用没有该限制,github.com/introclass/go-mod-example 的 go.mod 中标注的是 example.com/hello,代码获取地址 github.com/intraoclass/go-mode-example 与 example.com/hello 不一致,在另一个项目中用 github 地址加载时会失败:

    $ go get github.com/introclass/go-mod-examplego: finding github.com/introclass/go-mod-example latestgo: github.com/introclass/go-mod-example@v0.0.0-20190605063729-4a841a8278e3: parsing go.mod: unexpected module path "example.com/hello"go: error loading module requirements
    

    IDE 与 Go Modules 项目

    IntelliJ IDEA/Goland

    在 IntelliJ IDEA 或者 Goland 中(需要是最新的2019.1版本)导入使用 Go Module 的项目的时候,要选择 Go Module(vgo),否则 IDE 找不到 import 导入的代码,create-a-project-with-vgo-integration 有更多介绍:

     
    image

    IntelliJ IDEA/Goland 左侧编码显示的依赖代码(带有版本号或者 commit id):

     
    image

    vim

    vim插件 vim-go 从 v1.19 开始支持 go.mod,但是代码跳转等还不支持。

    cmd/go: track tools/tooling updates to support modules 列出了一些工具对 go module 的支持情况。

    参考

    1. Using Go Modules

    2. Where is the module cache in golang?

    3. create-a-project-with-vgo-integration

    4. cmd/go: track tools/tooling updates to support modules

    上一篇:源代码阅读方法(附Go语言项目的代码阅读技巧)

    关注后加作者微信

     
    image

    公众号原文地址:https://mp.weixin.qq.com/s/SGGV3tWEg5AAJ7I_FcK0cg

  • 相关阅读:
    浅谈社交网络中的用户心理
    QQ公众号&微信公众号,左右互搏?
    双11预售不能无理由退货?
    大数据:70多个网站让你免费获取大数据存储库
    雜項.筆記
    字母源流
    心經日語讀法
    throttle與debounce算法的邏輯
    解決中英混合輸入時標點切換問題的辦法
    漢譯Promises/A+規範
  • 原文地址:https://www.cnblogs.com/lijiaocn/p/11178642.html
Copyright © 2011-2022 走看看