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:

au BufRead,BufNewFile Gomfile setlocal ft=ruby

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 packages
doc      -- Run godoc for bundles
exec     -- Execute command with bundle environment
gen      -- Generate .travis.yml or Gomfile
install  -- Install bundled packages into vendor directory
run      -- Run go file with bundles
test     -- 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.

blog comments powered by Disqus