first draft of golang-makefiles

master
Adam Veldhousen 4 years ago
parent b2edf22e11
commit d0506a484e
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -0,0 +1,120 @@
---
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 {linenos=table,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 {linenos=table,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/
Loading…
Cancel
Save