With nearly half a year of Go, it is really a kind of feeling. Concise syntax, perfect and powerful development tool chain, save novices a lot of time, you can focus on writing code. During this period, a number of idioms have been learned, such as Prosumer, which is a framework for implementing the producer/consumer model by stepping on the Chan/Timer pit. But instead of talking about how chan is used, I’m going to talk about the basic question of where the Go code should Go, which is actually the package management mechanism of Go, sometimes called versioning, which actually means the same thing. This seems simple, but it gave a lot of Gopher a hard time.

This article is in general order: the history of package management; New package management mode Module; Finally, add a troubleshooting problem to solve the problem of how to place the Go code.

Go package manager

GOPATH

Before the go mod, all go projects needed to be in the same workspace: $GOPATH/ SRC, for example:

src/
    github.com/golang/example/
        .git/                      # Git repository metadata
    outyet/
        main.go                # command source
        main_test.go           # test source
    stringutil/
        reverse.go             # package source
        reverse_test.go        # test source
Copy the code

This limitation is somewhat incomprehensible compared to other languages. In fact, this is closely related to one of Go’s design philosophies:

Package management should be decentralized

So there is no package management tool like Maven/NPM in Go, just a Go Get that supports hosting from public code platforms (Bitbucket/GitHub..). Download dependencies and, of course, host them yourself. For details, see the official documentation: Remote Import Paths.

Since there is no central repository, the Go project location determines its import path, and in order to be consistent with Go GET, generally our project name is in the form of github.com/user/repo. Of course, it may not be in this form, but it is not easy for others to refer to, and we will see how to achieve this effect in go Mod later.

flowers

Simple and violent way to download dependencies using Go for seven years Vendor was officially supported only in 1.6 (2016/02/17) downloading all dependencies into the current project solving the problem of reproducible builds But you cannot manage dependent versions. A variety of package management tools have emerged in the community to make it easier for developers to build dependent versions. Because different management tools use different meta-information formats (such as Godep’s Godeps.json, Glide’s Glide. Yaml), which is not good for the community development, Go officially launched DEP.

Dep is designed to experiment and explore how to manage versions. It will not be directly integrated into the Go tool chain. The Go core team will learn from the experience of using DEP and community feedback to develop the next generation package management tool Modules, which will be officially supported in 1.13 released on 2019/09/03. Module Mirror, Index, Checksum are released to solve software distribution, man-in-the-middle attacks, etc. The image below is taken from Go’s official blog

Module Big Picture

Modules

A Module is a collection of packages, the basic unit of version management, and you use the go.mod file to record dependent modules.

Go. mod is located in the root directory of the project and supports four commands: module, require, replace, and exclude. Example:

The module github.com/my/repo the require (github.com/some/dependency v1.2.3 github.com/another/dependency/v4 v4.0.0)Copy the code
  • Module declares the Module path, which prefixes the import path of all packages in a module

If a module has the following directory structure, go. Mod uses the example above

repo
|-- bar
|   `-- bar.go
|-- foo
|   `-- foo.go
`-- go.mod
Copy the code

The import path of the bar package is:

import "github.com/my/repo/bar"
Copy the code

Here we focus on the two most useful aspects of Modules: the Semantic version and the replace directive.

Semantic Version

Semantic version

Semantic versions require backward compatibility for v1 and later versions, and v2 and later versions must be represented in the Module path, for example

The module github.com/my/mod/v2 require github.com/my/mod/v2 v2.0.1 import "github.com/my/mod/v2/mypkg" go a get github.com/my/mod/[email protected]Copy the code

The reason why the version number is required to be placed in module path is to solve the breaking changes between different large versions. Here’s a scenario:

dependency hell

As can be seen from the figure above, A depends directly or indirectly on three different versions of D. How to load the correct version if the version number is not put in the Module path? Of course, there is a prerequisite, the same large version must ensure backward compatibility!

To further explore the use of go.mod, take a look at Prometheus’ go.mod

# 1.13 the require module github.com/prometheus/prometheus go (indirect says rely on indirect cloud.google.com/go v0.44.1 / / indirect K8s. IO /klog v0.4.0 # Without a version tag github.com/alecthomas/units v0.0.0-20190717042225 - # c3de453c63f4 incompatible said no use go go - autorest module, The version is larger than v2 github.com/Azure/go-autorest v11.2.8+incompatible github.com/aws/aws-sdk-go v1.23.12) replace (# denotes from Github.com download klog, rather than k8s. IO k8s. IO/klog = > github.com/simonpasquier/klog-gokit v0.1.0)Copy the code

Here, I’ll focus on pseudo_versions, which are intended to accommodate dependencies that are not module adopted. General form:

  • V0.0.0 yyyymmddhhmmss -- abcdefabcdef

The intermediate time is UTC, which is used to compare the old and new versions of the two pseudo-versions. The last part is the first 12 characters of the COMMIT ID.

Replace

The replace of go.mod is mainly used to replace module Path, which is very helpful for referencing local dependencies. For example, if a library github.com/user/lib has a bug and you need to fork it locally, then you need to reference the branch of the local fork in your own project.

replace github.com/user/lib => /some/path/on/your/disk
Copy the code

The import path in the code remains unchanged.

Similarly, the project module name does not have to be prefixed with the hosting platform. I created a sample project strutil with the following go.mod:

The module strutil go 1.12Copy the code

To reference the project, just do this:

/ / go. Mod replace strutil = > github.com/jiacai2050/strutil v0.0.1 / / str_test go import strutil func TestModule (t *testing.T) { s := strutil.Reverse("hello") assert.Equal(t, "olleh", s) }Copy the code

Common commands

For projects developed using Module, use go mod init {moduleName} and import the package name directly from the source file. Commands such as go test/build will be automatically paralized. Add it to require in go.mod without having to modify it yourself. After the development test is complete, you need to tag for other users to use. The project version number generally starts from V0.1.0, indicating that the first feature is started. When bugfix is available, the third version number, such as V0.1.1, is changed. When there is a new feature, change the intermediate version number, such as V0.2.0. When there are breaking changes, change the first version number, such as v1.0.0.

In addition, you also need to configure the following variables:

Export GO111MODULE=on # 1.13 The previous version only supports a export GOPROXY = https://goproxy.cn, https://mirrors.aliyun.com/goproxy, direct # 1.13 support, private module configuration, Don't go to check checksum export GOPRIVATE = *. Corp.example.com, RSC. IO/privateCopy the code

For more information about module verification, please refer to:

  • Golang.org/cmd/go/#hdr…

Other common commands are:

  • go list -m allView the final version of module used by the current project
  • Go mod download-json View module details

    type Module struct { Path string // module path Version string // module version Error string // error loading module Info string // absolute path to cached .info file GoMod string // absolute path to cached .mod file Zip string // absolute path to cached .zip file Dir string // absolute path to cached source root directory Sum string // checksum for  path, version (as in go.sum) GoModSum string // checksum for go.mod (as in go.sum) }Copy the code
  • go get -u=patch ./… Update all modules to the latest patch version

  • go mod tidyClean up modules that are not needed in go.mod/go.sum
  • go mod vendorCreate vendor-dependent directory. In order to be compatible with the previous directory, you can add it when running the go test/build command-mod=vendorThis build flag states to use vendor dependencies so that the Go mod doesn’t go$GOPATH/pkg/modLook inside.

Troubleshoot problems

I encountered a problem in using the Go mod. I was very confused before, but later I realized that it was not clear what the parameters of the go command meant. Let’s first look at the project directory structure:

CD ~/code/ Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard/Standard ├─ ├─ download.txt TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXTCopy the code

As you can see, the project root directory ceresdb-go-sdk does not have the GO source code, but is placed in the ceresdb subdirectory. This is designed to ensure that the import path is consistent with the directory name

import "github.com/user/ceresdb-go-sdk/ceresdb"
c := ceresdb.NewClient(...)
Copy the code

You can also remove the ceresdb directory and place the source code in the project root directory, so that the import becomes

import "github.com/user/ceresdb-go-sdk"
c := ceresdb.NewClient(...)
Copy the code

It doesn’t feel very friendly, and some ides are smart enough to automatically populate aliases

# this also indicates that the package name can be different from the directory! But generally you want to be consistent, otherwise it can be very confusing import ceresdb "github.com/user/ceresdb-go-sdk"Copy the code

But for now, let’s assume the subdirectory approach and execute go test./…

time go test -v -x ./... can't load package: package github.com/user/ceresdb-go-sdk: unknown import path "github.com/user/ceresdb-go-sdk": Cannot find Module providing package github.com/user/ceresdb-go-sdk # Note the time it took, there was no output during this time, Even if -x flag real 1m2.472s user 0m0.111s sys 0m0.082s is addedCopy the code

For this mistake some meng, my project is not github.com/user/ceresdb-go-sdk, how can not find it? To explain why, consider the general form of the go command:

go command [command_args] [build flags] [packages]
Copy the code
  • Command refers to commands like test/build/mod/list
  • Command_args is a parameter associated with a specific command
  • Build flags are supported by most commands. The most common ones are:
    • -raceEnable RACE detection
    • -vPrints the name of the package being compiled
    • -xRun detailed commands to locate faults when Go Get is stuck
    • -modSpecifying that module depends on download mode currently has only two values: readonly or vendor
    • -tagsComma-separated compiled tags are used to distinguish the build environment, for exampleIntegration testing
  • Packages Specifies the packages used by the current command

The error above is on the last parameter. We know that the package import path needs to be added to the Module path. The error above also proves this. In particular, for./ XXX the package will find the corresponding directory, do not write module path,./… Means to recursively find all packages in the current directory, including the current directory. The problem is that there are no go source files in the project root directory, so you can’t find packages in the current directory! The solution is simple:

Go test./ceresdb # go test github.com/user/ceresdb-go-sdk/ceresdb PASS ok github.com/user/ceresdb-go-sdk/ceresdb 0.017 sCopy the code

Alternatively, add a go file to the root directory

$echo 'package ABC '> ABC. Go $go test.? github.com/user/ceresdb-go-sdk [no test files]Copy the code

So you don’t get an error.

vgo

Vgo is a prototype of Go’s core developer Russ Cox’s attempt to add package management to Go. It is a separate command, equivalent to automatically adding Go command GO111MODULE=on. It is called Modules when the package management function is integrated into Go command.

The reference implementation was named vgo, but support for modules is being integrated into the go command itself. The feature within the go command is called “Versioned Go modules” (or “modules” for short), not “vgo.

Russ Cox has a series of vGO articles on package management for interested readers.

conclusion

Through the introduction of this article, I hope to give you a clearer understanding of the design of Modules and how to troubleshoot problems, to achieve semantic version, large version backward compatibility. If your project is more complex, look at the structure of the project:

  • Github.com/golang-stan…

Rich Hickey, author of Clojure, has a famous talk about Simple Made Easy, which talks about how Simple tools can reduce the complexity of software development, and I believe Go falls into this camp.

Simplicity

reference