Introduction to Makefiles

I have a love-hate relationship with Makefiles. They are useful but have a lot of gotchas. In this lesson, we will explore the good, the bad, and the ugly of Makefiles. We will also discuss Makefiles for Go programs specifically.


In previous lessons we have embedded information into multi-git using some esoteric go build flags, we have built a simple CI/CD pipeline for multi-git using GitHub Actions, and we have Dockerized multi-git using several Docker commands. Those are all great, but something is still missing: a decent way to tie them all together and use them easily. For example, the GitHubActions pipeline is pretty automatic, but you can’t run it locally. If you want to test something you have to actually commit and push. Embedding information in a Go executable takes a lot of typing as well as running multi-git from Docker. We could write some shell scripts to help us, but there is an old and established way to address these concerns: Makefiles. Let’s see what Makefiles are all about and how to incorporate them into multi-git.

What is Make and what are Makefiles?

The make command is a build automation tool that dates back to Bell Labs and early Unix days, in 1976. The make command operates on text files called Makefiles and can understand dependencies in order to correctly build your code. In a nutshell, it allows you to have a local CI/CD pipeline. Here is a sample Makefile to build a helloworld program in C:

all: helloworld

helloworld: helloworld.o
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^

clean: FRC
	rm -f helloworld helloworld.o

## This is an explicit suffix rule. It may be omitted on systems
## that handle simple rules like this automatically.
	$(CC) $(CFLAGS) -c $<


When running make without arguments on a Makefile it will build the first target, which is all in this case.

Each target can depend on other targets or files. There are many capabilities and rules. To learn more about make and Makefiles, study the documentation for your system. Here, we will focus on using make to manage multi-git.

Makefiles - The good, the bad and the ugly

Makefiles are a controversial piece of tooling. Let’s break them down into buckets, so you can make up your mind if you would like to use Makefiles or not. Makefiles are definitely much easier to use than to write.

Makefiles - the good

  • They’re easy to use. You just type make
  • They have good dependency management.
  • They get the job done and only builds what is needed
  • They have survived the test of time.

Makefiles - the bad

  • They’re not supported natively on Windows.
  • They’re not standard.
  • They’re ancient. They’re beginning to show their age and become less effective.

Makefiles - the Ugly

  • They have non-intuitive syntax.
  • They are hard to debug.
  • They are white-space sensitive, meaning they must use tabs.
  • Recursive Makefiles are error-prone.

Makefile for Go programs

What kind of targets are relevant for a Go program?

Well, we want to build our program. We want to test it. We also want to commit and push our changes to source control. This is something you may prefer to do using your source control program CLI, probably git or even using your IDE.

In addition, we want to build a Docker image, tag it, and push it to DockerHub.

Finally, it would be nice to have a little help screen for all the available targets.

Other possible targets are for running the programs, installing them locally, and cleaning up artifacts.

Get hands-on with 1200+ tech skills courses.