This chapter develops the worked example described in A Small COLLECTIVE Project.  Again, the example is heavily colored by my own views, and there certainly are other, very different, but equally valid ways of achieving the same objectives.

I will explain how I incorporated libtool into the Sic project, and how to put the project documentation and test suite under the control of COLLECTIVE.  I pointed out some problems with the project when I first introduced it -- this chapter will address those issues, and present my favored solution to each.

Using Libtool Libraries
=======================

As you have seen, It is very easy to convert automake built static libraries to automake built Libtool libraries. In order to build libsic as a Libtool library, I have changed the name of the library from libsic.a (the old archive name in Libtool terminology) to libsic.la (the pseudo-library), and must use the LTLIBRARIES Automake primary:

     m4_include(examples/sic-large/sic/Makefile_1.texi)

Notice the la in libsic_la_SOURCES is new too.

It is similarly easy to take advantage of Libtool convenience libraries.  For the purposes of Sic, libreplace is an ideal candidate for this treatment -- I can create the library as a separate entity from selected sources in their own directory, and add those objects to libsic.  This technique ensures that the installed library has all of the support functions it needs without having to link libreplace as a separate object.

In replace/Makefile.am, I have again changed the name of the library from
 libreplace.a to libreplace.la, and changed the automake primary from LIBRARIES to LTLIBRARIES. Unfortunately, those changes alone are insufficient.  Libtool libraries are compiled from Libtool objects (which have the .lo suffix), so I cannot use LIBOBJS which is a list of .o suffixed objects (Actually the suffix will be  whatever is appropriate for the target host: such as .obj on Windows for example.). See Extra Macros for Libtool, for more details.  Here is replace/Makefile.am:

     m4_include(examples/sic-large/replace/Makefile_1.texi)

And not forgetting to set and use the LTLIBOBJS configure substitution (see Extra Macros for Libtool):

     m4_include(examples/sic-large/configure_1.texi)

As a consequence of using libtool to build the project libraries, the increasing number of configuration files being added to the config directory will grow to include ltconfig and ltmain.sh.  These files will be used on the installer's machine when Sic is configured, so it is important to distribute them.  The naive way to do it is to give the config directory a Makefile.am of its own;  however, it is not too difficult to distribute these files from the top Makefile.am, and it saves clutter, as you can see here:

     m4_include(examples/sic-large/Makefile_1.texi)

The dist-hook rule is used to make sure the config directory and the files it contains are correctly added to the distribution by the make dist rules, see Introduction to Distributions.

I have been careful to use the configure script's location for ac_aux_dir, so that it is defined (and can be changed) in only one place.  This is achieved by adding the following macro to configure.in:

     AC_SUBST(ac_aux_dir)

There is no need to explicitly set a macro in the Makefile.am, because Automake automatically creates macros for every value that you AC_SUBST from configure.in.

I have also added the AC_PROG_LIBTOOL macro to configure.in in place of AC_PROG_RANLIB as described in Using GNU Libtool.

Now I can upgrade the configury to use libtool -- the greater part of this is running the libtoolize script that comes with the Libtool distribution.   The bootstrap script then needs to be updated to run libtoolize at the correct juncture:

     m4_include(examples/sic-large/bootstrap.texi)

Now I can re-bootstrap the entire project so that it can make use of libtool:

     $ ./bootstrap
     + aclocal -I config
     + libtoolize --force --copy
     Putting files in AC_CONFIG_AUX_DIR, `config'.
     + autoheader
     + automake --add-missing --copy
     automake: configure.in: installing `config/install-sh'
     automake: configure.in: installing `config/mkinstalldirs'
     automake: configure.in: installing `config/missing'
     + autoconf

The new macros are evident by the new output seen when the newly regenerated configure script is executed:

     $ ./configure --with-readline
     ...

     checking host system type... i586-pc-linux-gnu
     checking build system type... i586-pc-linux-gnu
     checking for ld used by GCC... /usr/bin/ld
     checking if the linker (/usr/bin/ld) is GNU ld... yes
     checking for /usr/bin/ld option to reload object files... -r
     checking for BSD-compatible nm... /usr/bin/nm -B
     checking whether ln -s works... yes
     checking how to recognise dependent libraries... pass_all
     checking for object suffix... o
     checking for executable suffix... no
     checking for ranlib... ranlib
     checking for strip... strip
     ...

     checking if libtool supports shared libraries... yes
     checking whether to build shared libraries... yes
     checking whether to build static libraries... yes
     creating libtool
     ...

     $ make
     ...
     gcc -g -O2 -o .libs/sic sic.o sic_builtin.o sic_repl.o sic_syntax.o \
     ../sic/.libs/libsic.so -lreadline -Wl,--rpath -Wl,/usr/local/lib
     creating sic
     ...

     $ src/sic
     ] libtool --mode=execute ldd src/sic
         libsic.so.0 => /tmp/sic/sic/.libs/libsic.so.0 (0x40014000)
         libreadline.so.4 => /lib/libreadline.so.4 (0x4001e000)
         libc.so.6 => /lib/libc.so.6 (0x40043000)
         libncurses.so.5 => /lib/libncurses.so.5 (0x40121000)
         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
     ] exit
     $

As you can see, sic is now linked against a shared library build of libsic, but not directly against the convenience library, libreplace.

Removing --foreign
==================

Now that I have the bulk of the project in place, I want it to adhere to the GNU standard layout.  By removing the --foreign option from the call to automake in the bootstrap file, automake is able to warn me about missing, or in some cases (For example, when I come to using the make dist rule.), malformed files, as follows:

     $ ./bootstrap
     + aclocal -I config
     + libtoolize --force --copy
     Putting files in AC_CONFIG_AUX_DIR, `config'.
     + autoheader
     + automake --add-missing --copy
     automake: Makefile.am: required file `./NEWS' not found
     automake: Makefile.am: required file `./README' not found
     automake: Makefile.am: required file `./AUTHORS' not found
     automake: Makefile.am: required file `./THANKS' not found
     + autoconf

The GNU standards book (The GNU standard is distributed from http://www.gnu.org/prep/standards.html.) describes the contents of these files in more detail.  Alternatively, take a look at a few other GNU packages from ftp://ftp.gnu.org/gnu.

Installing Header Files
=======================

     One of the more difficult problems with COLLECTIVE driven projects is that each of them depends on config.h (or its equivalent) and the project specific symbols that it defines.  The purpose of this file is to be #included from all of the project source files.  The preprocessor can tailor then the code in these files to the target environment.

     It is often difficult and sometimes impossible to not introduce a dependency on config.h from one of the project's installable header files. It would be nice if you could simply install the generated config.h, but even if you name it carefully or install it to a subdirectory to avoid filename problems, the macros it defines will clash with those from any other COLLECTIVE based project which also installs its config.h.

     For example, if Sic installed its config.h as /usr/include/sic/config.h, and had #include <sic/config.h> in the installed common.h, when another COLLECTIVE based project came to use the Sic library it might begin like this:

          #if HAVE_CONFIG_H
          #  include <config.h>
          #endif

          #if HAVE_SIC_H
          #  include <sic.h>
          #endif

          static const char version_number[] = VERSION;

     But, sic.h says #include <sic/common.h>, which in turn says #include <sic/config.h>.  Even though the other project has the correct value for VERSION in its own config.h, by the time the preprocessor reaches the version_number definition, it has been redefined to the value in sic/config.h.  Imagine the mess you could get into if you were using several libraries which each installed their own config.h definitions.  GCC issues a warning when a macro is redefined to a different value which would help you to catch this error.  Some compilers do not issue a warning, and perhaps worse, other compilers will warn even if the repeated definitions have the same value, flooding you with hundreds of warnings for each source file that reads multiple config.h headers.

     The Autoconf macro AC_OUTPUT_COMMANDS (This is for Autoconf version 2.13.  Autoconf version 2.50 recommends AC_CONFIG_COMMANDS.) provides a way to solve this problem.  The idea is to generate a system specific but installable header from the results of the various tests performed by configure.  There is a 1-to-1 mapping between the preprocessor code that relied on the configure results written to config.h, and the new shell code that relies on the configure results saved in config.cache.

The following code is a snippet from configure.in, in the body of the AC_OUTPUT_COMMANDS macro:

     m4_include(examples/sic-large/configure_4.texi)

Compare this with the equivalent C pre-processor code from sic/common.h, which it replaces:

     #if STDC_HEADERS || HAVE_STDLIB_H
     #  include <stdlib.h>
     #endif

     #if HAVE_UNISTD_H
     #  include <unistd.h>
     #endif

     #if HAVE_SYS_WAIT_H
     #  include <sys/wait.h>
     #endif

     #if HAVE_ERRNO_H
     #  include <errno.h>
     #endif
     #ifndef errno
     /* Some systems #define this! */
     extern int errno;
     #endif

     #if HAVE_STRING_H
     #  include <string.h>
     #else
     #  if HAVE_STRING_H
     #    include <strings.h>
     #  endif
     #endif

     #if HAVE_ASSERT_H
     #  include <assert.h>
     #  define SIC_ASSERT assert
     #else
     #  define SIC_ASSERT(expr) ((void) 0)
     #endif

Apart from the mechanical process of translating the preprocessor code, there is some plumbing needed to ensure that the common.h file generated by the new code in configure.in is functionally equivalent to the old code, and is generated in a correct and timely fashion.

Taking my lead from some of the Automake generated make rules to regenerate Makefile from Makefile.in by calling config.status, I have added some similar rules to sic/Makefile.am to regenerate common.h from common-h.in.

     m4_include(examples/sic-large/sic/Makefile_2.texi)

The way that AC_OUTPUT_COMMANDS works, is to copy the contained code into config.status (see Generated File Dependencies).  It is actually config.status that creates the generated files -- for example, automake generated

Makefiles are able to regenerate themselves from corresponding Makefile.ins by calling config.status if they become out of date.  Unfortunately, this means that config.status doesn't have direct access to the cache values generated while configure was running (because it has finished its work by the time config.status is called).  It is tempting to read in the cache file at the top of the code inside AC_OUTPUT_COMMANDS, but that only works if you know where the cache file is saved.  Also the package installer can use the --cache-file option of configure to change the location of the file, or turn off caching entirely with --cache-file=/dev/null.

AC_OUTPUT_COMMANDS accepts a second argument which can be used to pass the variable settings discovered by configure into config.status.  It's not pretty, and is a little error prone. In the first argument to AC_OUTPUT_COMMANDS, you must be careful to check that every single configure variable referenced is correctly set somewhere in the second argument.

A slightly stripped down example from the sic project configure.in looks like this:

     m4_changequote(,)m4_dnl
     m4_include(examples/sic-large/configure_5.texi)
     m4_changequote(`,')m4_dnl

You will notice that the contents of common-h.in are copied into common.h verbatim as it is generated.  It's just an easy way of collecting together the code that belongs in common.h, but which doesn't rely on configuration tests, without cluttering configure.in any more than necessary.

I should point out that, although this method has served me well for a number of years now, it is inherently fragile because it relies on undocumented internals of both Autoconf and Automake.  There is a very real possibility that if you also track the latest releases of COLLECTIVE, it may stop working.  Future releases of COLLECTIVE will address the interface problems that force us to use code like this, for the lack of a better way to do things.

Including Texinfo Documentation
===============================

Automake provides a few facilities to make the maintenance of Texinfo documentation within projects much simpler than it used to be.  Writing a Makefile.am for Texinfo documentation is extremely straightforward:

     m4_include(examples/sic-large/doc/Makefile_1.texi)

The TEXINFOS primary will not only create rules for generating .info files suitable for browsing with the GNU info reader, but also for generating .dvi and .ps documentation for printing.

You can also create other formats of documentation by adding the appropriate make rules to Makefile.am.  For example, because the more recent Texinfo distributions have begun to support generation of HTML documentation from the .texi format master document, I have added the appropriate rules to the Makefile.am:

     m4_include(examples/sic-large/doc/Makefile_2.texi)

For ease of maintenance, these make rules employ a suffix rule which describes how to generate HTML from equivalent .texi source -- this involves telling make about the .html suffix using the automake SUFFIXES macro.  I haven't defined MAKEINFO explicitly (though I could have done) because I know that Automake has already defined it for use in the .info generation rules.

The html target is for convenience; typing make html is a little easier than typng make sic.html.  I have also added a .PHONY target so that featureful make programs will know that the html target doesn't actually generate a file called literally, html.  As it stands, this code is not quite complete, since the toplevel Makefile.am doesn't know how to call the html rule in the doc subdirectory.

There is no need to provide a general solution here in the way Automake does for its dvi target, for example. A simple recursive call to doc/Makefile is much simpler:

     m4_include(examples/sic-large/Makefile_2.texi)

Another useful management function that Automake can perform for you with respect to Texinfo documentation is to automatically generate the version numbers for your Texinfo documents.  It will add make rules to generate a suitable version.texi, so long as automake sees @include version.texi in the body of the Texinfo source:

     m4_include(examples/sic-large/doc/sic_1.texi)

version.texi sets Texinfo variables, VERSION, EDITION and UPDATE, which can be expanded elsewhere in the main Texinfo documentation by using @value{EDITION} for example. This makes use of another auxiliary file, mdate-sh which will be added to the scripts in the $ac_aux_dir subdirectory by Automake after adding the version.texi reference to sic.texi:

     $ ./bootstrap
     + aclocal -I config
     + libtoolize --force --copy
     Putting files in AC_CONFIG_AUX_DIR, `config'.
     + autoheader
     + automake --add-missing --copy
     doc/Makefile.am:22: installing `config/mdate-sh'
     + autoconf
     $ make html
     /bin/sh ./config.status --recheck
     ...
     Making html in ./doc
     make[1]: Entering directory `/tmp/sic/doc'
     Updating version.texi
     makeinfo --html sic.texi
     make[1]: Leaving directory `/tmp/sic/doc'

Hopefully, it now goes without saying that I also need to add the doc subdirectory to AC_OUTPUT in configure.in and to SUBDIRS in the top-level Makefile.am.

Adding a Test Suite
===================

Automake has very flexible support for automated test-suites within a project distribution, which are discussed more fully in the Automake manual.  I have added a simple shell script based testing facility to Sic using this support -- this kind of testing mechanism is perfectly adequate for command line projects.  The tests themselves simply feed prescribed input to the uninstalled sic interpreter and compare the actual output with what is expected.

Here is one of the test scripts:

     m4_include(examples/sic-large/tests/incomplete_1.texi)

The tricky part of this script is the first part which discovers the location of (and loads)  $srcdir/defs.  It is a little convoluted because it needs to work if the user has compiled the project in a separate build tree -- in which case the defs file is in a separate source tree and not in the actual directory in which the test is executed.

The defs file allows me to factor out the common definitions from each of the test files so that it can be maintained once in a single file that is read by all of the tests:

     m4_changequote(,)m4_dnl
     m4_include(examples/sic-large/tests/defs.texi)
     m4_changequote(`,')m4_dnl

Having written a few more test scripts, and made sure that they are working by running them from the command line, all that remains is to write a suitable Makefile.am so that automake can run the test suite automatically.

     m4_include(examples/sic-large/tests/Makefile_1.texi)

I have used the testsubdir macro to run the tests in their own subdirectory so that the directory containing the actual test scripts is not polluted with lots of fallout files generated by running the tests. For completeness I have used a hook target (This is a sort of callback function which will be called by the make rules generated by Automake.) to remove this subdirectory when the user types:

     $ make distclean
     ...
     rm -rf testSubDir
     ...

Adding more tests is accomplished by creating a new test script and adding it to the list in noinst_SCRIPTS.  Remembering to add the new tests subdirectory to configure.in and the top-level Makefile.am, and reconfiguring the project to propagate the changes into the various generated files, I can run the whole test suite from the top directory with:

     $ make check

It is often useful run tests in isolation, either when developing new tests, or to examine more closely why a test has failed unexpectedly. Having set this test suite up as I did, individual tests can be executed with:

     $ VERBOSE=1 make check TESTS=incomplete.test
     make  check-TESTS
     make[1]: Entering directory
     `/tmp/sic/tests'
     === Running test incomplete.test
     1
     2
     3
     PASS: incomplete.test
     ==================
     All 1 tests passed
     ==================
     make[1]: Leaving directory `/tmp/sic/tests'
     $ ls testSubDir/
     err   errok   in.sic   ok   out

The testSubDir subdirectory now contains the expected and actual output from that particular test for both stdout and stderr, and the input file which generated the actual output. Had the test failed, I would be able to look at these files to decide whether there is a bug in the program or simply a bug in the test script.  Being able to examine individual tests like this is invaluable, especially when the test suite becomes very large -- because you will, naturally, add tests every time you add features to a project or find and fix a bug.

Another alternative to the pure shell based test mechanism I have presented here is the Autotest facility by François Pinard, as used in Autoconf after release 2.13.

Later in A Complex COLLECTIVE Project, the Sic project will be revisited to take advantage of some of the more advanced features of COLLECTIVE.  But first these advanced features will be discussed in the next several chapters -- starting, in the next chapter, with a discussion of how COLLECTIVE can help you to make a tarred distribution of your own projects.