From 5058a94ee717d96285da20423324af3478df175d Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 25 Feb 2013 01:38:36 -0500 Subject: [PATCH] Expand the loadable object support. Provide a simple API for loaded objects to interact with GNU make. I still won't guarantee that this API won't change but it's much closer to something that's supported and provides easy-to-use interfaces with a public header file. --- .gitignore | 3 +- ChangeLog | 27 ++++++ Makefile.am | 14 ++-- dep.h | 1 - doc/make.texi | 148 ++++++++++++++++++++++++++++----- function.c | 125 ++++++++++++++++------------ gmk-default.scm | 4 - gnumake.h | 29 +++++++ guile.c | 34 +++++--- load.c | 2 +- loadapi.c | 49 +++++++++++ main.c | 2 +- read.c | 6 +- tests/ChangeLog | 9 ++ tests/run_make_tests.pl | 35 ++++++-- tests/scripts/features/load | 69 +++++++-------- tests/scripts/features/loadapi | 84 +++++++++++++++++++ tests/scripts/functions/guile | 8 +- variable.h | 2 +- 19 files changed, 503 insertions(+), 148 deletions(-) create mode 100644 loadapi.c create mode 100644 tests/scripts/features/loadapi diff --git a/.gitignore b/.gitignore index 5d0af62a..0173826b 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ config.ami config.h-vms config.h.W32 configh.dos -make*.tar.* +make-[0-9]*/ +make-[0-9]*.tar.* diff --git a/ChangeLog b/ChangeLog index a2d2796d..9918dc6b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2013-02-25 Paul Smith + + Add a proposed supported API for GNU make loaded objects. + + * doc/make.texi (Loaded Object API): Document it. + * Makefile.am (make_SOURCES): Add new loadapi.c. + * dep.h: Remove eval_buffer(); moved to loadapi.c:gmk_eval(). + * read.c (eval_buffer): Change eval_buffer() signature. + * main.c (main): Change eval_buffer() signature. + * variable.h (define_new_function): Change func_ptr signature. + * load.c (SYMBOL_EXTENSION): Change the extension. + * loadapi.c: Implement the new API. + * gnumake.h (gmk_eval): New function prototype. + (gmk_expand) Ditto. + (gmk_add_function) Ditto. + * gmk-default.scm (gmk-eval): Remove: now implemented in guile.c. + * guile.c (guile_expand_wrapper): Use gmk_expand() + (guile_eval_wrapper): Implement eval here to avoid double-expansion. + (guile_define_module): Define gmk-eval. + (func_guile): Use new func_ptr calling model. + (guile_gmake_setup): Use gmk_add_function() to declare $(guile ...) + * function.c (function_table_entry): Provide alternative func_ptr. + (func_eval): New signature for eval_buffer(); + (function_table_init): New initialization for function_table_entry. + (expand_builtin_function): Support alternative invocation signature. + (define_new_function): Ditto. + 2013-01-20 Paul Smith * gnumake.h: New file to contain externally-visible content. diff --git a/Makefile.am b/Makefile.am index 3e3b4800..35ff73be 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,8 +40,8 @@ else endif make_SOURCES = ar.c arscan.c commands.c default.c dir.c expand.c file.c \ - function.c getopt.c getopt1.c implicit.c job.c load.c main.c \ - misc.c read.c remake.c rule.c signame.c \ + function.c getopt.c getopt1.c implicit.c job.c load.c \ + loadapi.c main.c misc.c read.c remake.c rule.c signame.c \ strcache.c variable.c version.c vpath.c hash.c \ $(remote) @@ -184,18 +184,18 @@ loadavg_LDADD = @GETLOADAVG_LIBS@ MAKETESTFLAGS = check-regression: - @if test -f "$(srcdir)/tests/run_make_tests"; then \ + @if test -f '$(srcdir)/tests/run_make_tests'; then \ if $(PERL) -v >/dev/null 2>&1; then \ - case `cd $(srcdir); pwd` in `pwd`) : ;; \ + case `cd '$(srcdir)'; pwd` in `pwd`) : ;; \ *) test -d tests || mkdir tests; \ rm -f srctests; \ - if ln -s "$(srcdir)/tests" srctests; then \ + if ln -s '$(srcdir)/tests' srctests; then \ for f in run_make_tests run_make_tests.pl test_driver.pl scripts; do \ rm -f tests/$$f; ln -s ../srctests/$$f tests; \ done; fi ;; \ esac; \ - echo "cd tests && $(PERL) ./run_make_tests.pl -make ../make$(EXEEXT) $(MAKETESTFLAGS)"; \ - cd tests && $(PERL) ./run_make_tests.pl -make ../make$(EXEEXT) $(MAKETESTFLAGS); \ + echo "cd tests && $(PERL) ./run_make_tests.pl -srcdir $(abs_srcdir) -make ../make$(EXEEXT) $(MAKETESTFLAGS)"; \ + cd tests && $(PERL) ./run_make_tests.pl -srcdir '$(abs_srcdir)' -make '../make$(EXEEXT)' $(MAKETESTFLAGS); \ else \ echo "Can't find a working Perl ($(PERL)); the test suite requires Perl."; \ fi; \ diff --git a/dep.h b/dep.h index 3854f310..1a50e255 100644 --- a/dep.h +++ b/dep.h @@ -87,5 +87,4 @@ struct dep *copy_dep_chain (const struct dep *d); void free_dep_chain (struct dep *d); void free_ns_chain (struct nameseq *n); struct dep *read_all_makefiles (const char **makefiles); -void eval_buffer (char *buffer); int update_goal_chain (struct dep *goals); diff --git a/doc/make.texi b/doc/make.texi index f5ca1160..2fe7aa6b 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -348,6 +348,7 @@ Loading Dynamic Objects * load Directive:: Loading dynamic objects as extensions. * Remaking Loaded Objects:: How loaded objects get remade. +* Loaded Object API:: Programmatic interface for loaded objects. @end detailmenu @end menu @@ -10658,23 +10659,24 @@ exports these procedures as public interfaces from that module: @table @code @item gmk-expand +@findex gmk-expand This procedure takes a single argument which is converted into a string. The string is expanded by @code{make} using normal @code{make} expansion rules. The result of the expansion is converted into a Guile string and provided as the result of the procedure. @item gmk-eval +@findex gmk-eval This procedure takes a single argument which is converted into a string. The string is evaluated by @code{make} as if it were a makefile. This is the same capability available via the @code{eval} function (@pxref{Eval Function}). The result of the @code{gmk-eval} procedure is always the empty string. -@item gmk-var -This procedure takes a single argument which is converted into a -string. The string is assumed to be the name of a @code{make} -variable, which is then expanded. The expansion is converted into a -string and provided as the result of the procedure. +Note that @code{gmk-eval} is not quite the same as using +@code{gmk-expand} with the @code{eval} function: in the latter case +the evaluated string will be expanded @emph{twice}; first by +@code{gmk-expand}, then again by the @code{eval} function. @end table @@ -10756,8 +10758,8 @@ symbol to be stored in a @code{make} variable. @node Loading Objects, , Guile Integration, Extending make @section Loading Dynamic Objects -@cindex loading objects -@cindex objects, loading +@cindex loaded objects +@cindex objects, loaded @cindex extensions, loading @cartouche @@ -10767,12 +10769,6 @@ The @code{load} directive and extension capability is considered a to experiment with this feature and we appreciate any feedback on it. However we cannot guarantee to maintain backward-compatibility in the next release. - -In particular, for this feature to be useful your extensions will need -to invoke various functions internal to GNU @code{make}. In this -release there is no stable programming interface defined for -@code{make}: any internal function may change or even disappear in -future releases. @end quotation @end cartouche @@ -10791,6 +10787,7 @@ for example, and the ``setup'' function would register them with GNU @menu * load Directive:: Loading dynamic objects as extensions. * Remaking Loaded Objects:: How loaded objects get remade. +* Loaded Object API:: Programmatic interface for loaded objects. @end menu @node load Directive, Remaking Loaded Objects, Loading Objects, Loading Objects @@ -10829,7 +10826,7 @@ If no @var{symbol-name} is provided, the initializing function name is created by taking the base file name of @var{object-file}, up to the first character which is not a valid symbol name character (alphanumerics and underscores are valid symbol name characters). To -this prefix will be appended the suffix @code{_gmake_setup}. +this prefix will be appended the suffix @code{_gmk_setup}. More than one object file may be loaded with a single @code{load} directive, and both forms of @code{load} arguments may be used in the @@ -10848,7 +10845,7 @@ load ../mk_funcs.so will load the dynamic object @file{../mk_funcs.so}. After the object is loaded, @code{make} will invoke the function (assumed to be defined -by the shared object) @code{mk_funcs_gmake_setup}. +by the shared object) @code{mk_funcs_gmk_setup}. On the other hand: @@ -10875,11 +10872,11 @@ generated if an object fails to load. The failed object is not added to the @code{.LOADED} variable, which can then be consulted to determine if the load was successful. -@node Remaking Loaded Objects, , load Directive, Loading Objects +@node Remaking Loaded Objects, Loaded Object API, load Directive, Loading Objects @subsection How Loaded Objects Are Remade -@cindex updating load objects -@cindex remaking load objects -@cindex load objects, remaking of +@cindex updating loaded objects +@cindex remaking loaded objects +@cindex loaded objects, remaking of Loaded objects undergo the same re-make procedure as makefiles (@pxref{Remaking Makefiles, ,How Makefiles Are Remade}). If any @@ -10891,6 +10888,119 @@ support this.@refill It's up to the makefile author to provide the rules needed for rebuilding the loaded object. +@node Loaded Object API, , Remaking Loaded Objects, Loading Objects +@subsection Loaded Object Interface +@cindex loaded object API +@cindex interface for loaded objects + +@cartouche +@quotation Warning +For this feature to be useful your extensions will need to invoke +various functions internal to GNU @code{make}. The programming +interfaces provided in this release should not be considered stable: +functions may be added, removed, or change calling signatures or +implementations in future versions of GNU @code{make}. +@end quotation +@end cartouche + +To be useful, loaded objects must be able to interact with GNU +@code{make}. This interaction includes both interfaces the loaded +object provides to makefiles and also interfaces the loaded object can +use to manipulate @code{make}'s operation. + +The interface between loaded objects and @code{make} is defined by the +@file{gnumake.h} C header file. All loaded objects written in C +should include this header file. Any loaded object not written in C +will need to implement the interface defined in this header file. + +Typically, a loaded object will register one or more new GNU +@code{make} functions using the @code{gmk_add_function} routine from +within its setup function. The implementations of these @code{make} +functions may make use of the @code{gmk_expand} and @code{gmk_eval} +routines to perform their tasks. + +@subsubheading Data Structures + +@table @code +@item gmk_floc +This structure represents a filename/location pair. It is provided +when defining items, so GNU @code{make} can inform the user later +where the definition occurred if necessary. +@end table + +@subsubheading Registering Functions + +There is currently one way for makefiles to invoke operations provided +by the loaded object: through the @code{make} function call +interface. A loaded object can register one or more new functions +which may then be invoked from within the makefile in the same way as +any other function. + +Use @code{gmk_add_function} to create a new @code{make} function. Its +arguments are as follows: + +@table @code +@item name +The function name. This is what the makefile should use to invoke the +function. The name must be between 1 and 255 characters long. + +@item func_ptr +A pointer to a function that @code{make} will invoke when it expands +the function in a makefile. This function must be defined by the +loaded object. GNU @code{make} will call it with three arguments: +@code{name} (the same name as above), @code{argc} (the number of +arguments to the function), and @code{argv} (the list of arguments to +the function). The last argument (that is, @code{argv[argc]} will be +null (@code{0}). + +@item min_args +The minimum number of arguments the function will accept. Must be +between 0 and 255. GNU @code{make} will check this and fail before +invoking @code{func_ptr} if the function was invoked with too few +arguments. + +@item max_args +The maximum number of arguments the function will accept. Must be +between 0 and 255. GNU @code{make} will check this and fail before +invoking @code{func_ptr} if the function was invoked with too few +arguments. If the value is 0, then any number of arguments is +accepted. If the value is greater than 0, then it must be greater +than or equal to @code{min_args}. + +@item expand_args +If this value is 0, then @code{make} will not expand the arguments to +the function before passing them to @code{func_ptr}. If the value is +non-0, then the arguments will be expanded first. +@end table + +@subsubheading GNU @code{make} Facilities + +There are some facilities exported by GNU @code{make} for use by +loaded objects. Typically these would be run from within the +setup function and/or the functions registered via +@code{gmk_add_function}, to retrieve or modify the data @code{make} +works with. + +@table @code +@item gmk_expand +This function takes a string and expands it using @code{make} +expansion rules. The result of the expansion is returned in a string +that has been allocated using @code{malloc}. The caller is +responsible for calling @code{free} on the string when done. + +@item gmk_eval +This function takes a buffer and evaluates it as a segment of makefile +syntax. This function can be used to define new variables, new rules, +etc. It is equivalent to using the @code{eval} @code{make} function. + +Note that there is a difference between @code{gmk_eval} and calling +@code{gmk_expand} with a string using the @code{eval} function: in +the latter case the string will be expanded @emph{twice}; once by +@code{gmk_expand} and then again by the @code{eval} function. Using +@code{gmk_eval} the buffer is only expanded once, at most (as it's +read by the @code{make} parser). +@end table + @node Features, Missing, Extending make, Top @chapter Features of GNU @code{make} @cindex features of GNU @code{make} diff --git a/function.c b/function.c index 15b7aea6..b6060d6f 100644 --- a/function.c +++ b/function.c @@ -29,12 +29,16 @@ this program. If not, see . */ struct function_table_entry { + union { + char *(*func_ptr) (char *output, char **argv, const char *fname); + char *(*alloc_func_ptr) (const char *fname, int argc, char **argv); + } fptr; const char *name; unsigned char len; unsigned char minimum_args; unsigned char maximum_args; - char expand_args; - char *(*func_ptr) (char *output, char **argv, const char *fname); + unsigned char expand_args:1; + unsigned char alloc_fn:1; }; static unsigned long @@ -1361,7 +1365,7 @@ func_eval (char *o, char **argv, const char *funcname UNUSED) install_variable_buffer (&buf, &len); - eval_buffer (argv[0]); + eval_buffer (argv[0], NULL); restore_variable_buffer (buf, len); @@ -2186,49 +2190,51 @@ func_abspath (char *o, char **argv, const char *funcname UNUSED) static char *func_call (char *o, char **argv, const char *funcname); +#define FT_ENTRY(_name, _min, _max, _exp, _func) \ + { (_func), STRING_SIZE_TUPLE(_name), (_min), (_max), (_exp), 0 } 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}, - { STRING_SIZE_TUPLE("file"), 1, 2, 1, func_file}, + /* Name MIN MAX EXP? Function */ + FT_ENTRY ("abspath", 0, 1, 1, func_abspath), + FT_ENTRY ("addprefix", 2, 2, 1, func_addsuffix_addprefix), + FT_ENTRY ("addsuffix", 2, 2, 1, func_addsuffix_addprefix), + FT_ENTRY ("basename", 0, 1, 1, func_basename_dir), + FT_ENTRY ("dir", 0, 1, 1, func_basename_dir), + FT_ENTRY ("notdir", 0, 1, 1, func_notdir_suffix), + FT_ENTRY ("subst", 3, 3, 1, func_subst), + FT_ENTRY ("suffix", 0, 1, 1, func_notdir_suffix), + FT_ENTRY ("filter", 2, 2, 1, func_filter_filterout), + FT_ENTRY ("filter-out", 2, 2, 1, func_filter_filterout), + FT_ENTRY ("findstring", 2, 2, 1, func_findstring), + FT_ENTRY ("firstword", 0, 1, 1, func_firstword), + FT_ENTRY ("flavor", 0, 1, 1, func_flavor), + FT_ENTRY ("join", 2, 2, 1, func_join), + FT_ENTRY ("lastword", 0, 1, 1, func_lastword), + FT_ENTRY ("patsubst", 3, 3, 1, func_patsubst), + FT_ENTRY ("realpath", 0, 1, 1, func_realpath), + FT_ENTRY ("shell", 0, 1, 1, func_shell), + FT_ENTRY ("sort", 0, 1, 1, func_sort), + FT_ENTRY ("strip", 0, 1, 1, func_strip), + FT_ENTRY ("wildcard", 0, 1, 1, func_wildcard), + FT_ENTRY ("word", 2, 2, 1, func_word), + FT_ENTRY ("wordlist", 3, 3, 1, func_wordlist), + FT_ENTRY ("words", 0, 1, 1, func_words), + FT_ENTRY ("origin", 0, 1, 1, func_origin), + FT_ENTRY ("foreach", 3, 3, 0, func_foreach), + FT_ENTRY ("call", 1, 0, 1, func_call), + FT_ENTRY ("info", 0, 1, 1, func_error), + FT_ENTRY ("error", 0, 1, 1, func_error), + FT_ENTRY ("warning", 0, 1, 1, func_error), + FT_ENTRY ("if", 2, 3, 0, func_if), + FT_ENTRY ("or", 1, 0, 0, func_or), + FT_ENTRY ("and", 1, 0, 0, func_and), + FT_ENTRY ("value", 0, 1, 1, func_value), + FT_ENTRY ("eval", 0, 1, 1, func_eval), + FT_ENTRY ("file", 1, 2, 1, func_file), #ifdef EXPERIMENTAL - { STRING_SIZE_TUPLE("eq"), 2, 2, 1, func_eq}, - { STRING_SIZE_TUPLE("not"), 0, 1, 1, func_not}, + FT_ENTRY ("eq", 2, 2, 1, func_eq), + FT_ENTRY ("not", 0, 1, 1, func_not), #endif }; @@ -2241,23 +2247,38 @@ static char * expand_builtin_function (char *o, int argc, char **argv, const struct function_table_entry *entry_p) { + char *p; + if (argc < (int)entry_p->minimum_args) fatal (*expanding_var, _("insufficient number of arguments (%d) to function '%s'"), argc, entry_p->name); - /* I suppose technically some function could do something with no - arguments, but so far none do, so just test it for all functions here + /* I suppose technically some function could do something with no arguments, + but so far no internal ones do, so just test it for all functions here rather than in each one. We can change it later if necessary. */ - if (!argc) + if (!argc && !entry_p->alloc_fn) return o; - if (!entry_p->func_ptr) + if (!entry_p->fptr.func_ptr) fatal (*expanding_var, _("unimplemented on this platform: function '%s'"), entry_p->name); - return entry_p->func_ptr (o, argv, entry_p->name); + if (!entry_p->alloc_fn) + return entry_p->fptr.func_ptr (o, argv, entry_p->name); + + /* This function allocates memory and returns it to us. + Write it to the variable buffer, then free it. */ + + p = entry_p->fptr.alloc_func_ptr (entry_p->name, argc, argv); + if (p) + { + o = variable_buffer_output (o, p, strlen (p)); + free (p); + } + + return o; } /* Check for a function invocation in *STRINGP. *STRINGP points at the @@ -2486,26 +2507,28 @@ func_call (char *o, char **argv, const char *funcname UNUSED) void define_new_function(const gmk_floc *flocp, const char *name, int min, int max, int expand, - char *(*func)(char *, char **, const char *)) + char *(*func)(const char *, int, char **)) { + struct function_table_entry *ent; size_t len = strlen (name); - struct function_table_entry *ent = xmalloc (sizeof (struct function_table_entry)); if (len > 255) fatal (flocp, _("Function name too long: %s\n"), name); if (min < 0 || min > 255) fatal (flocp, _("Invalid minimum argument count (%d) for function %s\n"), min, name); - if (max < 0 || max > 255 || max < min) + if (max < 0 || max > 255 || (max && max < min)) fatal (flocp, _("Invalid maximum argument count (%d) for function %s\n"), max, name); + ent = xmalloc (sizeof (struct function_table_entry)); ent->name = name; ent->len = len; ent->minimum_args = min; ent->maximum_args = max; ent->expand_args = expand ? 1 : 0; - ent->func_ptr = func; + ent->alloc_fn = 1; + ent->fptr.alloc_func_ptr = func; hash_insert (&function_table, ent); } diff --git a/gmk-default.scm b/gmk-default.scm index 84a21140..d33dfb2e 100644 --- a/gmk-default.scm +++ b/gmk-default.scm @@ -45,10 +45,6 @@ (walk x) (string-join (reverse! acc)))) -;; eval (GNU make eval) the input string S -(define (gmk-eval s) - (gmk-expand (format #f "$(eval ~a)" (obj-to-str s)))) - ;; Return the value of the GNU make variable V (define (gmk-var v) (gmk-expand (format #f "$(~a)" (obj-to-str v)))) diff --git a/gnumake.h b/gnumake.h index 1648b335..c6f7bd82 100644 --- a/gnumake.h +++ b/gnumake.h @@ -1,4 +1,6 @@ /* External interfaces usable by dynamic objects loaded into GNU Make. + --THIS API IS A "TECHNOLOGY PREVIEW" ONLY. IT IS NOT A STABLE INTERFACE-- + Copyright (C) 2013 Free Software Foundation, Inc. This file is part of GNU Make. @@ -24,4 +26,31 @@ typedef struct unsigned long lineno; } gmk_floc; + +/* Run $(eval ...) on the provided string BUFFER. */ +void gmk_eval (const char *buffer, const gmk_floc *floc); + +/* Run GNU make expansion on the provided string STR. + Returns an allocated buffer that the caller must free. */ +char *gmk_expand (const char *str); + +/* Register a new GNU make function NAME (maximum of 255 chars long). + When the function is expanded in the makefile, FUNC will be invoked with + the appropriate arguments. + + The return value of FUNC must be either NULL, in which case it expands to + the empty string, or a pointer to the result of the expansion in a string + created by malloc(). GNU make will free() the memory when it's done. + + MIN_ARGS is the minimum number of arguments the function requires. + MAX_ARGS is the maximum number of arguments (or 0 if there's no maximum). + MIN_ARGS and MAX_ARGS must be >= 0 and <= 255. + + If EXPAND_ARGS is 0, the arguments to the function will not be expanded + before FUNC is called. If EXPAND_ARGS is non-0, they will be expanded. +*/ +void gmk_add_function (const char *name, + char *(*func)(const char *nm, int argc, char **argv), + int min_args, int max_args, int expand_args); + #endif /* _GNUMAKE_H_ */ diff --git a/guile.c b/guile.c index 47a21f90..28fcf39b 100644 --- a/guile.c +++ b/guile.c @@ -14,6 +14,8 @@ A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include "gnumake.h" + #include "makeint.h" #include "debug.h" #include "dep.h" @@ -40,7 +42,7 @@ guile_expand_wrapper (SCM obj) char *res; DB (DB_BASIC, (_("guile: Expanding '%s'\n"), str)); - res = allocated_variable_expand (str); + res = gmk_expand (str); ret = scm_from_locale_string (res); free (str); @@ -49,6 +51,18 @@ guile_expand_wrapper (SCM obj) return ret; } +/* Perform the GNU make eval function. */ +static SCM +guile_eval_wrapper (SCM obj) +{ + char *str = cvt_scm_to_str (obj); + + DB (DB_BASIC, (_("guile: Evaluating '%s'\n"), str)); + gmk_eval (str, 0); + + return SCM_BOOL_F; +} + /* Invoked by scm_c_define_module(), in the context of the GNU make module. */ static void guile_define_module (void *data UNUSED) @@ -59,6 +73,9 @@ guile_define_module (void *data UNUSED) /* Register a subr for GNU make's eval capability. */ scm_c_define_gsubr ("gmk-expand", 1, 0, 0, guile_expand_wrapper); + /* Register a subr for GNU make's eval capability. */ + scm_c_define_gsubr ("gmk-eval", 1, 0, 0, guile_eval_wrapper); + /* Define the rest of the module. */ scm_c_eval_string (GUILE_module_defn); } @@ -87,19 +104,12 @@ internal_guile_eval (void *arg) /* This is the function registered with make */ static char * -func_guile (char *o, char **argv, const char *funcname UNUSED) +func_guile (const char *funcname UNUSED, int argc UNUSED, char **argv) { if (argv[0] && argv[0][0] != '\0') - { - char *str = scm_with_guile (internal_guile_eval, argv[0]); - if (str) - { - o = variable_buffer_output (o, str, strlen (str)); - free (str); - } - } + return scm_with_guile (internal_guile_eval, argv[0]); - return o; + return NULL; } /* ----- Public interface ----- */ @@ -113,7 +123,7 @@ guile_gmake_setup (const gmk_floc *flocp UNUSED) scm_with_guile (guile_init, NULL); /* Create a make function "guile". */ - define_new_function (NILF, "guile", 0, 1, 1, func_guile); + gmk_add_function ("guile", func_guile, 0, 1, 1); return 1; } diff --git a/load.c b/load.c index 0ed05f9a..081d66f0 100644 --- a/load.c +++ b/load.c @@ -24,7 +24,7 @@ this program. If not, see . */ #include #include -#define SYMBOL_EXTENSION "_gmake_setup" +#define SYMBOL_EXTENSION "_gmk_setup" static void *global_dl = NULL; diff --git a/loadapi.c b/loadapi.c new file mode 100644 index 00000000..3170dd12 --- /dev/null +++ b/loadapi.c @@ -0,0 +1,49 @@ +/* API for GNU Make dynamic objects. +Copyright (C) 2013 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . */ + +#include "gnumake.h" + +#include "makeint.h" + +#include "filedef.h" +#include "variable.h" + +/* Evaluate a buffer as make syntax. + Ideally eval_buffer() will take const char *, but not yet. */ +void +gmk_eval (const char *buffer, const gmk_floc *floc) +{ + char *s = xstrdup (buffer); + eval_buffer (s, floc); + free (s); +} + +/* Expand a string and return an allocated buffer. + Caller must free() this buffer. */ +char * +gmk_expand (const char *ref) +{ + return allocated_variable_expand (ref); +} + +/* Register a function to be called from makefiles. */ +void +gmk_add_function (const char *name, + char *(*func)(const char *nm, int argc, char **argv), + int min, int max, int expand) +{ + define_new_function (reading_file, name, min, max, expand, func); +} diff --git a/main.c b/main.c index d06825e4..0a2774cd 100644 --- a/main.c +++ b/main.c @@ -1648,7 +1648,7 @@ main (int argc, char **argv, char **envp) { p = xstrdup (eval_strings->list[i]); len += 2 * strlen (p); - eval_buffer (p); + eval_buffer (p, NULL); free (p); } diff --git a/read.c b/read.c index 68287b73..af5cf01d 100644 --- a/read.c +++ b/read.c @@ -435,7 +435,7 @@ eval_makefile (const char *filename, int flags) } void -eval_buffer (char *buffer) +eval_buffer (char *buffer, const gmk_floc *floc) { struct ebuffer ebuf; struct conditionals *saved; @@ -448,7 +448,9 @@ eval_buffer (char *buffer) ebuf.buffer = ebuf.bufnext = ebuf.bufstart = buffer; ebuf.fp = NULL; - if (reading_file) + if (floc) + ebuf.floc = *floc; + else if (reading_file) ebuf.floc = *reading_file; else ebuf.floc.filenm = NULL; diff --git a/tests/ChangeLog b/tests/ChangeLog index 2c1cb1b4..afa1cbe2 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,12 @@ +2013-02-25 Paul Smith + + * run_make_tests.pl (valid_option): Support the -srcdir flag. + (set_more_defaults): Set up $srcdir if it's not set yet. + + * scripts/functions/guile: Verify gmk-eval doesn't expand twice. + * scripts/features/load: Rework to test just the load capability. + * scripts/features/loadapi: New set of tests for the load API. + 2013-01-19 Paul Smith * scripts/features/load: Test loaded files with and without "./" diff --git a/tests/run_make_tests.pl b/tests/run_make_tests.pl index 4accd4aa..a596328c 100755 --- a/tests/run_make_tests.pl +++ b/tests/run_make_tests.pl @@ -35,6 +35,9 @@ $memcheck_args = '--num-callers=15 --tool=memcheck --leak-check=full --suppressi $massif_args = '--num-callers=15 --tool=massif --alloc-fn=xmalloc --alloc-fn=xcalloc --alloc-fn=xrealloc --alloc-fn=xstrdup --alloc-fn=xstrndup'; $pure_log = undef; +# The location of the GNU make source directory +$srcdir = ''; + $command_string = ''; $all_tests = 0; @@ -59,6 +62,15 @@ sub valid_option return 1; } + if ($option =~ /^-srcdir$/i) { + $srcdir = shift @argv; + if (! -f "$srcdir/gnumake.h") { + print "$option $srcdir: Not a valid GNU make source directory.\n"; + exit 0; + } + return 1; + } + if ($option =~ /^-all([-_]?tests)?$/i) { $all_tests = 1; return 1; @@ -226,14 +238,16 @@ sub run_make_with_options { sub print_usage { &print_standard_usage ("run_make_tests", - "[-make_path make_pathname] [-memcheck] [-massif]",); + "[-make MAKE_PATHNAME] [-srcdir SRCDIR] [-memcheck] [-massif]",); } sub print_help { &print_standard_help ( - "-make_path", + "-make", "\tYou may specify the pathname of the copy of make to run.", + "-srcdir", + "\tSpecify the make source directory.", "-valgrind", "-memcheck", "\tRun the test suite under valgrind's memcheck tool.", @@ -327,12 +341,8 @@ sub set_more_defaults $make_name = $1; } else { - if ($make_path =~ /$pathsep([^\n$pathsep]*)$/) { - $make_name = $1; - } - else { - $make_name = $make_path; - } + $make_path =~ /^(?:.*$pathsep)?(.+)$/; + $make_name = $1; } # prepend pwd if this is a relative path (ie, does not @@ -348,6 +358,15 @@ sub set_more_defaults $mkpath = $make_path; } + # If srcdir wasn't provided on the command line, see if the + # location of the make program gives us a clue. Don't fail if not; + # we'll assume it's been installed into /usr/include or wherever. + if (! $srcdir) { + $make_path =~ /^(.*$pathsep)?/; + my $d = $1 || '../'; + -f "${d}gnumake.h" and $srcdir = $d; + } + # Get Purify log info--if any. if (exists $ENV{PURIFYOPTIONS} diff --git a/tests/scripts/features/load b/tests/scripts/features/load index dd3daf80..47267fee 100644 --- a/tests/scripts/features/load +++ b/tests/scripts/features/load @@ -6,6 +6,8 @@ $details = "Test dynamic loading of modules."; # Don't do anything if this system doesn't support "load" exists $FEATURES{load} or return -1; +my $sobuild = '$(CC) '.($srcdir? "-I$srcdir":'').' -g -shared -fPIC -o $@ $<'; + # First build a shared object # Provide both a default and non-default load symbol @@ -16,67 +18,56 @@ print $F <<'EOF' ; #include #include -void define_new_function (void *, const char *, int, int, int, - char *(*)(char *, char **, const char *)); - -char *variable_buffer_output (char *, const char *, unsigned int); - -static char * -func_test(char *o, char **argv, const char *funcname) -{ - return variable_buffer_output (o, funcname, strlen (funcname)); -} +#include "gnumake.h" int -testload_gmake_setup () +testload_gmk_setup () { - define_new_function (0, "func-a", 1, 1, 1, func_test); + gmk_eval ("TESTLOAD = implicit", 0); return 1; } int explicit_setup () { - define_new_function (0, "func-b", 1, 1, 1, func_test); + gmk_eval ("TESTLOAD = explicit", 0); return 1; } EOF close($F) or die "close: testload.c: $!\n"; -run_make_test('testload.so: testload.c ; @$(CC) -g -shared -fPIC -o $@ $<', - '', ''); +# Make sure we can compile +run_make_test('testload.so: testload.c ; @'.$sobuild, '', ''); # TEST 1 run_make_test(q! -all: ; @echo $(func-a foo) $(func-b bar) +PRE := $(.LOADED) load testload.so +POST := $(.LOADED) +all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD) !, - '', "func-a\n"); + '', "pre= post=testload.so implicit\n"); # TEST 2 -# Load a different function +# Load using an explicit function run_make_test(q! -all: ; @echo $(func-a foo) $(func-b bar) +PRE := $(.LOADED) load ./testload.so(explicit_setup) +POST := $(.LOADED) +all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD) !, - '', "func-b\n"); - -# TEST 3 -# Verify the .LOADED variable -run_make_test(q! -all: ; @echo $(filter testload.so,$(.LOADED)) $(func-a foo) $(func-b bar) -load testload.so(explicit_setup) -!, - '', "testload.so func-b\n"); + '', "pre= post=testload.so explicit\n"); # TEST 4 # Check multiple loads run_make_test(q! -all: ; @echo $(filter testload.so,$(.LOADED)) $(func-a foo) $(func-b bar) +PRE := $(.LOADED) load ./testload.so load testload.so(explicit_setup) +POST := $(.LOADED) +all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD) !, - '', "testload.so func-a\n"); + '', "pre= post=testload.so implicit\n"); # TEST 5 # Check auto-rebuild of loaded file that's out of date @@ -84,22 +75,24 @@ utouch(-10, 'testload.so'); touch('testload.c'); run_make_test(q! -all: ; @echo $(func-a foo) $(func-b bar) +PRE := $(.LOADED) load ./testload.so -testload.so: testload.c ; @echo "rebuilding $@"; $(CC) -g -shared -fPIC -o $@ $< -!, - '', "rebuilding testload.so\nfunc-a\n"); +POST := $(.LOADED) +all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD) +testload.so: testload.c ; @echo "rebuilding $@"; !.$sobuild, + '', "rebuilding testload.so\npre= post=testload.so implicit\n"); # TEST 5 # Check auto-rebuild of loaded file when it doesn't exist unlink('testload.so'); run_make_test(q! -all: ; @echo $(func-a foo) $(func-b bar) +PRE := $(.LOADED) -load ./testload.so(explicit_setup) -%.so: %.c ; @echo "rebuilding $@"; $(CC) -g -shared -fPIC -o $@ $< -!, - '', "rebuilding testload.so\nfunc-b\n"); +POST := $(.LOADED) +all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD) +%.so: %.c ; @echo "rebuilding $@"; !.$sobuild, + '', "rebuilding testload.so\npre= post=testload.so explicit\n"); unlink(qw(testload.c testload.so)) unless $keep; diff --git a/tests/scripts/features/loadapi b/tests/scripts/features/loadapi new file mode 100644 index 00000000..cecb114f --- /dev/null +++ b/tests/scripts/features/loadapi @@ -0,0 +1,84 @@ +# -*-perl-*- +$description = "Test the shared object load API."; + +$details = "Verify the different aspects of the shared object API."; + +# Don't do anything if this system doesn't support "load" +exists $FEATURES{load} or return -1; + +my $sobuild = '$(CC) '.($srcdir? "-I$srcdir":'').' -g -shared -fPIC -o $@ $<'; + +# First build a shared object +# Provide both a default and non-default load symbol + +unlink(qw(testapi.c testapi.so)); + +open(my $F, '> testapi.c') or die "open: testapi.c: $!\n"; +print $F <<'EOF' ; +#include +#include + +#include "gnumake.h" + +static char * +test_eval (const char *buf) +{ + gmk_eval (buf, 0); + return NULL; +} + +static char * +test_expand (const char *val) +{ + return gmk_expand (val); +} + +static char * +func_test (const char *funcname, int argc, char **argv) +{ + if (strcmp (funcname, "test-expand") == 0) + return test_expand (argv[0]); + + if (strcmp (funcname, "test-eval") == 0) + return test_eval (argv[0]); + + return strdup ("unknown"); +} + +int +testapi_gmk_setup () +{ + gmk_add_function ("test-expand", func_test, 1, 1, 1); + gmk_add_function ("test-eval", func_test, 1, 1, 1); + return 1; +} +EOF +close($F) or die "close: testapi.c: $!\n"; + +run_make_test('testapi.so: testapi.c ; @'.$sobuild, '', ''); + +# TEST 1 +# Check the gmk_expand() function +run_make_test(q! +EXPAND = expansion +all: ; @echo $(test-expand $$(EXPAND)) +load testapi.so +!, + '', "expansion\n"); + +# TEST 2 +# Check the eval operation. Prove that the argument is expanded only once +run_make_test(q! +load testapi.so +TEST = bye +ASSIGN = VAR = $(TEST) $(shell echo there) +$(test-eval $(value ASSIGN)) +TEST = hi +all:;@echo '$(VAR)' +!, + '', "hi there\n"); + +unlink(qw(testapi.c testapi.so)) unless $keep; + +# This tells the test driver that the perl test script executed properly. +1; diff --git a/tests/scripts/functions/guile b/tests/scripts/functions/guile index 93a18ab6..c63bec9b 100644 --- a/tests/scripts/functions/guile +++ b/tests/scripts/functions/guile @@ -44,8 +44,12 @@ x:;@echo '$(VAR)' '', "hi"); # Verify the gmk-eval function +# Prove that the string is expanded only once (by eval) run_make_test(q! -$(guile (gmk-eval "VAR = hi $(shell echo there)")) +TEST = bye +EVAL = VAR = $(TEST) $(shell echo there) +$(guile (gmk-eval "$(value EVAL)")) +TEST = hi x:;@echo '$(VAR)' !, '', "hi there"); @@ -80,7 +84,7 @@ define fib ;; A procedure for counting the n:th Fibonacci number ;; See SICP, p. 37 (define (fib n) - (cond ((= n 0) 0) + (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))))) diff --git a/variable.h b/variable.h index ac096d45..9b5d6130 100644 --- a/variable.h +++ b/variable.h @@ -167,7 +167,7 @@ void init_hash_global_variable_set (void); void hash_init_function_table (void); void define_new_function(const gmk_floc *flocp, const char *name, int min, int max, int expand, - char *(*func)(char *, char **, const char *)); + char *(*func)(const char *, int, char **)); struct variable *lookup_variable (const char *name, unsigned int length); struct variable *lookup_variable_in_set (const char *name, unsigned int length, const struct variable_set *set);