Wednesday, February 26, 2014

Why I use just one GOPATH

I write programs in Go. All my Go projects have something in common: they can be classified by these categories:
  • personal
  • work
  • contributor/fork
And the following is true about all projects in each of these categories:
  • under source control
  • fully-qualified (unique) folder paths
The fully-qualified directory structure is important. I'll show you what I mean, with emphasis on the src folder:
  • ~
    • dev
      • src
        • github.com
          • mholt
            • jquery.parse
            • json-to-go
            • makemypi
          • smartystreets
            • goconvey
          • codegangsta
            • martini
            • inject
          • martini-contrib
            • binding
        • bitbucket.org
          • mattholt
            • cs345
          • smartystreets
            • liveaddress
      • pkg
      • bin
      • etc
      • doc
Turns out that not only my Go projects can be categorized like this, but nearly everything I develop can be. This is why I've combined all my work, personal, and contributor/forked projects in one workspace, or GOPATH.

Benefits of using a single GOPATH

The structure above just happens to be how Go workspaces are defined (except the etc and doc folders; I use those for spike code and documentation). The package name is simply the path inside the src directory. Plus, this integrates seamlessly with go get.

I have also found that this structure allows me to combine my work, personal, and forked/contributor projects into ~/dev/src. One central place for my Go code means that I don't have to keep switching my GOPATH multiple times per day between work, school, and hobby/personal projects.

I actually used to maintain multiple GOPATHs and found myself getting confused about which ones had the latest dependencies. Updating GoConvey in one would cause me to forget that I hadn't in another, and when I wrote code in another for the latest GoConvey, it wouldn't work. That would stump me until I remembered that I have as many copies of GoConvey on my computer as I do GOPATHs. Besides juggling dependencies and compiled binaries and workspace configurations, always changing the environment variable was so annoying after a while.

Anyway, these projects in the example above are all under source control, but even if they aren't, I choose a folder path for those projects as if they would some day be committed to source control.

All dependencies are right in their place and I know where to find them. Since dependencies are also retrieved via source control, I can fork and branch the code to snapshot or modify it. Git becomes my dependency manager, and it's really intuitive and flexible in my experience.

Not only that, but binaries and object files are appropriately put into bin and pkg files, adjacent to the src folder. I added ~/dev/bin to my PATH and now all my compiled binaries are easily executable anywhere on my system.

A GOPATH for more than just Go code

Here's the really powerful, neat feature about this directory structure: I have since migrated all of my code into my GOPATH, even if it's not Go code. This same directory now houses all of my development projects. Can you believe I used to lose them sometimes? I would actually forget where I stored a certain project because they were scattered abroad in my dev folder, or websites in my sites folder... and it wasn't consistent.

Look again at my example above, and notice that I have jquery.parse and makemypi and json-to-go in my github.com folder, even though none of those are Go projects!

This way of using GOPATH is simple. I don't get lost. My code is nicely organized and tucked away into its proper place; the same place, in fact, that I find them on the Internet. Dependencies have never been a problem for me, and if they are, I can fork or branch the project thanks to git.

How do you organize your dev projects?

I have found this to be the most efficient, organized, simple, and consistent for my own setup, but everyone's situation is different. Do you structure your code like this? Would you try? Why or why not? (Let's not start a fight in the holy war of Go dependency management, though.)