Add post: A Makefile for Golang CLI tools

This commit is contained in:
Ariejan de Vroom 2015-10-03 11:49:08 +02:00
parent 628065f4c2
commit 31f054881a

View File

@ -0,0 +1,126 @@
+++
date = "2015-10-03"
title = "A makefile for Golang CLI tools"
tags = ["golang"]
description = "Golang is very useful for creating command line interfaces. It does get complicated when you also want to set variables at compile time. I've made a Makefile for that."
slug = "a-makefile-for-golang-clie-tools"
+++
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. (I'm not even sure I'm going to use this a my next Dungeons & Dragons session.)
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](https://golang.org/cmd/link/) 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
## The Makefile
Makefiles have always been scary to me. Lot's of magic and weird syntax and I never had a need or 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.