Make has been used extensively for forty years and offers incremental builds, parallelization, and a declarative syntax. In this post we’ll take a look at how the
.DELETE_ON_ERROR special target helps eliminate possible downstream problems in your makefiles. You’ll also come to understand why most makefiles should include it.
Makefile Lingo refresher 🔗
A makefile consists of one or more rules. Here’s an example with one rule:
languages.csv: projects.json python extract_languages.py projects.json > languages.csv
The first line informs us that languages.csv, the target, depends on projects.json, its only prerequisite. The next line—the rule’s recipe—is a script that creates the target from its prerequisites. Recipes often consist of multiple lines.
Special targets 🔗
Certain names that begin with a period have special meaning if they appear as targets. For example, consider a rule having
.PHONY as its target name. The rule prerequisites will be considered phony targets and will always have their recipes run, regardless of their last modified times.
.PHONY is one example of a special target that configures how make treats its prerequisites. Others act as global configuration. For example, the
.ONE_SHELL special targets act globally.
If we added
.DELETE_ON_ERROR to our makefile example it would look as follows:
.DELETE_ON_ERROR: languages.csv: projects.json python extract_languages.py projects.json > languages.csv
A trailing colon signals that
.DELETE_ON_ERROR is the target of a rule, even though it has no prerequisites and no recipe.
How Make runs recipes 🔗
Make processes a rule when it compares the last modification time of the target to that of its prerequisites. If a prerequisite is newer than the target (or doesn’t exist), then make runs the recipe.
Make executes each recipe line in a new shell, one by one, and quits if an invocation exits with a non-zero status. If the target file was altered prior to exiting, then its last modified time will indicate that it’s up to date—even though it’s likely corrupt or incomplete. Thus, next time make runs it won’t update the target file.
This behavior is often confusing and may lead to malformed build artifacts. But if the makefile includes the
.DELETE_ON_ERROR special target, then make deletes the target file if a recipe has an error. This behaviour solves several problems.
Problem 1: rerunning Make with invalid targets 🔗
Imagine that you introduce a syntax error while updating projects.json. You run our example makefile and extract_languages.py has a non-zero exit status—but not before the shell’s output redirection truncates language.csv and updates its last modified time. Thus languages.csv will be empty, although make considers it up to date.
Fixing the syntax error in projects.json increments its last modified time and informs make that it needs to rebuild languages.csv.
However, imagine this rule is part of a complex makefile which was run in parallel. It’s easy to imagine a developer—especially one not familiar with the project—rerunning make without fixing the error. If an empty languages.csv didn’t cause problems downstream, make would successfully complete, possibly without the developer realizing the build targets are invalid.
.DELETE_ON_ERROR prevents this from occurring.
Problem 2: defects in missing dependencies 🔗
Now imagine there is a bug in extract_languages.py. Since that isn’t a prerequisite, make won’t recognize that languages.csv is out of date even if you fix the bug. Therefore, once you do you’ll need to manually delete languages.csv to have make build it again.
The ideal solution is to include your scripts as prerequisites. Doing so protects you in this situation while also ensuring that make rebuilds your targets if your scripts are updated. However, maintaining script dependencies can be challenging (extract_languages.py may depend on many other python files).
.DELETE_ON_ERROR is a practical alternative. It avoids the need to manually clean invalid targets while not forcing you to maintain your scripts as prerequisites.
Problem 3: flaky recipes 🔗
Finally, imagine that extract_languages.py makes an HTTP request to a web server. If the server is unavailable, languages.csv will be invalid. Once it’s back online, you would need to manually delete languages.csv before make would run the recipe again.
.DELETE_ON_ERROR ensures you don’t need to manually clean targets constructed from flaky recipes.
.DELETE_ON_ERROR have downsides? 🔗
The GNU Make Manual says this about deleting target files after a recipe error:
This is almost always what you want make to do, but it is not historical practice; so for compatibility, you must explicitly request it.
After considering the problems
.DELETE_ON_ERROR solves, it’s reasonable to ask if there are situations when you wouldn’t want to include it in your makefiles.
The main reason to not use
.DELETE_ON_ERROR is that it’s a GNU make extension. GNU make is quite pervasive, but you may not want to rely on it if you build on multiple platforms. (See this excellent tutorial about writing portable makefiles.)
You also may not want to enable
.DELETE_ON_ERROR if you want to keep the target files even after an error occurs (e.g., imagine a target takes weeks to compute). Here it may be appropriate to use
.DELETE_ON_ERROR, but to list these “precious” targets as prerequisites of the .PRECIOUS special target. This informs make not to delete them even if there is an error in the recipe.
Continue learning 🔗
Being easy to read and well written, Innolitics recommends the GNU Make Manual if you want to learn more about make. Its pages that discuss special targets and errors in recipes are especially relevant to this article.
John Graham-Cumming wrote a book about GNU Make; much of the book originated from his blog posts on GNU Make, which are still available and are useful.
How could we improve our sample makefile? Hint: check out the automatic variables page.
At Innolitics, our medical imaging software development team uses make to build regulatory documents, to transform the DICOM standard into a condensed JSON format (for our standard browser), and other tasks.