Makefiles for Golang

make, golang

Make is a build automation tool from the late 70’s that’s pretty popular in C and C++ world. Thanks to its age and popularity you can find tons of tutorials and Make is supported on basically every platform out there. I’m going to demonstrate how to set up a basic Makefile for Golang projects that will build, lint and test your code.

Make has a few simple rules that make it powerful, it expects that each task you create will be the name of an output file on disk. This is nice because if a file already exists with the same name as a task then Make will skip doing the work for that task.


For example if you create the following Makefile below and place it in the root of your project and run make, you will see a new hello_world binary built:

2  go build -o hello_world main.go

But at this point if you run it again, you’ll see:

1make: 'hello_world' is up to date.

So lets add a clean command to clean up the build output:

2  go build -o hello_world main.go
5  rm -rf ./hello_world

One issue here is that the clean task will only work as long as there isn’t a file in the project also named clean. If you want Make to ignore the file system for this task then you can add an entry to the .PHONY list:

2  go build -o hello_world main.go
5  rm -rf ./hello_world
7.PHONY: clean


Next we can run tests. You can define variables in your makefile that run shell commands for their value. I’m running go list and filtering out the vendor folder so we can run tests for every package in our project. Remember to add that test task to the .PHONY list:

1PKGS := $(shell go list ./... | grep -v vendor)
4  go test -v -cover $(PKGS)
6.PHONY: test


Now that we can build and test our code, lets try to lint it. My lint tool of choice is [golangci-lint][golangcilint] so I like to add an install task that runs go get to install it. To do this I take advantage of a Make feature called prerequisite tasks, where you can list tasks that are required to execute before another task runs. This makes it easy to set up the install task as a dependency of our lint command, ensuring its installed every time we run it:

1LINT_BIN := $(GOPATH)/bin/golangci-lint
4  go get -u
6lint: $(LINT_BIN)
7  @$(LINT_BIN) run -p format -p unused -p bugs # The '@' symbol hides the command in the output
9.PHONY: lint

Putting it together

Below is the complete makefile. I added the .SHELLFLAGS variable, which sets various flags in the shell that Make executes your commands in. The -euo pipefail runs your commands in a type of strict mode in Bash which will catch errors as they happen and make your life debugging shells scripts generally much easier.

 1.SHELLFLAGS := -euo pipefail
 2PKGS := $(shell go list ./... | grep -v vendor)
 3LINT_BIN := $(GOPATH)/bin/golangci-lint
 6  go build -o hello_world main.go
 8lint: $(LINT_BIN)
 9  @$(LINT_BIN) run -p format -p unused -p bugs # The '@' symbol hides the command in the output
12  go test -v -cover $(PKGS)
15  go get -u
18  rm -rf ./hello_world
20.PHONY: clean lint test

Articles from blogs I follow

My plans at FOSDEM: SourceHut, Hare, and Helios

FOSDEM is right around the corner, and finally in person after long years of dealing with COVID. I’ll be there again this year, and I’m looking forward to it! I have four slots on the schedule (wow! Thanks for arranging these, FOSDEM team) and I’ll be talkin…

via Drew DeVault's blog January 24, 2023

YSK: Google allows spoofing news headlines in search results

A minor scandal unfolding in the Swedish election highlights a way to influence news narratives: Google allows you to set headlines for news articles in search results by paying for adwords placements of legitimate articles. This is being used by political …

via Jacob Davis-Hansson September 9, 2022

Going multipath without Multipath TCP

Going multipath without Multipath TCP Gigabit ethernet has been around for a long time, it’s so ubiquitous that there is a very strong chance that if you have a RJ-45 port on your compu

via benjojo blog February 24, 2022

Generated by openring