Image by Kelvyn Skee

Canonical import paths in Golang

One of the features that has contributed to Go’s steady growth is the extremely friendly and minimalistic approach to package management via “go get”. Extreme simplicity however, does not come without limitations. Amongst other problems, having URLs as import paths makes your imported packages subject to link rot.

The upcoming shutdown of google code for example, will soon render hundreds of import paths obsolete. You should, of course, vendor your external dependences, and the Go team is currently working on a standard method to do so. But when it comes to the code that you expose to the world, there is a way for you to make it resilient to repository changes, define a canonical import path.

The go tool does not only fetch code from known source code hosting platforms, it does more, as you can read in the documentation about remote import paths:

If the import path is not a known code hosting site and also lacks a version control qualifier, the go tool attempts to fetch the import over https/http and looks for a tag in the document’s HTML . The meta tag has the form: <meta name="go-import" content="import-prefix vcs repo-root">

This means that if you have an html page under foo.com/bar with the go-import meta-tag pointing to the repo github.com/foo/bar , you can then use foo.com/bar as your import path, and the go tool will work with it seamlessly as if it was hosted under that URL, subpackages included. If some day you decide to move your code to bitbucket.org/foo/bar you simply need to change your meta-tag and nobody will need to touch their code. Thus foo.com/bar works as a permanent alias for your repo, a custom import path.

The Go Team itself has started using this technique. When I wrote my previous post about gorename, the import path was under code.google.com . Since then the code has been moved and now, along with all other go tools, it lives under the alias golang.org/x/tools . You can check for yourself how they do it:

➜ curl golang.org/x/tools/cmd/rename <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="go-import" content="golang.org/x/tools git https://go.googlesource.com/tools"> <meta name="go-source" content="golang.org/x/tools https://github.com/golang/tools/ https://github.com/golang/tools/tree/master{/dir} https://github.com/golang/tools/blob/master{/dir}/{file}#L{line}"> <meta http-equiv="refresh" content="0; url=https://godoc.org/golang.org/x/tools/cmd/rename"> </head> <body> Nothing to see here; <a href="https://godoc.org/golang.org/x/tools/cmd/rename">move along</a>. </body>

As you can see, golang.org/x/tool is just an alias for the git repository https://go.googlesource.com/tools . The other meta-tag go-source is indicating the go tool to generate the documentation links from the github mirror repo.

This is very useful to prevent link rot, but you might have noticed that it opens the door for some new problems. In one hand, if someone just finds your library in github, it’s unlikely that they’ll try to find a better import path to use instead. And what happens if we import something in 2 different places, one via the repository URL and another via its custom URL? Since the import path is the unique identifier for a package and the Go compiler creates an statically linked binary, we’ll end up with 2 copies of the same code in our executable.

To avoid this situation, Go 1.4 introduced the concept of canonical import paths:

Go 1.4 introduces an annotation for package clauses in Go source that identify a canonical import path for the package. If an import is attempted using a path that is not canonical, the go command will refuse to compile the importing package. The syntax is simple: put an identifying comment on the package line. For our example, the package clause would read: package pdf // import "rsc.io/pdf"

You can see this at work with gorename’s example. The canonical import path defined is golang.org/x/tools/cmd/gorename . And if when trying to “go get” from the github repository the go tool will let you know:

➜ go get github.com/golang/tools/cmd/gorename can't load package: package github.com/golang/tools/cmd/gorename: code in directory /tmp/gopath/src/github.com/golang/tools/cmd/gorename expects import "golang.org/x/tools/cmd/gorename"

Tags

[ golang ] [ dev ]