What Is a Makefile, and How Do I Use It?

Introduction
Many developers may recall the first day they started working on a new project. After cloning the main repository, there comes a point when you have to enter a lot of commands with certain flags and in a specific order. In most cases, it’s hard to grasp what is going on without a description of the commands. For example:
These commands are just a small portion of the project deployment process. The commands themselves are extensive and contain several flags, as shown in the example above, making them not only difficult to learn but also difficult to enter manually. Constantly maintaining documentation becomes more challenging as the project grows; it inevitably becomes outdated, and the entry barrier for newcomers increases since no one can remember all of the project's details. Some of these commands must be used on a daily basis, if not multiple times per day.
Over time, it became clear that we desperately needed a tool that could maintain such commands, and provide convenient shortcuts and self-documentation of the project. This is precisely what Makefile and the make
utility have turned into. In this guide, I'll show you how to reduce the deployment to a few short and straightforward commands using these tools:
What is make
and Makefile
Makefile is a file that is stored in the repository alongside the code. It is usually placed at the project’s root. It acts both as documentation and as executable code. The Makefile hides the implementation details and manages the commands, and the make
utility runs them from the Makefile in the current directory.
make
was originally designed to automate the building process of executable programs and libraries from source code. Most *nix distributions have it by default, which has contributed to its extensive use. Later, it turned out that this tool is convenient to use in other development projects because the process is essentially the same in most cases - automation and building applications.
The make
has become a standard for many developers, especially for those working on large projects. Examples of makefile can be found in projects such as Kubernetes, Babel, Ansible, and, of course, everywhere on Hexlet.
Makefile syntax
make
runs targets from a Makefile that contains the following commands:
However, it's not enough to just start using a Makefile in a project. To make its implementation more efficient, build a target-oriented command structure and give the targets semantically relevant names. At first, moving commands to a Makefile may result in all commands being merged into a single one with a vague name:
Several actions take place here at once: creating a file with environment variables, preparing the database, generating keys, installing dependencies, and launching the project. Since this is impossible to understand from the comments and target name, it’s best to separate these commands into different independent targets:
Now that the commands are divided into targets, you can individually install dependencies with the make install
command or run your app via make start
. But the remaining targets are only required during the project's initial deployment and must be performed in a specific sequence. In the Makefile world, the target has the following prerequisites:
Commands will only be executed in the specified order and only if the previous command proves to be successful. Therefore, you can add a setup
target to combine all the necessary actions:
Now it is enough to deploy and launch the project with two commands:
The project commands and flags are combined into a Makefile as a result of the Makefile's work. It ensures the correct execution order, regardless of the languages or technologies involved.
Advanced usage
Fake target
Using make
in a project may one day lead to the error make: <target name> is up to date.
, although everything is written correctly. This is frequently related to the existence of a directory or file that matches the target name. For example:
As stated previously, make
was designed to build programs from source code. Therefore, it searches for a directory or file with the given name and attempts to create a project from it. To alter this behavior, you need to add a .PHONY
pointer to the target at the end of the Makefile:
Running commands consecutively and ignoring errors
You can run commands one at a time: make setup
, make start
, make test
or all at once, space-separated: make setup start test
. The latter method works as a dependency between commands, although it is not documented in the Makefile. Difficulties may arise if one of the commands produces an error that must be ignored. In the previous examples, such a command was to create an .env-file when deploying the project:
The easiest (but not the only) way to "cover up” an error is to use a logical OR in the Makefile:
Be cautious applying such hacks so that you don't shoot yourself in the foot in more complex scenarios.
Variables
Configuration parameters, path indicators, and environment variables are often substituted into commands, and make
enables you to handle this as well. Variables can be written directly in the command within the makefile and passed when called:
Variables can be optional and have a default value. They are commonly declared at the beginning of the Makefile.
Some variables in the Makefile have names other than the system ones. For example, $PWD
is referred to as $CURDIR
in the Makefile:
Conclusion
In this guide, we covered the main features of Makefile and the make
utility. A deeper understanding of this tool will reveal many of its other useful features, such as conditions, cycles, and importing files. Makefile will be a great help in standardizing generic instructions in companies where multiple projects are written by different teams at different times: setup start test deploy ...
.
Because the Makefile can describe multi-line commands consecutively, it may be used as a "universal glue" between language managers and other utilities. The widespread use of this tool and its overall simplicity allows you to quickly implement it into your project without making any changes. However, Makefile can be extremely large and complicated, as shown by the following real-world applications:
Additional materials
- Modern Make Handbook — a summary of the documentation
Makefile examples from this guide were taken from:
pages.blog.posts.show.categories