free counter
Tech

Your Makefiles Are Wrong

Your Makefiles are filled with tabs and errors.An opinionated method of writing (GNU) Makefiles that I learned from Ben may be in a position to salvage them.

An opinionated method of (GNU) Make

That is my used account of the method of Make that I learned from Ben.If something is wrong, assume it had been lost in translation.

The big things I am hoping you eliminate are:

  • The file system is really a fundamental section of Make, dont fight it
  • Use sentinel files for targets that not yield exactly one file
  • Dont use tabs, set -ea -o pipefail, and some other sensible defaults
  • Utilize the above as guidelines, not dogma

Because itll make the examples throughout simpler to read, lets focus on setting sensible defaults.

Sensible defaults

Dont use tabs

Make leans heavily on the shell, and in the shell spaces matter.Hence, the default behavior in Label of using tabs forces one to mix tabs and spaces, and leading to readability issues.

Instead, ask make to utilize > because the block character, with the addition of this near the top of your makefile:

ifeq ($(origin .RECIPEPREFIX), undefined)  $(error This Make will not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)endif.RECIPEPREFIX = >

You truly just need the .RECIPEPREFIX = > part, however the version check is good since you can find plenty of old make binaries going swimming.

With this particular change, a makefile that previously appeared as if this:

hello:    echo "Hello"    echo "world".PHONY: hello

Would appear to be:

hello:> echo "Hello"> echo "world".PHONY: hello

And you may never again pull your own hair out because some editor swapped a tab for four spaces and made Put up insane things.

Always utilize (a recently available) bash

Make uses /bin/sh because the default shell.Folks have /bin/sh setup to indicate different shell implementations.Portable shell scripts is really a fantasy (see what I did so there?).

Tell Get this to Makefile is written with Bash because the shell with the addition of this to the very best of the Makefile:

SHELL := bash

With a well-known shell targeted, it is possible to confidently skip workarounds for cross-shell compatibility, and use modern bash features.

The main element message here, needless to say, is to select a specific shell.If youd rather use ZSH, or Python or Node for example, set it compared to that.Select a specific language so that you can stop targeting a lowest common denominator/

Use bash strict mode

This is true if you use bash, but matters particularly here because without this your build may keep executing even though there was failing in another of the targets.

Theres an excellent writeup of what these flags do here.

Add this to your Makefile:

.SHELLFLAGS := -eu -o pipefail -c  

Change some Make defaults

.ONESHELL ensures each Make recipe is ran as you single shell session, instead of one new shell per line.This both – for me – is more intuitive, also it enables you to do things such as loops, variable assignments and so forth in bash.

.DELETE_ON_ERROR does what it says on the box – in case a Make rule fails, its target file is deleted.This ensures next time you run Make, itll properly re-run the failed rule, and guards against broken files.

MAKEFLAGS += --warn-undefined-variables

This does what it says on the tin – in case you are discussing Make variables that dont exist, thats probably wrong and its own good to obtain a warning.

MAKEFLAGS += --no-builtin-rules

This disables the bewildering array of built-in rules to automatically build Yacc grammars from your data if you accidentally add the incorrect file suffix.Remove magic, dont do things unless I tell you firmly to, Make.

TL;DR

Utilize this preamble in your Makefiles:

SHELL := bash.ONESHELL:.SHELLFLAGS := -eu -o pipefail -c.DELETE_ON_ERROR:MAKEFLAGS += --warn-undefined-variablesMAKEFLAGS += --no-builtin-rulesifeq ($(origin .RECIPEPREFIX), undefined)  $(error This Make will not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)endif.RECIPEPREFIX = >

The file system

The trigger because of this post was a Makefile that has been delivered to me because the official this is one way you take action example from the large software company.

Like all of the Makefiles I wrote before Ben showed me better, it used Make regardless of its link with the file system.In this rendition, Make is really a tool for writing tab-indented named shell snippets that be determined by one another.

These uses would look something similar to this:

test:> npm run test        deploy: test> gsutil .. 

That is bad. Make sees each target (ensure that you deploy) because the output file name of the associated block of code.If the output file exists, Make may decide that it doesnt have to run the code, because the file has already been there.

Imagine you stashed some data in a file named test.The aforementioned Makefile notice test is really a file that exists, and that we now have no dependencies for the test rule.Hence, you don’t need to rebuild the test file, and voila youve deployed to prod without running tests.

If you strictly want a target that generates no files, tell Make by saying it really is .PHONY:

test:> npm run test.PHONY: test  # Make won't search for a file named `test` on the file system

Generating output files

Briefly, then, each Make rule follows a convention like

:   > 
		

		
	

Related Articles

Leave a Reply

Your email address will not be published.

Back to top button

Adblock Detected

Please consider supporting us by disabling your ad blocker