+++ 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-cli-tools" +++ _Note: I've received feedback on this post and written an update, which you can read [here](https://ariejan.net/2015/10/12/building-golang-cli-tools-update/)_ 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. ``` go 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. ``` go var ( Version string BuildTime string ) ``` Compilation would look like this: ``` shell 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. ``` make # 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. ``` make .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`. ``` make 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: ``` make 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. ``` make 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](https://github.com/ariejan/roll).