--- title: "Makefiles for Golang" date: 2020-01-07T00:50:32Z tags: [make, golang] draft: false --- [Make is a build automation tool from the late 70's][make-wiki] 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. ## Building 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: ```makefile hello_world: go build -o hello_world main.go ``` But at this point if you run it again, you'll see: ```sh make: 'hello_world' is up to date. ``` So lets add a clean command to clean up the build output: ```makefile {hl_lines=["4-5"]} hello_world: go build -o hello_world main.go clean: 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: ```makefile {hl_lines=[7]} hello_world: go build -o hello_world main.go clean: rm -rf ./hello_world .PHONY: clean ``` ## Testing 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: ```makefile PKGS := $(shell go list ./... | grep -v vendor) test: go test -v -cover $(PKGS) .PHONY: test ``` ## Linting 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: ```makefile LINT_BIN := $(GOPATH)/bin/golangci-lint $(LINT_BIN): go get -u github.com/golangci/golangci-lint/cmd/golangci-lint lint: $(LINT_BIN) @$(LINT_BIN) run -p format -p unused -p bugs # The '@' symbol hides the command in the output .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][strict-mode] which will catch errors as they happen and make your life debugging shells scripts generally much easier. ```makefile .SHELLFLAGS := -euo pipefail PKGS := $(shell go list ./... | grep -v vendor) LINT_BIN := $(GOPATH)/bin/golangci-lint hello_world: go build -o hello_world main.go lint: $(LINT_BIN) @$(LINT_BIN) run -p format -p unused -p bugs # The '@' symbol hides the command in the output test: go test -v -cover $(PKGS) $(LINT_BIN): go get -u github.com/golangci/golangci-lint/cmd/golangci-lint clean: rm -rf ./hello_world .PHONY: clean lint test ``` [make-wiki]: https://en.wikipedia.org/wiki/Make_(software) [strict-mode]: http://redsymbol.net/articles/unofficial-bash-strict-mode/