GNU Make: Adding a built-in function#
I saw this question on the help-make mailing list and I decided to see how easy it would be to register a new built-in function in GNU Make. It turned out to be quite easy (in comparison, it took me more time to write this post). I then found out that there is a section "Anatomy of a Built-In Function" in "The GNU Make Book" by John Graham-Cumming which helped me to see alternatives.
The sources#
curl -O https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && tar xvf make-4.4.tar.gz
cd make-4.4 && ./configure && make
The function#
static char *
func_location (char *o, char **argv, const char *funcname UNUSED)
{
const floc *flocp = reading_file;
if (flocp && flocp->filenm) {
const char *mode = argv[0];
if (!strcmp(mode, "file")) {
return variable_buffer_output(o, flocp->filenm, strlen(flocp->filenm));
} else if (!strcmp(mode, "line")) {
char buf[21];
snprintf(buf, sizeof(buf), "%lu", flocp->lineno + flocp->offset);
return variable_buffer_output(o, buf, strlen(buf));
}
}
return o;
}
All built-in functions have the same signature:
o: a pointer to the buffer where the result of the macro expansion would be substitutedargv: an array of argumentsfuncname: a label (which we don't use).
The logic of the code is not important1 -- a string containing filename/line number
is formed and variable_buffer_output(...) is called to insert it into the output
buffer. We then add func_location to src/function.c and include it into a hash table
of built-in functions (where we specify that there would be exactly one argument which
would be expanded as per usual):
static struct function_table_entry function_table_init[] =
{
/* Name MIN MAX EXP? Function */
FT_ENTRY ("abspath", 0, 1, 1, func_abspath),
// ...
FT_ENTRY ("location", 1, 1, 1, func_location),
};
Then we run make in make-4.4 again and we are done.
The example#
So now we have a function that takes one argument (either file or line). We define
two recursively expanded variables (__FILE__ and __LINE__) for convenience:
Running make -f Makefile.test outputs:
tell-lines at Makefile.test:17
[from included file] Makefile.other:3
[one logical line ] Makefile.test:9 (9)
[after empty lines ] Makefile.test:10
As noted in the comments above, the line numbers indicate "logical lines" and empty/commented lines after the first recipe line are not accounted for (my guess is that this is a consequence of "how makefiles are parsed").
-
I am sure I don't handle some edge-cases -- but that's beyond the point. ↩