Key Takeaways
- Makefiles are used by Make, which automates build processes via makefiles to compile code efficiently.
- Makefiles consist of rules with targets, dependencies, and actions.
- Makefiles require indentation using tabs, not spaces, so pay attention to your whitespace when working with makefiles.
If you’ve installed any software from source code, you’ve probably used Make. But what is this program and how does it work? Learn all about makefiles and how they can revolutionize your build process.
What Is Compilation?
Makefiles and compilation are inextricably linked. Although they’re not just for compilation, the Make program originated because of the frustrations that compilation can cause.
Compilation takes source files and converts them into another form. The classic example is a .c file that compiles into a .o object file. C programs also undergo a linking process that converts .o files into a final executable. In fact, there are four separate stages involved in building a C project:
Compilation was popularised in the early 1970s with the development of the C language, but newer languages like Typescript, Rust, and Sass also use it. The alternative is interpretation, which uses an intermediate program to run code rather than generating a standalone executable. Interpreted languages include JavaScript, Python, and Ruby.
Compiled programs will usually run faster—sometimes much faster—than interpreted ones. Compilation also checks your code for certain types of errors that interpretation can only check at run-time, so it’s easier to ensure that compiled programs are correct. But compilation can be a slow process, especially for projects with hundreds or thousands of source files.
What Do Makefiles Do?
Makefiles are used by Make, a program that helps you automate your build process, including tasks like compilation.
When you run the make command, it looks for a file called Makefile or makefile by default. This file should contain instructions on how to compile—or build—each file in your program.
Makefiles are built around the concept of a “dependency” (or “prerequisite”) which Make checks using file timestamps. This means that Make can speed up compilation dramatically: instead of recompiling each and every source file, Make only rebuilds the bare minimum. Generally speaking, if a source file has changed, Make recompiles it, otherwise it leaves it alone.
What Do Makefiles Look Like?
The first thing you must know about makefiles is that whitespace is significant. This is widely considered to be a bad design decision, but we’re stuck with it, so be careful!
A makefile contains a series of rules that describe how to build each component. The general pattern of a rule looks like this:
target: dependencies
actions
Here is, pretty much, the simplest makefile, which compiles a single source file into an executable:
program: program.c
gcc -o program program.c
The second line of this makefile is indented, and the indentation must be a single tab character. Whether you’re copy-pasting or typing out a makefile manually, be very careful to keep a single tab character at the beginning of indented lines. If, for example, you use four spaces instead of a tab, you’ll see an error like “Makefile:2: *** missing separator. Stop.”
In this example, the target is the name of the final executable: program. A target doesn’t have to match a filename, but it usually does.
There’s just one dependency of this target—program.c—that Make will use to decide what to do. If the “program” file is newer than “program.c,” Make will not do anything.
Writing Your First Makefile
There’s nothing like hands-on experience and, fortunately, Make is pretty easy to use in its simplest form.
Install Make
On a Linux distro that uses apt, like Ubuntu, Mint, or Debian, you can install Make like this:
sudo apt install make
On macOS, you can install Make via Xcode by running this command:
xcode-select --install
Alternatively, if you have homebrew installed, run:
brew install make
If you see an error like “make: command not found” when you try to run it, the make program is not installed in your PATH. You can fix this problem by checking that it’s installed correctly.
Write a Simple Test Program
You can see Make in action for yourself by testing the above makefile with a simple c program. You’ll just need gcc and make installed to try this out.
Create a file named “program.c” and populate it with the following:
#include int main()
{
printf("Hello, world.\n");
}
At this point, test your program is correct (and that you have gcc installed) by running the following:
gcc -o program program.c
Confirm that gcc successfully builds the program, then remove it:
rm program
Write the Basic Makefile
Now create the example makefile (as either “Makefile” or “makefile”) with the following contents:
program: program.c
gcc -o program program.c
Run Make
In your terminal, ensure your working directory contains the Makefile and program.c files, then run Make:
make
Once you’ve confirmed that Make compiled and built your program, test its behavior by running it again:
make
Make tells you that everything is up-to-date, so it doesn’t actually do anything now. This is one of Make’s biggest benefits: it will only spend time building components that need to be rebuilt. Try updating your source file and you’ll see the difference:
touch program.c
make
The touch command here just updates the file’s modified time to the current time. This is just a quick way of “tricking” Make into thinking the file has changed.
What Else Can Make Do?
Make is only really limited by its file-based nature. Any file can be a part of your build process and act as a target or dependency.
This means that Make is appropriate for a wide range of projects. Anything with predictable, repeatable build steps is suitable.
For example, take a static site generator that generates web pages from other sources, often including Markdown text files. This makefile will rebuild a specific web page:
index.html: index.md
pandoc index.md index.html
We’ve only touched the surface of what Make can do here. In reality, makefiles contain patterns to define rules for many different files, creating complex dependency chains.
Next time you build software, see if there’s a makefile and try exploring its contents. Hopefully, you can understand some of the basics and recognize some of what’s going on under the hood. With the GNU make manual in hand, you can start using Make for your own projects to avoid unnecessary downtime.