The make utility has been around since the early days of Unix. This tool is designed to create large projects by compiling and linking files based on dependencies. It takes care of a lot problems managing multi-module files to streamline the build process.
Like many of the early Unix tools, make is astonishingly cryptic. I’ve used it, but I don’t truly understand the details enough to consider myself even mildly proficient. Like many other coders, I typically copy and paste my makefiles.
Before getting into the details, and if you aren’t turned off already, using make is a dream. Especially for large project with multiple source code files (modules), the make utility builds things for you, updates, compiles and links, all automatically.
For example, I coded a game that had six or so source code files plus a custom header file. As I worked on the project, I would update various files to fix bugs or add features. Normally, to build such a file, I would use a command like:
clang -Wall main.c initialize.c windows.c robots.c human.c highscore.c helpmenu.c -lncurses -o robots
The command builds my robots program, compiling all the source code file and linking in the Ncurses library. But when I was developing the game, I never typed the above command. Instead, I just typed:
make
That’s it! The make utility looks for a text file in the current directory named makefile
. This file contains the instructions for how to build the program, citing which files depend on other files, the commands required to compile and link, and other items necessary to maintain the entire package.
In fact, it’s the make utility that builds programs you fetch and install from a Linux distro’s package manager. All that text that scrolls up the screen during installation is output from commands issued in a makefile. I hope you appreciate how vital this utility can be.
Then there’s the issue of creating the makefile
.
The make code runs by detected dependencies. For example, my memory-file project contains these files:
main.c
memfile.c
memfile.h
To build the program, I use the clang command shown earlier. If I modify memfile.c
, it must be re-compiled into an object file, memfile.o
, then re-linked with main.o
to build the program memfile
.
Likewise, if I modify memfile.h
, the files that depend on this header file must be re-compiled and linked.
A sample makefile
that coordinates these activities is shown in Figure 1.
From Figure 1, the label memfile:
lists dependencies. The memfile
program file requires that object files main.o
and memfile.o
be up-to-date. If not, the command on the following line is executed; the files are linked and the memfile
program is built.
For the main.o
file, files main.c
and memfile.h
must be up-to-date. if not, the command on the following line is executed, which creates the main.o
object file. Ditto for the memfile.o
file, which builds itself in the same manner.
The result of typing make on the command prompt is that all the necessary files are built and linked to create the program file. If you modify the memfile.h
header file and run make, the program is rebuilt.
The make utility has many more options and tricks you can use. I have makefiles I’ve used that I’ve copied from other sources and I have no clue how they work. The commands are delightfully cryptic. Still, make is a useful utility.
In an IDE, of course, the process of building a large file with multiple source code modules works differently. The steps required depend on the IDE, but things work the same: The program is built based on dependencies. I may explore this concept further in a future Lesson.
I believe Visual Studio maintains and runs a makefile for each project or solution but I may be wrong.
How make is implemented in various IDEs is interesting. I’ve tried to see how it’s done in Visual Studio Code, but that program is so deliberately complicated.