Multi-platform Makefile for Go
Something that Go does very well is multi-platform support. You can build a binary for just about any system without much hassle. On a single build machine, you can build binaries for Windows, macOS, and many flavors of Linux. All that is required is to change the GOOS
and GOARCH
env variables to the desired OS and architecture. I made a Makefile to take advantage of this.
First I started with a few variables. EXECUTABLE
is the name of the program to build. WINDOWS
, LINUX
, and DARWIN
are the actual names of the binaries to be created, based off of EXECUTABLE
. VERSION
is a version string derived from the latest git tag and commit hash, something like v1.1.1-8-g99740b5
.
EXECUTABLE=executable-name
WINDOWS=$(EXECUTABLE)_windows_amd64.exe
LINUX=$(EXECUTABLE)_linux_amd64
DARWIN=$(EXECUTABLE)_darwin_amd64
VERSION=$(shell git describe --tags --always --long --dirty)
Next up, I created the actual build targets. The first three targets (windows
|linux
|darwin
) are just for convenience so I can easily type make linux
and build for Linux. Setting GOOS
and GOARCH
before calling go build
causes the compiler to build for the specified environment. Setting main.version=$(VERSION)
will set a version
variable in the main
package to the value of VERSION
. This is extremely useful for auto-versioning binaries. The other flags aren’t as important to discuss here, but are for verbosity and optimization.
windows: $(WINDOWS) ## Build for Windows
linux: $(LINUX) ## Build for Linux
darwin: $(DARWIN) ## Build for Darwin (macOS)
$(WINDOWS):
env GOOS=windows GOARCH=amd64 go build -i -v -o $(WINDOWS) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/service/main.go
$(LINUX):
env GOOS=linux GOARCH=amd64 go build -i -v -o $(LINUX) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/service/main.go
$(DARWIN):
env GOOS=darwin GOARCH=amd64 go build -i -v -o $(DARWIN) -ldflags="-s -w -X main.version=$(VERSION)" ./cmd/service/main.go
Next step is to add a target to build for all three operating systems. I like echoing the version so I can easily see what I’ve built.
build: windows linux darwin ## Build binaries
@echo version: $(VERSION)
Next up, add some standard targets. test
to run any unit tests. clean
to remove old binaries. all
is the default for when you just run make
and I want it to run the tests, then build everything.
all: test build ## Build and run tests
test: ## Run unit tests
go test ./...
clean: ## Remove previous build
rm -f $(WINDOWS) $(LINUX) $(DARWIN)
.phony
is used to force the specified targets to always execute, regardless of whether the output already exists.
.PHONY: all test clean
And finally, some grep magic to create a help
target. I found this code around the internet somewhere and I’ve been using it ever since. It nicely formats and outputs the comments in the Makefile.
help: ## Display available commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
I use modified versions of this Makefile for all my various Go projects. Being able to easily build native binaries for almost any platform is a huge strength. The source for this example can be found here:
Gopher artwork by Ashley McNamara