| Autoconf, Automake, and Libtool | ||
|---|---|---|
| <<< Previous | A Small GNU Autotools Project | Next >>> |
What I need now, is a program that uses libsic.a, if only to give me confidence that it is working. In this section, I will write a simple shell which uses the library. But first, I'll create a directory to put it in:
$ mkdir src
$ ls -F
COPYING Makefile.am aclocal.m4 configure* config/ sic/
INSTALL Makefile.in bootstrap* configure.in replace/ src/
$ cd src
|
In order to put this shell together, we need to provide just a few things for integration with libsic.a...
In sic_repl.c [1] there is a loop for reading strings typed by the user, evaluating them and printing the results. GNU readline is ideally suited to this, but it is not always available - or sometimes people simply may not wish to use it.
With the help of GNU Autotools, it is very easy to cater for building with and without GNU readline. sic_repl.c uses this function to read lines of input from the user:
static char *
getline (FILE *in, const char *prompt)
{
static char *buf = NULL; /* Always allocated and freed
from inside this function. */
XFREE (buf);
buf = (char *) readline ((char *) prompt);
#ifdef HAVE_ADD_HISTORY
if (buf && *buf)
add_history (buf);
#endif
return buf;
}
|
To make this work, I must write an Autoconf macro which adds an option to configure, so that when the package is installed, it will use the readline library if --with-readline is used:
|
Having put this macro in the file config/readline.m4, I must also call the new macro (SIC_WITH_READLINE) from configure.in.
The syntax of the commands in the shell I am writing is defined by a set of syntax handlers which are loaded into libsic at startup. I can get the C preprocessor to do most of the repetitive code for me, and just fill in the function bodies:
#if HAVE_CONFIG_H
# include <sic/config.h>
#endif
#include "sic.h"
/* List of builtin syntax. */
#define syntax_functions \
SYNTAX(escape, "\\") \
SYNTAX(space, " \f\n\r\t\v") \
SYNTAX(comment, "#") \
SYNTAX(string, "\"") \
SYNTAX(endcmd, ";") \
SYNTAX(endstr, "")
/* Prototype Generator. */
#define SIC_SYNTAX(name) \
int name (Sic *sic, BufferIn *in, BufferOut *out)
#define SYNTAX(name, string) \
extern SIC_SYNTAX (CONC (syntax_, name));
syntax_functions
#undef SYNTAX
/* Syntax handler mappings. */
Syntax syntax_table[] = {
#define SYNTAX(name, string) \
{ CONC (syntax_, name), string },
syntax_functions
#undef SYNTAX
{ NULL, NULL }
};
|
This code writes the prototypes for the syntax handler functions, and creates a table which associates each with one or more characters that might occur in the input stream. The advantage of writing the code this way is that when I want to add a new syntax handler later, it is a simple matter of adding a new row to the syntax_functions macro, and writing the function itself.
In addition to the syntax handlers I have just added to the Sic shell, the language of this shell is also defined by the builtin commands it provides. The infrastructure for this file is built from a table of functions which is fed into various C preprocessor macros, just as I did for the syntax handlers.
One builtin handler function has special status, builtin_unknown. This is the builtin that is called, if the Sic library cannot find a suitable builtin function to handle the current input command. At first this doesn't sound especially important - but it is the key to any shell implementation. When there is no builtin handler for the command, the shell will search the users command path, $PATH, to find a suitable executable. And this is the job of builtin_unknown:
int
builtin_unknown (Sic *sic, int argc, char *const argv[])
{
char *path = path_find (argv[0]);
int status = SIC_ERROR;
if (!path)
{
sic_result_append (sic, "command \"");
sic_result_append (sic, argv[0]);
sic_result_append (sic, "\" not found");
}
else if (path_execute (sic, path, argv) != SIC_OKAY)
{
sic_result_append (sic, "command \"");
sic_result_append (sic, argv[0]);
sic_result_append (sic, "\" failed: ");
sic_result_append (sic, strerror (errno));
}
else
status = SIC_OKAY;
return status;
}
static char *
path_find (const char *command)
{
char *path = xstrdup (command);
if (*command == '/')
{
if (access (command, X_OK) < 0)
goto notfound;
}
else
{
char *PATH = getenv ("PATH");
char *pbeg, *pend;
size_t len;
for (pbeg = PATH; *pbeg != '\0'; pbeg = pend)
{
pbeg += strspn (pbeg, ":");
len = strcspn (pbeg, ":");
pend = pbeg + len;
path = XREALLOC (char, path, 2 + len + strlen(command));
*path = '\0';
strncat (path, pbeg, len);
if (path[len -1] != '/') strcat (path, "/");
strcat (path, command);
if (access (path, X_OK) == 0)
break;
}
if (*pbeg == '\0')
goto notfound;
}
return path;
notfound:
XFREE (path);
return NULL;
}
|
Running autoscan again at this point adds AC_CHECK_FUNCS(strcspn strspn) to configure.scan. This tells me that these functions are not truly portable. As before I provide fallback implementations for these functions incase they are missing from the target host - and as it turns out, they are easy to write:
/* strcspn.c -- implement strcspn() for architectures without it */
#if HAVE_CONFIG_H
# include <sic/config.h>
#endif
#include <sys/types.h>
#if STDC_HEADERS
# include <string.h>
#elif HAVE_STRINGS_H
# include <strings.h>
#endif
#if !HAVE_STRCHR
# ifndef strchr
# define strchr index
# endif
#endif
size_t
strcspn (const char *string, const char *reject)
{
size_t count = 0;
while (strchr (reject, *string) == 0)
++count, ++string;
return count;
}
|
There is no need to add any code to Makefile.am, because the configure script will automatically add the names of the missing function sources to @LIBOBJS@.
This implementation uses the autoconf generated config.h to get information about the availability of headers and type definitions. It is interesting that autoscan reports that strchr and strrchr, which are used in the fallback implementations of strcspn and strspn respectively, are themselves not portable! Luckily, the Autoconf manual tells me exactly how to deal with this: by adding some code to my common.h (paraphrased from the literal code in the manual):
#if !STDC_HEADERS
# if !HAVE_STRCHR
# define strchr index
# define strrchr rindex
# endif
#endif
|
And another macro in configure.in:
AC_CHECK_FUNCS(strchr strrchr)
|
Since the application binary has no installed header files, there is little point in maintaining a corresponding header file for every source, all of the structures shared by these files, and non-static functions in these files are declared in sic.h:
#ifndef SIC_H
#define SIC_H 1
#include <sic/common.h>
#include <sic/sic.h>
#include <sic/builtin.h>
BEGIN_C_DECLS
extern Syntax syntax_table[];
extern Builtin builtin_table[];
extern Syntax syntax_table[];
extern int evalstream (Sic *sic, FILE *stream);
extern int evalline (Sic *sic, char **pline);
extern int source (Sic *sic, const char *path);
extern int syntax_init (Sic *sic);
extern int syntax_finish (Sic *sic, BufferIn *in, BufferOut *out);
END_C_DECLS
#endif /* !SIC_H */
|
To hold together everything you have seen so far, the main function creates a Sic parser and initialises it by adding syntax handler functions and builtin functions from the two tables defined earlier, before handing control to evalstream which will eventually exit when the input stream is exhausted.
int
main (int argc, char * const argv[])
{
int result = EXIT_SUCCESS;
Sic *sic = sic_new ();
/* initialise the system */
if (sic_init (sic) != SIC_OKAY)
sic_fatal ("sic initialisation failed");
signal (SIGINT, SIG_IGN);
setbuf (stdout, NULL);
/* initial symbols */
sicstate_set (sic, "PS1", "] ", NULL);
sicstate_set (sic, "PS2", "- ", NULL);
/* evaluate the input stream */
evalstream (sic, stdin);
exit (result);
}
|
Now, the shell can be built and used:
$ bootstrap
... $ ./configure --with-readline
... $ make
... make[2]: Entering directory `/tmp/sic/src'
gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic.c
gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_builtin.c
gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_repl.c
gcc -DHAVE_CONFIG_H -I. -I.. -I../sic -I.. -I../sic -g -c sic_syntax.c
gcc -g -O2 -o sic sic.o sic_builtin.o sic_repl.o sic_syntax.o \
../sic/libsic.a ../replace/libreplace.a -lreadline
make[2]: Leaving directory `/tmp/sic/src'
...
$ ./src/sic
] pwd
/tmp/sic ] ls -F
Makefile aclocal.m4 config.cache configure* sic/
Makefile.am bootstrap* config.log configure.in src/
Makefile.in config/ config.status* replace/ ] exit
$ |
This chapter has developed a solid foundation of code, which I will return to in the chapter called A Large GNU Autotools Project, when Libtool will join the fray. The chapters leading up to that explain what Libtool is for, how to use it and integrate it into your own projects, and the advantages it offers over building shared libraries with Automake (or even just Make) alone.
| [1] | Read Eval Print Loop. |
| <<< Previous | Home | Next >>> |
| A Simple Shell Builders Library | Up | Introducing GNU Libtool |