Ultimate Makefile for Golang
Get latest articles directly in your inbox
Developing and testing extensive codebases can be a time-consuming, error-prone, and repetitive endeavor. While Golang offers support for multi-platform builds, the process often necessitates executing multiple commands to generate binaries for various platforms, resulting in additional time consumption and repetition. Moreover, most projects entail dependencies that must be installed before compiling the binary, and it’s imperative to conduct thorough testing, as well as ensure code quality using linters and code coverage tools.
Enter the utility tool Make, renowned for its capacity to automate tasks. By streamlining development and automating repetitive procedures with a single command, Make significantly enhances efficiency. It proves invaluable for testing, building, cleaning, and installing Go projects. In this tutorial, we will delve into harnessing the power of Make and makefiles to automate these tedious and repetitive tasks associated with Golang development. You will gain insights into how to utilize Make and a Makefile
to seamlessly build, clean, and test a sample Go project.
Leveraging Makefiles can revolutionize this process, ensuring that your project is built efficiently and consistently, while saving you valuable time and effort.
What is a Makefile?
A Makefile is a simple text file that contains instructions for building a software project. It is used by the make
utility, a popular build automation tool available on Unix-based systems, including Linux and macOS. The Makefile specifies the project’s dependencies, build rules, and targets, allowing the make
utility to compile and link your source files into an executable program.
Adding a Makefile To Your Project
To start using make commands, you first need to create a Makefile
in the root directory of your project. Let’s create a simple hello world
project with a Makefile
in it.
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
To run this project, you would normally need to build the project and run the binary:
go build main.go
If you want a different binary name and also want to create a build for a specific OS, you can specify this during the build:
GOARCH=amd64 GOOS=darwin go build -o hello-world main.go
If you want to run, each time you’ll have to execute this command.
go run hello-world
The above commands can be simplified using Makefile. You can specify rules to a specific command and run a simple make command. You would not need to remember the commands and the flags or environment variables needed for executing it.
Basic Makefile
APP_EXECUTABLE=hello-world
build:
GOARCH=amd64 GOOS=darwin go build -o ${APP_EXECUTABLE}-darwin main.go
GOARCH=amd64 GOOS=linux go build -o ${APP_EXECUTABLE}-linux main.go
GOARCH=amd64 GOOS=windows go build -o ${APP_EXECUTABLE}-windows main.go
run: build
./${APP_EXECUTABLE}
clean:
go clean
rm ${APP_EXECUTABLE}-darwin
rm ${APP_EXECUTABLE}-linux
rm ${APP_EXECUTABLE}-windows
Now with these simple commands, you can build and run the Go project:
make run
Finally, you can run the clean command for the cleanup of binaries:
make clean
These commands are very handy and help to streamline the development process. Now all of your team members can use the same command. This reduces inconsistency and helps to eliminate project build-related errors that can arise with inconsistent manual commands.
Ultimate Makefile
Our makefile has very basic commands right now. We can do a lot more while working with Makefile commands.
export GO111MODULE=on
# update app name. this is the name of binary
APP=myapp
APP_EXECUTABLE="./out/$(APP)"
ALL_PACKAGES=$(shell go list ./... | grep -v /vendor)
SHELL := /bin/bash # Use bash syntax
# Optional if you need DB and migration commands
# DB_HOST=$(shell cat config/application.yml | grep -m 1 -i HOST | cut -d ":" -f2)
# DB_NAME=$(shell cat config/application.yml | grep -w -i NAME | cut -d ":" -f2)
# DB_USER=$(shell cat config/application.yml | grep -i USERNAME | cut -d ":" -f2)
# Optional colors to beautify output
GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
CYAN := $(shell tput -Txterm setaf 6)
RESET := $(shell tput -Txterm sgr0)
## Quality
check-quality: ## runs code quality checks
make lint
make fmt
make vet
# Append || true below if blocking local developement
lint: ## go linting. Update and use specific lint tool and options
golangci-lint run --enable-all
vet: ## go vet
go vet ./...
fmt: ## runs go formatter
go fmt ./...
tidy: ## runs tidy to fix go.mod dependencies
go mod tidy
## Test
test: ## runs tests and create generates coverage report
make tidy
make vendor
go test -v -timeout 10m ./... -coverprofile=coverage.out -json > report.json
coverage: ## displays test coverage report in html mode
make test
go tool cover -html=coverage.out
## Build
build: ## build the go application
mkdir -p out/
go build -o $(APP_EXECUTABLE)
@echo "Build passed"
run: ## runs the go binary. use additional options if required.
make build
chmod +x $(APP_EXECUTABLE)
$(APP_EXECUTABLE)
clean: ## cleans binary and other generated files
go clean
rm -rf out/
rm -f coverage*.out
vendor: ## all packages required to support builds and tests in the /vendor directory
go mod vendor
wire: ## for wiring dependencies (update if using some other DI tool)
wire ./...
# [Optional] mock generation via go generate
# generate_mocks:
# go generate -x `go list ./... | grep - v wire`
# [Optional] Database commands
## Database
migrate: build
${APP_EXECUTABLE} migrate --config=config/application.test.yml
rollback: build
${APP_EXECUTABLE} migrate --config=config/application.test.yml
.PHONY: all test build vendor
## All
all: ## runs setup, quality checks and builds
make check-quality
make test
make build
.PHONY: help
## Help
help: ## Show this help.
@echo ''
@echo 'Usage:'
@echo ' ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} { \
if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
}' $(MAKEFILE_LIST)
Benefits of Using Makefiles
- Automation: Makefiles allow you to automate tedious and repetitive tasks, such as compiling code, running tests, and cleaning up build artifacts. This automation not only saves time but also reduces the risk of human error, ensuring that these tasks are performed consistently every time. With a Makefile, you can simply run the ‘make’ command to build your entire project without having to remember or type out lengthy compilation commands.
- Consistency: Makefiles ensure that your project is built consistently, regardless of who is building it or on which system it is being built. It ensures common standards are followed across projects. Makefiles also provide a standardized way to define and execute build processes, which helps maintain consistency across different development and production environments.
- Customization: Makefiles can be customized to include various build configurations, such as debug and release builds, and to support cross-compilation for different platforms.
Books to learn Golang
Do explore articles on Golang and System Design. You’ll learn something new 💡
Liked the article? Consider supporting me ☕️
I hope you learned something new. Feel free to suggest improvements ✔️
Follow me on Twitter for updates and resources. Let’s connect!
Keep exploring 🔎 Keep learning 🚀
Liked the content? Do support :)