The GNU Make Book (2015)
Chapter 6. The GNU Make Standard Library
The GNU Make Standard Library (GMSL) is a SourceForge-hosted, open source project that I started to capture common functions that makefile authors end up writing over and over again. To prevent makefile writers from reinventing the wheel, the GMSL implements common functions, such as reversing lists, uppercasing a string, or mapping a function across every element of a list.
The GMSL has list and string manipulation functions, a complete integer arithmetic library, and functions for data structures. Also included are GNU make implementations of associative arrays, sets, and stacks, as well as built-in debugging facilities.
In this chapter, you’ll learn how to use the functions of the GMSL in realistic makefiles. In addition, you’ll see a complete reference for the different categories of GMSL functions. For the latest revision of the GMSL, visit http://gmsl.sf.net/.
Importing the GMSL
The GMSL is implemented as a pair of makefiles named gmsl and __gmsl. __gmsl is imported by gmsl, so to include the GMSL in your makefile, just add this:
include gmsl
You can do this in as many files as you want. To prevent multiple definitions and unintended error messages, the GMSL automatically detects if it has already been included.
Of course, GNU make must be able to find gmsl and __gmsl. To do that, GNU make looks for makefiles in a number of places by default, including /usr/local/include, /usr/gnu/include/, /usr/include, the current directory, and any directories specified by the GNU make -I(or --include-dirL) command line option.
A good place to put gmsl and __gmsl is /usr/local/include, where they’ll be available to all your makefiles.
If GNU make can’t find gmsl or __gmsl, you’ll get the regular GNU make error message:
Makefile:1: gmsl: No such file or directory
The GMSL uses a little trick to make the location of gmsl completely flexible. Because gmsl uses include to find __gmsl, the gmsl makefile needs to know where to find __gmsl.
Let’s suppose that gmsl was stored in /foo and included with include /foo/gmsl. To make this work without having to modify gmsl to hardcode the location of __gmsl, gmsl figures out where it’s located using MAKEFILE_LIST and then prepends the appropriate path to the include __gmsl:
# Try to determine where this file is located. If the caller did
# include /foo/gmsl then extract the /foo/ so that __gmsl gets
# included transparently
__gmsl_root := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
# If there are any spaces in the path in __gmsl_root then give up
ifeq (1,$(words $(__gmsl_root)))
__gmsl_root := $(patsubst %gmsl,%,$(__gmsl_root))
else
__gmsl_root :=
endif
include $(__gmsl_root)__gmsl
That’s a handy technique if you want your makefiles to be location independent.
Calling a GMSL Function
The functions in the GMSL are implemented as normal GNU make function declarations. For example, the function last (which returns the last element of a list) is declared like this:
last = $(if $1,$(word $(words $1),$1))
The function is called using GNU make’s built-in $(call). For example, to return the last element of the list 1 2 3, do this:
$(call last,1 2 3)
This will return 3. $(call) expands the variable named in its first argument (in this case, last), setting special local variables ($1, $2, $3, . . .)to the arguments given to $(call) after the function name. So $1 is 1 2 3 in this case.
The GMSL defines the Boolean values true and false, which are just variables and can be accessed using $() or ${}: for example, $(true) or ${false}. false is an empty string, and true is the letter T; these definitions correspond to GNU make’s notion of true (a non-empty string) and false (an empty string). You can use true and false in GNU make’s $(if) function or within a preprocessor ifeq:
$(if $(true),It's true!,Totally false)
ifeq ($(true),$(true))
--snip--
endif
These examples are contrived. You’d expect the $(true) in the $(if) and the first $(true) in the ifeq to be the return values from a function call, not a constant value.
Checking the GMSL Version
The GMSL includes a function that you can use to check that the version included is compatible with your use of the GMSL. The function gmsl_compatible checks that the version number of the included GMSL is greater than or equal to the version number passed as an argument.
At the time of this writing, the current GMSL version is v1.1.7. To check that the included GMSL is at least, say, v1.1.2, call gmsl_compatible with a list argument containing three elements: 1 1 2.
$(call gmsl_compatible,1 1 2)
This will return $(true) because the current GMSL is v1.1.7, which is greater than v1.1.2. If we asked for v2.0.0, we’d get the response $(false):
$(call gmsl_compatible,2 0 0)
A simple way to make sure that you are using the right version of GMSL is to wrap the call to gmsl_compatible in an assertion:
$(call assert,$(call gmsl_compatible,1 0 0),Wrong GMSL version)
This will stop the make process with an error if an incompatible version of GMSL is found.
Example Real-World GMSL Use
Now that you’re set up with the GMSL, let’s look at some examples. All of these solve problems that real-world makefiles have to deal with, like caseinsensitive comparisons and searching a path for a file.
Case-Insensitive Comparison
GMSL contains two functions that let you create a simple function to do a case-insensitive comparison of two strings:
ifcase = $(call seq,$(call lc,$1),$(call lc,$2))
This works by lowercasing its two arguments (using the GMSL lc function) and then calling seq (the GMSL string equality function) to see if they are the same. Here’s one way to use ifcase:
CPPFLAGS += $(if $(call ifcase,$(DEBUG),yes),-DDEBUG,)
Here it’s used to see if the DEBUG variable has been set to yes; if it has, -DDEBUG is added to CPPFLAGS.
Finding a Program on the Path
Here’s a function definition that will search the PATH for an executable:
findpath = $(call first,$(call map,wildcard,$(call addsuffix,/$1,$(call split,:,$(PATH)))))
For example, $(call findpath,cat) will search the PATH for the first cat program. It uses three functions from the GMSL: first, map, and split. It uses two built-in functions: wildcard and addsuffix.
The call to split breaks the PATH variable into a list, separating it at colons. Then the built-in addsuffix function is called, which adds /$1 to each element of the PATH. $1 contains the parameter to findpath, which is the name of the program we’re searching for (in this case, it wascat).
Then the GMSL map function is called to perform a built-in wildcard on each possible program filename. With no wildcard characters in the filename, wildcard will return the name of the file if it exists or an empty string. So map has the effect of finding the location (or locations) of caton the PATH by testing each file in turn.
Finally, a call to the GMSL function first returns the first element of the list that map returns (the list of all cat programs on the PATH).
A debugging feature of GMSL is the ability to trace calls to GMSL functions. By setting GMSL_TRACE to 1, GMSL will output each call to a GMSL function with its parameters. For example:
Makefile:8: split(':', '/home/jgc/bin:/usr/local/bin:/usr/bin:/usr/X11R6/bin:/
bin:/usr/games:/opt/gnome/bin:/opt/kde3/bin:/usr/lib/java/jre/bin')
Makefile:8: map('wildcard',' /home/jgc/bin/make /usr/local/bin/make /usr/bin/
make /usr/X11R6/bin/make /bin/make /usr/games/make /opt/gnome/bin/make /opt/
kde3/bin/make /usr/lib/java/jre/bin/make')
Makefile:8: first(' /usr/bin/make')
Here we’re searching for cat using the findpath function with tracing turned on.
Using Assertions to Check Inputs
Typically, a makefile is executed specifying a goal for the build (or under the assumption that there’s an all target or similar at the start of the makefile). In addition, there are typically environment variables (like debug options, architecture settings, and so on) that affect the build. A quick way to check that these have been set correctly is to use GMSL assertion functions.
Here’s an example that checks that DEBUG has been set to yes or no, that ARCH contains the word Linux, that we’ve specified an output directory in the OUTDIR variable, and that that directory exists:
$(call assert,$(OUTDIR),Must set OUTDIR)
$(call assert_exists,$(OUTDIR),Must set OUTDIR)
$(call assert,$(if $(call seq,$(DEBUG),yes),$(true),$(call seq,$(DEBUG),no)),DEBUG must be yes or no)
$(call assert,$(call findstring,Linux,$(ARCH)),ARCH must be Linux)
The assertion functions will generate a fatal error if their first argument is $(false) (that is, an empty string).
The first assert checks that $(OUTDIR) has been set to something. If it has a non-empty value, the assertion passed; otherwise, an error is generated:
Makefile:3: *** GNU Make Standard Library: Assertion failure: Must set OUTDIR.
Stop.
The second assertion is of the form assert_exists, which checks to see whether its first argument exists in the file system. In this case, it checks to see whether the directory pointed to by $(OUTDIR) exists. It doesn’t check to see whether it’s a directory. We can add another assertion to do that, like this:
$(call assert,$(wildcard $(OUTDIR)/.),OUTDIR must be a directory)
This looks to see if $(OUTDIR) contains a dot (.). If not, $(OUTDIR) is not a directory, and the call to wildcard will return an empty string, causing the assertion to fail.
The third assertion checks that DEBUG is either yes or no using the GMSL seq function to check the value. Finally, we assert using findstring that $(ARCH) must contain the word Linux (with the L capitalized).
Is DEBUG Set to Y?
The GMSL has the logical operators and, or, xor, nand, nor, xnor, and not that work with GNU make’s concept of truth values and the GMSL variables $(true) and $(false).
You can use GNU make’s (and GMSL’s) Boolean values with both GMSL functions and GNU make’s built-in $(if). The GMSL logical operators were designed for use with $(if) and the GNU make preprocessor ifeq directive.
Imagine that a makefile has a debug option, enabled by setting the DEBUG environment variable to Y. Using the GMSL function seq (string equal) and the or operator, you can easily determine whether debugging is desired or not:
include gmsl
debug_needed := $(call or,$(call seq,$(DEBUG),Y),$(call seq,$(DEBUG),y))
Because the GMSL has a lowercase function (lc), you can write this example without the or:
include gmsl
debug_needed := $(call seq,$(call lc,$(DEBUG)),y)
But the logical operator or lets us be even more generous and accept YES as well as Y for the debug option:
include gmsl
debug_needed := $(call or,$(call seq,$(call lc,$(DEBUG)),y),$(call seq,$(call lc,$(DEBUG)),yes))
The function debug_needed is case insensitive too.
Is DEBUG Set to Y or N?
Another possible use of the logical operators is to force the user of the makefile to set DEBUG to either Y or N, thus avoiding problems if they forget about the debug option. The GMSL assertion function assert will output a fatal error if its argument is not true. So we can use it to assert thatDEBUG must be Y or N:
include gmsl
$(call assert,$(call or,$(call seq,$(DEBUG),Y),$(call seq,$(DEBUG),N)),DEBUG must be Y or N)
Here’s an example:
$ make DEBUG=Oui
Makefile:1: *** GNU Make Standard Library: Assertion failure: DEBUG must be Y or N.
Stop.
The assertion generates this error if the user makes the mistake of setting DEBUG to Oui.
Using Logical Operators in the Preprocessor
Because GNU make’s preprocessor (which has ifeq, ifneq, and ifdef directives) doesn’t have any logical operations, it’s difficult to write a complex statement. For example, to define a section of a makefile if DEBUG is set to Y or Yes in GNU make, you must either duplicate a section of code (yuck!) or write a statement that’s hard to understand:
ifeq ($(DEBUG),$(filter $(DEBUG),Y Yes))
--snip--
endif
This works by filtering the list Y Yes with the value of $(DEBUG), which returns an empty list if $(DEBUG) is not Y or Yes, or returns the value of $(DEBUG) if it is. The ifeq then compares the resulting value with $(DEBUG). That’s pretty ugly, hard to maintain, and contains a subtle bug. (What happens if $(DEBUG) is empty? Hint: empty is the same as Y or Yes.) Fixing the bug means doing something like this:
ifeq (x$(DEBUG)x,$(filter x$(DEBUG)x,xYx xYesx))
--snip--
endif
The GMSL or operator makes this much clearer:
include gmsl
ifeq ($(true),$(call or,$(call seq,$(DEBUG),Y),$(call seq,$(DEBUG),Yes)))
--snip--
endif
This is much more maintainable. It works by oring two calls to seq and comparing the result with $(true).
Removing Duplicates from a List
The GMSL function uniq removes duplicates from a list. GNU make has a built-in sort function that sorts a list and removes duplicates; uniq removes duplicates without sorting the list (which can be handy if list order is important).
For example, $(sort c b a a c) will return a b c, whereas $(call uniq,c b a a c) returns c b a.
Say you need to simplify the PATH variable by removing duplicate entries while preserving the order. The PATH is typically a colon-separated list of paths (like /usr/bin:/bin:/usr/local/bin:/bin). Here simple-path is the PATH with duplicates removed and order preserved:
include gmsl
simple-path := $(call merge,:,$(call uniq,$(call split,:,$(PATH))))
This uses three GMSL functions: uniq, split (which splits a string into a list at a certain separator character; in this case, a colon), and merge (which merges a list into a string separating list entries with a character; in this case, a colon).
Automatically Incrementing a Version Number
When it’s release time for a piece of software, it’s handy to have a way to increment the version number automatically. Suppose that a project contains a file called version.c that contains the current version number as a string:
char * ver = "1.0.0";
It would be ideal to just type make major-release, make minor-release, or make dot-release and have one of the three parts of the version number automatically update and the version.c file change.
Here’s how to do that:
VERSION_C := version.c
VERSION := $(shell cat $(VERSION_C))
space :=
space +=
PARTS := $(call split,",$(subst $(space),,$(VERSION)))
VERSION_NUMBER := $(call split,.,$(word 2,$(PARTS)))
MAJOR := $(word 1,$(VERSION_NUMBER))
MINOR := $(word 2,$(VERSION_NUMBER))
DOT := $(word 3,$(VERSION_NUMBER))
major-release minor-release dot-release:
➊ → @$(eval increment_name := $(call uc,$(subst -release,,$@)))
➋ → @$(eval $(increment_name) := $(call inc,$($(increment_name))))
➌ → @echo 'char * ver = "$(MAJOR).$(MINOR).$(DOT)";' > $(VERSION_C)
The VERSION variable contains the contents of the version.c file, which will be something like char * ver = "1.0.0";. The PARTS variable is a list created by first removing all the whitespace from VERSION and then splitting on the double quotes. That splits VERSION into the listchar*ver= 1.0.0 ;.
So PARTS is a list with three elements, and the second element is the current version number, which is extracted into VERSION_NUMBER and turned into a list of three elements: 1 0 0.
Next, variables called MAJOR, MINOR, and DOT are extracted from VERSION_NUMBER. If the version number in version.c was 1.2.3, then MAJOR will be 1, MINOR will be 2, and DOT will be 3.
Finally, three rules are defined for major, minor, and dot releases. These use some $(eval) trickery to use the same rule body to update the major, minor, or dot release number depending on which of major-release, minor-release, or dot-release was specified on the command line.
To understand how it works, follow what happens when you do make minor-release with an existing version number of 1.0.0.
The $(eval increment_name := $(call uc,$(subst -release,,$@))) ➊ first uses $(subst) to remove -release from the target name (so minor-release becomes simply minor).
Then it calls the GMSL uc function (which uppercases a string) to turn minor into MINOR. It stores that in a variable called increment-name. Here’s the tricky part: increment-name will be used as the name of a variable to increment (one of MAJOR, MINOR, or DOT).
At ➋, $(eval $(increment_name) := $(call inc,$($(increment_name)))) actually does that work. It uses the GMSL inc function to increment the value stored in the variable whose name is in increment-name (notice the $($(increment-name)), which finds the value of a variable whose name is in another variable) and then sets that value to the incremented value.
Finally, it just creates a new version.c containing the new version number ➌. For example:
$ make -n major-release
echo 'char * ver = "2.0.0";' > version.c
$ make -n minor-release
echo 'char * ver = "1.1.0";' > version.c
$ make -n dot-release
echo 'char * ver = "1.0.1";' > version.c
This is the result of using the -n option when starting from version 1.0.0 and asking for the different possible releases.
GMSL Reference
This section is a complete reference for the GNU Make Standard Library version 1.1.7 and covers GMSL logical operators; integer functions; list, string, and set manipulation functions; associative arrays; and named stacks. For each category of GMSL functions, you’ll see an introduction to the functions, followed by a quick reference section that lists arguments and returns. For the latest version of the complete reference, check the GMSL website at http://gmsl.sf.net/.
If you’re interested in advanced GNU make programming, it’s worth studying the source code of the GMSL (especially the file __gmsl). The techniques used to create individual GMSL functions are often useful in other situations.
Logical Operators
GMSL has Booleans $(true), a non-empty string actually set to the single character T, and $(false), an empty string. You can use the following operators with those variables or with functions that return those values.
Although these functions are consistent in that they always return $(true) or $(false), they are lenient about accepting any non-empty string that indicates true. For example:
$(call or,$(wildcard /tmp/foo),$(wildcard /tmp/bar))
This tests for the existence of either of two files, /tmp/foo and /tmp/bar, using $(wildcard) and the GMSL or function. Doing $(wildcard /tmp/foo) will return /tmp/foo if the file exists or an empty string if not. So the output of the $(wildcard /tmp/foo) can be fed directly into or, where /tmp/foo will be interpreted as true and an empty string as false.
If you feel more comfortable working exclusively with values like $(true) and $(false), define a make-bool function like this:
make-bool = $(if $(strip $1),$(true),$(false))
This will turn any non-empty string (after stripping off whitespace) into $(true) and leave a blank string (or one that had only whitespace in it) as $(false). make-bool can be handy when whitespace might slip into values returned by functions.
For example, here’s a small GNU make variable that is $(true) if the current month is January:
january-now := $(call make-bool,$(filter Jan,$(shell date)))
This runs the date shell command, extracts the word Jan, and turns it into a truth value using make-bool. Using $(filter) like this treats the result of date as a list and then filters out any word in the list that is not Jan. This technique can be handy in other situations for extracting parts of a string.
You can make a generic function to discover if a list contains a word:
contains-word = $(call make-bool,$(filter $1,$2))
january-now := $(call contains-word,Jan,$(shell date))
Using contains-word, you can redefine january-now.
not
The GMSL includes all the common logical operators. The simplest is the not function, which logically negates its argument:
not
Argument: A single boolean value
Returns: $(true) if the boolean is $(false) and vice versa
For example, $(call not,$(true)) returns $(false).
and
The and function returns $(true) if (and only if) both its arguments are true:
and
Arguments: Two boolean values
Returns: $(true) if both of the arguments are $(true)
For example, $(call and,$(true),$(false)) returns $(false).
or
The or function returns $(true) if either of its arguments is true:
or
Arguments: Two boolean values
Returns: $(true) if either of the arguments is $(true)
For example, $(call or,$(true),$(false)) returns $(true).
xor
The xor function is exclusive or:
xor
Arguments: Two boolean values
Returns: $(true) if exactly one of the booleans is true
For example, $(call xor,$(true),$(false)) returns $(true).
nand
nand is simply not and:
nand
Arguments: Two boolean values
Returns: Value of 'not and'
For example, $(call nand,$(true),$(false)) returns $(true) where $(call and,$(true),$(false)) returns $(false).
nor
nor is simply not or:
nor
Arguments: Two boolean values
Returns: Value of 'not or'
For example, $(call nor,$(true),$(false)) returns $(false) where $(call or,$(true),$(false)) returns $(true).
xnor
The rarely used xnor is not xor:
xnor
Arguments: Two boolean values
Returns: Value of 'not xor'
Note that the GMSL logical functions and and or are not short circuiting; both of the arguments to those functions are expanded before performing the logical and or or. GNU make 3.81 introduced built-in and and or functions that are short circuiting: they evaluate their first argument and then decide whether it’s necessary to evaluate their second.
Integer Arithmetic Functions
In Chapter 5, you saw how to perform arithmetic inside GNU make by representing non-negative integers as lists of xs. For example, 4 is x x x x. GMSL uses the same representation for integers and provides a wide range of functions for integer calculations.
The arithmetic library functions come in two forms: one form of each function takes integers as arguments, and the other form takes encoded arguments (xs created by a call to int_encode). For example, there are two plus functions: plus (called with integer arguments, returns an integer) and int_plus (called with encoded arguments, returns an encoded result).
plus will be slower than int_plus because its arguments and result must be translated between the x format and integers. If you’re doing a complex calculation, use the int_* forms with a single encoding of inputs and single decoding of the output. For simple calculations, you can use the direct forms.
int_decode
The int_decode function takes a number in x-representation and returns the decimal integer that it represents:
int_decode
Arguments: 1: A number in x-representation
Returns: The integer for human consumption that is represented
by the string of x's
int_encode
int_encode is the opposite of int_decode: it takes a decimal integer and returns the x-representation:
int_encode
Arguments: 1: A number in human-readable integer form
Returns: The integer encoded as a string of x's
int_plus
int_plus adds two numbers in x-representation together and returns their sum in x-representation:
int_plus
Arguments: 1: A number in x-representation
2: Another number in x-representation
Returns: The sum of the two numbers in x-representation
plus
To add decimal integers, use the plus function, which converts to and from x-representation and calls int_plus:
plus (wrapped version of int_plus)
Arguments: 1: An integer
2: Another integer
Returns: The sum of the two integers
int_subtract
int_subtract subtracts two numbers in x-representation and returns the difference in x-representation:
int_subtract
Arguments: 1: A number in x-representation
2: Another number in x-representation
Returns: The difference of the two numbers in x-representation,
or outputs an error on a numeric underflow
If the difference will be less than 0 (which can’t be represented), an error occurs.
subtract
To subtract decimal integers, use the subtract function, which converts to and from x-representation and calls int_subtract:
subtract (wrapped version of int_subtract)
Arguments: 1: An integer
2: Another integer
Returns: The difference of the two integers, or outputs an error on a
numeric underflow
If the difference will be less than 0 (which can’t be represented), an error occurs.
int_multiply
int_multiply multiplies two numbers that are in x-representation:
int_multiply
Arguments: 1: A number in x-representation
2: Another number in x-representation
Returns: The product of the two numbers in x-representation
multiply
multiply will multiply two decimal integers and return their product. It automatically converts to and from x-representation and calls int_multiply:
multiply (wrapped version of int_multiply)
Arguments: 1: An integer
2: Another integer
Returns: The product of the two integers
int_divide
int_divide divides one number by another; both are in x-representation, as is the result:
int_divide
Arguments: 1: A number in x-representation
2: Another number in x-representation
Returns: The result of integer division of argument 1 divided
by argument 2 in x-representation
divide
The divide function calls int_divide to divide two decimal integers, automatically converting to and from x-representation:
divide (wrapped version of int_divide)
Arguments: 1: An integer
2: Another integer
Returns: The integer division of the first argument by the second
int_max and int_min
int_max and int_min return the maximum and minimum, respectively, of two numbers in x-representation:
int_max, int_min
Arguments: 1: A number in x-representation
2: Another number in x-representation
Returns: The maximum or minimum of its arguments in x-representation
max and min
The decimal integer equivalents of int_max and int_min are max and min; they automatically convert to and from x-representation:
max, min
Arguments: 1: An integer
2: Another integer
Returns: The maximum or minimum of its integer arguments
int_inc
int_inc is a small helper function that just adds one to an x-representation number:
int_inc
Arguments: 1: A number in x-representation
Returns: The number incremented by 1 in x-representation
inc
The inc function adds one to a decimal integer:
inc
Arguments: 1: An integer
Returns: The argument incremented by 1
int_dec
The opposite of int_inc is int_dec: it decreases a number by one:
int_dec
Arguments: 1: A number in x-representation
Returns: The number decremented by 1 in x-representation
dec
The dec function decrements a decimal integer by one:
dec
Arguments: 1: An integer
Returns: The argument decremented by 1
int_double
The double and halve functions (and their int_double and int_halve equivalents) are provided for performance reasons. If you’re multiplying by two or dividing by two, these functions will be faster than multiplication and division.
int_double will double an integer:
int_double
Arguments: 1: A number in x-representation
Returns: The number doubled (* 2) and returned in x-representation
double
double will double a decimal integer:
double
Arguments: 1: An integer
Returns: The integer times 2
Internally, it converts to x-representation and calls int_double.
int_halve
You can perform an integer division by two by calling int_halve on an x-representation number:
int_halve
Arguments: 1: A number in x-representation
Returns: The number halved (/ 2) and returned in x-representation
halve
Finally, there’s halve:
halve
Arguments: 1: An integer
Returns: The integer divided by 2
This is the decimal integer equivalent of int_halve.
Integer Comparison Functions
All the integer comparison functions return $(true) or $(false):
int_gt, int_gte, int_lt, int_lte, int_eq, int_ne
Arguments: Two x-representation numbers to be compared
Returns: $(true) or $(false)
int_gt First argument is greater than second argument
int_gte First argument is greater than or equal to second argument
int_lt First argument is less than second argument
int_lte First argument is less than or equal to second argument
int_eq First argument is numerically equal to the second argument
int_ne First argument is not numerically equal to the second argument
These can be used with GNU make and GMSL functions as well as with directives that expect Boolean values (such as the GMSL logical operators).
But you are more likely to use these versions of the comparison functions:
gt, gte, lt, lte, eq, ne
Arguments: Two integers to be compared
Returns: $(true) or $(false)
int_gt First argument is greater than second argument
int_gte First argument is greater than or equal to second argument
int_lt First argument is less than second argument
int_lte First argument is less than or equal to second argument
int_eq First argument is numerically equal to the second argument
int_ne First argument is not numerically equal to the second argument
These operate on decimal integers, not the internal x-representation that GMSL uses.
Miscellaneous Integer Functions
Most likely, you’re not going to need to do anything advanced with GNU make arithmetic, but the miscellaneous functions detailed here do base conversions and generation of numeric sequences. They can, on occasion, be useful.
sequence
You use the sequence function to generate a sequence of numbers:
sequence
Arguments: 1: An integer
2: An integer
Returns: The sequence [arg1 arg2] if arg1 >= arg2 or [arg2 arg1] if arg2 > arg1
For example, $(call sequence,10,15) will be the list 10 11 12 13 14 15. To create a decreasing sequence, you invert the parameters to sequence. For example, $(call sequence,15,10) will be the list 15 14 13 12 11 10.
dec2hex, dec2bin, and dec2oct
The dec2hex, dec2bin, and dec2oct functions perform conversion between decimal numbers and hexadecimal, binary, and octal forms:
dec2hex, dec2bin, dec2oct
Arguments: 1: An integer
Returns: The decimal argument converted to hexadecimal, binary or octal
For example, $(call dec2hex,42) will be 2a.
No options are available for padding with leading zeroes. If that’s necessary, you can use GMSL string functions. For example, here’s a padded version of dec2hex that takes two parameters: a decimal number to be converted to hexadecimal and the number of digits to output:
__repeat = $(if $2,$(call $0,$1,$(call rest,$2),$1$3),$3)
repeat = $(call __repeat,$1,$(call int_encode,$2),)
This works by defining some helper functions. First, repeat creates a string consisting of a number of copies of another string. For example, $(call repeat,10,A) will be AAAAAAAAAA.
Some subtle things are happening in this definition. The repeat function calls __repeat with three parameters: $1 is the string to be repeated, $2 is the number of times to repeat $1, and $3 has been set to a blank string by the trailing comma in the $(call) to __repeat. The $0 variable contains the name of the current function; in __repeat it will be __repeat.
The __repeat function is recursive and uses the $2 as the recursion guard. The repeat function converts the number of desired repeats into the x-representation used by GMSL arithmetic functions and passes it to __repeat. For example, $(call repeat,Hello,5) turns into $(call __repeat,Hello,x x x x x,), and __repeat chops an x off $2 each time around until $2 is empty.
With repeat written, we just need a way to pad a string to some number of characters with a padding character. The function pad achieves that:
pad = $(call repeat,$1,$(call subtract,$2,$(call strlen,$3)))$3
paddeddec2hex = $(call pad,0,$2,$(call dec2hex,$1))
Its three arguments are the character to pad with, the total width of the padded output in character, and the string to pad. For example, $(call pad,0,4,2a) would return 002a. From that, a padded dec2hex can easily be defined. It takes two parameters: the first is the decimal number to convert to hexadecimal, and the second is the number of characters to pad to.
As you’d expect, $(call paddeddec2hex,42,8) returns 0000002a.
List Manipulation Functions
In GNU make and GMSL terms, a list is a string of characters that has whitespace as separators. Both the GNU make built-in functions that work on lists and the GMSL functions treat multiple whitespaces as a single space. So the lists 1 2 3 and 1 2 3 are the same.
I’ll explain a few of the list manipulation functions in detail in the following sections. These functions are more complicated than the others in their use, and they’re typically available in functional languages.
Applying a Function to a List with map
When you’re working with GNU make functions (either built-ins or your own), you’re actually programming in a simple functional language. In functional programming, it’s common to have a map function that applies a function to every element of a list. GMSL defines map to do exactly that. For example:
SRCS := src/FOO.c src/SUBMODULE/bar.c src/foo.c
NORMALIZED := $(call uniq,$(call map,lc,$(SRCS)))
Given a list of filenames (perhaps with paths specified) in SRCS, this will ensure that all the filenames are lowercased and then apply the uniq function to get a unique list of source files.
This uses the GMSL function lc to lowercase each filename in SRCS. You can use the map function with both built-in and user-defined functions. Here, NORMALIZED would be src/foo.c src/submodule/bar.c.
Another use of map might be to get the size of every source file:
size = $(firstword $(shell wc -c $1))
SOURCE_SIZES := $(call map,size,$(SRCS))
Here we define a size function that uses $(shell) to call wc, and then we apply it to every file in SRCS.
Here SOURCE_SIZES might be something like 1538 1481 with one element for each source file.
Making a reduce Function
Another common function in functional languages is reduce. reduce applies a function that takes two parameters to successive elements of a list, feeding the return value from the function into the next call to it. The GMSL doesn’t have a built-in reduce function, but you can easily define it:
reduce = $(if $2,$(call $0,$1,$(call rest,$2),$(call $1,$3,$(firstword $2))),$3)
Summing a List of Numbers Using reduce
Combining reduce with the plus function, you can easily make a GNU make function that sums a list of numbers:
sum-list = $(call reduce,plus,$1,0)
The sum-list function takes a single parameter, a list of numbers, and returns the sum of those numbers. It passes three things to reduce: the name of the function to call for each element of the list (in this case, plus), the list of numbers, and a starting number (in this case, 0).
Here’s how it works. Suppose $(call sum-list,1 2 3 4 5) is called. The following sequence of calls to plus will be performed:
$(call plus,1,0) which returns 1
$(call plus,1,2) which returns 3
$(call plus,3,3) which returns 6
$(call plus,6,4) which returns 10
$(call plus,10,5) which returns 15
The first call uses the first element of the list and the starting number 0. Each subsequent call uses the next element from the list and the last result of calling plus.
You could combine sum-list with the SOURCE_SIZES variable to get the total size of the source code:
TOTAL_SIZE := $(call sum-list,$(SOURCE_SIZES))
In this case, TOTAL_SIZE would be 3019.
Mapping a Function Across a Pair of Lists
The other interesting function that GMSL defines for lists is pairmap. It takes three arguments: two lists (which should be the same length) and a function. The function is applied to the first element of each list, the second element, the third element, and so on.
Suppose SRCS contains a list of source files. Using the size function we defined, combined with map, we defined SOURCE_SIZES, which contains a list of the sizes of each source file. Using pairmap, we can zip the two lists together to output the name of each file and its size:
zip = $1:$2
SOURCES_WITH_SIZES := $(call pairmap,zip,$(SRCS),$(SOURCE_SIZES))
The zip function is applied to each source filename and size in turn, and makes a string separating the filename and its size with a colon. Using our example files and sizes from this section, SOURCES_WITH_SIZES would be src/foo.c:1538 src/submodule/bar.c:1481.
first
first takes in a list and returns its first element:
first
Arguments: 1: A list
Returns: Returns the first element of a list
Note that first is identical to the GNU make function $(firstword).
last
The last function returns the final element of a list:
last
Arguments: 1: A list
Returns: The last element of a list
GNU make 3.81 introduced $(lastword), which works the same way last does.
rest
The rest function is almost the opposite of first. It returns everything but the first element of a list:
rest
Arguments: 1: A list
Returns: The list with the first element removed
chop
To remove the last element of a list, use the chop function:
chop
Arguments: 1: A list
Returns: The list with the last element removed
map
The map function iterates over a list (its second argument) and calls a function (named in its first argument) on each list element. The list of values returned by each call to the named function is returned:
map
Arguments: 1: Name of function to $(call) for each element of list
2: List to iterate over calling the function in 1
Returns: The list after calling the function on each element
pairmap
pairmap is similar to map but iterates over a pair of lists:
pairmap
Arguments: 1: Name of function to $(call) for each pair of elements
2: List to iterate over calling the function in 1
3: Second list to iterate over calling the function in 1
Returns: The list after calling the function on each pair of elements
The function in the first argument is called with two arguments: one element from each of the lists being iterated over.
leq
The leq list equality testing function will correctly return $(true) for lists that are identical other than having different whitespace:
leq
Arguments: 1: A list to compare against...
2: ...this list
Returns: $(true) if the two lists are identical
For example, leq considers 1 2 3 and 1 2 3 to be the same list.
lne
lne is the opposite of leq: it returns $(true) when two lists are not equal:
lne
Arguments: 1: A list to compare against...
2: ...this list
Returns: $(true) if the two lists are different
reverse
To reverse a list can be useful (particularly because it can then be fed into $(foreach) and iterated backward).
reverse
Arguments: 1: A list to reverse
Returns: The list with its elements in reverse order
uniq
The built-in $(sort) function will deduplicate a list, but it does so at the same time as sorting it. The GMSL uniq function deduplicates a list while preserving the order in which elements are first found:
uniq
Arguments: 1: A list to deduplicate
Returns: The list with elements in the original order but without duplicates
For example, $(call uniq,a c b a c b) will return a c b.
length
To find out the number of elements in a list, call length:
length
Arguments: 1: A list
Returns: The number of elements in the list
The length function is the same as the GNU make $(words) function.
String Manipulation Functions
A string is a sequence of any characters, including whitespace. The string equality (and string inequality) function seq works even with strings that contain whitespace or consist only of whitespace. For example:
# space contains the space character
space :=
space +=
# tab contains a tab
tab :=→ # needed to protect the tab character
$(info $(call seq,White Space,White Space))
$(info $(call seq,White$(space)Space,White Space))
$(info $(call sne,White$(space)Space,White$(tab)Space))
$(info $(call seq,$(tab),$(tab)))
$(info $(call sne,$(tab),$(space)))
This outputs T five times, indicating that each call to seq or sne returned $(true).
As with the list manipulation functions, I’ll cover a few of the more complicated functions in detail in the following sections.
Splitting CSV Data into a GNU make List
You can use the split function to turn a value in CSV format into a GNU make list. For example, splitting on a comma turns a CSV line into a list from which individual items can be extracted:
CSV_LINE := src/foo.c,gcc,-Wall
comma := ,
FIELDS := $(call split,$(comma),$(CSV_LINE))
$(info Compile '$(word 1,$(FIELDS))' using compiler '$(word 2,$(FIELDS))' with \
options '$(word 3,$(FIELDS))')
Notice how the variable comma is defined to contain a comma character so it can be used in the $(call) to the split function. This trick was discussed in Chapter 1.
Making a PATH from a List of Directories
The merge function does the opposite of split: it makes a string from a list by separating the list items by some character. For example, to turn a list of directories into a form suitable for the PATH (which is usually separated by colons), define list-to-path as follows:
DIRS := /usr/bin /usr/sbin /usr/local/bin /home/me/bin
list-to-path = $(call merge,:,$1)
$(info $(call list-to-path,$(DIRS)))
This outputs /usr/bin:/usr/sbin:/usr/local/bin:/home/me/bin.
Translating Characters Using tr
The most complicated string function is tr, which operates like the tr shell program. It transforms each character from a collection of characters into a corresponding character in a second list. The GMSL defines some common character classes for use with tr. For example, it defines variables called [A-Z] and [a-z] (yes, those are really the names) that contain the uppercase and lowercase characters.
We can use tr to make a function that translates to leet-speak:
leet = $(call tr,A E I O L T,4 3 1 0 1 7,$1)
$(info $(call leet,I AM AN ELITE GNU MAKE HAXOR))
This outputs 1 4M 4N 31173 GNU M4K3 H4X0R.
seq
The slightly confusingly named seq function tests whether two strings are equal:
seq
Arguments: 1: A string to compare against...
2: ...this string
Returns: $(true) if the two strings are identical
sne
The opposite, string inequality, is tested with sne:
sne
Arguments: 1: A string to compare against...
2: ...this string
Returns: $(true) if the two strings are not the same
streln
The length function gets the length of a list; the equivalent for strings is strlen:
strlen
Arguments: 1: A string
Returns: The length of the string
substr
It’s possible to extract a substring using the substr function:
substr
Arguments: 1: A string
2: Starting offset (first character is 1)
3: Ending offset (inclusive)
Returns: A substring
Note that in GMSL, strings start from position 1, not 0.
split
To split a string into a list, you use the split function:
split
Arguments: 1: The character to split on
2: A string to split
Returns: A list separated by spaces at the split character in the
first argument
Note that if the string contains spaces, the result may not be as expected. GNU make’s use of spaces as the list delimiter makes working with spaces and lists together very difficult. See Chapter 4 for more on GNU make’s handling of spaces.
merge
merge is the opposite of split. It takes a list and outputs a string with a character between each list element:
merge
Arguments: 1: The character to put between fields
2: A list to merge into a string
Returns: A single string, list elements are separated by the character in
the first argument
tr
You use the tr function to translate individual characters, and it’s a building block for creating the uc and lc functions:
tr
Arguments: 1: The list of characters to translate from
2: The list of characters to translate to
3: The text to translate
Returns: The text after translating characters
uc
uc performs simple uppercasing of the alphabet a-z:
uc
Arguments: 1: Text to uppercase
Returns: The text in uppercase
lc
Finally, we have lc:
lc
Arguments: 1: Text to lowercase
Returns: The text in lowercase
This performs simple lowercasing of the alphabet A-Z.
Set Manipulation Functions
Sets are represented by sorted, deduplicated lists. To create a set from a list, use set_create or start with the empty_set and set_insert individual elements. The empty set is defined by the variable empty_set.
For example, a makefile could keep track of all the directories that it made using the marker technique discussed in Making Directories:
MADE_DIRS := $(empty_set)
marker = $1.f
make_dir = $(eval $1.f: ; @$$(eval MADE_DIRS := $$(call \
set_insert,$$(dir $$@),$$(MADE_DIRS))) mkdir -p $$(dir $$@); \
touch $$@)
all: $(call marker,/tmp/foo/) $(call marker,/tmp/bar/)
→ @echo Directories made: $(MADE_DIRS)
$(call make_dir,/tmp/foo/)
$(call make_dir,/tmp/bar/)
Updating the make_dir function (which creates rules to make directories) with a call to set_insert means that the variable MADE_DIRS will keep track of the set of directories created.
In a real makefile, many directories would likely be built, and using a set would be an easy way to discover which had been built at any point.
Note that because a set is implemented as a GNU make list, you can’t insert an item that contains a space.
set_create
You create a set by using the set_create function:
set_create
Arguments: 1: A list of set elements
Returns: The newly created set
It takes a list of elements and adds them to a set. The set itself is returned. Note that set elements may not contain spaces.
set_insert
Once a set has been created using set_create, you can add an element to it using set_insert:
set_insert
Arguments: 1: A single element to add to a set
2: A set
Returns: The set with the element added
set_remove
To remove an element from a set, call set_remove:
set_remove
Arguments: 1: A single element to remove from a set
2: A set
Returns: The set with the element removed
It is not an error to remove an element from a set when that element was not present.
set_is_member
To test whether an element is a member of a set, call set_is_member. It returns a Boolean value indicating whether the element was present:
set_is_member
Arguments: 1: A single element
2: A set
Returns: $(true) if the element is in the set
set_union
You merge two sets together by calling the set_union function on the two sets. The merged set is returned:
set_union
Arguments: 1: A set
2: Another set
Returns: The union of the two sets
set_intersection
To determine the elements common to two sets, use set_intersection. It returns the set of elements that were present in both sets passed in as arguments:
set_intersection
Arguments: 1: A set
2: Another set
Returns: The intersection of the two sets
set_is_subset
It is sometimes useful to know if one set is a subset of another, which you can test by calling set_is_subset:
set_is_subset
Arguments: 1: A set
2: Another set
Returns: $(true) if the first set is a subset of the second
set_is_subset returns a Boolean value indicating whether the first set is a subset of the second.
set_equal
To determine if two sets are equal, call set_equal:
set_equal
Arguments: 1: A set
2: Another set
Returns: $(true) if the two sets are identical
set_equal returns $(true) if the two sets have exactly the same elements.
Associative Arrays
An associative array maps a key value (a string with no spaces in it) to a single value (any string). Associative arrays are sometimes referred to as maps or even hash tables (although that’s an implementation detail, and the GMSL associative arrays do not use hashing).
You can use associative arrays as lookup tables. For example:
C_FILES := $(wildcard *.c)
get-size = $(call first,$(shell wc -c $1))
$(foreach c,$(C_FILES),$(call set,c_files,$c,$(call get-size,$c)))
$(info All the C files: $(call keys,c_files))
$(info foo.c has size $(call get,c_files,foo.c))
This small makefile gets a list of all the .c files in the current directory and their sizes, and then it makes an associative array mapping from a filename to its size.
The get-size function uses wc to get the number of bytes in a file. The C_FILES variable contains all the .c files in the current directory. The $(foreach) uses the GMSL set function to set a mapping in an associative array called c_files between each .c file and its size.
Here’s an example run:
$ make
All the C files: bar.c foo.c foo.c
has size 551
The first line is a list of all the .c files found; it’s printed using the keys function to get all the keys in the associative array. The second line comes from looking up the length of foo.c using get.
set
The GMSL keeps internal track of named associative arrays, but it is not necessary to explicitly create them. Simply call set to add elements to the array, and the array will be created if it does not exist. Note that array keys cannot contain spaces.
set
Arguments: 1: Name of associative array
2: The key value to associate
3: The value associated with the key
Returns: Nothing
get
To retrieve an item from an associate array, call get. If the key is not present, get will return an empty string.
get
Arguments: 1: Name of associative array
2: The key to retrieve
Returns: The value stored in the array for that key
keys
The keys function returns a list of all the keys present in an associative array. You can use this with $(foreach) to iterate an associative array:
keys
Arguments: 1: Name of associative array
Returns: A list of all defined keys in the array
defined
To test whether a key is present in an associated array, call defined:
defined
Arguments: 1: Name of associative array
2: The key to test
Returns: $(true) if the key is defined (i.e., not empty)
defined returns a Boolean indicating whether the key was defined or not.
Named Stacks
A stack is an ordered list of strings (with no spaces in them). In GMSL, stacks are stored internally, and they have names, like associative arrays do. For example:
traverse-tree = $(foreach d,$(patsubst %/.,%,$(wildcard $1/*/.)), \
$(call push,dirs,$d)$(call traverse-tree,$d))
$(call traverse-tree,sources)
dump-tree = $(if $(call sne,$(call depth,dirs),0),$(call pop,dirs) \
$(call dump-tree))
$(info $(call dump-tree))
This small makefile uses a stack to follow a tree of directories.
traverse-tree
The traverse-tree function finds all the subdirectories of its argument (stored in $1) using the $(wildcard) function to find the . file that is always present in a directory. It uses the $(patsubst) function to strip off the trailing /. from each value returned by $(wildcard) to get the full directory name.
Before it traverses down into that directory, it pushes the directory found onto a stack called dirs.
dump-tree
The dump-tree function pops items off the dirs tree until there are none left (until the depth becomes 0).
Example 6-1 shows a directory structure.
Example 6-1. A directory structure
$ ls -R sources
sources:
bar foo
sources/bar:
barsub
sources/bar/barsub:
sources/foo:
subdir subdir2
sources/foo/subdir:
subsubdir
sources/foo/subdir/subsubdir:
sources/foo/subdir2:
If this directory structure exists under sources, the makefile will output:
sources/foo sources/foo/subdir2 sources/foo/subdir sources/foo/subdir/
subsubdir sources/bar sources/bar/barsub
If it’s desirable to traverse the directory tree in a depth-first fashion, you can use the stack functions to define dfs, which searches a directory tree and builds the dirs stack containing the directories in depth-first order:
__dfs = $(if $(call sne,$(call depth,work),0),$(call push,dirs,$(call \
peek,work)$(foreach d,$(patsubst %/.,%,$(wildcard $(call \
pop,work)/*/.)),$(call push,work,$d)))$(call __dfs))
dfs = $(call push,work,$1)$(call __dfs)
$(call dfs,sources)
dump-tree = $(if $(call sne,$(call depth,dirs),0),$(call pop,dirs) $(call \
dump-tree))
$(info $(call dump-tree,dirs))
The dump-tree function hasn’t changed (it just outputs everything in the stack by successive calls to pop). But the dfs function is new. It uses a working stack called work to keep track of directories to visit. It first pushes the starting directory onto the work stack and then calls the __dfshelper.
The real work is done by __dfs. It pushes the current directory onto the dirs stack, pushes all the children of that directory onto the work stack, and then it recurses. Recursion stops when the work stack is empty.
The output for the directory structure in Example 6-1 is now:
sources/bar/barsub sources/bar sources/foo/subdir/subsubdir sources/foo/subdir
sources/foo/subdir2 sources/foo sources.
push
Anyone who has used a stack will be familiar with pushing and popping elements. The GMSL stack functions are very similar. To add an element to the top of the stack, call push:
push
Arguments: 1: Name of stack
2: Value to push onto the top of the stack (must not contain
a space)
Returns: None
pop
To retrieve the top element, call pop:
pop
Arguments: 1: Name of stack
Returns: Top element from the stack after removing it
peek
The peek function operates like pop but doesn’t remove the top stack element; it just returns its value:
peek
Arguments: 1: Name of stack
Returns: Top element from the stack without removing it
depth
Finally, you can call depth:
depth
Arguments: 1: Name of stack
Returns: Number of items on the stack
depth determines how many elements are present on the stack.
Function Memoization
To reduce the number of calls to slow functions, such as $(shell), a single memoization function is provided. For example, suppose a makefile needs to know the MD5 values of various files and defines a function md5.
md5 = $(shell md5sum $1)
That’s a pretty expensive function to call (because of the time md5sum would take to execute), so it would be desirable to call it only once for each file. A memoized version of the md5 function looks like this:
md5once = $(call memoize,md5,$1)
It will call the md5sum function just once for each inputted filename and record the returned value internally so that a subsequent call to md5once with the same filename returns the MD5 value without having to run md5sum. For example:
$(info $(call md5once,/etc/passwd))
$(info $(call md5once,/etc/passwd))
This prints out the MD5 value of /etc/passwd twice but executes md5sum only once.
The actual memoize function is defined using the GMSL associative array functions:
memoize
Arguments: 1: Name of function to memoize
2: String argument for the function
Returns: Result of $1 applied to $2 but only calls $1 once for each unique $2
Miscellaneous and Debugging Facilities
Table 6-1 shows constants that GMSL defines.
Table 6-1. GMSL Constants
Constant |
Value |
Purpose |
true |
T |
The Boolean value true |
false |
(an empty string) |
The Boolean value false |
gmsl_version |
1 1 7 |
Current GMSL version number (major minor revision) |
You access these constants as normal GNU make variables by wrapping them in $() or ${}.
gmsl_compatible
You know the gmsl_compatible function from Checking the GMSL Version:
gmsl_compatible
Arguments: List containing the desired library version number (major minor
revision)
Returns: $(true) if the current version of the library is compatible
with the requested version number, otherwise $(false)
In Chapter 1, you saw a recipe for outputting variable values using a pattern rule with target print-%. Because this is such a useful rule, GMSL defines its own gmsl-print-% target that you can use to print the value of any variable defined in a makefile that includes GMSL.
For example:
include gmsl
FOO := foo bar baz
all:
gmsl-print-%
gmsl-print-% can be used to print any makefile variable, including variables inside GMSL. For example, make gmsl-print-gmsl_version would print the current GMSL version.
gmsl-print-% (target not a function)
Arguments: The % should be replaced by the name of a variable that you
wish to print
Action: Echoes the name of the variable that matches the % and its value
assert
As discussed in Makefile Assertions, it can be useful to have assertions in a makefile. GMSL provides two assertion functions: assert and assert_exists.
assert
Arguments: 1: A boolean that must be true or the assertion will fail
2: The message to print with the assertion
Returns: None
assert_exists
To assert that an individual file or directory exists, GMSL provides the assert_exists function:
assert_exists
Arguments: 1: Name of file that must exist, if it is missing an assertion
will be generated
Returns: None
Environment Variables
Table 6-2 shows GMSL environment variables (or command line overrides), which control various bits of functionality.
Table 6-2. GMSL Environment Variables
Variable |
Purpose |
GMSL_NO_WARNINGS |
If set, prevents GMSL from outputting warning messages. For example, arithmetic functions can generate underflow warnings. |
GMSL_NO_ERRORS |
If set, prevents GMSL from generating fatal errors: division by zero or failed assertions are fatal. |
GMSL_TRACE |
Enables function tracing. Calls to GMSL functions will result in name and arguments being traced. See Tracing Variable Values for a discussion of makefile tracing. |
These environment variables can all be set in the environment or on the command line.
For example, this makefile contains an assertion that will always fail, stopping the make process:
include gmsl
$(call assert,$(false),Always fail)
all:
Setting GMSL_NO_ERRORS prevents the assertion from stopping the make process. In that case the output of the assert is hidden and make continues normally:
$ make
Makefile:5: *** GNU Make Standard Library: Assertion failure: Always fail.
Stop.
$ make GMSL_NO_ERRORS=1
make: Nothing to be done for `all'.
A few well-placed GMSL assertions in a makefile can make a big difference. By checking for makefile prerequisites (such as the presence of a specific file or that a compiler has a certain version number), a conscientious makefile writer can alert a user to a problem without forcing them to debug the often arcane output from make.