A journey in golang package manager
Go is an incredible language, and the go tools are one of its many strengths.
But it isn't perfect. In particular, managing a project's dependencies can be
difficult.
By default, go get
installs the latest commit from master, unless there is a
go1
branch (in which case it installs the latest commit from this branch).
Semver? Forget about it. The official golang way is to
always have a clean master branch that maintains ascendant compatibility. In
practice, this often works well, but issues do occasionally arise. It's also
problematic if you want reproducible builds on different computers.
There are a large number of third-party tools to help with package management,
but be warned, most of them are still young, have bugs, are opinionated about
the workflow you should use, lack features, etc. I've tried some of them, and
in this post I wanted to talk about my journey, as I think it will help some
developpers choose the right tool for them.
My first attempt was godep. On paper, it looks
like the most advanced tool. Well, it doesn't have as many features as Ruby
bundler by far, but the additional tools aren't better. I couldn't get it to
run (see the issue on github). It's
probably my fault though, so you shouldn't reject it too quickly.
Next, I tried going in the opposite direction: trying the simplest thing and
hoping it would work. The tool was
dondur, and all it does is create a
.dondur.lock
file that consists of all the imported packages and their
current version control hashes. It ran, but I wasn't satisfied. It's too
basic: it doesn't skip the standard packages, and it doesn't include the
dependencies of the imported packages.
So, I looked to the next possibility:
johnny-deps. But it didn't take
me long to see a blocker: it only works when external packages are hosted on
github, and I use a library, gofpdf, which
is hosted on google code. Johnny-deps has a fork,
gpm, but that has the same limitation.
So I moved onto the next tool: Go Manager, or
gom for short. And this time, success! Gom works with a Gomfile
that lists
the dependencies, and you can constrain the tag, branch or commit you want to
bundle for each package. Gom will install them in the vendor
directory, and
gom commands will use your vendor as your GOPATH
to use the bundled
packages.
If you already have a project with some dependencies, you can avoid typing
them out in the Gomfile by using gom gen gomfile
. It will parse your package
and its dependencies recursively to find all the external packaged required to
build your project. Then, you can add the commit/tag/branch constraints by
editing the Gomfile, and install them with gom install
. To
build/test/install/run your project, just replace go
with gom
, for example
gom run main.go
. You also have gom exec
for using the bundled packages in
other cases.
As a bonus, I've got two tips for you. Firstly, the Gomfile
uses a syntax
inspired by Ruby, so you can get syntax highlighting in vim by adding this
line in your vimrc
:
<span class="k">au</span> <span class="nb">BufRead</span><span class="p">,</span><span class="nb">BufNewFile</span> Gomfile <span class="k">setlocal</span> <span class="nb">ft</span><span class="p">=</span><span class="k">ruby</span>
Secondly, I'm also a zsh user. So I wrote a
completion script for gom.
You can install it in $HOME/.zsh/Completion/
to get nice completion by
typing tab:
$ gom <TAB>build -- Build with vendor packagesdoc -- Run godoc for bundlesexec -- Execute command with bundle environmentgen -- Generate .travis.yml or Gomfileinstall -- Install bundled packages into vendor directoryrun -- Run go file with bundlestest -- Run tests with bundles
A few last words: if you want to know more about this topic, you should read
Go Package Management, a insightful cry for
gophers to unify on a dependency management solution.