What’s That Tiny Makefile?

When I’m working with programming students, I usually write a quick Makefile in the same directory as our code. That file (named “Makefile“, no extension) is only a few lines long, and it’s what lets us recompile our code by typing

$ make

during our tutoring session, instead of having to write out a long compilation command like:

$ cc -Wall -Wextra -Wpedantic -Werror -std=c99 -o my_prog my_prog.c helper_module.c

Even though compiling our code isn’t so annoying if we’re working in Java, typing “make” is still faster and easier than typing:

$ javac *.java

In this post, we’ll see how can we very quickly create a rudimentary Makefile that lets us recompile our code by typing “make“. The Makefile won’t be elegant, but we can write it in seconds, and it’ll do the job we need it to while we’re drafting our code.

Makefiles Execute Commands

Makefiles are a powerful tool for executing complex shell commands. But, the purpose of this blog post isn’t to teach you everything about them. There are already myriad resources out there: tutorials, and even a formal document describing the base language shared by all the different variations of make(1). Instead, the purpose of this blog post is to demonstrate what I’m slapping together in a few seconds, to make running the command for recompiling our code less of a pain.

At its core, the make(1) utility is designed to execute commands conditionally. A Makefile might contain the instructions: when we type “make“, please check if any of my_prog.c, helper_module.c, or helper_module.h have changed — if any of them have, then recompile our code.

But when I’m quickly putting together a Makefile during our tutoring sessions, I don’t care about the elegance of conditional execution. The condition is: I typed “make“. When I type “make“, I just want the computer to execute the command that recompiles our code, even if nothing in the source files has changed.

Executing Commands Unconditionally

I’ll keep the terminology and explanations in this blog post to a minimum, but there’s one useful term to know. In a Makefile, we group command-line instructions to execute, along with the conditions to execute them, into recipes. A recipe is formatted like this:

recipe_name: recipe_conditions
	recipe_instructions

(Of note, that’s a tab character before the recipe instructions, not spaces.)

If we want Makefile instructions to execute unconditionally, we can leave the conditions blank:

recipe_name:
	recipe_instructions

Here’s a complete, two-line Makefile with one recipe. We’ve arbitrarily (though by convention) called it “all“, and it unconditionally executes our compilation command whenever we type “make“:

all:
	cc -Wall -Wextra -Wpedantic -Werror -std=c99 -o my_prog my_prog.c helper_module.c

The make(1) utility will echo commands back to us as it executes them. So, running “make” on my command line looks like this:

$ make
cc -Wall -Wextra -Wpedantic -Werror -std=c99 -o my_prog my_prog.c helper_module.c

If we’re working in Java instead, we could similarly have written:

all:
	javac *.java

And then executing “make” would look like this:

$ make
javac *.java

In both of these cases (though particularly in the C example), being able to run “make” is faster and less error-prone than having to type that long compilation command. For this reason, I encourage students to use Makefiles (even short, inelegant ones like these) when working on assignments. Along with a local Git setup on your own computer, these are two simple tools that — used even in their most basic forms — can save students a great deal of time and headaches.

Exercise

Makefiles can have multiple recipes. For example, you can type

$ make

to execute the first recipe in a Makefile (such as our “all” recipe above), or

$ make clean

to explicitly run a recipe called “clean“.

In the C example from before, the “all” recipe created a single output file: my_prog. Complete the following Makefile, so that running “make clean” unconditionally deletes the my_prog executable (not the my_prog.c source file!):

all:
	cc -Wall -Wextra -Wpedantic -Werror -std=c99 -o my_prog my_prog.c helper_module.c

clean:
	[What goes here to delete my_prog?]

Completing this Makefile together, or writing more complex conditional Makefiles, can be part of any tutoring session. For more discussions, and to arrange for personalized tutoring for yourself or your study group, check out Vancouver Computer Science Tutoring.