Central Iowa Railroad Herald

CIRR.COM

Compile Time

Programming Portably: Feature Definition Generation


In September, we discussed the significant advantage of re-implementing desired, but less common functions: if you use a feature of your local operating system, but discover it doesn't exist on other platforms, write your own implementation and make that code a part of your distribution. In that column we also discussed the benefit of testing for features in your code: feature test macros make code easier to port, and far easier to read.

This month, we'll discuss four ways to generate feature definitions for your project for any Unix-like platform. The four techniques, in order of current popularity are: by hand, OS definition based, using metaconfig, and using autoconf.

Feature Test Macros Made By Hand

One of the oldest mechanisms for configuring software is doing it I. This usually means opening up the configuration file in your favorite text editor, and going through it line by line. The process of configuring the software meant referring to the system manuals, and likely the software's documentation to find out which features were available on or suitable for the local system. Listing 0 provides an example of such a configuration file.

/* 
 * features.h -- features used by logger(1l) from the local system
 */

/*
 * HAS_SYSLOG -- does the system have a working syslog implementation?
 */
#define HAS_SYSLOG 0

/*
 * HAS_SNPRINTF -- does the system have a working snprintf() function?
 *	If not, we'll use our local version.  snprintf() is a bounds 
 *	checking version of sprintf(3s).
 */
#define HAS_SNPRINTF 0

/*
 * HAS_STRFTIME -- does the system have an strftime() implementation
 *	we can use to generate time stamps?  If not, we'll fall back 
 *	on a version pulled from the *BSD source trees.
 */
#define HAS_STRFTIME 0

/*
 * Policy level decisions.
 */

/*
 * PATH_LOGFILE -- where to drop the logfile.
 */
#define PATH_LOGFILE	"/var/log/syslog"

/*
 * PATH_CONSOLE -- path to the console device
 *		on any UNIX-like system, the default should be
 *		reasonable enough.
 */
#define PATH_CONSOLE	"/dev/console"

LISTING 0

We'll use Listing 0 as the basis of all future examples.

Generating feature definitions by hand may be easy for you, the developer of the software, but it's terribly inconvenient for the end-user (since you're distributing code, the end-user could be another programmer, a system administrator, or a true end-user).The end-user is expected, nay, required to know if a given feature is available on their system and how it acts. Fortunately, the system manual pages usually provide the needed information.

To allow the end-user to generate the feature definitions, you should provide an include file with all of the features required or desired. The file should include a description of the features used, and what to do if the feature doesn't exist on the end users platform.This documentation should include where to enable any needed re-implementation sources and objects in the projects Makefiles.

It's also good practice for all projects to write their configuration include file in the manner above. It provides excellent documentation of the features required/expected by your software. It also greatly eases the porting effort should your software be moved to some platform where your automated configuration tools don't work. Imagine trying to port to Windows without cygwin, or any other operating system that doesn't look at lot like UNIX, but having a C language development environment (assuming project development in C.) Having all of the features clearly documented in the configuration file may greatly ease the porting effort to such platforms.

By the by, you use a variation of configuring by hand every time you type make config in /usr/src/linux to configure your Linux system.

An example of configuring software entirely by hand is the 5.X and early 6.X versions of tcsh.

The advantages of hand configuration fall on the developer. They include not having to write a script (or generate one) to find the feature definitions and the ease of tweaking/modifying the configuration. Fundamentally, it's simple for the developer.

The disadvantages of hand configuration fall on the user. The largest disadvantage is having to delve deeply into the system documentation to determine if required features are available. On the other hand, tweaking/modifying the configuration is easy, and the process allows the user to more easily debug compilation and runtime problems, mostly due to the familiarity learned during the configuration process.

Features by OS Definition

The second oldest method of generating feature definitions is attempting to do so based on OS definition. The early versions of this showed up as lots of #ifdef OSNAME littered throughout the code. But we all know that is bad style. So it has mutated into a single source file that converts OS names into feature definition lists.

To use OS definitions to configure your software package, an include file should be created containing only the feature definitions for the supported operating systems. For each supported operating system, create the feature definition block protected by a preprocessor macro that identifies a the operating system. For many platforms, the C compiler provides a nice single definition to uniquely identify the system without outside intervention. However, on some systems (such as the many System V, release 4 systems for Intel released in the early '90's), a new macro may need to be defined to differentiate between variations (this may be needed to support various Linux distributions.)

Inside the OS specific preprocessor block, all of the supported features are turned on (or off, as the case may be.)

Listing 1 provides an example of such a configuration.

/*
 * configure.h -- determine what features are available by
 *		looking at compiler definitions
 */

#include "features.h"

/*
 * Linux, in its various incarnations
 */
#if defined(__linux__)
    /* some reasonable defaults for any late model LINUX */
# define HAS_SYSLOG 1
# define HAS_STRFTIME 1
# if defined(__GLIBC__)
#  if __GLIBC__ > 1 && __GLIBC_MINOR__ > 1
    /* we're on a linux system with glibc > 2.1 -- yeah! */
#   define HAS_SNPRINTF 1
#  endif
# endif
#endif	/* defined (__linux__) */

/*
 * NetBSD -- tested on NetBSD 1.5.x, 1.6
 */
#if defined(__NetBSD_Version__)
    /* For All versions of NetBSD *
# define HAS_SYSLOG 1
# define HAS_STRFTIME 1
# if __NetBSD_Version__ > 104000000
    /* only releases after 1.4 */
#  define HAS_SNPRINTF 1
# endif
#endif	/* defined (__NetBSD_Version__) */

/*
 * EmbededOS -- an OS that supports ANSI C, but only barely looks like
 *		UNIX (hmm, maybe more like VMS)
 */
#if defined(__EmbeddedOS__)
# define HAS_SYSLOG 0
# define HAS_STRFTIME 1
# define HAS_SNSPRINTF 0
# define PATH_CONSOLE	"cons:"
# define PATH_LOGFILE	"DUA0:[0,0]LOG.TXT"
#endif	/* defined (__EmbeddedOS__) */

LISTING 1

To reliably set such a mechanism up, you need either access to a large number of systems, or you need a user community that is willing to send back patches/updates with feature definitions for previously unknown OSTYPEs. And changes between OS releases may not be properly handled.

An example of a project that uses this method is sendmail.

The largest advantage of using the OS based feature definition is it comes the closest to providing a true ``compile and go'' software distribution to the end user.

Among the disadvantages of using the OS based feature definitions is the complexity of maintaining the per OS feature definition lists for the maintainers, and the relative complexity of supporting previously unknown systems to the end users (if an OS isn't known, the defaults _will_ cause something to break without adequate explanation.

Auto-configuration with metaconfig

metaconfig is the automatic configuration file generator developed by Larry Wall (and a cast of others.) An early use of metaconfig was with Wall's news reader, rn, and later with trn. It's also used with Perl. metaconfig was one of the earliest (perhaps the earliest) singing and dancing scripts to determine features available on an OS. It's been in regular use since 1987 (at least.) Strangely, enough, metaconfig's package is known as dist. Probably because it provides a strong set of distribution tools in addition to generating a Configure script.

metaconfig-generated Configure scripts are interactive scripts that provide directions to the end-user, and ask for confirmation of some potentially weak information it determines. For the average user, on a relatively normal system, running Configure scripts is mostly an exercise in typing RETURN. It does allow the software developer to ask the person doing the software configuration/installation some questions that might be site policy specific. Examples of such questions are the organization name and the email address of the site administrator.

metaconfig provides a rich, standard set of feature definitions to the developer. At the time of this writing, there are 465 feature macros for use in C language modules and 468 feature macros for use in shell scripts.

To use metaconfig, the following tasks are done during the initial configuration of the project for metaconfig:

Listing 2 is an initial MANIFEST.new for a project including features.h defined in Listing 0

MANIFEST	This file, a listing of all included sources
Makefile.SH	Generates into a Makefile to build logger(1l)
features.h	Feature definitions 
logger.1	Manual page, in *BSD mdoc format
logger.c	logger(1m) main module
snprintf.c	re-implementation of snprintf(), from sendmail (from UCB)
strftime.c	re-implementation of strftime(), from UCB
syslog.c	file based re-implementation of syslog(3)
syslog.h	supporting include file for re-implementation of syslog(3)

LISTING 2

Listing 3 is the MANIFEST.new after running metaconfig.

MANIFEST	This file, a listing of all included sources
Makefile.SH	Generates into a Makefile to build logger(1l)
features.h	Feature definitions 
logger.1	Manual page, in *BSD mdoc format
logger.c	logger(1m) main module
snprintf.c	re-implementation of snprintf(), from sendmail (from UCB)
strftime.c	re-implementation of strftime(), from sendmail (from UCB)
syslog.c	file based re-implementation of syslog(3)
syslog.h	supporting include file for re-implementation of syslog(3)
Configure                    Portability tool
config_h.SH                  Produces config.h

LISTING 3

Listing 4 is the generated config_h.SH, which when run through the shell, will become config.h for all the sources to include. This config_h.SH doesn't (yet) contain the path information for the default log file or console (mostly because the author hasn't figured out how to get Configure to prompt for it.. Bugger.)

case $CONFIG in
'')
	if test -f config.sh; then TOP=.;
	elif test -f ../config.sh; then TOP=..;
	elif test -f ../../config.sh; then TOP=../..;
	elif test -f ../../../config.sh; then TOP=../../..;
	elif test -f ../../../../config.sh; then TOP=../../../..;
	else
		echo "Can't find config.sh."; exit 1
	fi
	. $TOP/config.sh
	;;
esac
case "$0" in
*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
esac
echo "Extracting config.h (with variable substitutions)"
sed <<!GROK!THIS! >config.h -e 's!^#undef\(.*/\)\*!/\*#define\1 \*!' -e 's!^#un-def!#undef!'
/*
 * This file was produced by running the config_h.SH script, which
 * gets its values from config.sh, which is generally produced by
 * running Configure.
 *
 * Feel free to modify any of this as the need arises.  Note, however,
 * that running config_h.SH again will wipe out any changes you've made.
 * For a more permanent change edit config.sh and rerun config_h.SH.
 *
 * \$Id: 2002-Dec.html,v 1.2 2002/11/27 03:25:46 eric Exp $
 */

/*
 * Package name      : $package
 * Source directory  : $src
 * Configuration time: $cf_time
 * Configured by     : $cf_by
 * Target system     : $myuname
 */

#ifndef _config_h_
#define _config_h_

/* BSD:
 *	This symbol, if defined, indicates that the program is running under
 *	a BSD system.
 */
#$d_bsd BSD		/**/

/* HASCONST:
 *	This symbol, if defined, indicates that this C compiler knows about
 *	the const type. There is no need to actually test for that symbol
 *	within your programs. The mere use of the "const" keyword will
 *	trigger the necessary tests.
 */
#$d_const HASCONST	/**/
#ifndef HASCONST
#define const
#endif

/* HAS_STRFTIME:
 *	This symbol, if defined, indicates that the strftime routine is
 *	available to format locale-specific times.
 */
#$d_strftime HAS_STRFTIME	/**/

/* HAS_SYSLOG:
 *	This symbol, if defined, indicates that the program can rely on the
 *	system providing syslog().  Otherwise, the syslog code provided by
 *	the package should be used.
 */
#$d_syslog HAS_SYSLOG	/**/

/* CAN_PROTOTYPE:
 *	If defined, this macro indicates that the C compiler can handle
 *	function prototypes.
 */
/* _:
 *	This macro is used to declare function parameters for folks who want
 *	to make declarations with prototypes using a different style than
 *	the above macros.  Use double parentheses.  For example:
 *
 *		int main _((int argc, char *argv[]));
 */
#$prototype	CAN_PROTOTYPE	/**/
#ifdef CAN_PROTOTYPE
#define	_(args) args
#else
#define	_(args) ()
#endif

/* VOIDFLAGS:
 *	This symbol indicates how much support of the void type is given by this
 *	compiler.  What various bits mean:
 *
 *	    1 = supports declaration of void
 *	    2 = supports arrays of pointers to functions returning void
 *	    4 = supports comparisons between pointers to void functions and
 *		    addresses of void functions
 *	    8 = suports declaration of generic void pointers
 *
 *	The package designer should define VOIDUSED to indicate the requirements
 *	of the package.  This can be done either by #defining VOIDUSED before
 *	including config.h, or by defining defvoidused in Myinit.U.  If the
 *	latter approach is taken, only those flags will be tested.  If the
 *	level of void support necessary is not present, defines void to int.
 */
#ifndef VOIDUSED
#define VOIDUSED $defvoidused
#endif
#define VOIDFLAGS $voidflags
#if (VOIDFLAGS & VOIDUSED) != VOIDUSED
#define void int		/* is void to be avoided? */
#define M_VOID			/* Xenix strikes again */
#endif

#endif
!GROK!THIS!

LISTING 4

Once the software tests out correctly, you can use makedist to generate distribution sets. makedist will gladly try to generate shell archives suitable for posting to the USENET comp.sources.* groups.

The entire sample project using metaconfig can be found at ftp://ftp.cirr.com/pub/compiletime/2002-Dec/MetaConfig-example.tar.gz.

Example packages that use metaconfig are trn and Perl.

Probably the largest advantage to using metaconfig is the interactivity. It allows you, as a software developer, to ask policy type questions of the installer on the installation system. trn uses this flexibility to ask for USENET news distributions to support, the preferred news server, organization name, etc.

The next largest advantage is that metaconfig automatically generates the Configure script directly from the sources, and provides the tools to greatly ease packaging and distribution.

The biggest disadvantage is listed above as the greatest advantage. metaconfig generated Configure scripts are extremely interactive. This annoys many people.

A second disadvantage is that the metaconfig distributions appear to have forked. This is probably due more to the extremely small number of packages using it more than anything else. At the moment, there does not seem to be a single metaconfig maintainer, so each project using it is maintaining their own distribution, usually based off of the current perl distribution.

GNU autoconf

The final feature definition generation system we'll look at is the Free Software Foundations autoconf utility. autoconf is frequently used with automake and libtool, although we won't delve deeply into either of those here. automake and libtool are topics suitable for columns of there own.

autoconf is newest of the feature definition generation tools we're going to discuss. It's has been in around in its current form for about 10 years now.

autoconf-generated configure scripts attempt to determine everything needed in a batch fashion. Any information that cannot be determined by poking and prodding the system under investigation must be provided by command line switches to the generated configure script.

To use autoconf to generate a configure script, you create a configure.in file using various autoconf macros to describe the features to be tested. autoconf has no intrinsic knowledge of the files in the product distribution. This means that autoconf doesn't provide a mechanism to automatically search source files for feature definitions.

Setting up logger(1m) to make use of autoconf would follow the following steps:

The entire sample project using autoconf can be found at ftp://ftp.cirr.com/pub/compiletime/2002-Dec/AutoConf-example.tar.gz.

As with all the feature definition generation techniques we've discussed, autoconf and automake have their advantages and disadvantages.

The advantages include automated generation of feature testing scripts for a software package, allowing the end user to get by with knowing less about their system and an easy, non-interactive, two step process getting from raw source to compiled executable.

Two of the largest disadvantages are the steep learning curve to generate the configuration script, and the resulting scripts batch nature (which was also called a big advantage above.)

Final configuration

We've looked at four different methods of determining the feature definitions to define for a given platform. They are by hand; based on OS definitions; by using a metaconfig generated Configure script; and by using an autoconf generated configure script.

The current favorite mechanism for generating feature definitions definitions is autoconf generated configure scripts. However, an extremely popular package, namely perl uses a metaconfig generated Configure script. So, evaluate the different tools, and pick the one most appropriate to your package.

Future Compile Time Columns

Is there some topic you'd like to see discussed in Compile Time?

The only restrictions are that the topic be somewhat UNIX/Linux-centric, and that some documentation on the topic exist (pointers are always helpful too.)


References

Metaconfig manual page: http://www.vmlinux.org/cgi-bin/man2html?metaconfig+1

Metaconfig tarball: http://www.netsw.org/softeng/configuration/dist/dist-3.0@70.tar.gz

(For trn) http://trn.sourceforge.net/dist-3.0-70-wd.tar.gz

(For Perl) http://www.cpan.org/authors/id/JHI/metaconfig-for-Perl-5.8.0.tar.gz

Autoconf Info documentation: http://www.gnu.org/manual/autoconf-2.53/html_node/index.html

GNU Autoconfig, Automake, and Libtool
2001 New Riders; ISBN 1-57870-190-2
Gary V. Vaughan, Ben Elliston, Tom Tromey, and Ian Lance Taylor

The metaconfig sample project can be found at ftp://ftp.cirr.com/pub/compiletime/2002-Dec/MetaConfig-example.tar.gz.

The autoconf sample project can be found at ftp://ftp.cirr.com/pub/compiletime/2002-Dec/AutoConf-example.tar.gz.


If you have any questions about our site, please send us mail.
Copyright 2000,2001 Central Iowa (Model) Railroad Contact Us Referral
Program
Support
$Id: 2002-Dec.html,v 1.2 2002/11/27 03:25:46 eric Exp $ Terms of Service Privacy Information