Quick tip: A time-saving Makefile for your Go projects
Whenever I start a new Go project, one of the first things I do is create a Makefile in the root of my project directory.
This Makefile serves two purposes. The first is to automate common admin tasks (like running tests, checking for vulnerabilities, pushing changes to a remote repository, and deploying to production), and the second is to provide short aliases for Go commands that are long or difficult to remember. I find that it's a simple way to save time and mental overhead, as well as helping me to catch potential problems early and keep my codebases in good shape.
While the exact contents of the Makefile changes from project to project, in this post, I want to share the boilerplate that I'm currently using as a starting point. It's generic enough that you should be able to use it as-is for almost all projects.
The Makefile
is organized into several sections, each with its own set of targets:
1. HELPERS
help
: Prints a help message for the Makefile, including a list of available targets and their descriptions.confirm
: Prompts the user to confirm an action with a "y/N" prompt.no-dirty
: Checks that there there are no untracked files or uncommitted changes to the tracked files in the current git repository.
2. QUALITY CONTROL
audit
: Runs quality control checks on the codebase, including usinggo mod tidy -diff
to check that thego.mod
andgo.sum
files are up-to-date and correctly formatted, verifying the dependencies withgo mod verify
, runningtest -z "$(shell gofmt -l .)"
to check that all.go
files are correctly formatted, running static analysis withgo vet
andstaticcheck
, checking for vulnerabilities usinggovulncheck
, and running all tests. Note that it usesgo run
to execute the latest versions of the remotestaticcheck
andgovulncheck
packages, meaning that you don't need to install these tools first. I've written more about this pattern in a previous post.test
: Runs all tests. Note that we enable the race detector and embed build info in the test binary.test/cover
: Runs all tests and outputs a coverage report in HTML format.
3. DEVELOPMENT
tidy
: Updates the dependencies and formats thego.mod
andgo.sum
usinggo mod tidy
, and formats all.go
files usinggo fmt
.build
: Builds the package atmain_package_path
and outputs a binary at/tmp/bin/{binary_name}
.run
: Calls thebuild
target and then runs the binary. Note that my main reason for not usinggo run
here is thatgo run
doesn't embed build info in the binary.run/live
: Use theair
tool to run the application with live reloading enabled. When changes are made to any files with the specified extensions, the application is rebuilt and the binary is re-run.- Depending on the project I often add more to this section, such as targets for connecting to a development database instance and managing SQL migrations. Here's an example.
4. OPERATIONS
push
: Push changes to the remote Git repository. This asks for y/N confirmation first, and automatically runs theaudit
andno-dirty
targets to make sure that all audit checks are passing and there are no uncommitted changes in the repository before the push is executed.production/deploy
: Builds the a binary for linux/amd64 architecture, compress it usingupx
, and then run any deployment steps. Note that this target asks for y/N confirmation before anything is executed, and also runs theaudit
andno-dirty
checks too.- Depending on the project I often add more to this section too. For example, a
staging/deploy
rule for deploying to a staging server,production/connect
for SSHing into a production server,production/log
for viewing production logs,production/db
for connecting to the production database, andproduction/upgrade
for updating and upgrading software on a production server.
Usage
Each of these targets can be executed by running make
followed by the target name in your terminal. For example:
If you run make help
(or the naked make
command without specifiying a target) then you'll get a description of the available targets.