Golang Before||After Modules

Daz Wilkin
Google Cloud - Community
3 min readJul 8, 2019

Although I am familiar (and a fan of) the old ${GOPATH} way, to remain current and because of the many benefits, I’ve begun to use Go Modules.

Like others, I found the switch to be confusing. So…

Before Modules

WORKDIR=[[PATH-TO-YOUR-WORKING-DIRECTORY]]mkdir -p ${WORKDIR}/go
export GOPATH=${WORKDIR}/go
export PATH=${GOPATH}/bin:${PATH}
mkdir -p {${WORKDIR}/go/src/foo, ${WORKDIR}/go/src/foo/bar}

Then create ${WORKDIR}/go/src/foo/bar/library.go:

package barfunc Something() (string) {
return "Hello Freddie"
}

Then create ${WORKDIR}/go/src/foo/main.go:

package mainimport (
"fmt"
"foo/bar"
)
func main() {
fmt.Printf("%s", bar.Something())
}

You’ll have a structure like this:

.
└── go
└── src
└── foo
├── bar
│ └── library.go
└── main.go

Then you may run this in any of the following ways:

GO111MODULE=off go run ${WORKDIR}/go/src/foo/main.go
Hello Freddie!
cd ${WORKDIR}/go/src/foo
GO111MODULE=off go run main.go
Hello Freddie!
GO111MODULE=off go run foo
Hello Freddie!

NB With the above, you may omit GO111MODULE=off because this is implicit when sources are within ${GOPATH}. I’ve included it to be explicit with intent.

After Modules

Assuming you’ve done the above!

You don’t need to do this step but this is the new best practice. We’re moving our sources outside of ${GOPATH}. ${GOPATH} is still used to store our versioned packages.

NB In this trivial example, we're not using external packages so ${GOPATH} remains empty. See the end for an example.

mv ${WORKDIR}/go/src/foo ${WORKDIR}
rm ${WORKDIR}/go/src

You should have a structure like this:

.
├── foo
│ ├── bar
│ │ └── library.go
│ └── main.go
└── go

NB Our sources rooted on foo are now outside of ${GOPATH}

cd ${WORKDIR}/fooGO111MODULE=on go mod init foo
more go.mod
module foo
go 1.12GO111MODULE=on go run foo
Hello Freddie!
GO111MODULE=on go run main.go
Hello Freddie!

For Completeness

To show the difference with external packages. Not using Modules, go get pulls the latest version of the package into ${GOPATH}/src:

GO111MODULE=off go get github.com/golang/glog.
├── foo
│ ├── bar
│ │ └── library.go
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── go
└── src
└── github.com
└── golang
└── glog
├── glog_file.go
├── glog.go
├── glog_test.go
├── LICENSE
└── README

Versus using Modules, pulls a specific version (or versions) of the package into ${GOPATH}/pkg:

GO111MODULE=on go get github.com/golang/glog.
├── foo
│ ├── bar
│ │ └── library.go
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── go
└── pkg
├── linux_amd64
│ └── github.com
│ └── golang
│ └── glog.a
└── mod
├── cache
│ ├── download
│ │ └── github.com
│ │ └── golang
│ │ └── glog
└── github.com
└── golang
└── glog@v0.0.0-20160126235308-23def4e6c14b
├── glog_file.go
├── glog.go
├── glog_test.go
├── LICENSE
└── README

Go Modules Mirror

See https://proxy.golang.org/

You include PROXY=https://proxy.golang.org in all the above commands to take advantage of the Golang team’s (Google Trillian based) Mirror:

GO111MODULE=on \
GOPROXY=https://proxy.golang.org \
go get github.com/golang/glog

Trick

Thanks to this link for this useful trick finding packages that will be installed somewhere in go/pkg/mod when using Modules:

GO111MODULE=on \
go list -f '{{ .Dir }}' github.com/golang/glog
go: finding github.com/golang/glog latest
/path/to/go/pkg/mod/github.com/golang/glog@v0.0.0-20160126235308-23def4e6c14b

Visual Studio Code

A few folks asked for my configuration settings for Visual Studio Code w/ Go Modules. Here’s what I have:

{
"go.autocompleteUnimportedPackages": true,
"go.useLanguageServer": true,
"[go]": {
"editor.snippetSuggestions": "none",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"gopls": {
"usePlaceholders": true // add parameter placeholders when completing a function
},
"files.eol": "\n", // formatting only supports LF line endings
"go.toolsEnvVars": {
"GO111MODULE": "on",
"GOPROXY": "https://proxy.golang.org"
},
}

A combination of help from this page and this reply from a Google Engineer. I try to remain current but the Golang team’s gopls had passed me by entirely :(

https://github.com/golang/go/wiki/gopls

Conclusion

Modules are the future of package management in Golang. Having now begun the plunge, I mostly see benefits. Even with my simple Golang repos, there’s utility in being able to define specific versions of packages that are imported.

Initially I didn’t understand (and maybe still don’t understand) the uility in not including my sources under ${GOPATH} but, one advantage is that it becomes more evident which sources are my sources and which are others packages. When everything’s smooshed under ${GOPATH} this was confusing.

In part, the use of semantic versioning (aka SemVer) also means that, once you’ve go get useful-package@v1.0.0 you should (!) never need to re-get it. I’ve previously always kept my ${GOPATH} directories distinct by project to keep code isolated. But, by pulling my sources out of ${GOPATH} and because I only need one copy of useful-package@v1.0.0, and (!) because I know this package is immutable, I will now begin using a single, central ${GOPATH} for all my projects.

That’s all!

--

--