github go labs

A Possible Solution For Managing External Dependencies in Go

Finally getting the chance to dive into Go has been an absolutely amazing experience. While the language has been a pleasure to work with my pair hit two pain points that I am sure most new Go-phers will run into:

  1. Code Organization
  2. Remote Dependency Management

A complete beginner to the language will likely spend a majority of their first few hours with Go figuring out how to properly setup a $GOPATH and how the directory tree should be organized (reading many conflicting articles along the way).

Lucky for me and my pair we had some veteran Go guidance when it came to getting our project organized in a clean way. In short, only one directory should be in your $GOPATH at any point in time, the root directory to where all of your system-wide Go files live. DO NOT and I can’t repeat this loud enough, DO NOT add project specific directories to your $GOPATH.

A nice $GOPATH:

  export GOPATH="~/workspace/go"

A $GOPATH made of razorblades and death:

  export GOPATH="~/workspace/go:~/workspace/go-the-hood-parts"

Check out more of our directory structure for the project and also some interesting ideas on code organization at our project repo here:

https://github.com/pivotal/gumshoe

Once we finished tripping over how to organize our directory structure development proceeded smoothly (to a point). Reaching the point of pushing our code to GitHub and making it public prompted an interesting discussion. How do we version the three external package dependencies we are using in our project? Running a “go get” on the packages we had included within our repo would have resulted in the user pulling down the current master of whatever repo specified. This is really great for causing all sorts of unexpected problems.

The first suggestion we attempted was Git submodules which admittedly sounded like a good idea at the time. The primary issue with the use of a Git submodule is that it requires a remote repo setup to hold any of the project specific changes that would be needed. The import statements across all of the packages we were using needed to be modified to now point at our local copies of these dependencies. We dejectedly abandoned any hope of having versioned remote dependencies as hosting three forked repos just to maintain versioned dependencies felt like too much overhead.

Thanks to another Pivot we were pointed in the direction of using a Git subtree, what’s the difference between the two? The most obvious benefit of using a Git subtree over a Git submodule is that no additional remote repositories are required when making modification to any of the repos that you have included within your subtree. More info about Git subtrees here and they are pretty fabulous.

Setting up the subtree was deceptively simple, requiring just one short command. Here’s an example of us versioning the awesome Gomega matchers into our own repos folder:

  git subtree add --prefix ./repos https://github.com/onsi/gomega  --squash

After doing a quick find and replace on all of the import statements (to repoint them at our local repos) we have had no issues with either running tests or working with the external packages, it has been one of the best decisions we have made.

Updating remote dependencies within your local subtree is a little bit more involved but not at all difficult either, it also requires one command:

  git subtree pull --prefix ./repos https://github.com/onsi/gomega  --squash

We feel this is a reasonable solution to managing external packages, at least until an official solution is included within Go itself.

Hopefully the Go landscape becomes a little less “wild west” with these additional pieces of information at your disposal.