Jun 8, 2014

Conditional assignment in GNU make

GNU Make provides a feature to conditionally assign variables using ?= operator. I.e. the operator assigns the variable the given value only if it's not defined yet. This is commonly (although mistakenly, as we'll see) used to accommodate user's preference to pass Make variables through the command line. For that purpose, it's not uncommon to find conditional assignments in top-level Makefiles, beaming author's generosity.

# User is the king
BOSS ?= me

sandwich:
 @echo Made sandwich for $(BOSS)

The intention is quite clear: If the user says they're boss, let them be; otherwise I'm the boss. The author then expects the Makefile to be used as:

$ make BOSS=$USER sandwich

And this prints:

Made sandwich for jeenu

So this works as expected; but for a different reason!

If one were to skim the Make documentation to discover the ?= operator and what it does, they'd be left with the first impression that they have to use this operator in Makefiles to let the user have a say. Actually that's not the case. The subtlety is hidden further deep in the documentation, with respect to variable overriding.

What happens is that when a user assigns value of a Make variable from the command line (through arguments of the form of X=Y), that assignment overrides all assignments from within the Makefile. It's worth noting that assignment at the command line isn't the only override that user can specify; users can also specify the command line option -e so that environment variables of the same name overrides those in Makefile. When either method of override is employed, no matter what kind of operator is used, normal assignments to the variable from within Makefile has no effect, and are ignored! In fact, the documentation is quite clear on the behaviour of ?= operator, which is the same as:

ifeq ($(origin FOO), undefined)
  FOO = bar
endif

This means that the assignment is seen by Make only if it's undefined. When the variable is assigned to from the command line, or is inherited from the environment, it's no more undefined. In effect, the Makefile looks as if all assignments to the that variable are removed! To revisit the first example above, it worked not because ?= operator was used, but because the override that happens regardless. In fact, even normal assignment using := operator would have sufficed for assigning a default value.

So what then is the use for ?= operator? Well, it still serves the purpose of conditional assignment, for example, in sub-Makefiles included from the top-level one for cooperative usage. But the author still should bear in mind that any assignment the user makes through the command line or environment would make all those in the Makefile futile.

It's a bit dull to have user assignments override authors intention. However, it's not the end of the world. GNU Make provides the override directive, so that the author can have the final laugh. Assignments done with the override directive ignores anything that comes from outside the Makefile. I.e. all assignments in the Makefile are honoured. In other words, with this directive, the author gets the final say as to what the variable's value is. One ought to use the override directive at the first instance of initialization.

override FOO = bar

One another nifty usage is to honor user assignment and append it to an existing variable than to overwrite it. Often it's useful for the user to specify additional C flags for compilation (optimization level, for example). Using override directive, the user's preference can be appended to the default C flags as below. Without the override directive, the user assignment completely replaces the C flags, thus breaking the build.

override CFLAGS := -g $(CFLAGS)

To summarize, user is free to override any variable in the Makefile through command line argument or environment inheritance. If the author absolutely can't let user override a variable, they should use the override directive with variable initialization. Conditional assignment alone isn't always the best solution for accommodating user preferences, and such usage should account for user overrides.

No comments:

Post a Comment