Ryan Bigg

⟵ Posts

Go package management

02 Sep 2014

In Ruby-land, we have a wonderful tool called Bundler to manage package dependencies. I can specify dependencies like this:

gem 'rails', '4.1.5'

When I run bundle install, Bundler will install not only the rails dependency, but all of its dependencies and all of their dependencies and so on. It will then take note of the dependencies which have been resolved and save them to a Gemfile.lock. I can commit this file to Git and push it up to GitHub, then when other people work on my project they can clone it and run bundle install on their machine and work with the exact same dependencies. Another benefit of this is that I can have multiple versions of Rails installed on the machine and when I use Bundler, it will use the correct version of Rails as specified in the Gemfile.

There’s tools out there in Go-land, such as gom and godep which provide similar functionality. I prefer godep, but truth be told I wish that there was a Bundler-for-Go. Godep saves the dependencies in a JSON file:

{
  "ImportPath": "github.com/radar/my-project",
  "GoVersion": "go1.3",
  "Packages": [
    "./..."
  ],
  "Deps": [
    {
      "ImportPath": "github.com/codegangsta/cli",
      "Comment": "1.2.0-22-g687db20",
      "Rev": "687db20fc379d1686465a28e9959707cd1acc990"
    },
    {
      "ImportPath": "github.com/fatih/color",
      "Rev": "3161cccfa22c6243e02aa984cf2886d022024cec"
    },
    {
      "ImportPath": "gopkg.in/check.v1",
      "Rev": "5b76b26efe7f426789852e983fbde4de62c42282"
    }
  ]
}

This is a pretty good solution. godep save will update the dependencies (similar to bundle update), and godep restore will install the dependencies (similar to bundle install) if they aren’t available in GOPATH already.


Here’s what I would like though: I want a way that I can specify dependencies for Go projects like I can specify for Ruby projects. Ruby projects have a .gemspec file which lists dependencies, and I would like Go projects to do the same thing. For instance, in my project I’d like to specify my dependencies in a very simple JSON syntax (let’s call it deps.json):

[
  {
    path: "github.com/codegangsta/cli",
    rev: "1.2.0",
  },
  {
    path: "github.com/fatih/color",
    rev: "master",
  }
]

Then I would like to run a bundle install-equivalent which creates a deps.json.lock:

[
  {
    path: "github.com/codegangsta/cli",
    rev: "565493f259bf868adb54d45d5f4c68d405117adf",
  },
  {
    path: "github.com/fatih/color",
    rev: "3161cccfa22c6243e02aa984cf2886d022024cec",
  }
]

That’s the first step. The second step is a lot more complex than that, and it involves fetching the dependencies from their sources and setting up a proper GOPATH. See, the issue with the normal GOPATH is that all your dependencies are thrown into the one src directory. This means that if you want to use “Version A” of a project in “Codebase A” and “Version B” of that same project in “Codebase B”, you’re going to have a bad time. This is why I think having a global GOPATH is a terrible idea.

Instead, there should be a project-specific GOPATH. It looks the same as a normal GOPATH, but has the correct dependencies in it. For instance, instead of having a ~/Projects/go/src/github.com/radar/my-project, I would have ~/Projects/go/my-project, and then underneath that it would have bin, pkg and src directories relevant only to that project. The project’s code itself would exist within Projects/go/my-project/src/github.com/radar/my-project, only because that’s what Go expects.

To setup the project, I would create ~/Projects/go/my-project/src/github.com/radar/my-project, and then run that bundle install equivalent. This would clone github.com/codegangsta/cli into ~/Projects/go/my-project/src/github.com/codegangsta/cli, and check it out to the ref specified in deps.json.lock. It would then do the same thing with github.com/fatih/color. Now I have the first layer of dependencies that my-project needs.

The third step is where it gets even more difficult. These first layer of dependencies might have dependencies themselves, and so this tool should check for deps.json.lock (or deps.json) within those projects and resolve them as well. This is difficult because you may run into issues like circular dependencies and conflicting version requirements.

I believe if project setup was done this way, multiple Go projects on the same machine can use varying dependencies very easily.

There’s of course caveats: potential disk usage problems, every project needs a deps.json, and the resolution problems. By no means am I suggesting that this is The Way Things Should Be Done. It’s just something that occurred to me tonight that could be a potential beginning towards a solution for the Go package management puzzle.