Conan: A Go package manager

The source code

You can just clone the following example repository:

$ git clone https://github.com/lasote/conan-goserver-example

Or, alternatively, manually create the folder and copy the following files inside:

$ mkdir conan-goserver-example
$ cd conan-goserver-example
$ mkdir src
$ mkdir src/server

The files are:

src/server/main.go is a small http server that will answer “Hello world!” if we connect to it.

package main

import "github.com/go-martini/martini"

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}

Declaring and installing dependencies

Create a conanfile.txt, with the following content:

conanfile.txt
 [requires]
 go-martini/1.0@lasote/stable

 [imports]
 src, * -> ./deps/src

Our project requires a package, go-martini/1.0@lasote/stable, and we indicate that all src contents from all our requirements have to be copied to ./deps/src.

The package go-martini depends on go-inject, so Conan will handle automatically the go-inject dependency.

$ conan install .

This command will download our packages and will copy the contents in the ./deps/src folder.

Running our server

Just add the deps folder to GOPATH:

# Linux / Macos
$ export GOPATH=${GOPATH}:${PWD}/deps

# Windows
$ SET GOPATH=%GOPATH%;%CD%/deps

And run the server:

$ cd src/server
$ go run main.go

Open your browser and go to localhost:9300

Hello World!

Generating Go packages

Creating a Conan package for a Go library is very simple. In a Go project, you compile all the code from sources in the project itself, including all of its dependencies.

So we don’t need to take care of settings at all. Architecture, compiler, operating system, etc. are only relevant for pre-compiled binaries. Source code packages are settings agnostic.

Let’s take a look at the conanfile.py of the go inject library:

conanfile.py
 from conans import ConanFile

 class InjectConan(ConanFile):
     name = "go-inject"
     version = "1.0"

     def source(self):
         self.run("git clone https://github.com/codegangsta/inject.git")
         self.run("cd inject && git checkout v1.0-rc1")  # TAG v1.0-rc1

     def package(self):
         self.copy(pattern='*', dst='src/github.com/codegangsta/inject', src="inject", keep_path=True)

If you have read the Building a hello world package, the previous code may look quite simple to you.

We want to pack version 1.0 of the go inject library, so the version variable is “1.0”. In the source() method, we declare how to obtain the source code of the library, in this case just by cloning the github repository and making a checkout of the v1.0-rc1 tag. In the package() method, we are just copying all the sources to a folder named “src/github.com/codegangsta/inject”.

This way, we can keep importing the library in the same way:

import "github.com/codegangsta/inject"

We can export and upload the package to a remote and we are done:

$ conan export . lasote/stable  # Or any other user/channel
$ conan upload go-inject/1.0@lasote/stable --all

Now look at the go martini conanfile:

conanfile.py
 from conans import ConanFile

 class InjectConan(ConanFile):
     name = "go-martini"
     version = "1.0"
     requires = 'go-inject/1.0@lasote/stable'

     def source(self):
         self.run("git clone https://github.com/go-martini/martini.git")
         self.run("cd martini && git checkout v1.0")  # TAG v1.0

     def package(self):
         self.copy(pattern='*', dst='src/github.com/go-martini/martini', src="martini", keep_path=True)

It is very similar. The only difference is the requires variable. It defines the go-inject/1.0@lasote/stable library, as a requirement.

$ conan export . lasote/stable  # Or any other user/channel
$ conan upload go-martini/1.0@lasote/stable  --all

Now we are able to use them easily and without the problems of versioning with github checkouts.