Some notes on relative imports and vendor/ in Go

January 2, 2018

For reasons beyond the scope of this entry, I've become interested in ways to work with multiple packages in Go without doing it in the normal way, with a $GOPATH/src and packages and programs that can be go get'd from Github or any of the other places that the go tool supports. The short version of what I'm interested in is that I'd like to create self contained program source code trees that are still modularized into multiple Go packages, instead of throwing everything into 'package main'.

(If you put everything in main, you can put all your source code in one directory and then tell people to just cd there and do 'go build' to build it.)

Go has two plausible mechanisms for this, vendoring and the rarely used relative import path (which is more properly a filesystem path). I'll start with the latter. Relative import paths come from 'go help packages':

An import path that is a rooted path or that begins with a . or .. element is interpreted as a file system path and denotes the package in that directory.

(Italics mine.)

Suppose that you have a program and you want to have two sub-packages, a and b. Then you can put together a directory tree like this:


Your main.go will use 'import "./a" to make a relative import from the current directory, while your a.go would use 'import "../b" to reach package b (if it needed to). You have to put a.go and b.go into appropriate subdirectories, even if each package only has a single file, because even this sort of import applies to the entire directory.

However, relative imports turn out to have one drawback, and that's that you can't use them in a package or program that has its source code under your $GOPATH. If you do, you'll get an error message:

main.go:3:8: local import "./a" in non-local package

(Note that you get this error even if you are in the directory itself and just running 'go build', to build the current directory. It's possible that this is new behavior in Go 1.10, but if so, well, you're going to have to use Go 1.10 or later at some point.)

The other plausible mechanism is vendoring. Given the same main program and two sub-packages a and b, construct your directory tree as:


Now everything simply does 'import "a"' or 'import "b", which works in both main.go and a.go.

The drawback of vendoring is the exact reverse of relative imports; instead of not working under your $GOPATH, it only works under your $GOPATH. This unfortunately makes vendoring not entirely useful for my particular purpose here, because if I'm going to require a $GOPATH, I might as well use something approximating a proper Go package hierarchy:


(The whole cmd/program subdirectory tree feels a bit excessive here, but I'm not sure if there's a generally accepted better way.)

With this setup I can refer to things as 'import "cslab/program/a"' and so on. There's relatively little need to hide them in a vendor/ subdirectory just so I can confuse everyone with 'import "a"'.

In fact, I think this potential confusion from vendoring makes it better to use relative imports if you have to pick one of these two options, because at least when people see them it's fairly clear what they mean.

(It would be useful if the vendor/ subdirectory worked even outside $GOPATH, but it doesn't currently so even if this was accepted as a bug by the Go people it would be some time before a fix appeared and was more or less universally usable.)

Written on 02 January 2018.
« Is the C runtime and library a legitimate part of the Unix API?
A brief review of the Dell XPS 13 as a Fedora laptop »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Jan 2 03:57:49 2018
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.