Skip to content

Go Modules - Part 1 #5

@oufeng

Description

@oufeng
  • Go 1.13及以后版本GO111MODULE=auto的行为变化。

    GO111MODULE的默认值依然为auto。但无论repo是否在GOPATH,只要目录有go.mod,go编译器都会使用go module来管理依赖

  • 如何创建模块

    新建工作区和项目文件夹

    $ mkdir -p $HOME/Desktop/go/hello
    $ cd $HOME/Desktop/go/hello
    $
    

    init mod init $path
    一是作为模块标识,二是作为模块的import path
    此时会生成go.mod文件
    当其他项目引用这个模块下的 package 时都会以该 import $path 作为共同的前缀

    $ go mod init example.com/hello
    go: creating new go.mod: module example.com/hello
    $ cat go.mod
    module example.com/hello
    
    go 1.15
    $
    

    新建go文件hello.go

    package hello
    
    func Hello() string {
        return "Hello, world."
    }
    

    新建测试文件hello_test.go,测试是否能正常调用Hello方法

    package hello
    
    import "testing"
    
    func TestHello(t *testing.T) {
        want := "Hello, world."
        if got := Hello(); got != want {
            t.Errorf("Hello() = %q, want %q", got, want)
        }
    }
    

    运行测试文件

    $ go test
    PASS
    ok      example.com/hello       1.221s
    $
    
  • 如何添加依赖项

    修改hello.go。用rsc.io/quote来实现Hello

    package hello
    
    import "rsc.io/quote"
    
    func Hello() string {
        return quote.Hello()
    }
    

    再次运行测试。看来Hello()方法实现了国际化功能,需要修改一下测试用例。

    ...
    func TestHello(t *testing.T) {
        want := "你好,世界。"
        if got := Hello(); got != want {
            t.Errorf("Hello() = %q, want %q", got, want)
        }
    }
    

    测试通过了,证明Hello功能正常

    $ go test
    PASS
    ok      example.com/hello       0.377s
    $
    

    此时会把直接依赖写到go.mod中

    $ cat go.mod
    module example.com/hello
    
    go 1.15
    
    require rsc.io/quote v1.5.2
    $
    

    尝试列出目前的所有依赖。当前模块(主模块)始终是第一行,随后的才是真正的依赖关系

    $ go list -m all
    example.com/hello
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
    rsc.io/quote v1.5.2
    rsc.io/sampler v1.3.0
    

    尝试列出所有的依赖关系。清晰明了,这个功能大赞

    $ go mod graph
    example.com/hello rsc.io/quote@v1.5.2
    rsc.io/quote@v1.5.2 rsc.io/sampler@v1.3.0
    rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c
    

    所有真正的依赖和哈希值会记录到go.sum中。哈希值的作用是确保所有依赖项不会被恶意修改。

    $ cat go.sum
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
    rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
    rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
    rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
    
  • 如何更新依赖项。版本号最好带着语义信息,v0.1.2。主要版本是0,次要版本是1,补丁版本是2

    尝试升级一下golang.org/x/text

    $ go get golang.org/x/text
    go: golang.org/x/text upgrade => v0.3.3
    go: downloading golang.org/x/text v0.3.3
    $
    

    执行go list -m all并且看看go.mod和go.sum情况。
    golang.org/x/text已经升级到v0.3.3版本,go.mod也改成了golang.org/x/text v0.3.3 // indirect
    indirect表示该依赖不是由当前项目直接依赖,而是由其他依赖包间接依赖的。

    $ go list -m all
    example.com/hello
    golang.org/x/text v0.3.3
    golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
    rsc.io/quote v1.5.2
    rsc.io/sampler v1.3.0
    $ cat go.mod
    module example.com/hello
    
    go 1.15
    
    require (
            golang.org/x/text v0.3.3 // indirect
            rsc.io/quote v1.5.2
    )
    $ cat go.sum
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
    golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
    golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
    golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
    rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
    rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
    rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
    rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
    

    尝试升级rsc.io/sampler。发现版本太先进,兼容不了旧版本

    $ go get rsc.io/sampler
    go: rsc.io/sampler upgrade => v1.99.99
    go: downloading rsc.io/sampler v1.99.99
    $ go test
    --- FAIL: TestHello (0.00s)
    hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
    FAIL
    exit status 1
    FAIL	example.com/hello	0.014s
    $
    

    尝试列出可用的版本以及挑选一个补丁版本。go get默认是获取@latest版本

    $ go list -m -versions rsc.io/sampler    
    rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
    $ go get rsc.io/sampler@v1.3.1
    

    此时。$GOPATH/pkg/mod/rsc.io存在rsc.io/sampler的多个版本

    $ cd $HOME/go/pkg/mod/rsc.io
    $ ls
    quote@v1.5.2     sampler@v1.3.0   sampler@v1.3.1   sampler@v1.99.99
    $
    
  • 如何引用同一个依赖的不同主版本

    修改hello.go。引用rsc.io/quote/v3

    package hello
    
    import (
        "rsc.io/quote"
        quoteV3 "rsc.io/quote/v3"
    )
    
    func Hello() string {
        return quote.Hello()
    }
    
    func Proverb() string {
        return quoteV3.Concurrency()
    }
    

    给测试文件添加以下行

    ...
    func TestProverb(t *testing.T) {
        want := "Concurrency is not parallelism."
        if got := Proverb(); got != want {
            t.Errorf("Proverb() = %q, want %q", got, want)
        }
    }
    

    再次测试一下

    $ go test
    PASS
    ok      example.com/hello       0.377s
    $
    

    此时项目同时依赖rsc.io/quote和rsc.io/quote/v3

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

    总结:

    • 主要版本不相同时要发布不同的模块,模块必须以版本号结尾
    • 相同主要版本之间要兼容
    • 像rsc.io/quote如果有多个主要版本。需要发布rsc.io/quote,rsc.io/quote/v2和rsc.io/quote/v3等模块
  • 引用同一个依赖的不同版本毕竟不是什么好事。尝试将旧的api升级到新的api,然后将旧的依赖删除。

    查看rsc.io/quote/v3版本有什么api。像是Hello升级到了HelloV3

    $ go doc rsc.io/quote/v3
    package quote // import "rsc.io/quote/v3"
    
    Package quote collects pithy sayings.
    
    func Concurrency() string
    func GlassV3() string
    func GoV3() string
    func HelloV3() string
    func OptV3() string
    $ 
    

    修改hello.go,更换一下api

    package hello
    
    import quoteV3 "rsc.io/quote/v3"
    
    func Hello() string {
        return quoteV3.HelloV3()
    }
    
    func Proverb() string {
        return quoteV3.Concurrency()
    }
    

    既然没有了多个版本,可以不用alias

    package hello
    
    import "rsc.io/quote/v3"
    
    func Hello() string {
        return quote.HelloV3()
    }
    
    func Proverb() string {
        return quote.Concurrency()
    }
    

    重新运行test。一切正常

    $ go test 
    PASS
    ok      example.com/hello       1.626s
    
  • 如何删除已下载但未引用的依赖。例如rsc.io/quote

    $ go list -m all
    example.com/hello
    golang.org/x/text v0.3.3
    golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
    rsc.io/quote v1.5.2
    rsc.io/quote/v3 v3.1.0
    rsc.io/sampler v1.3.1
    
    $ cat go.mod
    module example.com/hello
    
    go 1.15
    
    require (
            golang.org/x/text v0.3.3 // indirect
            rsc.io/quote v1.5.2
            rsc.io/quote/v3 v3.1.0
            rsc.io/sampler v1.3.1 // indirect
    )
    

    利用tidy命令删除未使用的依赖项

    $ go mod tidy
    $ go list -m all
    example.com/hello
    golang.org/x/text v0.3.3
    golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e
    rsc.io/quote/v3 v3.1.0
    rsc.io/sampler v1.3.1
    $ cat go.mod
    module example.com/hello
    
    go 1.15
    
    require (
            golang.org/x/text v0.3.3 // indirect
            rsc.io/quote/v3 v3.1.0
            rsc.io/sampler v1.3.1 // indirect
    )
    

    重新测试

    $ go test
    PASS
    ok      example.com/hello       3.088s
    

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions