Sponsors

 


TechWell



 

We have 282 guests and 1 member online

Home Ask Mr. Make GNU Make user-defined functions, part 2

GNU Make user-defined functions, part 2

E-mail
Ask Mr. Make
Written by John Graham-Cumming   
Tuesday, 04 March 2008 18:16
basicsdesign0308Last month I wrote about creating user-defined functions in GNU Make. This month I'm going to look inside the GNU Make source code to show how
you can enhance GNU Make with your own built-in functions by writing some C code.

The Source
First, we need to get the source code of GNU Make which is available from the Free Software Foundation.  For this aritcle I'm going to be working with the very latest GNU Make: 3.81.

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.


John Graham-Cumming is Mr Make and Chief Scientist at Electric Cloud, Inc., a Silicon Valley start-up that speeds up builds by 10-20x using cluster technology.

Trackback(0)

Comments (0)add comment


Write comment

You must be logged in to post a comment. Please register if you do not have an account yet.

busy
Last Updated on Wednesday, 05 March 2008 16:37