The GNU Make Standard Library

[article]
Summary:

The GMSL contains functions for list and string manipulation, has a complete integer arithmetic library, and functions for data structures: there are GNU Make implementations of associative arrays and stacks. There are also built-in debugging facilities.

The GMSL contains functions for list and string manipulation, has a complete integer arithmetic library, and functions for data structures: there are GNU Make implementations of associative arrays and stacks. There are also built-in debugging facilities.

In this article I'll show how to use the functions of the GMSL in a realistic Makefile.Importing the GMSLThe 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 simply add the line

    include gmsl

You can do this in as many files as you want, the GMSL automatically detects if it has already been included to prevent multiple definitions and unintended error messages.

Of course, GNU Make must be able to find gmsl and __gmsl. GNU Make looks in a number of places by default for Makefiles: /usr/local/include, /usr/gnu/include/, /usr/include, the current directory and any directories specified by the GNU Make -I (or -include-dir) command-line option.

A good place to put gmsl and __gmsl is /usr/local/include : then 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 such as:

    Makefile:1: gmsl: No such file or directory

Calling a GMSL FunctionThe functions in the GMSL are implemented as variable 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))

And the function is called by using GNU Make's $(call) built-in function. For example, to return the last element of the list 1 2 3 do

    $(call last,1 2 3)

which 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 argument 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).true and false can be used in GNU Make's $(if) function or within a preprocessor ifeq:

    $(if $(true),It's true!,Totally false)
ifeq ($(true),$(true))
...do this...
endif

Obviously, those examples are contrived and you'd expect the $(true) in the $(if) and the first $(true) in the ifeq to be the return values from a function call, and not a constant value. Checking the GMSL VersionThe GMSL includes a function that can be used 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.

The current GMSL is v1.0.0. To check that the included GMSL is at least version, say, v0.9.0 call gmsl_compatible with a list argument containing three elements: 0 9 0.

    $(call gmsl_compatible,0 9 0)

It will return $(true) because the current GMSL is v1.0.0 and hence greater than v1.0.0. 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 (of which, more below):

    $(call assert,$(call gmsl_compatible,1 0 0),Wrong GMSL version)

Example: Case insensitive comparisonGMSL contains two functions that make it possible to define a simple function to do a case insensitive comparison of two strings:

    ifcase = $(call seq,$(call lc,$1),$(call lc,$2))

This works by lower-casing its two arguments and then calls seq (the string equality function) to see if they are the same. Here it's used to see if the DEBUG variable has been set to yes; if it has then CPPFLAGS gets -DDEBUG added to it.

    CPPFLAGS += $(if $(call ifcase,$(DEBUG),yes),-DDEBUG,)

Example: Finding a program on the pathHere's a little function definition that will search the PATH for an executable.

    findpath = $(call first, $(call map,wildcard, $(call addsuffix,/$1,
$(call split,:,$(PATH)))))

Calling findpath, $(call findpath,cat), will search the PATH for the first cat program. It uses three functions from the GMSL: first, map and split ; and it uses two built-in functions: wildcard and addsuffix.

The call to split breaks the PATH variable into a list separating it at : 's. 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 being searched for.

Then map is called to perform a built-in wildcard in each possible program. With no wildcard characters in the filename wildcard will return the name of the file if it exists or an empty string. Thus map has the affect of finding the location (or locations) of cat on 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. Here's an example of searching for cat using the findpath function with tracing turned on:

    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/gmake /usr/local/bin/gmake 
/usr/bin/gmake
/usr/X11R6/bin/gmake /bin/gmake /usr/games/gmake 
/opt/gnome/bin/gmake /opt/kde3/bin/gmake
/usr/lib/java/jre/bin/gmake')
Makefile:8: first(' /usr/bin/gmake')

Example: Using assertions to check inputsTypically 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 (e.g. debug options, architecture settings, ...) that affect the build. A quick way to check that these have been set correctly is to use GMSL's assert functions.Here's an example that checks that DEBUG has been set to yes or no, that ARCH contains the word Linux, and that we've specified an output directory in the OUTDIR variable and that that directory exists. The assertion functions will generate a fatal error if their first argument is $(false) (i.e. an empty string).

    $(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 first assert checks that $(OUTDIR) has been set to something. If it has a non-empty value then the assertion passed, if not then an error will be 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 if its first argument exists in the file system. In this case it checks to see if the directory pointed to by $(OUTDIR) exists. It doesn't check to see if it's a directory. We can add another assertion for that like this:

    $(call assert,$(wildcard $(OUTDIR)/.*),OUTDIR must be a directory)

That works by looking to see if $(OUTDIR) contains any files starting with a . (such as the usual . and .. ). If not then $(OUTDIR) is not a directory and the call to wildcard will return an empty string causing the assertion to fail.

The third assertion above 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).
Where to learn moreThe GNU Make Standard Library has documentation built in (just open the __gmsl file) or you can browse http://gmsl.sf.net. GMSL is very new and is by no means complete; I'd love to hear from people who are using it, or from people who have enhancements to suggest.

About the author

CMCrossroads is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.