From d0506a484e4f4fc45bebfc12adf3b3290f136d5e Mon Sep 17 00:00:00 2001 From: Adam Veldhousen Date: Tue, 7 Jan 2020 22:26:14 -0600 Subject: [PATCH] first draft of golang-makefiles --- content/posts/golang-makefiles.md | 120 ++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100755 content/posts/golang-makefiles.md diff --git a/content/posts/golang-makefiles.md b/content/posts/golang-makefiles.md new file mode 100755 index 0000000..ac5aedd --- /dev/null +++ b/content/posts/golang-makefiles.md @@ -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/