parent
b2edf22e11
commit
d0506a484e
@ -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…
Reference in new issue