Go Study Notes (10) - Catastrophic Record of Migrating Old Projects to Go Modules

Last updated: 2 years ago

Recently, I have been working on revamping an early project that involves replacing the original dependency management using “Vendor” with “Go Modules”. However, the process has been quite tumultuous. Here, I will summarize the issues encountered during this “Go Modules” revamp, as well as the solutions.

Background

  • go version:

    1
    2
    $ go version
    go version go1.16.5 darwin/amd64
  • Here is a simplified demo, it’s “simple” enough that we just need to output “hello world”.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package main

    import (
    "github.com/coreos/etcd/pkg/transport"
    "github.com/google/certificate-transparency-go/tls"
    "github.com/qiniu/api.v7/auth/qbox"
    "go.etcd.io/etcd/clientv3"
    "google.golang.org/grpc"
    "qiniupkg.com/x/log.v7"
    )

    func main() {

    _ = transport.TLSInfo{}

    _ = clientv3.WatchResponse{}

    _, _ = clientv3.New(clientv3.Config{})

    _ = qbox.NewMac("", "")

    _ = tls.DigitallySigned{}

    _ = grpc.ClientConn{}

    log.Info("hello world")
    }

Practical experience

Initialize directly and tidy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ go mod init demo-go/gomod
go: creating new go.mod: module demo-go/gomod
go: to add module requirements and sums:
go mod tidy

$ go mod tidy
go: finding module for ...
demo-go/gomod imports
qiniupkg.com/x/log.v7: module qiniupkg.com/[email protected] found (v1.11.5), but does not contain package qiniupkg.com/x/log.v7
demo-go/gomod imports
github.com/qiniu/api.v7/auth/qbox imports
github.com/qiniu/x/bytes.v7/seekable: module github.com/qiniu/[email protected] found (v1.11.5), but does not contain package github.com/qiniu/x/bytes.v7/seekable
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context: package github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc: package github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc/credentials: package github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc/credentials provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/storage/storagepb: package github.com/coreos/etcd/storage/storagepb provided by github.com/coreos/etcd at latest version v2.3.8+incompatible but not at required version v3.3.10+incompatible

Oh boy, there’s an error. Let’s take a look at the first two lines:

  1. qiniupkg.com/[email protected] doesn’t have qiniupkg.com/x/log.v7;
  2. github.com/qiniu/[email protected] doesn’t have github.com/qiniu/x/bytes.v7/seekable;

This seems to be an issue. qiniupkg.com/x and github.com/qiniu/x should be the same package on different mirrors. So I went to Github and checked the code for version @latest, and indeed there’s no bytes.v7 package. After some manual searching, we found the bytes.v7 package in version v1.7.8.

Therefore, we can specify a version.

1
2
go mod edit -replace qiniupkg.com/x=qiniupkg.com/[email protected]
go mod edit -replace github.com/qiniu/x=github.com/qiniu/[email protected]

Keep reading, the following questions are related to etcd.

It means that go.etcd.io/etcd/clientv3 imports github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context, while github.com/coreos/[email protected] provides github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context. However, we need github.com/coreos/[email protected] here, and this version does not provide github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context.

Let’s try updating etcd to v3.3.10 directly.

1
go mod edit -replace go.etcd.io/etcd=go.etcd.io/[email protected]+incompatible

Let’s run go mod tidy again.

1
2
3
4
5
6
7
8
9
$ go mod tidy
go: demo-go/gomod imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/auth imports
github.com/coreos/etcd/mvcc/backend imports
github.com/coreos/bbolt: github.com/coreos/[email protected]: parsing go.mod:
module declares its path as: go.etcd.io/bbolt
but was required as: github.com/coreos/bbolt

This error is consistent with the one described in the article Etcd使用go module的灾难 . The package names go.etcd.io/bbolt and github.com/coreos/bbolt are inconsistent, so we need to replace them.

1
go mod edit -replace github.com/coreos/[email protected]=go.etcd.io/[email protected]

Continue and run go mod tidy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ go mod tidy
...
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/clientv3/balancer: module github.com/coreos/[email protected] found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/clientv3/balancer/picker: module github.com/coreos/[email protected] found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer/picker
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/clientv3/balancer/resolver/endpoint: module github.com/coreos/[email protected] found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
demo-go/gomod imports
go.etcd.io/etcd/clientv3 imports
github.com/coreos/etcd/clientv3/credentials: module github.com/coreos/[email protected] found (v2.3.8+incompatible), but does not contain package github.com/coreos/etcd/clientv3/credentials
demo-go/gomod imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/integration imports
github.com/coreos/etcd/proxy/grpcproxy imports
google.golang.org/grpc/naming: module google.golang.org/[email protected] found (v1.39.0), but does not contain package google.golang.org/grpc/naming

Wow, it’s etcd again. Upon closer inspection, we have imported two versions of etcd: github.com/coreos/etcd and go.etcd.io/etcd. We only replaced one of them earlier. Now we’ll replace the other one as well.

1
go mod edit -replace github.com/coreos/etcd=github.com/coreos/[email protected]+incompatible

After running go mod tidy again, the previous error disappeared, but there was still an error with grpc. I continued to investigate and discovered that version v1.39.0 of google.golang.org/grpc did not have the google.golang.org/grpc/naming package. I went to the Github repository and searched through previous versions, and found that the package did exist in version v1.29.1, so I proceeded to replace it with that version.

1
go mod edit -replace google.golang.org/grpc=google.golang.org/[email protected]

Finally, go mod tidy passed and I can happily output hello world. However,

1
2
3
4
5
6
7
$ go run main.go
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
../../../go/pkg/mod/github.com/coreos/[email protected]+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:114:78: undefined: resolver.BuildOption
../../../go/pkg/mod/github.com/coreos/[email protected]+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:182:31: undefined: resolver.ResolveNowOption
# github.com/coreos/etcd/clientv3/balancer/picker
../../../go/pkg/mod/github.com/coreos/[email protected]+incompatible/clientv3/balancer/picker/err.go:37:44: undefined: balancer.PickOptions
../../../go/pkg/mod/github.com/coreos/[email protected]+incompatible/clientv3/balancer/picker/roundrobin_balanced.go:55:54: undefined: balancer.PickOptions

Surprise, surprise! It turns out that the etcd package depends on the resolver package from grpc, but the v1.29.1 version of grpc that I imported doesn’t have this package. I went through the versions in the grpc repository (https://github.com/grpc/grpc-go/blob/v1.26.0/resolver/resolver.go) one by one and found that only version v1.26.0 declares type BuildOption. So, we’ll have to replace it again.

1
go mod edit -replace google.golang.org/grpc=google.golang.org/[email protected]

Run tidy again! Finally, you will see the long-lost hello world!

1
2
$ go run main.go
2021/07/20 12:27:09.642431 [INFO] /Users/razeen/wspace/github/demo-go/gomod/main.go:26: hello world

Summary

Project Standards

Now let’s take a look back at this demo project, which actually has a lot of issues.

1
2
3
4
5
6
"github.com/coreos/etcd/pkg/transport"
"github.com/google/certificate-transparency-go/tls"
"github.com/qiniu/api.v7/auth/qbox"
"go.etcd.io/etcd/clientv3"
"google.golang.org/grpc"
"qiniupkg.com/x/log.v7"

It is possible to unify the packages of etcd and qiniupkg and import only one of them! Also, we later discovered that the log.v7 package was accidentally imported…

This was a problem we encountered when refactoring some of our older projects. We didn’t notice these issues when using vendor and go get before, so this needs to be standardized in advance.

Understanding go.mod

Let’s take a look at the go.mod file we obtained after going through various difficulties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module demo-go/gomod

go 1.16

replace qiniupkg.com/x => qiniupkg.com/x v1.7.8

replace github.com/qiniu/x => github.com/qiniu/x v1.7.8

replace go.etcd.io/etcd => go.etcd.io/etcd v3.3.20+incompatible

replace github.com/coreos/bbolt v1.3.6 => go.etcd.io/bbolt v1.3.6

replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.20+incompatible

replace google.golang.org/grpc => google.golang.org/grpc v1.26.0

require (
github.com/coreos/bbolt v1.3.6 // indirect
github.com/coreos/etcd v3.3.10+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/google/certificate-transparency-go v1.1.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/qiniu/api.v7 v7.2.5+incompatible
github.com/qiniu/x v0.0.0-00010101000000-000000000000 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c
google.golang.org/grpc v1.29.1
qiniupkg.com/x v0.0.0-00010101000000-000000000000
sigs.k8s.io/yaml v1.2.0 // indirect
)

Let’s take a look at some common directives in this documentation:

  • module defines the path of the main module;
  • go specifies the Go version used to write the mod file;
  • require declares the minimum required version of a given module dependency;
  • replace manually specifies a dependency module (can replace all versions, specific versions, local versions, etc.).

There is also the +incompatible after v3.3.20, which indicates a compatible version, meaning that the dependency library version is v2 or higher, but the go.mod and dependency library paths are not named according to the official specifications, hence the tag is added.

v0.0.0-00010101000000-000000000000 is a pseudo-version, which is used when an incompatible module or a tagged version is not available.

// indirect indicates that these are not dependencies that we directly reference.

In addition, the following directives are also worth noting.

1
2
3
4
5
6
7
8
9
go list -m all

go list -m -versions go.etcd.io/etcd

go get [email protected]

go mod why -m all

go mod why -m google.golang.org/grpc

For more detailed information, please refer to the official documentation. Thank you for reading.

Reference


Unless otherwise stated, all articles on this blog are written in CC BY-SA 4.0 , Please indicate the source when reproducing!