说一说golang中的软件包管理

1,035 阅读3分钟

我是从18年开始接触golang这门语言的,在当时,广泛应用于包管理的,是vendor.在项目开发的时候,我们只要在项目的根目录下,执行一下vendor初始化(govendor init),那么这个项目根目录下就会生产用于包管理的文件目录vendor/.该目录下有一个vendor.json文件,这个文件类似 godep 工具中的描述文件版本的功能。

下面是一个这个文件的示例

{
	"comment": "",
	"ignore": "",
	"package": [
		{
			"checksumSHA1": "qL4naKgOOJE2hXxKKYu+5W2ULHU=",
			"path": "github.com/certifi/gocertifi",
			"revision": "0944d244cd40db6fa14fa14fc8c53b29f0ad66e4",
			"revisionTime": "2019-10-21T19:10:39Z"
		},
        ......
        ],
	"rootPath": "/path/to/project"
}

当然了,在项目创建之初,还未导入软件包的时候,是没有package列表的内容的。当我们在项目开发过程中,引进了软件包之后,只要执行一下govendor add +external命令,就会将引用的软件包,加入到package列表中。

除此之外,还会将当前版本的软件包,放入到vendor目录中,详细可以看一下下方的示例

vendor中的软件包

我们在项目代码管理系统中,需要把vendor文件进行统一管理。

最近在新的项目中,引入了从golang 1.11 1.12版本中开始引进的包管理工具mod.

这个和vendor还是不太一样的。我们接下来看看mod是如何管理软件包的。

模块是存储在文件树中的Go软件包的集合,其根目录中有go.mod文件。 go.mod文件定义了模块的模块路径(这也是用于根目录的导入路径)及其依赖项要求,它们是成功构建所需的其他模块。 每个依赖性要求都写为模块路径和特定的语义版本。

通过一个简单的示例来看一下

我们将micro中的greeter示例,拿出来,写一个简单的客户端服务器模式的hello world.

首先看一下greeter.proto文件

syntax = "proto3";

service Greeter {
        rpc Hello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
        string name = 1;
}

message HelloResponse {
        string greeting = 2;
}

整个项目的功能可谓是非常简单来,那我们来看看如何通过mod来进行包管理

首先,我们来通过go mod init /path/to/project来进行初始化操作,这个操作,会在项目的根目录下生产一个go.mod文件

module modtest

go 1.13

我们通过protoc生产相应的go代码.

protoc  --micro_out=. --go_out=. ./greeter/greeter.proto 

然后实现一个简单的服务器

➜  server more server.go 
package main

import (
        "context"
        "fmt"

        proto "modtest/greeter"

        micro "github.com/micro/go-micro"
)

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
        rsp.Greeting = "Hello " + req.Name
        return nil
}

func main() {
        // Create a new service. Optionally include some options here.
        service := micro.NewService(
                micro.Name("greeter"),
        )

        // Init will parse the command line flags.
        service.Init()

        // Register handler
        proto.RegisterGreeterHandler(service.Server(), new(Greeter))

        // Run the server
        if err := service.Run(); err != nil {
                fmt.Println(err)
        }
}

将服务器运行起来,之后,实现一个客户端来跟服务器进行通讯

➜  client more client.go 
package main

import (
        "context"
        "fmt"

        proto "modtest/greeter"

        micro "github.com/micro/go-micro"
)

func main() {
        // Create a new service. Optionally include some options here.
        service := micro.NewService(micro.Name("greeter.client"))
        service.Init()

        // Create new greeter client
        greeter := proto.NewGreeterService("greeter", service.Client())

        // Call the greeter
        rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
        if err != nil {
                fmt.Println(err)
        }

        // Print response
        fmt.Println(rsp.Greeting)
}

然后我们现在来看看其依赖包的管理吧

➜  modtest more go.mod 
module modtest

go 1.13

require github.com/micro/go-micro v1.18.0

自动引入了go-micro包。并且指定了版本。同时,你会发现,项目目录下,多了一个文件go.sum。 截取部分内容看一下

➜  modtest more go.sum 
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=

这些都是软件版本的加密哈希。确保以后这些模块的下载与第一次下载时检索的位相同,以确保您的项目所依赖的模块不会由于恶意,意外或其他原因而意外更改。

应该将go.modgo.sum都提交到代码版本控制中。

那如果我们升级依赖该怎么办呢?

$ go list -m all
github.com/coreos/bbolt v1.3.3
github.com/coreos/etcd v3.3.17+incompatible
github.com/coreos/go-semver v0.3.0
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f

发现么,有的直接指定了其特定版本,有的直接给的v0.0.0

一个给定的版本分为三个部分:主体,小版本,补丁,比如v1.3.3,主要版本为1,小版本为3,补丁版本为3.而这种v0.0.0是未标记的版本。也就是说,我们升级这种版本为最新版本也没有影响,项目一样可以正常运行。

➜  modtest go get github.com/coreos/pkg
go: finding github.com/coreos/pkg latest

运行一下我们的项目

go run server.go 报错

➜  server go run server.go
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
../../../../pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:114:78: undefined: resolver.BuildOption
../../../../pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:182:31: undefined: resolver.ResolveNowOption
# github.com/coreos/etcd/clientv3/balancer/picker
../../../../pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/clientv3/balancer/picker/err.go:37:44: undefined: balancer.PickOptions
../../../../pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/clientv3/balancer/picker/roundrobin_balanced.go:55:54: undefined: balancer.PickOptions

查看一下我们的go.mod

➜  modtest more go.mod 
module modtest

go 1.13

require (
        github.com/golang/protobuf v1.4.1
        github.com/micro/go-micro v1.18.0
        google.golang.org/protobuf v1.25.0
)

软件版本不兼容。如何解决这样的问题呢,我们想看错误的信息中有什么比较有价值的东西,确实大家都能看出来是不兼容,谁和谁不兼容,如何解决不兼容。 我们先来看看etcd这个代码库的3.3.17这个版本其关联的代码库版本,能和我们go.mod管理的软件包有管理的也就是google.golang.org/protobuf了,我们的go.mod中版本是v1.25.0.

etcd 3.3.17所需要的版本是v1.23.0版本的grpc。在看v1.23.0版本的grpc grpcv1.23.0需要的protobuf版本

所以解决此类冲突的版本要么升级etcd要么降级protobuf.考虑到易操作性和符合思考方式的解决方案就是直接更新protobuf的版本。

go.mod中的protobuf版本更新为v1.23.0之后,启动服务器成功

module modtest

go 1.13

require (
        github.com/golang/protobuf v1.4.1
        github.com/micro/go-micro v1.18.0
        google.golang.org/protobuf v1.23.0
)

最后,可能由于在调试过程中,引入了很多不必要的版本。导致go.mod比较混乱,这个时候可以通过

$go mod tidy

进行更新包的管理。

针对mod方式的软件包依赖管理和vendor,大家有什么看法,更喜欢哪个?欢迎大家一起讨论