commit bb174f231330b75a09b8dfb14a6f6a3211fae534
Author: Dmitry Goncharov <dgoncharov@users.sf.net>
Date:   Sat Oct 15 20:08:02 2022 -0400

    [SV 63219] Implement close function for loadable plugins.
    
    * src/makeint.h: Declare unload_all.
    * src/load.c: Introduce typedef close_func_t.
    (struct load_list): Introduce field "close" of type close_func_t.
    (load_object): Find the close function symbol and save the function in
    load_list's "close" field.
    (unload_file): Call "close" function of this instance of load_list.
    (unload_all): Call "close" function of each loaded plugin and unload all
    loaded plugins.
    * src/main.c (die): Call unload_all.
    * doc/make.texi: Document close function.
    Extend mk_temp loadable plugin example with a close function.
    * tests/scripts/features/loadapi: Add new tests.

diff --git a/tests/scripts/features/loadapi b/tests/scripts/features/loadapi
index 640ac745..cade29dd 100644
--- a/tests/scripts/features/loadapi
+++ b/tests/scripts/features/loadapi
@@ -93,6 +93,32 @@ testapi_gmk_setup (const gmk_floc *floc)
 
     return 1;
 }
+
+int
+alternative_setup ()
+{
+    gmk_add_function ("test-expand", func_test, 1, 1, GMK_FUNC_DEFAULT);
+    gmk_add_function ("test-noexpand", func_test, 1, 1, GMK_FUNC_NOEXPAND);
+    gmk_add_function ("test-eval", func_test, 1, 1, GMK_FUNC_DEFAULT);
+    gmk_add_function ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.", func_test, 0, 0, 0);
+
+    if (getenv ("TESTAPI_VERBOSE"))
+      printf ("alternative_setup\n");
+
+    if (getenv ("TESTAPI_KEEP"))
+      return -1;
+
+    return 1;
+}
+
+void
+testapi_gmk_close ()
+{
+    const char *s = getenv ("TESTAPI_VERBOSE");
+    if (s && *s == '3')
+      printf ("testapi_gmk_close\n");
+}
+
 EOF
 close($F) or die "close: testapi.c: $!\n";
 
@@ -222,7 +248,70 @@ force:;
 ", '', "testapi_gmk_setup\n#MAKEFILE#:2\ntestapi.so\ntestapi_gmk_setup\n#MAKEFILE#:2\nhello\n#MAKE#: 'all' is up to date.\n");
 }
 
+my @names  = ('testapi.so', './testapi.so', '#PWD#/testapi.so');
+
+for my $name (@names) {
+
+# Test the make correctly figures out the name of the close function and runs
+# the close function.
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test("
+load $name
+all:; \$(info \$(test-expand hello))
+", '', "testapi_gmk_setup\nhello\n#MAKE#: 'all' is up to date.\ntestapi_gmk_close\n");
+}
+
+# Same as above, but the setup function is custom.
+@names  = ('testapi.so(alternative_setup)', './testapi.so(alternative_setup)',
+           '#PWD#/testapi.so(alternative_setup)');
+for my $name (@names) {
+
+# Test the close function.
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test("
+load $name
+all:; \$(info \$(test-expand hello))
+", '', "alternative_setup\nhello\n#MAKE#: 'all' is up to date.\ntestapi_gmk_close\n");
+}
+
+# Test that makes runs the close function on failure.
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test(q!
+load testapi.so
+all: bad_preqreq; :
+!, '', "testapi_gmk_setup\n#MAKE#: *** No rule to make target 'bad_preqreq', needed by 'all'.  Stop.\ntestapi_gmk_close\n", 512);
+
+# Test that make unloads a shared object, calls the close function, loads
+# the plugin again, and then calls the close function again on exit.
+&utouch(-10, 'testapi.so');
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test("
+load testapi.so
+all:; \$(info \$(test-expand hello))
+testapi.so: testapi.c; $sobuild
+", '', "testapi_gmk_setup\ntestapi_gmk_close\n$sobuild\ntestapi_gmk_setup\nhello\n#MAKE#: 'all' is up to date.\ntestapi_gmk_close\n");
+
+# Test that make unloads a shared object, calls the close function, loads
+# the plugin again, and then calls the close function again on exit.
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test(q!
+load testapi.so
+all:; $(info $(test-expand hello))
+testapi.so: force; $(info $@)
+force:;
+.PHONY: force
+!, '', "testapi_gmk_setup\ntestapi_gmk_close\ntestapi.so\ntestapi_gmk_setup\nhello\n#MAKE#: 'all' is up to date.\ntestapi_gmk_close\n");
+
 unlink(qw(testapi.c testapi.so)) unless $keep;
 
+# Test that make does not run the close function, unless the shared object
+# loaded successfully.
+unlink(testapi.so);
+$ENV{TESTAPI_VERBOSE} = 3;
+run_make_test(q!
+load testapi.so
+all:; :
+!, '', "#MAKEFILE#:2: testapi.so: cannot open shared object file: $ERR_no_such_file\n#MAKEFILE#:2: *** testapi.so: failed to load.  Stop.\n", 512);
+
 # This tells the test driver that the perl test script executed properly.
 1;
