Create a Golang CLI application with Cobra and goxc

August 23rd, 2016 1269 Words

Cobra is an awesome and widely used library and generator for Command Line applications written in Go. Together with goxc you can easily create a neat setup to get started with CLI interactions.

Create Cobra basics

First install Cobra with go get and initialize the command line project. In this case the go application is called github.com/heft/cli and after this tutorial it will feature one command to display the compiled version of the heft command.

$ > go get -v github.com/spf13/cobra/cobra
$ > $GOPATH/bin/cobra init github.com/heft/cli

Your Cobra application is ready at
/Users/sebastian/Workspace/src/github.com/heft/cli
Give it a try by going there and running `go run main.go`
Add commands to it by running `cobra add [cmdname]`

After the application is created you can use the cobra generator to add commands with cobra add . As said above the application should display its current version, so add a version command:

$ > $GOPATH/bin/cobra add version

Thanks to the Cobra framework everything is predefined and you can already use the project. Just run the generated code and you will see basic instructions on how to use the new CLI application.

$ > go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
  cli [command]

Available Commands:
  version     A brief description of your command

Flags:
      --config string   config file (default is $HOME/.cli.yaml)
  -h, --help            help for cli
  -t, --toggle          Help message for toggle

Use "cli [command] --help" for more information about a command.

Branding with a custom name

As the project is stored in github.com/heft/cli cobra uses cli as the default name. It’s possible to change the command name in the root.go file: Rename the file to heft.go and update the RootCmd configuration:

var RootCmd = &cobra.Command{
  Use:   "heft",
  Short: "Access heft.io from the command line",
}

As a naming convention I prefer to rename the version.go file to cmd_version.go as well and get rid of all the unneeded lines inside the command file.

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

var versionCmd = &cobra.Command{
  Use:   "version",
  Short: "Show heft.io client version",
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println(RootCmd.Use + " " + VERSION)
  },
}

func init() {
  RootCmd.AddCommand(versionCmd)
}

You may notice the custom variable VERSION which is not part of the default content Cobra created. The version will be configured in main.go and be updated with our build process using goxc later on. So add a global variable to main.go and pass it to the cmd.Execute function:

package main

import "github.com/heft/cli/cmd"

var (
  // VERSION is set during build
  VERSION = "0.0.1"
)

func main() {
  cmd.Execute(VERSION)
}

The compiler will inform you that cmd.Execute is not expecting a parameter, so open heft.go (formerly root.go) and add the needed lines. First define a variable which will hold the version information:

var (
  // VERSION is set during build
  VERSION string
)

After the variable is initialized update the Execute function to allow the code in main.go to pass the current version to our application.

// Execute adds all child commands to the root command
func Execute(version string) {
  VERSION = version

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

Now you are able to run the project again and call the version command. Cobra responds with the application name and version which is configured in main.go:

$ > go run main.go version
heft 0.0.1

Nice! We have created a nifty little command line tool which is able to print its name and current version. Let’s say our application is now kind of feature complete: It’s time to compile a binary for the first time!

$ > go build
$ > ./cli version
heft 0.0.1

Build the application with goxc

When using go build the binary file be named cli per default, this will change with the goxc configuration. To get started create a file called .goxc.json with the following content:

{
  "AppName": "heft",
  "ConfigVersion": "0.9",
  "BuildSettings": {
    "LdFlags": "-s",
    "LdFlagsXVars": {
      "Version": "main.VERSION"
    }
  },
  "Tasks": ["interpolate-source", "default"]
}

The configuration contains the application name heft and defines a list of tasks which should be called by goxc when building the binary.

For a first build it’s totally fine to stick with the default task together with interpolate-source for setting the defined VERSION variable. After the configuration file is saved install goxc and give it try:

$ > go get -v github.com/laher/goxc
$ > $GOPATH/bin/goxc \
  -pv=0.0.1 \
  -build-ldflags "-X main.VERSION=0.0.1"

You will notice the fans of your machine speed up because goxc makes use of all the power your machine has to offer:

[goxc:xc] Parallelizing xc for 16 platforms, using max 7 of 8 processors

So let’s stop this again and tell goxc to just compile one binary for now and configure a path where it should save the binary afterwards.

$ > $GOPATH/bin/goxc \
  -bc="darwin,amd64" \
  -pv=0.0.1 \
  -d=build \
  -build-ldflags "-X main.VERSION=0.0.1"

The goxc command has compiled the application for darwin,amd64 only and stored all files inside the build/ directory.

$ >  tree build
build
└── 0.0.1
    ├── LICENSE
    ├── downloads.md
    └── heft_0.0.1_darwin_amd64.zip

As we want to use the application after compiling it we can configure goxc to not archive the binary and to skip a couple of other tasks in order to speed up the local build process:

{
  "AppName": "heft",
  "ConfigVersion": "0.9",
  "BuildSettings": {
    "LdFlags": "-s",
    "LdFlagsXVars": {
      "Version": "main.VERSION"
    }
  },
  "Tasks": ["interpolate-source", "default"],
  "TasksExclude": [
    "archive",
    "codesign",
    "copy-resources",
    "deb",
    "deb-dev",
    "downloads-page",
    "go-vet",
    "go-test",
    "go-install",
    "rmbin"
  ]
}

Together with the updated configuration goxc will now only create a binary file and skip all other unrelated tasks:

$ > $GOPATH/bin/goxc \
  -bc="darwin,amd64" \
  -pv=0.0.1 \
  -d=build \
  -build-ldflags "-X main.VERSION=0.0.1"
$ > tree build
build
└── 0.0.1
    └── darwin_amd64
        └── heft

Now there is a heft binary application which works fine on current Mac OS X machines and contains the Cobra application.

$ > ./build/0.0.1/darwin_amd64/heft version
heft v0.0.1

Usa a Makefile for easy handling

Of course you will forget about all the parameters you can pass to goxc and the handling of new versions is kind of messy. Thankfully we have make available! Move all commands into a neat Makefile with the following content:

VERSION=0.0.1

clean:
  @rm -rf ./build

build: clean
  @$(GOPATH)/bin/goxc \
    -bc="darwin,amd64" \
    -pv=$(VERSION) \
    -d=build \
    -build-ldflags "-X main.VERSION=$(VERSION)"

version:
  @echo $(VERSION)

Run make build to compile the binary file and use make version to display the current version. If you set the VERSION variable in your Makefile to 0.0.2 and run make build again you will get a new binary which responds with the increased version:

$ > make build
$ > ./build/0.0.2/darwin_amd64/heft version
heft v0.0.2

The next needed make command is install for copying the binary to it’s final destination. The final Makefile now looks like this:

VERSION=0.0.2
PATH_BUILD=build/
FILE_COMMAND=heft
FILE_ARCH=darwin_amd64

clean:
  @rm -rf ./build

build: clean
  @$(GOPATH)/bin/goxc \
    -bc="darwin,amd64" \
    -pv=$(VERSION) \
    -d=$(PATH_BUILD) \
    -build-ldflags "-X main.VERSION=$(VERSION)"

version:
  @echo $(VERSION)

install:
  install -d -m 755 '$(HOME)/bin/'
  install $(PATH_BUILD)$(VERSION)/$(FILE_ARCH)/$(FILE_COMMAND) '$(HOME)/bin/$(FILE_COMMAND)'

That’s all! Cobra offers a great toolkit to get started with command line applications really fast and together with goxc it’s a solid setup for your next CLI application!

$ > make build
$ > make install
$ > ~/bin/heft version
heft v0.0.2

You can find the sources for the heft application on GitHub. In an upcoming tutorial I will explain how to upload the binary to Amazon S3 and how to configure Homebrew for easy distribution.