From 31f054881a03dc338ff12ef6e416a049669448e8 Mon Sep 17 00:00:00 2001 From: Ariejan de Vroom Date: Sat, 3 Oct 2015 11:49:08 +0200 Subject: [PATCH] Add post: A Makefile for Golang CLI tools --- ...5-10-03-a-makefile-for-golang-cli-tools.md | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 content/posts/2015-10-03-a-makefile-for-golang-cli-tools.md diff --git a/content/posts/2015-10-03-a-makefile-for-golang-cli-tools.md b/content/posts/2015-10-03-a-makefile-for-golang-cli-tools.md new file mode 100644 index 0000000..984baa8 --- /dev/null +++ b/content/posts/2015-10-03-a-makefile-for-golang-cli-tools.md @@ -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.