Skip to main content

A makefile for Golang CLI tools

·738 words·4 mins· ·
Golang
Ariejan de Vroom
Author
Ariejan de Vroom
Jack of all Trades, Professional Software Craftsman
Table of Contents

Note: I’ve received feedback on this post and written an update, which you can read here

It’s no secret I love the power and simplicity of Go. To further train my skills I wrote a simple app that will roll dice from the command line, because you know, that’s very useful.

There are two goals for me in this project right now: make it trivial to use compile time variables and have a Makefile for easy compilation, installation and clean up. I’m sure I’ll think of other features to try. These will get their own posts.

Compile time variables
#

The classic example for compile time variables is setting a version number and build date for the binaries you compile. You could manually edit code before each compile, because you would never forget to do that.

var (
    Version   = "1.0.0"
    BuildTime = "2015-10-03T11:08:49+0200"
)

Luckily there’s a nice alternative provided by Go: the link docs command allows you to set string variables at compile time with the -X option. Let’s take a look at our code and build command.

var (
    Version   string
    BuildTime string
)

Compilation would look like this:

go build -ldflags "-X github.com/ariejan/roll/core.Version=1.0.0 -X github.com/ariejan/roll/core.BuildTime=2015-10-03T11:08:49+0200" main.go

Note: this is the format used with Go 1.5.1, previous versions do not use the = sign, instead separate the variable and value with a space.

The Makefile
#

Makefiles have always been scary to me. Lot’s of magic and weird syntax and I’ve never had the need nor the desire to dive into them. As it turns out, Makefiles can be very useful. Let’s start by building one for the roll project.

First, let’s start with the build command that passes in Version and BuildTime and refactor it so it becomes more managable and we can easily set both variables to proper values.

# This is how we want to name the binary output
BINARY=roll

# These are the values we want to pass for Version and BuildTime
VERSION=1.0.0
BUILD_TIME=`date +%FT%T%z`

# Setup the -ldflags option for go build here, interpolate the variable values
LDFLAGS=-ldflags "-X github.com/ariejan/roll/core.Version=${VERSION} -X github.com/ariejan/roll/core.BuildTime=${BUILD_TIME}"

all:
    go build ${LDFLAGS} -o ${BINARY} main.go

Now, if you run make all your binary will be compiled with the proper variables passed in. There are a few caveats here, though.

Each Make target (we only named all) will check for an output file named all and decide if it needs compiling or not. This is how Make can speed up large builds - by not compiling things that don’t need compiling. In the case of our project I want to make two changes: properly name our target after the binary we create and make sure we only re-compile if any of the Go files have changed.

First, let’s rename our build target to the name of our binary. Also, set it as the default target and make sure make all will also compile the binary for us.

.DEFAULT_GOAL: $(BINARY)

$(BINARY):
    go build ${LDFLAGS} -o ${BINARY} main.go

Next let’s get a list of all go source files we want Make to watch, for this we’ll rely on find.

SOURCEDIR=.
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')

Remember how each Make target corresponds to a file on disk? Make will check if that target file exists or was changed. This means we can add the list of source go files as dependencies to the build task. If any of the source files were changed, make will re-run the task.

This is the Makefile so far:

SOURCEDIR=.
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')

BINARY=roll

VERSION=1.0.0
BUILD_TIME=`date +%FT%T%z`

LDFLAGS=-ldflags "-X github.com/ariejan/roll/core.Version=${VERSION} -X github.com/ariejan/roll/core.BuildTime=${BUILD_TIME}"

.DEFAULT_GOAL: $(BINARY)

$(BINARY): $(SOURCES)
    go build ${LDFLAGS} -o ${BINARY} main.go

For fun we can add two more tasks: install and clean. Because both of these do not result in a file in our repository (like the build command), we mark these targets with .PHONY, telling Make not to expect a file to appear.

SOURCEDIR=.
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')

BINARY=roll

VERSION=1.0.0
BUILD_TIME=`date +%FT%T%z`

LDFLAGS=-ldflags "-X github.com/ariejan/roll/core.Version=${VERSION} -X github.com/ariejan/roll/core.BuildTime=${BUILD_TIME}"

.DEFAULT_GOAL: $(BINARY)

$(BINARY): $(SOURCES)
    go build ${LDFLAGS} -o ${BINARY} main.go

.PHONY: install
install:
    go install ${LDFLAGS} ./...

.PHONY: clean
clean:
    if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi

It’s a basic Makefile that makes compiling your Golang command line tools a whole lot easier. Enjoy, and stay tuned for more posts on Golang and Makefiles.

You can find the code for Roll at https://github.com/ariejan/roll.

Related

Synchronize goroutines in your tests
·460 words·3 mins
Golang Go Goroutines Testing Synchronization