11.5 Convenience Libraries
Sometimes it is useful to group objects together in an intermediate
stage of a project's compilation to provide a useful handle for that
group without having to specify all of the individual objects every
time. Convenience libraries are a portable way of creating such a
partially linked object: Libtool will handle all of the
low level details in a way appropriate to the target host. This section
describes the use of convenience libraries in conjunction with
Automake. The principles of convenience libraries are
discussed in Creating Convenience Libraries.
The key to creating Libtool convenience libraries with
Automake is to use the `noinst_LTLIBRARIES' macro. For
the Libtool libraries named in this macro, Automake will
create Libtool convenience libraries which can subsequently be linked
into other Libtool libraries.
In this section I will create two convenience libraries, each in their
own subdirectory, and link them into a third Libtool library, which is
ultimately linked into an application.
If you want to follow this example, you should create a directory
structure to hold the sources by running the following shell commands:
| $ mkdir convenience
$ cd convenience
$ mkdir lib
$ mkdir replace
|
The first convenience library is built from two source files in the
`lib' subdirectory.
- `source.c':
|
#if HAVE_CONFIG_H
# include <config.h>
#endif
#if HAVE_MATH_H
# include <math.h>
#endif
void
foo (double argument)
{
printf ("cos (%g) => %g\n", argument, cos (argument));
}
|
This file defines a single function to display the cosine of its
argument on standard output, and consequently relies on an
implementation of the cos function from the system libraries.
Note the conditional inclusion of `config.h', which will contain a
definition of `HAVE_MATH_H' if `configure' discovers a
`math.h' system header (the usual location for the declaration of
cos ). The `HAVE_CONFIG_H' guard is by convention, so that
the source can be linked by passing the preprocessor macro definitions
to the compiler on the command line -- if `configure.in' does not
use `AM_CONFIG_HEADER' for instance.
- `source.h':
|
extern void foo (double argument);
|
For brevity, there is no #ifndef SOURCE_H guard. The header is
not installed, so you have full control over where it is
#include ed, and in any case, function declarations can be safely
repeated if the header is accidentally processed more than once. In a
real program, it would be better to list the function parameters in
the declaration so that the compiler can do type checking. This would
limit the code to working only with ANSI compilers, unless you also
use a PARAMS macro to conditionally preprocess away the
parameters when a K&R compiler is used. These details are beyond
the scope of this convenience library example, but are described in full
in 9.1.6 K&R Compilers.
You also need a `Makefile.am' to hold the details of how this
convenience library is linked:
|
## Makefile.am -- Process this file with automake to produce Makefile.in
noinst_LTLIBRARIES = library.la
library_la_SOURCES = source.c source.h
library_la_LIBADD = -lm
|
The `noinst_LTLIBRARIES' macro names the Libtool convenience
libraries to be built in this directory, `library.la'. Although
not required for compilation, `source.h' is listed in the
`SOURCES' macro of `library.la' so that correct source
dependencies are generated, and so that it is added to the distribution
tarball by automake 's `dist' rule.
Finally, since the foo function relies on the cos function
from the system math library, `-lm' is named as a required
library in the `LIBADD' macro. As with all Libtool libraries,
interlibrary dependencies are maintained for convenience libraries so
that you need only list the libraries you are using directly when you
link your application later. The libraries used by those libraries are
added by Libtool.
The parent directory holds the sources for the main executable,
`main.c', and for a (non-convenience) Libtool library,
`error.c' & `error.h'.
Like `source.h', the functions exported from the Libtool library
`liberror.la' are listed in `error.h':
|
extern void gratuitous (void);
extern void set_program_name (char *path);
extern void error (char *message);
|
The corresponding function definitions are in `error.c':
|
#include <stdio.h>
#include "source.h"
static char *program_name = NULL;
void
gratuitous (void)
{
/* Gratuitous display of convenience library functionality! */
double argument = 0.0;
foo (argument);
}
void
set_program_name (char *path)
{
if (!program_name)
program_name = basename (path);
}
void
error (char *message)
{
fprintf (stderr, "%s: ERROR: %s\n", program_name, message);
exit (1);
}
|
The gratuitous() function calls the foo() function defined
in the `library.la' convenience library in the `lib'
directory, hence `source.h' is included.
The definition of error() displays an error message to standard
error, along with the name of the program, program_name , which is
set by calling set_program_name() . This function, in turn,
extracts the basename of the program from the full path using the system
function, basename() , and stores it in the library private
variable, program_name .
Usually, basename() is part of the system C library, though older
systems did not include it. Because of this, there is no portable
header file that can be included to get a declaration, and you might see
a harmless compiler warning due to the use of the function without a
declaration. The alternative would be to add your own declaration in
`error.c'. The problem with this approach is that different
vendors will provide slightly different declarations (with or without
const for instance), so compilation will fail on those
architectures which do provide a declaration in the system
headers that is different from the declaration you have guessed.
For the benefit of architectures which do not have an implementation of
the basename() function, a fallback implementation is provided in
the `replace' subdirectory. The file `basename.c' follows:
|
#if HAVE_CONFIG_H
# include <config.h>
#endif
#if HAVE_STRING_H
# include <string.h>
#elif HAVE_STRINGS_H
# include <strings.h>
#endif
#if !HAVE_STRRCHR
# ifndef strrchr
# define strrchr rindex
# endif
#endif
char*
basename (char *path)
{
/* Search for the last directory separator in PATH. */
char *basename = strrchr (path, '/');
/* If found, return the address of the following character,
or the start of the parameter passed in. */
return basename ? ++basename : path;
}
|
For brevity, the implementation does not use any const
declarations which would be good style for a real project, but would
need to be checked at configure time in case the end user needs to
compile the package with a K&R compiler.
The use of strrchr() is noteworthy. Sometimes it is declared in
`string.h', otherwise it might be declared in `strings.h'.
BSD based Unices, on the other hand, do not have this function at
all, but provide an equivalent function, rindex() . The
preprocessor code at the start of the file is designed to cope with all
of these eventualities. The last block of preprocessor code assumes
that if strrchr is already defined that it holds a working macro,
and does not redefine it.
`Makefile.am' contains:
|
## Process this file with automake to produce Makefile.in
noinst_LTLIBRARIES = libreplace.la
libreplace_la_SOURCES = dummy.c
libreplace_la_LIBADD = @LTLIBOBJS@
|
Once again, the `noinst_LTLIBRARIES' macro names the convenience
library, `libreplace.la'. By default there are no sources, since
we expect to have a system definition of basename() . Additional
Libtool objects which should be added to the library based on tests at
configure time are handled by the `LIBADD' macro. `LTLIBOBJS'
will contain `basename.lo' if the system does not provide
basename , and will be empty otherwise. Illustrating another
feature of convenience libraries: on many architectures,
`libreplace.la' will contain no objects.
Back in the toplevel project directory, all of the preceding objects are
combined by another `Makefile.am':
|
## Process this file with automake to produce Makefile.in
SUBDIRS = replace lib .
CPPFLAGS = -I$(top_srcdir)/lib
include_HEADERS = error.h
lib_LTLIBRARIES = liberror.la
liberror_la_SOURCES = error.c
liberror_la_LDFLAGS = -no-undefined -version-info 0:0:0
liberror_la_LIBADD = replace/libreplace.la lib/library.la
bin_PROGRAMS = convenience
convenience_SOURCES = main.c
convenience_LDADD = liberror.la
|
The initial `SUBDIRS' macro is necessary to ensure that the
libraries in the subdirectories are built before the final library and
executable in this directory.
Notice that I have not listed `error.h' in
`liberror_la_SOURCES' this time, since `liberror.la' is an
installed library, and `error.h' defines the public interface to
that library. Since the `liberror.la' Libtool library is
installed, I have used the `-version-info' option, and I have
also used `-no-undefined' so that the project will compile on
architectures which require all library symbols to be defined at link
time -- the reason program_name is maintained in
`liberror' rather than `main.c' is so that the library does
not have a runtime dependency on the executable which links it.
The key to this example is that by linking the `libreplace.la' and
`library.la' convenience libraries into `liberror.la', all of
the objects in both convenience libraries are compiled into the single
installed library, `liberror.la'. Additionally, all of the
inter-library dependencies of the convenience libraries (`-lm',
from `library.la') are propagated to `liberror.la'.
A common difficulty people experience with Automake is knowing when to
use a `LIBADD' primary versus a `LDADD' primary. A useful
mnemonic is: `LIBADD' is for ADDitional LIBrary objects.
`LDADD' is for ADDitional linker (LD) objects.
The executable, `convenience', is built from `main.c', and
requires only `liberror.la'. All of the other implicit
dependencies are encoded within `liberror.la'. Here is
`main.c':
|
#include <stdio.h>
#include "error.h"
int
main (int argc, char *argv[])
{
set_program_name (argv[0]);
gratuitous ();
error ("This program does nothing!");
}
|
The only file that remains before you can compile the example is
`configure.in':
|
# Process this file with autoconf to create configure.
AC_PREREQ(2.59)
AC_INIT([convenience], [2.0], [[email protected]])
AC_CONFIG_HEADERS([config.h:config-h.in])
AM_INIT_AUTOMAKE([1.9 foreign])
AC_PROG_CC
AC_PROG_LIBTOOL
AC_CHECK_HEADERS([math.h])
AC_CHECK_HEADERS([string.h strings.h], [break])
AC_CHECK_FUNCS([strrchr])
AC_REPLACE_FUNCS([basename])
AC_CONFIG_FILES([replace/Makefile lib/Makefile Makefile])
AC_OUTPUT
|
There are checks for all of the features used by the sources in the
project: `math.h' and either `string.h' or `strings.h';
the existence of strrchr (after the tests for string
headers); adding `basename.o' to `LIBOBJS' if there is no
system implementation; and the shell code to set `LTLIBOBJS'.
With all the files in place, you can now bootstrap the project:
| $ ls -R
.:
Makefile.am configure.in error.c error.h lib main.c replace
lib:
Makefile.am source.c source.h
replace:
Makefile.am basename.c
$ aclocal
$ autoheader
$ automake --add-missing --copy
automake: configure.in: installing ./install-sh
automake: configure.in: installing ./mkinstalldirs
automake: configure.in: installing ./missing
configure.in: 7: required file ./ltconfig not found
$ autoconf
$ ls -R
.:
Makefile.am config.h.in error.c ltconfig mkinstalldirs
Makefile.in config.sub error.h ltmain.sh replace
aclocal.m4 configure install-sh main.c
config.guess configure.in lib missing
lib:
Makefile.am Makefile.in source.c source.h
replace:
Makefile.am Makefile.in basename.c
|
With these files in place, the package can now be configured:
| $ ./configure
...
checking how to run the C preprocessor... gcc -E
checking for math.h... yes
checking for string.h... yes
checking for strrchr... yes
checking for basename... yes
updating cache ./config.cache
creating ./config.status
creating replace/Makefile
creating lib/Makefile
creating Makefile
creating config.h
|
Notice that my host has an implementation of basename() .
Here are the highlights of the compilation itself:
| $ make
Making all in replace
make[1]: Entering directory /tmp/replace
/bin/sh ../libtool --mode=link gcc -g -O2 -o libreplace.la
rm -fr .libs/libreplace.la .libs/libreplace.* .libs/libreplace.*
ar cru .libs/libreplace.al
ranlib .libs/libreplace.al
creating libreplace.la
(cd .libs && rm -f libreplace.la && ln -s ../libreplace.la \
libreplace.la)
make[1]: Leaving directory /tmp/replace
|
Here the build descends into the `replace' subdirectory and creates
`libreplace.la', which is empty on my host since I don't need an
implementation of basename() :
| Making all in lib
make[1]: Entering directory /tmp/lib
/bin/sh ../libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I. \
-g -O2 -c source.c
rm -f .libs/source.lo
gcc -DHAVE_CONFIG_H -I. -I. -g -O2 -c -fPIC -DPIC source.c \
-o .libs/source.lo
gcc -DHAVE_CONFIG_H -I. -I. -g -O2 -c source.c \
-o source.o >/dev/null 2>&1
mv -f .libs/source.lo source.lo
/bin/sh ../libtool --mode=link gcc -g -O2 -o library.la source.lo -lm
rm -fr .libs/library.la .libs/library.* .libs/library.*
ar cru .libs/library.al source.lo
ranlib .libs/library.al
creating library.la
(cd .libs && rm -f library.la && ln -s ../library.la library.la)
make[1]: Leaving directory /tmp/lib
|
Next, the build enters the `lib' subdirectory to build
`library.la'. The `configure' preprocessor macros are passed
on the command line, since no `config.h' was created by
AC_CONFIG_HEADER :
Here, `main.c' is compiled (not to a Libtool object, since it is
not compiled using libtool ), and linked with the
`liberror.la' Libtool library:
| gcc -DHAVE_CONFIG_H -I. -I. -I./lib -g -O2 -c main.c
/bin/sh ./libtool --mode=link gcc -g -O2 -o convenience main.o \
liberror.la
gcc -g -O2 -o .libs/convenience main.o ./.libs/liberror.so -lm \
-Wl,--rpath -Wl,/usr/local/lib
creating convenience
make[1]: Leaving directory /tmp/convenience
|
libtool calls gcc to link the convenience
executable from `main.o' and the shared library component of
`liberror.la'. libtool also links with `-lm', the
propagated inter-library dependency of the `library.la' convenience
library. Since `libreplace.la' and `library.la' were
convenience libraries, their objects are already present in
`liberror.la', so they are not listed again in the final link line
-- the whole point of convenience archives.
This just shows that it all works:
| $ ls
Makefile config.h configure.in install-sh main.c
Makefile.am config.h.in convenience lib main.o
Makefile.in config.log error.c liberror.la missing
aclocal.m4 config.status error.h libtool mkinstalldirs
config.cache config.sub error.lo ltconfig replace
config.guess configure error.o ltmain.sh
$ libtool --mode=execute ldd convenience
liberror.so.0 => /tmp/.libs/liberror.so.0 (0x40014000)
libm.so.6 => /lib/libm.so.6 (0x4001c000)
libc.so.6 => /lib/libc.so.6 (0x40039000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
$ ./convenience
cos (0) => 1
lt-convenience: ERROR: This program does nothing!
|
Notice that you are running the uninstalled executable, which is in
actual fact a wrapper script, See section 10.5 Executing Uninstalled Binaries.
That is why you need to use libtool to run ldd on
the real executable. The uninstalled executable called by the wrapper
script is called lt-convenience , hence the output from
basename() .
Finally, you can see from the output of ldd , that
convenience really isn't linked against either
`library.la' and `libreplace.la'.
|