Use make for project basic commands

Use make for project basic commands

2020, Nov 01    

When we start a new project or we switch to an old project we always bump to the problem with the different commands to execute to start, build, deploy our application. Sometimes we use docker, sometimes we compile a binary to run and sometimes we have a list of commands to execute only to have our environment ready for development. 

One way to keep a trace of the available commands is to define them inside a Makefile and to use the globally know make command to execute the needed one. 

In this post I will explain some of the commands that I use and why its important to keep all this information available and why to Makefile is one of the solutions.

Introduction

Make

Make is an utility that is mostly known for building binaries, but we can also use it to run commands on projects based on a non-compilation language or even for starting our program inside a container.

Makefile

The Makefile defines the set of commands available to the make utility. You must create the file in the root folder of your project and execute the make commands always in the root folder.

Commands

Help

This command is very usefull to be able to display all the (as I call them) public commands that your Makefile propose. Of course all the commands that you define inside a Makefile are always available but you may want to push forward only the main ones and not the sub-commands.

help: ## Prints this help message
	@grep -E '^[a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

All the commands that are followed by ## will be displayed when you type make help. In this example we have the commented text of the help command that will be displayed.

>make help
help				Prints this help message

Init

This command is used to Init the workspace. We can include the clear of the cache files, copy or specific configuration, download of external packages or bundles, etc. 

init: ## Init all dependencies
	go mod init go_app
	go mod vendor

Build

This command is used for building our application or if we use docker to generate the container image

Example with a Go generated binary : 

build: ## Build application
	go build -o ./cmd/go_app/go_app -v ./cmd/...

Example with a docker image build :

build_docker: ## Build docker container
	docker build --rm -t "${APP_NAME}:${VERSION}" \
	--build-arg VERSION=${VERSION} \
	-f docker/Dockerfile .

Start

The start command is used to start the application. You can always use different commands for different environments. I always use start_local to be sure that the command is specific for my local environment. The start command will create all the necessary dependencies to be able to run the application locally

start_local: ## Start a local instance of the application
	docker stack deploy -c docker/docker-compose.yml go_app

Stop

The stop command will stop the application and destroy all the links that need to be deleted. For example if we are running a container it can also purge the specific networks and volumes created.

stop_local: ## Stop the local instance of the application
	docker stack rm go_app

Test

The name says it all. You can also add all the different kind of tests that you want to have and be able to execute them all togheter.

test: ## Run all the application tests
	go test ./... -cover -v

Example Makefile

Here is an example makefile that I have for a project that will be executed inside a container.

M = $(shell printf "\033[34;1m▶\033[0m")

.PHONY: help
help: ## Prints this help message
	@grep -E '^[a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: init
init: ## Init all dependencies
	$(info $(M) Starting init of the project)
	go mod init go_app
	go mod vendor

.PHONY: build
build: ## Build application
	$(info $(M) Starting build of the application)
	go build -o ./cmd/go_app/go_app -v ./cmd/...

.PHONY: build_docker
build_docker: ## Build docker container
	$(info $(M) Starting build of the container image)
	docker build --rm -t "${APP_NAME}:${VERSION}" \
	--build-arg VERSION=${VERSION} \
	-f docker/Dockerfile .

.PHONY: start_local
start_local: ## Start a local instance of the application
	$(info $(M) Starting local instance)
	docker stack deploy -c docker/docker-compose.yml go_app

.PHONY: stop_local
stop_local: ## Stop the local instance of the application
	$(info $(M) Stopping local instance)
	docker stack rm go_app

.PHONY: test
test: ## Run all the application tests
	$(info $(M) Starting application tests)
	go test ./... -cover -v

.DEFAULT_GOAL := help

And here is the output when you type make help

>make help
help                           Prints this help message
init                           Init all dependencies
build                          Build application
build_docker                   Build docker container
start_local                    Start a local instance of the application
stop_local                     Stop the local instance of the application
test                           Run all the application tests

Conclusion

Make is a powerful utility that can be used not only for creating binaries but also to access easily all the important commands of your project. It can be a huge time saver and let you concentrate on your code rather on its initialization.

The commands that I have listed are only the ones that I use in most of the projects but you can add every single command that comes on your mind.