Autoconf, Automake, and Libtool: A Loadable Module

| ## 20.2 A Loadable Module A feature of the Sic interpreter is that it will use the ``unknown'` built-in to handle any command line which is not handled by any of the other registered built-in callback functions. This mechanism is very powerful, and allows me to lookup unhandled built-ins in the user's ``PATH'`, for instance. Before adding any modules to the project, I have created a separate subdirectory, ``modules'`, to put the module source code into. Not forgetting to list this new subdirectory in the `AC_OUTPUT` macro in ``configure.in'`, and the `SUBDIRS` macro in the top level ``Makefile.am'`, a new ``Makefile.am'` is needed to build the loadable modules:
    ## Makefile.am -- Process this file with automake to produce Makefile.in
    INCLUDES        = -I$(top_builddir) -I$(top_srcdir) \
                    -I$(top_builddir)/sic -I$(top_srcdir)/sic \
                    -I$(top_builddir)/src -I$(top_srcdir)/src

    pkglib_LTLIBRARIES = unknown.la
`pkglibdir` is a Sic specific directory where modules will be installed, See section Installing and Uninstalling Configured Packages. For a library to be maximally portable, it should be written so that it does not require back-linking(47) to resolve its own symbols. That is, if at all possible you should design all of your libraries (not just dynamic modules) so that all of their symbols can be resolved at linktime. Sometimes, it is impossible or undesirable to architect your libraries and modules in this way. In that case you sacrifice the portability of your project to platforms such as AIX and Windows. The key to building modules with libtool is in the options that are specified when the module is linked. This is doubly true when the module must work with libltdl's dlpreopening mechanism.
    unknown_la_SOURCES = unknown.c
    unknown_la_LDFLAGS = -no-undefined -module -avoid-version
    unknown_la_LIBADD  = $(top_builddir)/sic/libsic.la
Sic modules are built without a ``lib'` prefix (``-module'`), and without version suffixes (``-avoid-version'`). All of the undefined symbols are resolved at linktime by ``libsic.la'`, hence ``-no-undefined'`. Having added ``ltdl.c'` to the ``sic'` subdirectory, and called the `AC_LIB_LTDL` macro in ``configure.in'`, ``libsic.la'` cannot build correctly on those architectures which do not support back-linking. This is because ``ltdl.c'` simply abstracts the native `dlopen` API with a common interface, and that local interface often requires that a special library be linked -- ``-ldl'` on linux, for example. `AC_LIB_LTDL` probes the system to determine the name of any such dlopen library, and allows you to depend on it in a portable way by using the configure substitution macro, ``@LIBADD_DL@'`. If I were linking a `libtool` compiled libltdl at this juncture, the system library details would have already been taken care of. In this project, I have bypassed that mechanism by compiling and linking ``ltdl.c'` myself, so I have altered ``sic/Makefile.am'` to use ``@LIBADD_DL@'`:
    lib_LTLIBRARIES         = libcommon.la libsic.la

    libsic_la_LIBADD        = $(top_builddir)/replace/libreplace.la \
                            libcommon.la @LIBADD_DL@
    libsic_la_SOURCES         = builtin.c error.c eval.c list.c ltdl.c \
                            module.c sic.c syntax.c
Having put all this infrastructure in place, the code for the ``unknown'` module is a breeze (helper functions omitted for brevity):
    #if HAVE_CONFIG_H
    #  include <config.h>
    #endif

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sic/module.h>

    #define builtin_table   unknown_LTX_builtin_table

    static char *path_find  (const char *command);
    static int path_execute (Sic *sic, const char *path, char *const argv[]);

    /* Generate prototype. */
    SIC_BUILTIN (builtin_unknown);

    Builtin builtin_table[] = {
      { "unknown", builtin_unknown, 0, -1 },
      { 0, 0, -1, -1 }
    };

    BUILTIN_DECLARATION(unknown)
    {
      char *path = path_find (argv[0]);
      int status = SIC_ERROR;

      if (!path)
        sic_result_append (sic, "command \"", argv[0], "\" not found",
                           NULL);
      else if (path_execute (sic, path, argv) != SIC_OKAY)
        sic_result_append (sic, "command \"", argv[0],"\" failed: ",
                           strerror (errno), NULL);
      else
        status = SIC_OKAY;

      return status;
    }
In the first instance, notice that I have used the preprocessor to redefine the entry point functions to be compatible with libltdls `dlpreopen`, hence the `unknown_LTX_builtin_table` `cpp` macro. The ``unknown'` handler function itself looks for a suitable executable in the user's path, and if something suitable is found, executes it. Notice that Libtool doesn't relink dependent libraries (``libsic'` depends on ``libcommon'`, for example) on my GNU/Linux system, since they are not required for the static library in any case, and because the dependencies are also encoded directly into the shared archive, ``libsic.so'`, by the original link. On the other hand, Libtool will relink the dependent libraries if that is necessary for the target host.
    $ make
    /bin/sh ../libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I. -I.. \
    -I.. -I.. -I../sic -I../sic -I../src -I../src    -g -O2 -c unknown.c
    mkdir .libs
    gcc -DHAVE_CONFIG_H -I. -I. -I.. -I.. -I.. -I../sic -I../sic -I../src \
    -I../src -g -O2 -Wp,-MD,.deps/unknown.pp -c unknown.c  -fPIC -DPIC \
    -o .libs/unknown.lo
    gcc -DHAVE_CONFIG_H -I. -I. -I.. -I.. -I.. -I../sic -I../sic -I../src \
    I../src -g -O2 -Wp,-MD,.deps/unknown.pp -c unknown.c -o unknown.o \
    >/dev/null 2>&1
    mv -f .libs/unknown.lo unknown.lo
    /bin/sh ../libtool --mode=link gcc  -g -O2  -o unknown.la -rpath \
    /usr/local/lib/sic -no-undefined -module -avoid-version unknown.lo \
    ../sic/libsic.la
    rm -fr .libs/unknown.la .libs/unknown.* .libs/unknown.*
    gcc -shared  unknown.lo -L/tmp/sic/sic/.libs ../sic/.libs/libsic.so \
    -lc  -Wl,-soname -Wl,unknown.so -o .libs/unknown.so
    ar cru .libs/unknown.a  unknown.o
    creating unknown.la
    (cd .libs && rm -f unknown.la && ln -s ../unknown.la unknown.la)
    $ ./libtool --mode=execute ldd ./unknown.la
            libsic.so.0 => /tmp/sic/.libs/libsic.so.0 (0x40002000)
            libc.so.6 => /lib/libc.so.6 (0x4000f000)
            libcommon.so.0 => /tmp/sic/.libs/libcommon.so.0 (0x400ec000)
            libdl.so.2 => /lib/libdl.so.2 (0x400ef000)
            /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
After compiling the rest of the tree, I can now use the ``unknown'` module:
    $ SIC_MODULE_PATH=`cd ../modules; pwd` ./sic
    ] echo hello!
    command "echo" not found.
    ] load unknown
    ] echo hello!
    hello!
    ] unload unknown
    ] echo hello!
    command "echo" not found.
    ] exit
    $

This document was generated by Gary V. Vaughan on February, 8 2006 using texi2html