GNU Make User-Defined Functions
Creating GNU Make built-in functions is easy, but it does create a maintenance problem: the next time GNU Make is updated we'll need to port our changes to the new version.
In my first article on GNU Make, I covered the basics. This article continues the conversation.
If we download make-3.81.tar.gz and ungzip and untar it we'll end up with a directory structure that looks something like this:
$ ls -F
ABOUT-NLS doc/
AUTHORS
dosbuild.bat
COPYING expand.c
ChangeLog file.c
INSTALL
filedef.h
Makefile function.c
Makefile.DOS getloadavg.c
Makefile.am
getopt.c
Makefile.ami getopt.h
Makefile.in getopt1.c
NEWS
gettext.h
NMakefile glob/
README hash.c
README.Amiga
hash.h
README.DOS implicit.c
README.OS2 job.c
README.W32
job.h
README.customs main.c
SCOPTIONS make.1
SMakefile
make.dot
acinclude.m4 make.h
aclocal.m4 make.lnk
alloca.c
make2.dot
amiga.c make_msvc_net2003.sln
amiga.h
make_msvc_net2003.vcproj
ansi2knr.1 makefile.com
ansi2knr.c
makefile.vms
ar.c misc.c
arscan.c po/
build.sh* read.c
build.sh.in*
readme.vms
build_w32.bat remake.c
commands.c remote-cstms.c
commands.h
remote-stub.c
config/ rule.c
config.ami rule.h
config.h
signame.c
config.h-vms stamp-h1
config.h.W32 strcache.c
config.h.in
subproc.bat
config.log tests/
config.status* variable.c
configh.dos
variable.h
configure* version.c
configure.bat vmsdir.h
configure.in
vmsfunctions.c
debug.h vmsify.c
default.c vmsjobs.c
dep.h
vpath.c
dir.c w32/
The first step is to build GNU Make using the standard configure then make:
$ ./configure
$
make
Once we've done that successfully we are left with a working GNU Make in the same directory. Since I find it handy to be able to tell which GNU Make I'm running the first modification will be to change the message printed out when we ask for the version information. Here's the default:
$ ./make -v
GNU Make 3.81
Copyright (C) 2006 Free
Software Foundation, Inc.
This is free software; see the source for copying
conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR
A
PARTICULAR PURPOSE.
This program built for
i386-apple-darwin9.2.0
As you can see I'm working on a Mac (that final string will change depending upon the machine you are working with), and it's version 3.81.
~ I'm going to change that message so that it prints "(with jgc's modifications)" after the version number. To do that we need to open
the file main.c in a text editor and find the function called print_version (it's at line 2,922) which looks like this:
/* Print version information. */
static
void
print_version (void)
{
~ static int printed_version =
0;
~ char *precede = print_data_base_flag ? "# " : "";
~ if
(printed_version)
~ /* Do it only once. */
~ return;
~ /*
Print this untranslated. The coding standards recommend
translating
the
~ (C) to the copyright symbol, but this string is going to change
every
~ year, and none of the rest of it should be translated (including
the
~ word "Copyright", so it hardly seems worth it. */
~ printf
("%sGNU Make %s\n\
%sCopyright (C) 2006 Free Software Foundation,
Inc.\n",
~ precede, version_string, precede);
~ printf
(_("%sThis is free software; see the source for
copying
conditions.\n\
%sThere is NO warranty; not even for
MERCHANTABILITY or FITNESS FOR A\n\
%sPARTICULAR
PURPOSE.\n"),
~ precede, precede, precede);
~ if
(!remote_description || *remote_description == '\0')
~ printf (_("\n%sThis
program built for %s\n"), precede, make_host);
~ else
~ printf
(_("\n%sThis program built for %s (%s)\n"),
~ precede, make_host,
remote_description);
~ printed_version = 1;
~ /* Flush stdout so
the user doesn't have to wait to see the
~ version information while
things are thought about. */
~ fflush (stdout);
}
The first printf in that function is where the version number if printed and we can modify it like this:
~ printf ("%sGNU Make %s (with jgc's
modifications)\n\
%sCopyright (C) 2006 Free Software Foundation,
Inc.\n",
~ precede, version_string, precede);
Save the file and then rerun make. Now when we type make - -v we know which version we are working with:
$ ./make -v
GNU Make 3.81
(with jgc's modifications)
Copyright (C) 2006 Free Software Foundation,
Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR
PURPOSE.
This program built for
i386-apple-darwin9.2.0
Anatomy of a built-in function
GNU Make's built-in functions are defined in the file function.c. The first place to look is the table of functions
that GNU Make knows about. It's called function_table_init[] and can be found on line
2,046:
static struct function_table_entry function_table_init[]
=
{
~ /* Name/size */ /* MIN MAX EXP? Function */
~
{ STRING_SIZE_TUPLE("abspath"), 0, 1, 1, func_abspath},
~ {
STRING_SIZE_TUPLE("addprefix"), 2, 2,
1,
func_addsuffix_addprefix},
~ { STRING_SIZE_TUPLE("addsuffix"), 2,
2, 1,
func_addsuffix_addprefix},
~ { STRING_SIZE_TUPLE("basename"),
0, 1, 1, func_basename_dir},
~ { STRING_SIZE_TUPLE("dir"), 0,
1, 1, func_basename_dir},
~ { STRING_SIZE_TUPLE("notdir"), 0, 1,
1, func_notdir_suffix},
~ { STRING_SIZE_TUPLE("subst"), 3, 3, 1,
func_subst},
~ { STRING_SIZE_TUPLE("suffix"), 0, 1, 1,
func_notdir_suffix},
~ { STRING_SIZE_TUPLE("filter"), 2, 2, 1,
func_filter_filterout},
~ { STRING_SIZE_TUPLE("filter-out"), 2, 2, 1,
func_filter_filterout},
~ { STRING_SIZE_TUPLE("findstring"), 2, 2, 1,
func_findstring},
~ { STRING_SIZE_TUPLE("firstword"), 0, 1, 1,
func_firstword},
~ { STRING_SIZE_TUPLE("flavor"), 0, 1, 1,
func_flavor},
~ { STRING_SIZE_TUPLE("join"), 2, 2, 1,
func_join},
~ { STRING_SIZE_TUPLE("lastword"), 0, 1, 1,
func_lastword},
~ { STRING_SIZE_TUPLE("patsubst"), 3, 3, 1,
func_patsubst},
~ { STRING_SIZE_TUPLE("realpath"), 0, 1, 1,
func_realpath},
~ { STRING_SIZE_TUPLE("shell"), 0, 1, 1,
func_shell},
~ { STRING_SIZE_TUPLE("sort"), 0, 1, 1,
func_sort},
~ { STRING_SIZE_TUPLE("strip"), 0, 1, 1,
func_strip},
~ { STRING_SIZE_TUPLE("wildcard"), 0, 1, 1,
func_wildcard},
~ { STRING_SIZE_TUPLE("word"), 2, 2, 1,
func_word},
~ { STRING_SIZE_TUPLE("wordlist"), 3, 3, 1,
func_wordlist},
~ { STRING_SIZE_TUPLE("words"), 0, 1, 1,
func_words},
~ { STRING_SIZE_TUPLE("origin"), 0, 1, 1,
func_origin},
~ { STRING_SIZE_TUPLE("foreach"), 3, 3, 0,
func_foreach},
~ { STRING_SIZE_TUPLE("call"), 1, 0, 1,
func_call},
~ { STRING_SIZE_TUPLE("info"), 0, 1, 1,
func_error},
~ { STRING_SIZE_TUPLE("error"), 0, 1, 1,
func_error},
~ { STRING_SIZE_TUPLE("warning"), 0, 1, 1,
func_error},
~ { STRING_SIZE_TUPLE("if"), 2, 3, 0,
func_if},
~ { STRING_SIZE_TUPLE("or"), 1, 0, 0,
func_or},
~ { STRING_SIZE_TUPLE("and"), 1, 0, 0,
func_and},
~ { STRING_SIZE_TUPLE("value"), 0, 1, 1,
func_value},
~ { STRING_SIZE_TUPLE("eval"), 0, 1, 1,
func_eval},
#ifdef EXPERIMENTAL
~ { STRING_SIZE_TUPLE("eq"),
2, 2, 1, func_eq},
~ { STRING_SIZE_TUPLE("not"), 0, 1, 1,
func_not},
#endif
};
Each line defines a single function, and consists of 5 pieces of information: the name of the function, the minimum number of arguments that the function must have, the maximum number of arguments (specifying a maximum of zero with a non-zero minimum means that function can have unlimited number of arguments), whether the arguments should be expanded, and the name of the C function that actually performs the function.
For example, here's the definition of the findstring function:
~ {
STRING_SIZE_TUPLE("findstring"), 2, 2, 1, func_findstring},
So
that function is called findstring, it has a minimum of 2
arguments,
a maximum of 2 and the arguments should be expanded before
calling the C
function func_findstring.
func_findstring (which is in function.c at line 819) does the work:
static
char*
func_findstring (char *o, char **argv, const char *funcname
UNUSED)
{
~ /* Find the first occurrence of the first string in the
second. */
~ if (strstr (argv[1], argv[0]) != 0)
~ o =
variable_buffer_output (o, argv[0], strlen (argv[0]));
~ return
o;
}
The C functions that implement GNU Make built in functions have three arguments: [tt]o[/tt] (a pointer to a buffer into which output of the function should be written), argv (the arguments of the function as a null-terminated array of strings) and funcname (which is a string containing the name of the function; most functions don't need this but it can be helpful if one C routine handles more than one GNU Make function).
You can see that [tt]func_findstring[/tt] just used the standard library strstrfunction to find the presence of its second argument (in argv[1]) in its first (in argv[0]).
func_findstring makes use of a very handy GNU Make C function called variable_buffer_output (which is defined in expand.c at line 57). variable_buffer_output
is used to copy a string into the output buffer o of a GNU Make function. The first argument should be the output buffer, the second the string to copy and the last the amount of the string to copy.
func_findstring either copies all of its first argument (if the strstr was successful) or leaves [tt]o[/tt] untouched (and, hence, empty since it is initialized to an empty string before func_findstring is called).
With that we have enough information to start making our own GNU Make function.
Reverse a string
There's no easy wasy to reverse a string in GNU Make, but it's easy to write a C function that does that and insert it into GNU Make.
First, we'll add the definition of reverse to the list of functions that GNU Make knows about. reverse will have a single argument that must be expanded and will call a C function
named func_reverse.
Here's the entry to add to the function_table_init[]:
~ { STRING_SIZE_TUPLE("reverse"),
1, 1, 1, func_reverse},
[/tt]
And now we can define
[tt]func_reverse[/tt] which reverses the string in
[tt]argv[0][/tt] by
swapping characters and then updates the output
buffer
[tt]o[/tt].:
[tt]
static char*
func_reverse (char *o, char **argv,
const char *funcname UNUSED)
{
~ int len = strlen(argv[0]);
~ if (
len > 0 ) {
~ char * p = argv[0];
~ int left = 0;
~ int
right = len - 1;
~ while ( left < right ) {
~ char temp = *(p +
left);
~ *(p + left) = *(p + right);
~ *(p + right) =
temp;
~ left++;
~ right--;
~ }
~ o =
variable_buffer_output (o, p, len);
~ }
~ return
o;
}
This function works by walking from the start of the string and from the end of the string at the same time and swapping characters as they go until they meet in the middle.
To test it out we can write a little Makefile that tries three possibilities: an empty string, a string with even length and a string with odd length calling the new built-in function reverse:
EMPTY :=
$(info Empty string:
[$(reverse $(EMPTY))]);
EVEN := 1234
$(info Even length string:
[$(reverse $(EVEN))]);
ODD := ABCDE
$(info Odd length string:
[$(reverse $(ODD))]);
[/tt]
And the output shows that it works
correctly:
[tt]
$ ./make
Empty string: []
Even length string:
[4321]
Odd length string: [EDCBA]
Since writing in C gives you access to the full range of C library functions, the GNU Make built-in functions you create are only limited by your imagination and your needs.
Conclusion
Creating GNU Make built-in functions is easy, but it does create a maintenance problem: the next time GNU Make is updated we'll need to port our changes to the new version.
If we can do what we need with GNU Make built-ins without resorting to modiying the source then Makefiles will be more portable. One helping hand is the GNU Make Standard Library project that provides lots of additional functionality without modifying the GNU Make source.