From 7670c84f7732db29f5a9d9c145c2327f4b575f91 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Mon, 29 Oct 2012 07:05:21 +0000 Subject: Implement new "load" directive. Provides support for dynamically loadable objects in GNU make, as a "technology preview". --- ChangeLog | 12 + Makefile.am | 2 +- NEWS | 7 +- configure.in | 63 +++- doc/make.texi | 593 +++++++++++++++++++++++-------------- guile.c | 7 +- load.c | 157 ++++++++++ main.c | 13 +- make.h | 7 +- read.c | 73 ++++- tests/ChangeLog | 4 + tests/run_make_tests.pl | 29 +- tests/scripts/features/load | 84 ++++++ tests/scripts/features/parallelism | 2 +- tests/scripts/functions/guile | 14 + 15 files changed, 807 insertions(+), 260 deletions(-) create mode 100644 load.c create mode 100644 tests/scripts/features/load diff --git a/ChangeLog b/ChangeLog index 0b7e3d9..fb91f7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2012-10-29 Paul Smith + + New feature: "load" directive for dynamically-loaded objects. + + * NEWS: Document new "load" directive. + * doc/make.texi (Extending make): New chapter on extensions to make. + * configure.in: Check for dlopen/dlsym/dlerror and -ldl. + * Makefile.am (make_SOURCES): Add new file load.c. + * make.h: Prototype for load_file(). + * main.c (main): Add "load" to .FEATURES if it's available. + * read.c (eval): Parse "load" and "-load" directives. + 2012-09-29 Paul Smith * configure.in: Require a new version of gettext (1.18.1). diff --git a/Makefile.am b/Makefile.am index 592de9f..47f2919 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ 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 main.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 \ strcache.c variable.c version.c vpath.c hash.c \ $(remote) diff --git a/NEWS b/NEWS index 63b5f01..3e48acc 100644 --- a/NEWS +++ b/NEWS @@ -9,7 +9,7 @@ manual, which is contained in this distribution as the file doc/make.texi. See the README file and the GNU make manual for instructions for reporting bugs. -Version 3.82.90 +Version 3.99.90 A complete list of bugs fixed in this version is available here: @@ -50,6 +50,11 @@ http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=101&set GNU Guile serves as an embedded extension language for make. See the "Guile Function" section in the GNU Make manual for details. +* New feature: Loadable objects + This version of GNU make contains a "technology preview": the ability to + load dynamic objects into the make runtime. These objects can be created by + the user and can add extended functionality, usable by makefiles. + * New function: $(file ...) writes to a file. * On failure, the makefile name and linenumber of the recipe that failed are diff --git a/configure.in b/configure.in index f8c2482..7c06757 100644 --- a/configure.in +++ b/configure.in @@ -252,9 +252,7 @@ AS_IF([test "$PATH_SEPARATOR" = ';'], [Define to 1 if your system requires backslashes or drive specs in pathnames.]) ]) - # See if the user wants to use pmake's "customs" distributed build capability - AC_SUBST([REMOTE]) REMOTE=stub use_customs=false AC_ARG_WITH([customs], @@ -280,7 +278,6 @@ AC_ARG_WITH([customs], AM_CONDITIONAL([USE_CUSTOMS], [test "$use_customs" = true]) # See if the user asked to handle case insensitive file systems. - AH_TEMPLATE([HAVE_CASE_INSENSITIVE_FS], [Use case insensitive file names]) AC_ARG_ENABLE([case-insensitive-file-system], AC_HELP_STRING([--enable-case-insensitive-file-system], @@ -288,7 +285,6 @@ AC_ARG_ENABLE([case-insensitive-file-system], [AS_IF([test "$enableval" = yes], [AC_DEFINE([HAVE_CASE_INSENSITIVE_FS])])]) # See if we can handle the job server feature, and if the user wants it. - AC_ARG_ENABLE([job-server], AC_HELP_STRING([--disable-job-server], [disallow recursive make communication during -jN]), @@ -324,11 +320,57 @@ AS_CASE([/$make_cv_job_server/$user_job_server/], [Define to 1 to enable job server support in GNU make.]) ]) +# If dl*() functions are supported we can enable the load operation +AC_CHECK_DECLS([dlopen, dlsym, dlerror], [], [], + [[#include ]]) + +AC_ARG_ENABLE([load], + AC_HELP_STRING([--disable-load], + [disable support for the 'load' operation]), + [make_cv_load="$enableval" user_load="$enableval"], + [make_cv_load="yes"]) + +AS_CASE([/$ac_cv_func_dlopen/$ac_cv_func_dlsym/$ac_cv_func_dlerror/], + [*/no/*], [make_cv_load=no]) + +AS_CASE([/$make_cv_load/$user_load/], + [*/no/*], [make_cv_load=no], + [AC_DEFINE(MAKE_LOAD, 1, + [Define to 1 to enable 'load' support in GNU make.]) + ]) + +# We might need -ldl +AS_IF([test "$make_cv_load" = yes], [ + AC_SEARCH_LIBS([dlopen], [dl], [], [make_cv_load=]) + ]) + +# If we want load support, we might need to link with export-dynamic. +# See if we can figure it out. Unfortunately this is very difficult. +# For example passing -rdynamic to the SunPRO linker gives a warning +# but succeeds and creates a shared object, not an executable! +AS_IF([test "$make_cv_load" = yes], [ + AC_MSG_CHECKING([If the linker accepts -Wl,--export-dynamic]) + old_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + AC_LINK_IFELSE([int main(){}], + [AC_MSG_RESULT([yes]) + AC_SUBST([AM_LDFLAGS], [-Wl,--export-dynamic])], + [AC_MSG_RESULT([no]) + AC_MSG_CHECKING([If the linker accepts -rdynamic]) + LDFLAGS="$old_LDFLAGS -rdynamic" + AC_LINK_IFELSE([int main(){}], + [AC_MSG_RESULT([yes]) + AC_SUBST([AM_LDFLAGS], [-rdynamic])], + [AC_MSG_RESULT([no])]) + ]) + LDFLAGS="$old_LDFLAGS" +]) + # if we have both lstat() and readlink() then we can support symlink # timechecks. AS_IF([test "$ac_cv_func_lstat" = yes && test "$ac_cv_func_readlink" = yes], -[ AC_DEFINE([MAKE_SYMLINKS], [1], - [Define to 1 to enable symbolic link timestamp checking.]) + [ AC_DEFINE([MAKE_SYMLINKS], [1], + [Define to 1 to enable symbolic link timestamp checking.]) ]) # Find the SCCS commands, so we can include them in our default rules. @@ -458,6 +500,15 @@ AS_IF([test "x$make_cv_job_server" = xno && test "x$user_job_server" = xyes], echo ]) +AS_IF([test "x$make_cv_load" = xno && test "x$user_load" = xyes], +[ echo + echo "WARNING: 'load' support requires a POSIX-ish system that" + echo " supports the dlopen(), dlsym(), and dlerror() functions." + echo " Your system doesn't appear to provide one or more of these." + echo " Disabling 'load' support." + echo +]) + # Specify what files are to be created. AC_CONFIG_FILES([Makefile glob/Makefile po/Makefile.in config/Makefile \ doc/Makefile w32/Makefile]) diff --git a/doc/make.texi b/doc/make.texi index 67f45c6..3a16dd3 100644 --- a/doc/make.texi +++ b/doc/make.texi @@ -100,6 +100,7 @@ Cover art by Etienne Suvasa. * Implicit Rules:: Use implicit rules to treat many files alike, based on their file names. * Archives:: How @code{make} can update library archives. +* Extending make:: Using extensions to @code{make}. * Features:: Features GNU @code{make} has over other @code{make}s. * Missing:: What GNU @code{make} lacks from other @code{make}s. * Makefile Conventions:: Conventions for writing makefiles for @@ -277,13 +278,7 @@ Functions for Transforming Text * Flavor Function:: Find out the flavor of a variable. * Make Control Functions:: Functions that control how make runs. * Shell Function:: Substitute the output of a shell command. -* Guile Function:: Call the GNU Guile embedded scripting language. - -The @code{guile} Function - -* Guile Types:: Converting Guile types to @code{make} strings. -* Guile Interface:: Invoking @code{make} functions from Guile. -* Guile Example:: Example using Guile in @code{make}. +* Guile Function:: Use GNU Guile embedded scripting language. How to Run @code{make} @@ -339,6 +334,21 @@ Implicit Rule for Archive Member Targets * Archive Symbols:: How to update archive symbol directories. +Extending GNU @code{make} + +* Guile Integration:: Using Guile as an embedded scripting language. +* Loading Objects:: Loading dynamic objects as extensions. + +GNU Guile Integration + +* Guile Types:: Converting Guile types to @code{make} strings. +* Guile Interface:: Invoking @code{make} functions from Guile. +* Guile Example:: Example using Guile in @code{make}. + +Loading Dynamic Objects + +* load Directive:: Loading dynamic objects as extensions. + @end detailmenu @end menu @@ -6280,7 +6290,11 @@ Supports the @code{undefine} directive. @xref{Undefine Directive}. @item guile Has GNU Guile available as an embedded extension language. -@xref{Guile Function}. +@xref{Guile Integration, ,GNU Guile Integration}. + +@item load +Supports dynamically loadable objects for creating custom extensions. +@xref{Loading Objects, ,Loading Dynamic Objects}. @end table @@ -6422,12 +6436,12 @@ endif or: @example -@var{conditional-directive} +@var{conditional-directive-one} @var{text-if-one-is-true} -else @var{conditional-directive} -@var{text-if-true} +else @var{conditional-directive-two} +@var{text-if-two-is-true} else -@var{text-if-false} +@var{text-if-one-and-two-are-false} endif @end example @@ -6631,7 +6645,7 @@ be substituted. * Flavor Function:: Find out the flavor of a variable. * Make Control Functions:: Functions that control how make runs. * Shell Function:: Substitute the output of a shell command. -* Guile Function:: Call the GNU Guile embedded scripting language. +* Guile Function:: Use GNU Guile embedded scripting language. @end menu @node Syntax of Functions, Text Functions, Functions, Functions @@ -7896,208 +7910,17 @@ exists).@refill @findex guile @cindex Guile -GNU make may be built with support for GNU Guile as an embedded -extension language. You can check the @code{.FEATURES} variable for -the word @samp{guile} to determine if your version of GNU make -provides this capability. - -GNU Guile implements the Scheme language. A review of GNU Guile and -the Scheme language and its features is beyond the scope of this -manual: see the documentation for GNU Guile and Scheme. - -If GNU Guile is available as an extension language, there will be one -new @code{make} function available: @code{guile}. The @code{guile} -function takes one argument which is first expanded by @code{make} in -the normal fashion, then passed to the GNU Guile evaluator. The -result of the evaluator is converted into a string and used as the -expansion of the @code{guile} function in the makefile. - -Similarly, there are Guile procedures exposed by @code{make} for use -in Guile scripts. - -@menu -* Guile Types:: Converting Guile types to @code{make} strings. -* Guile Interface:: Invoking @code{make} functions from Guile. -* Guile Example:: Example using Guile in @code{make}. -@end menu - -@node Guile Types, Guile Interface, Guile Function, Guile Function -@subsection Conversion of Guile Types -@cindex convert guile types -@cindex guile, conversion of types -@cindex types, conversion of - -There is only one ``data type'' in @code{make}: a string. GNU Guile, -on the other hand, provides a rich variety of different data types. -An important aspect of the interface between @code{make} and GNU Guile -is the conversion of Guile data types into @code{make} strings. - -This conversion is relevant in two places: when a makefile invokes the -@code{guile} function to evaluate a Guile expression, the result of -that evaluation must be converted into a make string so it can be -further evaluated by @code{make}. And secondly, when a Guile script -invokes one of the procedures exported by @code{make} the argument -provided to the procedure must be converted into a string. - -The conversion of Guile types into @code{make} strings is as below: - -@table @code -@item #f -False is converted into the empty string: in @code{make} conditionals -the empty string is considered false. - -@item #t -True is converted to the string @samp{#t}: in @code{make} conditionals -any non-empty string is considered true. - -@item symbol -@item number -A symbol or number is converted into the string representation of that -symbol or number. - -@item character -A printable character is converted to the same character. - -@item string -A string containing only printable characters is converted to the same -string. - -@item list -A list is converted recursively according to the above rules. This -implies that any structured list will be flattened (that is, a result -of @samp{'(a b (c d) e)} will be converted to the @code{make} string -@samp{a b c d e}). - -@item other -Any other Guile type results in an error. In future versions of -@code{make}, other Guile types may be converted. - -@end table - -The translation of @samp{#f} (to the empty string) and @samp{#t} (to -the non-empty string @samp{#t}) is designed to allow you to use Guile -boolean results directly as @code{make} boolean conditions. For -example: - -@example -$(if $(guile (access? "myfile" R_OK)),$(info myfile exists)) -@end example - -As a consequence of these conversion rules you must consider the -result of your Guile script, as that result will be converted into a -string and parsed by @code{make}. If there is no natural result for -the script (that is, the script exists solely for its side-effects), -you should add @samp{#f} as the final expression in order to avoid -syntax errors in your makefile. - -@node Guile Interface, Guile Example, Guile Types, Guile Function -@subsection Interfaces from Guile to @code{make} -@cindex make interface to guile -@cindex make procedures in guile - -In addition to the @code{guile} function available in makefiles, -@code{make} exposes some procedures for use in your Guile scripts. At -startup @code{make} creates a new Guile module, @code{gnu make}, and -exports these procedures as public interfaces from that module: - -@table @code -@item 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 -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. - -@end table - -@node Guile Example, , Guile Interface, Guile Function -@subsection Example Using Guile in @code{make} -@cindex Guile example -@cindex example using Guile - -Here is a very simple example using GNU Guile to manage writing to a -file. These Guile procedures simply open a file, allow writing to the -file (one string per line), and close the file. Note that because we -cannot store complex values such as Guile ports in @code{make} -variables, we'll keep the port as a global variable in the Guile -interpreter. - -You can create Guile functions easily using @code{define}/@code{endef} -to create a Guile script, then use the @code{guile} function to -internalize it: - -@example -@group -define GUILEIO -;; A simple Guile IO library for GNU make - -(define MKPORT #f) - -(define (mkopen name mode) - (set! MKPORT (open-file name mode)) - #f) - -(define (mkwrite s) - (display s MKPORT) - (newline MKPORT) - #f) - -(define (mkclose) - (close-port MKPORT) - #f) - -#f -endef - -# Internalize the Guile IO functions -$(guile $(GUILEIO)) -@end group -@end example - -If you have a significant amount of Guile support code, you might -consider keeping it in a different file (e.g., @file{guileio.scm}) and -then loading it in your makefile using the @code{guile} function: - -@example -$(guile (load "guileio.scm")) -@end example - -An advantage to this method is that when editing @file{guileio.scm}, -your editor will understand that this file contains Scheme syntax -rather than makefile syntax. - -Now you can use these Guile functions to create files. Suppose you -need to operate on a very large list, which cannot fit on the command -line, but the utility you're using accepts the list as input as well: - -@example -@group -prog: $(PREREQS) - @@$(guile (mkopen "tmp.out" "w")) \ - $(foreach X,$^,$(guile (mkwrite "$(X)"))) \ - $(guile (mkclose)) - $(LINK) < tmp.out -@end group -@end example - -A more comprehensive suite of file manipulation procedures is possible -of course. You could, for example, maintain multiple output files at -the same time by choosing a symbol for each one and using it as the -key to a hash table, where the value is a port, then returning the -symbol to be stored in a @code{make} variable. +If GNU @code{make} is built with support for GNU Guile as an embedded +extension language then the @code{guile} function will be available. +The @code{guile} function takes one argument which is first expanded +by @code{make} in the normal fashion, then passed to the GNU Guile +evaluator. The result of the evaluator is converted into a string and +used as the expansion of the @code{guile} function in the makefile. +See @ref{Guile Integration, ,GNU Guile Integration} for details on +writing extensions to @code{make} in Guile. +You can determine whether GNU Guile support is available by checking +the @code{.FEATURES} variable for the word @var{guile}. @node Running, Implicit Rules, Functions, Top @chapter How to Run @code{make} @@ -10476,7 +10299,7 @@ When the recipe of a pattern rule is executed for @var{t}, the automatic variables are set corresponding to the target and prerequisites. @xref{Automatic Variables}. -@node Archives, Features, Implicit Rules, Top +@node Archives, Extending make, Implicit Rules, Top @chapter Using @code{make} to Update Archive Files @cindex archive @@ -10703,7 +10526,345 @@ in the normal way (@pxref{Suffix Rules}). Thus a double-suffix rule @w{@samp{.@var{x}.a}} produces two pattern rules: @samp{@w{(%.o):} @w{%.@var{x}}} and @samp{@w{%.a}: @w{%.@var{x}}}.@refill -@node Features, Missing, Archives, Top +@node Extending make, Features, Archives, Top +@chapter Extending GNU @code{make} +@cindex make extensions + +GNU @code{make} provides many advanced capabilities, including many +useful functions. However, it does not contain a complete programming +language and so it has limitations. Sometimes these limitations can be +overcome through use of the @code{shell} function to invoke a separate +program, although this can be inefficient. + +In cases where the built-in capabilities of GNU @code{make} are +insufficient to your requirements there are two options for extending +@code{make}. On systems where it's provided, you can utilize GNU +Guile as an embedded scripting language (@pxref{Guile Integration, +,GNU Guile Integration}). On systems which support dynamically +loadable objects, you can write your own extension in any language +(which can be compiled into such an object) and load it to provide +extended capabilities (@pxref{load Directive, ,The @code{load} Directive}). + +@menu +* Guile Integration:: Using Guile as an embedded scripting language. +* Loading Objects:: Loading dynamic objects as extensions. +@end menu + +@node Guile Integration, Loading Objects, Extending make, Extending make +@section GNU Guile Integration +@cindex Guile +@cindex extensions, Guile + +GNU @code{make} may be built with support for GNU Guile as an embedded +extension language. Guile implements the Scheme language. A review +of GNU Guile and the Scheme language and its features is beyond the +scope of this manual: see the documentation for GNU Guile and Scheme. + +You can determine if @code{make} contains support for Guile by +examining the @code{.FEATURES} variable; it will contain the word +@var{guile} if Guile support is available. + +The Guile integration provides one new @code{make} function: @code{guile}. +The @code{guile} function takes one argument which is first expanded +by @code{make} in the normal fashion, then passed to the GNU Guile +evaluator. The result of the evaluator is converted into a string and +used as the expansion of the @code{guile} function in the makefile. + +In addition, GNU @code{make} exposes Guile procedures for use in Guile +scripts. + +@menu +* Guile Types:: Converting Guile types to @code{make} strings. +* Guile Interface:: Invoking @code{make} functions from Guile. +* Guile Example:: Example using Guile in @code{make}. +@end menu + +@node Guile Types, Guile Interface, Guile Integration, Guile Integration +@subsection Conversion of Guile Types +@cindex convert guile types +@cindex guile, conversion of types +@cindex types, conversion of + +There is only one ``data type'' in @code{make}: a string. GNU Guile, +on the other hand, provides a rich variety of different data types. +An important aspect of the interface between @code{make} and GNU Guile +is the conversion of Guile data types into @code{make} strings. + +This conversion is relevant in two places: when a makefile invokes the +@code{guile} function to evaluate a Guile expression, the result of +that evaluation must be converted into a make string so it can be +further evaluated by @code{make}. And secondly, when a Guile script +invokes one of the procedures exported by @code{make} the argument +provided to the procedure must be converted into a string. + +The conversion of Guile types into @code{make} strings is as below: + +@table @code +@item #f +False is converted into the empty string: in @code{make} conditionals +the empty string is considered false. + +@item #t +True is converted to the string @samp{#t}: in @code{make} conditionals +any non-empty string is considered true. + +@item symbol +@item number +A symbol or number is converted into the string representation of that +symbol or number. + +@item character +A printable character is converted to the same character. + +@item string +A string containing only printable characters is converted to the same +string. + +@item list +A list is converted recursively according to the above rules. This +implies that any structured list will be flattened (that is, a result +of @samp{'(a b (c d) e)} will be converted to the @code{make} string +@samp{a b c d e}). + +@item other +Any other Guile type results in an error. In future versions of +@code{make}, other Guile types may be converted. + +@end table + +The translation of @samp{#f} (to the empty string) and @samp{#t} (to +the non-empty string @samp{#t}) is designed to allow you to use Guile +boolean results directly as @code{make} boolean conditions. For +example: + +@example +$(if $(guile (access? "myfile" R_OK)),$(info myfile exists)) +@end example + +As a consequence of these conversion rules you must consider the +result of your Guile script, as that result will be converted into a +string and parsed by @code{make}. If there is no natural result for +the script (that is, the script exists solely for its side-effects), +you should add @samp{#f} as the final expression in order to avoid +syntax errors in your makefile. + +@node Guile Interface, Guile Example, Guile Types, Guile Integration +@subsection Interfaces from Guile to @code{make} +@cindex make interface to guile +@cindex make procedures in guile + +In addition to the @code{guile} function available in makefiles, +@code{make} exposes some procedures for use in your Guile scripts. At +startup @code{make} creates a new Guile module, @code{gnu make}, and +exports these procedures as public interfaces from that module: + +@table @code +@item 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 +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. + +@end table + +@node Guile Example, , Guile Interface, Guile Integration +@subsection Example Using Guile in @code{make} +@cindex Guile example +@cindex example using Guile + +Here is a very simple example using GNU Guile to manage writing to a +file. These Guile procedures simply open a file, allow writing to the +file (one string per line), and close the file. Note that because we +cannot store complex values such as Guile ports in @code{make} +variables, we'll keep the port as a global variable in the Guile +interpreter. + +You can create Guile functions easily using @code{define}/@code{endef} +to create a Guile script, then use the @code{guile} function to +internalize it: + +@example +@group +define GUILEIO +;; A simple Guile IO library for GNU make + +(define MKPORT #f) + +(define (mkopen name mode) + (set! MKPORT (open-file name mode)) + #f) + +(define (mkwrite s) + (display s MKPORT) + (newline MKPORT) + #f) + +(define (mkclose) + (close-port MKPORT) + #f) + +#f +endef + +# Internalize the Guile IO functions +$(guile $(GUILEIO)) +@end group +@end example + +If you have a significant amount of Guile support code, you might +consider keeping it in a different file (e.g., @file{guileio.scm}) and +then loading it in your makefile using the @code{guile} function: + +@example +$(guile (load "guileio.scm")) +@end example + +An advantage to this method is that when editing @file{guileio.scm}, +your editor will understand that this file contains Scheme syntax +rather than makefile syntax. + +Now you can use these Guile functions to create files. Suppose you +need to operate on a very large list, which cannot fit on the command +line, but the utility you're using accepts the list as input as well: + +@example +@group +prog: $(PREREQS) + @@$(guile (mkopen "tmp.out" "w")) \ + $(foreach X,$^,$(guile (mkwrite "$(X)"))) \ + $(guile (mkclose)) + $(LINK) < tmp.out +@end group +@end example + +A more comprehensive suite of file manipulation procedures is possible +of course. You could, for example, maintain multiple output files at +the same time by choosing a symbol for each one and using it as the +key to a hash table, where the value is a port, then returning the +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 extensions, loading + +@cartouche +@quotation Warning +The @code{load} directive and extension capability is considered a +``technology preview'' in this release of GNU make. We encourage you +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 + +Many operating systems provide a facility for dynamically loading +compiled objects. If your system provides this facility, GNU +@code{make} can make use of it to load dynamic objects at runtime, +providing new capabilities which may then be invoked by your makefile. + +The @code{load} directive is used to load a dynamic object. Once the +object is loaded, a ``setup'' function will be invoked to allow the +object to initialize itself and register new facilities with GNU +@code{make}. Typically a dynamic object would create new functions, +for example, and the ``setup'' function would register them with GNU +@code{make}'s function handling system. + +@menu +* load Directive:: Loading dynamic objects as extensions. +@end menu + +@node load Directive, , Loading Objects, Loading Objects +@subsection The @code{load} Directive +@cindex load directive +@cindex extensions, load directive + +Objects are loaded into GNU @code{make} by placing the @code{load} +directive into your makefile. The syntax of the @code{load} directive +is as follows: + +@findex load +@example +load @var{object-file} @dots{} +@end example + +or: + +@example +load @var{object-file}(@var{symbol-name}) @dots{} +@end example + +In the first form, the file @var{object-file} is dynamically loaded by +GNU @code{make}. On failure, @code{make} will print a message and +exit. If the load succeeds @code{make} will invoke an initializing +function whose 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}, then this symbol will be invoked. + +In the second form, the function @var{symbol-name} will be invoked. + +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 +same directive. + +For example: + +@example +load ../mk_funcs.so +@end example + +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}. + +On the other hand: + +@example +load ../mk_funcs.so(init_mk_func) +@end example + +will load the dynamic object @file{../mk_funcs.so}. After the object +is loaded, @code{make} will invoke the function @code{init_mk_func}. + +Regardless of how many times an object file appears in a @code{load} +directive, it will only be loaded (and it's setup function will only +be invoked) once. + +@vindex .LOADED +After an object has been successfully loaded, its file name is +appended to the @code{.LOADED} variable. + +@findex -load +If you would prefer that failure to load a dynamic object not be +reported as an error, you can use the @code{-load} directive instead +of @code{load}. GNU @code{make} will not fail and no message will be +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 Features, Missing, Extending make, Top @chapter Features of GNU @code{make} @cindex features of GNU @code{make} @cindex portability diff --git a/guile.c b/guile.c index edba6d6..9c9d958 100644 --- a/guile.c +++ b/guile.c @@ -104,8 +104,10 @@ func_guile (char *o, char **argv, const char *funcname UNUSED) /* ----- Public interface ----- */ +/* We could send the flocp to define_new_function(), but since guile is + "kind of" built-in, that didn't seem so useful. */ int -setup_guile () +guile_gmake_setup (const struct floc *flocp UNUSED) { /* Initialize the Guile interpreter. */ scm_with_guile (guile_init, NULL); @@ -113,8 +115,5 @@ setup_guile () /* Create a make function "guile". */ define_new_function (NILF, "guile", 0, 1, 1, func_guile); - /* Add 'guile' to the list of features. */ - do_variable_definition (NILF, ".FEATURES", "guile", o_default, f_append, 0); - return 1; } diff --git a/load.c b/load.c new file mode 100644 index 0000000..076dd30 --- /dev/null +++ b/load.c @@ -0,0 +1,157 @@ +/* Loading dynamic objects for GNU Make. +Copyright (C) 2012 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 "make.h" + +#if MAKE_LOAD + +#include +#include +#include +#include +#include + +#define SYMBOL_EXTENSION "_gmake_setup" + +static void *global_dl = NULL; + +#include "debug.h" +#include "filedef.h" +#include "variable.h" + +static int +init_symbol (const struct floc *flocp, const char *ldname, load_func_t symp) +{ + int r; + const char *p; + int nmlen = strlen (ldname); + char *loaded = allocated_variable_expand("$(.LOADED)"); + + /* If it's already been loaded don't do it again. */ + p = strstr (loaded, ldname); + r = p && (p==loaded || p[-1]==' ') && (p[nmlen]=='\0' || p[nmlen]==' '); + free (loaded); + if (r) + return 1; + + /* Now invoke the symbol. */ + r = (*symp) (flocp); + + /* If it succeeded, add the symbol to the loaded variable. */ + if (r > 0) + do_variable_definition (flocp, ".LOADED", ldname, o_default, f_append, 0); + + return r; +} + +int +load_file (const struct floc *flocp, const char *ldname, int noerror) +{ + load_func_t symp; + const char *fp; + char *symname = NULL; + char *new = alloca (strlen (ldname) + CSTRLEN (SYMBOL_EXTENSION) + 1); + + if (! global_dl) + { + global_dl = dlopen (NULL, RTLD_NOW|RTLD_GLOBAL); + if (! global_dl) + fatal (flocp, _("Failed to open global symbol table: %s"), dlerror()); + } + + /* If a symbol name was provided, use it. */ + fp = strchr (ldname, '('); + if (fp) + { + const char *ep; + + /* If there's an open paren, see if there's a close paren: if so use + that as the symbol name. We can't have whitespace: it would have + been chopped up before this function is called. */ + ep = strchr (fp+1, ')'); + if (ep && ep[1] == '\0') + { + int l = fp - ldname;; + + ++fp; + if (fp == ep) + fatal (flocp, _("Empty symbol name for load: %s"), ldname); + + /* Make a copy of the ldname part. */ + memcpy (new, ldname, l); + new[l] = '\0'; + ldname = new; + + /* Make a copy of the symbol name part. */ + symname = new + l + 1; + memcpy (symname, fp, ep - fp); + symname[ep - fp] = '\0'; + } + } + + /* If we didn't find a symbol name yet, construct it from the ldname. */ + if (! symname) + { + char *p = new; + + fp = strrchr (ldname, '/'); + if (!fp) + fp = ldname; + else + ++fp; + while (isalnum (*fp) || *fp == '_') + *(p++) = *(fp++); + strcpy (p, SYMBOL_EXTENSION); + symname = new; + } + + DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), symname, ldname)); + + /* See if it's already defined. */ + symp = (load_func_t) dlsym (global_dl, symname); + if (! symp) { + void *dlp = dlopen (ldname, RTLD_LAZY|RTLD_GLOBAL); + if (! dlp) + { + if (noerror) + DB (DB_BASIC, ("%s", dlerror())); + else + error (flocp, "%s", dlerror()); + return 0; + } + + symp = dlsym (dlp, symname); + if (! symp) + fatal (flocp, _("Failed to load symbol %s from %s: %s"), + symname, ldname, dlerror()); + } + + /* Invoke the symbol to initialize the loaded object. */ + return init_symbol(flocp, ldname, symp); +} + +#else + +int +load_file (const struct floc *flocp, const char *ldname, int noerror) +{ + if (! noerror) + fatal (flocp, _("The 'load' operation is not supported on this platform.")); + + return 0; +} + +#endif /* MAKE_LOAD */ diff --git a/main.c b/main.c index a6d1454..7bcc07a 100644 --- a/main.c +++ b/main.c @@ -1148,6 +1148,12 @@ main (int argc, char **argv, char **envp) #endif #ifdef MAKE_SYMLINKS " check-symlink" +#endif +#ifdef HAVE_GUILE + " guile" +#endif +#ifdef MAKE_LOAD + " load" #endif ; @@ -1156,7 +1162,7 @@ main (int argc, char **argv, char **envp) #ifdef HAVE_GUILE /* Configure GNU Guile support */ - setup_guile (); + guile_gmake_setup (NILF); #endif /* Read in variables from the environment. It is important that this be @@ -1661,8 +1667,7 @@ main (int argc, char **argv, char **envp) /* Read all the makefiles. */ - read_makefiles - = read_all_makefiles (makefiles == 0 ? 0 : makefiles->list); + read_makefiles = read_all_makefiles (makefiles == 0 ? 0 : makefiles->list); #ifdef WINDOWS32 /* look one last time after reading all Makefiles */ @@ -3271,7 +3276,7 @@ die (int status) if (directory_before_chdir != 0) { /* If it fails we don't care: shut up GCC. */ - int _x; + int _x UNUSED; _x = chdir (directory_before_chdir); } diff --git a/make.h b/make.h index 87d7bdb..a1b34f3 100644 --- a/make.h +++ b/make.h @@ -472,8 +472,13 @@ const char *strcache_add_len (const char *str, unsigned int len); int strcache_setbufsize (unsigned int size); /* Guile support */ -int setup_guile (void); +#ifdef HAVE_GUILE +int guile_gmake_setup (const struct floc *flocp); +#endif +/* Loadable object support */ +typedef int (*load_func_t)(const struct floc *flocp); +int load_file (const struct floc *flocp, const char *filename, int noerror); #ifdef HAVE_VFORK_H # include diff --git a/read.c b/read.c index 677c233..912ca71 100644 --- a/read.c +++ b/read.c @@ -595,9 +595,8 @@ eval (struct ebuffer *ebuf, int set_default) when the start of the next rule (or eof) is encountered. When you see a "continue" in the loop below, that means we are moving on - to the next line _without_ ending any rule that we happen to be working - with at the moment. If you see a "goto rule_complete", then the - statement we just parsed also finishes the previous rule. */ + to the next line. If you see record_waiting_files(), then the statement + we are parsing also finishes the previous rule. */ commands = xmalloc (200); @@ -707,6 +706,9 @@ eval (struct ebuffer *ebuf, int set_default) struct variable *v; enum variable_origin origin = vmod.override_v ? o_override : o_file; + /* Variable assignment ends the previous rule. */ + record_waiting_files (); + /* If we're ignoring then we're done now. */ if (ignoring) { @@ -718,9 +720,7 @@ eval (struct ebuffer *ebuf, int set_default) if (vmod.undefine_v) { do_undefine (p, origin, ebuf); - - /* This line has been dealt with. */ - goto rule_complete; + continue; } else if (vmod.define_v) v = do_define (p, origin, ebuf); @@ -735,7 +735,7 @@ eval (struct ebuffer *ebuf, int set_default) v->private_var = 1; /* This line has been dealt with. */ - goto rule_complete; + continue; } /* If this line is completely empty, ignore it. */ @@ -779,6 +779,9 @@ eval (struct ebuffer *ebuf, int set_default) { int exporting = *p == 'u' ? 0 : 1; + /* Export/unexport ends the previous rule. */ + record_waiting_files (); + /* (un)export by itself causes everything to be (un)exported. */ if (*p2 == '\0') export_all_variables = exporting; @@ -803,7 +806,7 @@ eval (struct ebuffer *ebuf, int set_default) free (ap); } - goto rule_complete; + continue; } /* Handle the special syntax for vpath. */ @@ -812,6 +815,10 @@ eval (struct ebuffer *ebuf, int set_default) const char *cp; char *vpat; unsigned int l; + + /* vpath ends the previous rule. */ + record_waiting_files (); + cp = variable_expand (p2); p = find_next_token (&cp, &l); if (p != 0) @@ -828,7 +835,7 @@ eval (struct ebuffer *ebuf, int set_default) if (vpat != 0) free (vpat); - goto rule_complete; + continue; } /* Handle include and variants. */ @@ -843,6 +850,9 @@ eval (struct ebuffer *ebuf, int set_default) exist. "sinclude" is an alias for this from SGI. */ int noerror = (p[0] != 'i'); + /* Include ends the previous rule. */ + record_waiting_files (); + p = allocated_variable_expand (p2); /* If no filenames, it's a no-op. */ @@ -887,9 +897,51 @@ eval (struct ebuffer *ebuf, int set_default) /* Restore conditional state. */ restore_conditionals (save); - goto rule_complete; + continue; } + /* Handle the load operations. */ + if (word1eq ("load") || word1eq ("-load")) + { + /* A 'load' line specifies a dynamic object to load. */ + struct nameseq *files; + int noerror = (p[0] == '-'); + + /* Load ends the previous rule. */ + record_waiting_files (); + + p = allocated_variable_expand (p2); + + /* If no filenames, it's a no-op. */ + if (*p == '\0') + { + free (p); + continue; + } + + /* Parse the list of file names. + Don't expand archive references or strip "./" */ + p2 = p; + files = PARSE_FILE_SEQ (&p2, struct nameseq, '\0', NULL, + PARSEFS_NOAR|PARSEFS_NOSTRIP); + free (p); + + /* Load each file. */ + while (files != 0) + { + struct nameseq *next = files->next; + const char *name = files->name; + + free_ns (files); + files = next; + + if (! load_file (&ebuf->floc, name, noerror) && ! noerror) + fatal (&ebuf->floc, _("%s: failed to load"), name); + } + + continue; + } + /* This line starts with a tab but was not caught above because there was no preceding target, and the line might have been usable as a variable definition. But now we know it is definitely lossage. */ @@ -1293,7 +1345,6 @@ eval (struct ebuffer *ebuf, int set_default) /* We get here except in the case that we just read a rule line. Record now the last rule we read, so following spurious commands are properly diagnosed. */ - rule_complete: record_waiting_files (); } diff --git a/tests/ChangeLog b/tests/ChangeLog index 5e35053..7ffd6b0 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,7 @@ +2012-10-29 Paul Smith + + * scripts/features/load: New test suite for the "load" directive. + 2012-09-09 Paul Smith * scripts/functions/file: Get errors in the C locale, not the diff --git a/tests/run_make_tests.pl b/tests/run_make_tests.pl index 477b117..4accd4a 100755 --- a/tests/run_make_tests.pl +++ b/tests/run_make_tests.pl @@ -97,6 +97,17 @@ sub valid_option $old_makefile = undef; +sub subst_make_string +{ + local $_ = shift; + $makefile and s/#MAKEFILE#/$makefile/g; + s/#MAKEPATH#/$mkpath/g; + s/#MAKE#/$make_name/g; + s/#PERL#/$perl_name/g; + s/#PWD#/$pwd/g; + return $_; +} + sub run_make_test { local ($makestring, $options, $answer, $err_code, $timeout) = @_; @@ -114,16 +125,9 @@ sub run_make_test $makefile = &get_tmpfile(); } - # Make sure it ends in a newline. + # Make sure it ends in a newline and substitute any special tokens. $makestring && $makestring !~ /\n$/s and $makestring .= "\n"; - - # Replace @MAKEFILE@ with the makefile name and @MAKE@ with the path to - # make - $makestring =~ s/#MAKEFILE#/$makefile/g; - $makestring =~ s/#MAKEPATH#/$mkpath/g; - $makestring =~ s/#MAKE#/$make_name/g; - $makestring =~ s/#PERL#/$perl_name/g; - $makestring =~ s/#PWD#/$pwd/g; + $makestring = subst_make_string($makestring); # Populate the makefile! open(MAKEFILE, "> $makefile") || die "Failed to open $makefile: $!\n"; @@ -132,13 +136,8 @@ sub run_make_test } # Do the same processing on $answer as we did on $makestring. - $answer && $answer !~ /\n$/s and $answer .= "\n"; - $answer =~ s/#MAKEFILE#/$makefile/g; - $answer =~ s/#MAKEPATH#/$mkpath/g; - $answer =~ s/#MAKE#/$make_name/g; - $answer =~ s/#PERL#/$perl_name/g; - $answer =~ s/#PWD#/$pwd/g; + $answer = subst_make_string($answer); run_make_with_options($makefile, $options, &get_logfile(0), $err_code, $timeout); diff --git a/tests/scripts/features/load b/tests/scripts/features/load new file mode 100644 index 0000000..8117bbd --- /dev/null +++ b/tests/scripts/features/load @@ -0,0 +1,84 @@ +# -*-perl-*- +$description = "Test the load operator."; + +$details = "Test dynamic loading of modules."; + +# Don't do anything if this system doesn't support "load" +exists $FEATURES{load} or return -1; + +# First build a shared object +# Provide both a default and non-default load symbol + +unlink(qw(testload.c testload.so)); + +open(my $F, '> testload.c') or die "open: testload.c: $!\n"; +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)); +} + +int +testload_gmake_setup () +{ + define_new_function (0, "func-a", 1, 1, 1, func_test); + return 1; +} + +int +explicit_setup () +{ + define_new_function (0, "func-b", 1, 1, 1, func_test); + return 1; +} +EOF +close($F) or die "close: testload.c: $!\n"; + +run_make_test('testload.so: testload.c ; @$(CC) -g -shared -fPIC -o $@ $<', + '', ''); + +# TEST 1 +run_make_test(q! +all: ; @echo $(func-a foo) $(func-b bar) +load ./testload.so +!, + '', "func-a\n"); + +# TEST 2 +# Load a different function +run_make_test(q! +all: ; @echo $(func-a foo) $(func-b bar) +load ./testload.so(explicit_setup) +!, + '', "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"); + +# TEST 4 +# Check multiple loads +run_make_test(q! +all: ; @echo $(filter ./testload.so,$(.LOADED)) $(func-a foo) $(func-b bar) +load ./testload.so +load ./testload.so(explicit_setup) +!, + '', "./testload.so func-a\n"); + +unlink(qw(testload.c testload.so)) unless $keep; + +# This tells the test driver that the perl test script executed properly. +1; diff --git a/tests/scripts/features/parallelism b/tests/scripts/features/parallelism index 76d24a7..08c822e 100644 --- a/tests/scripts/features/parallelism +++ b/tests/scripts/features/parallelism @@ -229,7 +229,7 @@ file2: file1 ; @touch $@ !, '--no-print-directory -j2', "touch file3"); -#rmfiles('file1', 'file2', 'file3', 'file4'); +rmfiles('file1', 'file2', 'file3', 'file4'); if ($all_tests) { # Jobserver FD handling is messed up in some way. diff --git a/tests/scripts/functions/guile b/tests/scripts/functions/guile index 82c02bc..93a18ab 100644 --- a/tests/scripts/functions/guile +++ b/tests/scripts/functions/guile @@ -5,6 +5,20 @@ $description = 'Test the $(guile ...) function.'; $details = 'This only works on systems that support it.'; # If this instance of make doesn't support GNU Guile, skip it +# This detects if guile is loaded using the "load" directive +# $makefile = get_tmpfile(); +# open(MAKEFILE, "> $makefile") || die "Failed to open $makefile: $!\n"; +# print MAKEFILE q! +# -load guile +# all: ; @echo $(filter guile,$(.LOADED)) +# !; +# close(MAKEFILE) || die "Failed to write $makefile: $!\n"; +# $cmd = subst_make_string("#MAKEPATH# -f $makefile"); +# $log = get_logfile(0); +# $code = run_command_with_output($log, $cmd); +# read_file_into_string ($log) eq "guile\n" and $FEATURES{guile} = 1; + +# If we don't have Guile support, never mind. exists $FEATURES{guile} or return -1; # Verify simple data type conversions -- cgit v1.2.3