Minimal Golang Makefile

Having build many Golang projects I came with a minimalist makefile that might work for you.
by Ciro S. Costa , Nov 11 - 2017

Most of the time I see myself creating a Golang project: be it a one-file single-purpose thing or a more complex project.

In order to facilitate that process, I try to always keep the structure the same such that there’s no need to put much thought into the process of bootstrapping a new project.

The file structure usually looks like this:

  1. .
  2. ├── .editorconfig
  3. ├── Dockerfile
  4. ├── Makefile
  5. ├── VERSION
  6. ├── lib
  7. ├── foo.go
  8. ├── foo_test.go
  9. ├── something.go
  10. └── something_test.go
  11. └── main.go

Some things to pay attention:

  1. editorconfig is a real deal - I use vim with set shiftwidth=2; set tabstop=2, that is, 2 as the indentation. For go, tab with 8 spaces as indentation.

  2. be it a cli tool or a library, I always make use of a main.go in the root so that’s it’s obvious how to fetch it - go get -u <repo>.


Given the filestructure described above, below is the Makefile. In this case I extracted the Makefile from a test project I started some time ago (a “load-balancer” that makes use of fasthttp: cirocosta/l7).

  1. # I usually keep a `VERSION` file in the root so that anyone
  2. # can clearly check what's the VERSION of `master` or any
  3. # branch at any time by checking the `VERSION` in that git
  4. # revision
  5. VERSION := $(shell cat ./VERSION)
  6. IMAGE_NAME := cirocosta/l7
  7. # As a call to `make` without any arguments leads to the execution
  8. # of the first target found I really prefer to make sure that this
  9. # first one is a non-destructive one that does the most simple
  10. # desired installation. It's very common to people set it as `all`
  11. # but it could be anything like `a`.
  12. all: install
  13. # Install just performs a normal `go install` which builds the source
  14. # files from the package at `./` (I like to keep a `main.go` in the root
  15. # that imports other subpackages). As I always commit `vendor` to `git`
  16. # a `go install` will typically always work - except if there's an OS
  17. # limitation in the build flags (e.g, a linux-only project).
  18. install:
  19. go install -v
  20. # keeping `./main.go` with just a `cli` and `./lib/*.go` with actual
  21. # logic, `tests` usually reside under `./lib` (or some other subdirectories).
  22. # Here we could do something like `find . -name "*" -type d -exec ...` but IMO
  23. # that's unnecessary. Just `cd`ing to what matters to you is fine - no need to
  24. # handle the case of directories that you don't want to execute a command.
  25. test:
  26. cd ./lib && go test -v
  27. # Just like `test`, formatting what matters. As `main.go` is in the root,
  28. # `go fmt` the root package. Then just `cd` to what matters to you (`vendor`
  29. # doesn't matter).
  30. fmt:
  31. go fmt
  32. cd ./lib && go fmt
  33. # This target is only useful if you plan to also create a Docker image at
  34. # the end. I have a separate `gist` with a sample Dockerfile tailored for
  35. # golang that you can check out at <TODO>.
  36. # I really like publishing a Docker image together with the GitHub release
  37. # because Docker makes it very simple to someone run your binary without
  38. # having to worry about the retrieval of the binary and execution of it
  39. # - docker already provides the necessary boundaries.
  40. image:
  41. docker build -t cirocosta/l7 .
  42. # This is pretty much an optional thing that I tend to always include.
  43. # Goreleaser is a tool that allows anyone to integrate a binary releasing
  44. # process to their pipelines. Here in this target With just a simple
  45. # `make release` you can have a `tag` created in GitHub with multiple
  46. # builds if you wish.
  47. # See more at `gorelease` github repo.
  48. release:
  49. git tag -a $(VERSION) -m "Release" || true
  50. git push origin $(VERSION)
  51. goreleaser --rm-dist
  52. .PHONY: install test fmt release

Save that content in the Makefile file in root directory of the project, create a VERSION file with something like 0.0.1 (semver) and you’re ready to go.

Closing thoughts

I think it’s very useful to keep a standard way of performing basic operations across multiple repositories. In my experience this reduces the friction of moving from one project to another. Having a common flow of how to build, create an image and publish a project using a Makefile has helped me in such area.

Do you think the same? What are your thoughts?

Reach me on Twitter at any time @cirowrc and subscribe to the list if you’re a Golang developer or simply likes software stuff!


ft_authoradmin  ft_create_time2017-11-14 13:55
 ft_update_time2017-11-14 13:57