@jialin.huang
FRONT-ENDBACK-ENDNETWORK, HTTPOS, COMPUTERCLOUD, AWS, Docker
To live is to risk it all Otherwise you are just an inert chunk of randomly assembled molecules drifting wherever the Universe blows you

© 2024 jialin00.com

Original content since 2022

back
RSS

Go Package Management: Go Modules, go install & get Through the Versions

Why

I used to mix go get and go install, not really understanding the differences, even when being suggested to use one over the other. Based on growing uncertainty and the fact that modules weren't there from the beginning, I'm documenting this for clarity.

Go Module Era (Go 1.11~)

We can call the time before 1.11 the "GOPATH era" since everything was placed there without version control.

The confusion comes from using JavaScript with npm, which always had version control. So when Go modules appeared, I wondered why this needed special explanation.

If you've only used Go after version 1.11, you probably aren't confused because modules are the normal way to control package versions.

Module evolution timeline:

  1. 1.11-12: Required manual setting of GO111MODULE=on
    1. 111 in GO111MODULE represents a variable supported after version 1.11
  1. During Go 1.13-1.15: the default is GO111MODULE=auto
  1. 1.16+: Default setting changed to GO111MODULE=on
  1. Related commands
    1. go mod init
    1. go mod vendor
    1. go mod download

Go Install

  1. Before 1.11:
    1. Installed binaries to $GOPATH/bin
    1. Also installed packages to $GOPATH/pkg
  1. 1.11-15 (transition period with modules):
    1. Still installed binaries to $GOPATH/bin or $GOBIN
    1. No longer used $GOPATH/pkg for packages - you had to run go install in your project for version dependencies
  1. 1.16+: Supports version specification and became independent from project dependencies:
    1. Still installs binaries to $GOPATH/bin or $GOBIN
    1. No longer manages project package dependencies (not related to go.mod now)
    1. Similar to npm install --global xxx

Go Get

  1. After 1.16, go install stopped managing packages and dependencies - that job went to go get
  1. 1.11-1.15: Transition period where it downloaded packages and installed binaries
  1. Starting 1.16: Not recommended for installing binaries
  1. 1.17: Officially not recommended for binary installation, shows warnings but doesn't block it
  1. 1.18: The -d flag (download only, no installation) became the default behavior, completing the transition

Version Migration of Go Install and Go Get

Originally, these commands had clearly defined separate purposes. Over time, features were added for convenience, which led to overlapping responsibilities and confused users. This issue was finally addressed starting in Go 1.16.

Go 1.16 began separating the commands conceptually. go install could specify versions and didn't affect go.mod files.

https://go.dev/doc/go1.16#modules

From Go docs 1.16:

go install now accepts arguments with version suffixes (for example, go installexample.com/cmd@v1.0.0). This causes goinstall to build and install packages in module-aware mode, ignoring the go.mod file in the current directory or any parent directory, if there is one. This is useful for installing executables without affecting the dependencies of the main module.
go install, with or without a version suffix (as described above), is now the recommended way to build and install packages in module mode. go get should be used with the -d flag to adjust the current module’s dependencies without building packages, and use of go get to build and install packages is deprecated. In a future release, the -d flag will always be enabled.

Go 1.17 reinforced this separation but still allowed go get for binary installation with warnings:

https://go.dev/doc/go1.17#module-deprecation-comments

go get prints a deprecation warning when installing commands outside the main module (without the -d flag). go install cmd@version should be used instead to install a command at a specific version, using a suffix like @latest or @v1.2.3.
In Go 1.18, the
-d flag will always be enabled, and go get will only be used to change dependencies in go.mod.

Go 1.18 completed the separation - go get no longer supports binary installation and uses -d by default.

https://go.dev/doc/go1.18#go-command

Official summary:

https://go.dev/ref/mod#go-get

Since Go 1.16, go install is the recommended command for building and installing programs. When used with a version suffix (like @latest or @v1.4.6), go install builds packages in module-aware mode, ignoring the go.mod file in the current directory or any parent directory, if there is one.
go get is more focused on managing requirements in go.mod. The -d flag is deprecated, and in Go 1.18, it will always be enabled.

Evolution of $GOPATH/pkg, bin, src Folders

The src, pkg, bin folders were core components in the GOPATH era:

  1. src/: Where you put cloned projects (yours or third-party)
    • Then: Seemed intrusive but was necessary to resolve import paths
    • Now: Less important with modules; I don't even have this folder anymore
  1. pkg/
    • Then: Stored compiled artifacts (*.a)
    • Now: Changed to pkg/mod/ as a cache (note that it's read-only) for multiple projects; like a repository that can be copied to a project using go mod vendor
  1. bin/: Hasn't changed - always stored executables/binaries

GOPATH role change: From core dependency path to mainly storing cache and binary files

For Node/npm Users

  1. Think of go install as npm i --global
  1. Think of go get as npm i --save (updates go.mod/go.sum like package.json/package-lock.json)
    1. npm local installs to node_modules/, similar to go mod vendor
    1. go get installs to $GOPATH/pkg/mod/..
    1. go mod vendor first downloads to $GOPATH/pkg/mod/, then copies to project's /vendor/..

How to Run a Cloned Project?

When cloning someone's project, you usually don't need to run go mod tidy if they followed best practices (running go mod tidy before commits).

To be safe, you can run go mod tidy yourself.

Based on trust, you can directly:

  1. go build: Downloads and compiles

    if you need an executable For CRUD servers, go run main.go is more common

  1. go mod download: Downloads without compiling

    Useful for debugging in Docker by separating download from build

  1. go mod vendor: Needed when:
    1. There's a vendor/ directory in version control
    1. You want npm-like local package control

      Vendor vs node_modules: vendor should be in version control; when cloning, Go still caches in $GOPATH/pkg/mod then copies to /vendor

    1. You need to work offline (must go with go build -mod=vendor)
    1. You need to modify dependency code (must go with go build -mod=vendor)
    1. In air-gapped env or enterprise, vendor mode remains an important dependency management approach. In vendor mode, Go doesn't download dependencies from the network but uses code from the vendor directory

    go mod vendor is an active choice, useful for customizing packages

Go Install vs Go Build

The main difference: go install puts executables in $GOPATH/bin or $GOBIN directory.

go build just builds in the current directory.

Global Cache vs Project-Local Storage

When you use go get to download a package used in several different projects, the file structure looks like this:

$GOPATH/pkg/mod/
└── github.com/
    └── gin-gonic/
        └── gin@v1.6.3/          # a version
            ├── LICENSE
            ├── gin.go
            ├── ...else files
        └── gin@v1.7.7/          # b
            ├── LICENSE
            ├── gin.go
            ├── ...else files
        └── gin@v1.9.1/          # c
            ├── LICENSE
            ├── gin.go
            ├── ...else files

go mod vendor: has only one version:

Your-project/
├── go.mod
├── go.sum
├── vendor/
│   ├── github.com/
│   │   └── gin-gonic/
│   │       └── gin/          # only one version (specified in go.mod)
│   │           ├── LICENSE
│   │           ├── gin.go
│   │           └── ...else
│   └── modules.txt           # records all vendored modules

References

https://go.dev/doc/go1.16#modules

https://go.dev/doc/go1.17#module-deprecation-comments

https://go.dev/doc/go1.18#go-command

EOF