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:

├── .editorconfig
├── Dockerfile
├── Makefile
├── lib
│   ├── foo.go
│   ├── foo_test.go
│   ├── something.go
│   └── something_test.go
└── 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).

# I usually keep a `VERSION` file in the root so that anyone
# can clearly check what's the VERSION of `master` or any
# branch at any time by checking the `VERSION` in that git
# revision
VERSION         :=      $(shell cat ./VERSION)
IMAGE_NAME      :=      cirocosta/l7

# As a call to `make` without any arguments leads to the execution
# of the first target found I really prefer to make sure that this
# first one is a non-destructive one that does the most simple 
# desired installation. It's very common to people set it as `all`
# but it could be anything like `a`.
all: install

# Install just performs a normal `go install` which builds the source
# files from the package at `./` (I like to keep a `main.go` in the root
# that imports other subpackages). As I always commit `vendor` to `git`
# a `go install` will typically always work - except if there's an OS
# limitation in the build flags (e.g, a linux-only project).
	go install -v

# keeping `./main.go` with just a `cli` and `./lib/*.go` with actual 
# logic, `tests` usually reside under `./lib` (or some other subdirectories).
# Here we could do something like `find . -name "*" -type d -exec ...` but IMO
# that's unnecessary. Just `cd`ing to what matters to you is fine - no need to
# handle the case of directories that you don't want to execute a command.
	cd ./lib && go test -v

# Just like `test`, formatting what matters. As `main.go` is in the root,
# `go fmt` the root package. Then just `cd` to what matters to you (`vendor`
# doesn't matter).
	go fmt
	cd ./lib && go fmt

# This target is only useful if you plan to also create a Docker image at
# the end. I have a separate `gist` with a sample Dockerfile tailored for
# golang that you can check out at <TODO>.
# I really like publishing a Docker image together with the GitHub release
# because Docker makes it very simple to someone run your binary without
# having to worry about the retrieval of the binary and execution of it
# - docker already provides the necessary boundaries.
	docker build -t cirocosta/l7 .

# This is pretty much an optional thing that I tend to always include.
# Goreleaser is a tool that allows anyone to integrate a binary releasing
# process to their pipelines. Here in this target With just a simple 
# `make release` you can have a `tag` created in GitHub with multiple
# builds if you wish. 
# See more at `gorelease` github repo.
	git tag -a $(VERSION) -m "Release" || true
	git push origin $(VERSION)
	goreleaser --rm-dist

.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!