Using Govendor to manage your Go dependencies

I started recently using Go for work purposes. It’s a great language! While it doesn’t have the agility of Python or Node scripting, it’s fantastic as a compiled language. It’s easy to pick up and to use, maintainable, and has an active open-source community. By the time I’m writing this post, version 1.8 was just released, introducing deep performance improvements.

This is a small piece for people who’ve started developing with Go. To manage your dependencies, you must’ve quickly noticed that you can either import packages with their name - if it’s a native Go package, such as net, fmt or io - or with their full import path.

 Why would you use Govendor?

Let’s say you’d like to install Cobra, a command management tool, as a dependency to your Go application. You’d get the package in your $GOPATH with go get -u github.com/spf13/cobra, and then call it in your app:

package main

import "github.com/spf13/cobra"

func main() {
  // do stuff
}

This is fine for prototyping. The issue comes when you need to keep track of the versions of the dependencies you’re using. What if, in two months, your coworker needs to work on your project, downloads your code and your dependencies - but that since then, a dependency has been to a whole new version and breaks your build? Go doesn’t keep track of the versions you’re using. And what if another package you download expects a different version of Cobra?

This is why you’ll probably want to use Govendor. Govendor manages your dependencies’ versions and contains them to avoid conflicts between projects. It keeps your data and downloads the packages inside the vendor/ folder of your package. Let’s see how it works!

 Getting started with Govendor

To show you, I’ll create a new Go package, located at github.com/dotpy3/govendortest. This is my github.com/dotpy3/govendortest/main.go file:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var RootCmd = &cobra.Command{
    Use: "govendortest",
    Short: "A simple Go app to show how Govendor works",
    Run: func (cmd *cobra.Command, args []string) {
        fmt.Println("Hello world!")
    },
}

func main() {
    if err := RootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }
}

Alright - package created, it’s time to manage those dependencies! Right now, the Go compiler is using the github.com/spf13/cobra that’s already in my $GOPATH to create the binary. But I want to specify which version it is that I’m using. Let’s fire up Govendor! To install it, type go get -u github.com/kardianos/govendor in your shell. Once that’s done, go in your package’s folder (so $GOPATH/src/github.com/dotpy3/govendortest for me), and type govendor init.

What happened? If you check what’s in the folder now, you’ll see there’s a new folder called vendor/ :

$ ls
main.go vendor

This is where the magic happens. Since Go 1.5, every time a package is available from vendor/ at compilation, the Go compiler will prioritise this one, rather than the one that’s in the $GOPATH. This is where you’ll want to fetch your dependencies. For the moment, vendor/ only contains a single file, vendor.json. This is how it looks like:

{
    "comment": "",
    "ignore": "test",
    "package": [],
    "rootPath": "github.com/dotpy3/govendortest"
}

 Saving our dependencies in our package

For the moment, I have no external package saved. But I want to save which version of Cobra I was using! Let’s add our current package:

$ govendor add +external
$ ls vendor/
github.com  vendor.json
$ cat vendor/vendor.json
{
    "comment": "",
    "ignore": "test",
    "package": [
        {
            "checksumSHA1": "fUj88MYwrfU8xFWu+BPWpBXm8V4=",
            "path": "github.com/spf13/cobra",
            "revision": "ee4055870c2d5f7ce112642377e32c9929c3bbaf",
            "revisionTime": "2017-02-17T16:44:07Z"
        },
        {
            "checksumSHA1": "5KvHyB1CImtwZT3fwNkNUlc8R0k=",
            "path": "github.com/spf13/pflag",
            "revision": "9ff6c6923cfffbcd502984b8e0c80539a94968b7",
            "revisionTime": "2017-01-30T21:42:45Z"
        }
    ],
    "rootPath": "github.com/dotpy3/govendortest"
}

Wow! Not only has Govendor copied my dependencies (and Cobra’s dependency, pflag) from my $GOPATH/src folder, to make sure that the Go compiler builds with the right version of my package, but it has also saved in the vendor/vendor.json file the state they were when I started using them. Govendor identifies which version of a package you’re using with the SHA1 checksum of the Git commit of the version that you’re using. Now, I can keep track of what version I’ve been using, to make sure that I use the right one every time!

 Adding additional new dependencies

Now, let’s say I also want to use Viper, the flag management package. This time, my package has already been govendor init‘d - so to download the right one into my vendors, I’ll just type:

$ govendor fetch github.com/spf13/viper

Govendor will download the latest version of Viper, and add all the dependencies to the vendor.json file. This is exactly like typing npm install --save <some_package> with npm. What if I want to add a specific version of a package?

$ govendor fetch github.com/spf13/viper@7538d73b4eb9511d85a9f1dfef202eeb8ac260f4

 Getting those dependencies from scratch

I’m now sending my package to one of my coworkers. To download the package’s dependencies, he’d just need to type this command:

$ govendor sync

This command acts as a npm install, for those more familiar with NodeJS. It will fetch the dependencies according to the vendor.json file, and will download them in the vendor/ folder, so that it’s contained only for this package.

Hope this blogpost was useful for you - feel free to have a look at the official Govendor repo, and to contribute to the project if you’re confident you can add something more!

 
2
Kudos
 
2
Kudos

Now read this

First steps

I’ve been looking to start a blog for a long time now, but couldn’t just push myself to do it ; mostly by fear that that my content isn’t relevant enough. I’ve been studying software engineering for two years now - including a 6-month... Continue →