2018-01-02
Some notes on relative imports and vendor/
in Go
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:
program/ main.go a/a.go b/b.go
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:
program/ main.go vendor/ a/a.go b/b.go
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:
src/cslab/program cmd/program/main.go a/a.go b/b.go
(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.)