[ Home : Programs | libCVD | Hardware hacks | Publications | Teaching | TooN | Research ]

A brief introduction to Autoconf

Introduction

Autoconf is a system for generating a script which will automatically determine the presence of things which your project either depends upon, or can make use of. Once these have been determined, autoconf can export this information in various formats suitable for your project to use. Autoconf scripts themselves are written in a combination of shell and M4 (a macro processing language considerably more capable than the C preprocessor). Autoconf essentially runs the preprocessor on your script to produce a portable shell script which will perform all the requisite tests, produce handy log files and take a standard set of command line arguments.

If you already have a project and are currently writing tests using your own ad-hoc system, I strongly recommend you to switch to Autoconf. The longer you leave it, the more painful your own system will become, and the harder switching to another system (i.e. Autoconf) will be. I say this from personal experience.

There are several tools in the suite, including automake (for automatically generating makefiles) and autoscan (for scanning source trees to make configure scripts). I'm not going to cover them.

In order to read this tutorial, a knowledge of shell scripting is assumed, but not essential for the basic examples. No knowledge of M4 is needed, but it worth knowing especially if you want to write your own custom tests which you wish to reuse.

Finally, this is not a reference work (use the Autoconf documentation for that), it is a tutorial, and is designed to be read through in order. All of the configure script examples are complete scripts which should compile and run.

Hello, World.

Save the following script to configure.ac:

AC_INIT(myconfig, version-0.1)
AC_MSG_NOTICE([Hello, world.])
Now do:
autoconf configure.ac > configure
chmod +x configure
./configure
and you get:
configure: Hello, world.

Now that that's out of the way, there's a few basic things.

Performing basic tests

Detecting a compiler

Autoconf assumes that you're using C, C++ or Fortran. It then tests for libraries and header files using small programs written in one of these languages (the default is C). The first thing to do is to check for this language:
AC_INIT(myconfig, version-0.1)
echo "                             Testing for a C compiler"
AC_PROG_CC
echo "                             Testing for a C++ compiler"
AC_PROG_CXX
echo "                             Testing for a FORTRAN compiler"
AC_PROG_F77

AC_LANG(C++)
If you run this configure script you should get a bunch of output about testing for each of the three languages in question. The last instruction tells Autoconf that we're working in C++, so all the test programs will be compiled with the C++ compiler as opposed to the C compiler. You can, of course, omit tests for languages which you're not using.

Looking for header files

Try the following script to check for the presence of the C++ header iostream (you're a bit screwed if it's not there, after all...)
AC_INIT(myconfig, version-0.1)

AC_PROG_CXX
AC_LANG(C++)

AC_CHECK_HEADERS(iostream)
Run the script, and at the bottom, you should see the test for the script succeeding. You can check for multiple headers using AC_CHECK_HEADERS([header1.h header2.h])

Testing for Libraries

Autoconf actually checks for a specific function in a specific library, by compiling a test program, and seeing if it links correctly. To perform the test for cos in the maths library try the following:
AC_INIT(myconfig, version-0.1)
AC_PROG_CC
AC_CHECK_LIB(m, cos)

This will compile and link a program looking a bit like like this:

char cos ();
int main ()
{
  cos ();
  return 0;
}
If you want to see exactly what the program looks like, then create a test which you know will fail and examine config.log. If you're running out of ideas of which function to test for, then test for main.

Doing something useful with the tests.

You can now write a lovely configure script which will perform tests and print out lots of "yes"s and lots of "no"s and generally look very much like a proper configure script, but it currently doesn't do much since you don't do anything with result of the tests.

Using the C preprocessor (A cheap and dirty hack)

During a configuration, the configure script will create a file called confdefs.h, which is cleaned up at the end. If you copy this file before the end, you can keep its contents. The following script does just that:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC
AC_CHECK_LIB(m, cos)
AC_CHECK_HEADER(unistd.h)
AC_CHECK_HEADER(b0rk.h)
AC_DEFINE(my_own_define, yes)
cp confdefs.h my_config.h
You'll now have a file called my_config.h looking like:
#define HAVE_INTTYPES_H 1
#define HAVE_LIBM 1
#define HAVE_MEMORY_H 1
#define HAVE_STDINT_H 1
#define HAVE_STDLIB_H 1
#define HAVE_STRINGS_H 1
#define HAVE_STRING_H 1
#define HAVE_SYS_STAT_H 1
#define HAVE_SYS_TYPES_H 1
#define HAVE_UNISTD_H 1
#define PACKAGE_BUGREPORT ""
#define PACKAGE_NAME "myconfig"
#define PACKAGE_STRING "myconfig version-0.1"
#define PACKAGE_TARNAME "myconfig"
#define PACKAGE_VERSION "version-0.1"
#define STDC_HEADERS 1
#define my_own_define yes
Notice how we have HAVE_UNISTD_H and HAVE_LIBM, which are the tests performed successfully. Also notice how HAVE_B0RK_H is missing. You now have a working configure script. If you only have a few simple tests to perform, are happy to #include "my_config.h", and perform all of your logic in the C preprocessor, then you can probably stop reading now and start coding. If not, then pay particular attention to how we we got our own #define in to the file.

This works, is quick and easy to use, but confdefs.h is not considered an ``exported'' interface. I have now been told that doing this is not good practice, and that you should do things properly, as described in the next section. I am leaving it in, however, since you may well encounter this when examining other scripts for ideas (which is where I got the idea from).

Makefiles, etc (doing it properly)

Configure can also preform test substitutions on files on files called ???.ac producing an output file with the .ac suffix stripped off, and all instances of @foo@ replaced with the contents of the variable foo. Here's an example. Create the following configure.ac:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC
AC_SUBST(stuff, hello)

AC_OUTPUT(test)
and create the following file called test.in:
Prefix: @prefix@
C compiler: @CC@
Stuff: @stuff@
Run the configure script and examine the contents of test. It should look like this:
Prefix: /usr/local
C compiler: gcc
Stuff: hello
As you can see, a standard option (prefix) was set, a tested for option (the C compiler) was set, and one of out own substitutions was made, using AC_SUBST. It is at this point that the script begins to accept standard command line arguments as well. Try running:
./configure  CC=g77 --prefix=/tmp
and examining the output file (it is a quirk of GCC that g77 will compile C programs). You will get:
Prefix: /tmp
C compiler: g77
Stuff: hello
This tutorial is not about writing makefiles, either, but if you already know how to write a makefile, it should be clear now how you can generate a working make file which uses the correct C compiler, install directory, etc... config.log contains a complete list of the substitutions in the "Output variables" section. Note that tests for libraries and header files do not appear in the list of substitutions to be made.

One further useful point to note: now that you have used AC_OUTPUT, there is a file created called config.status. If you alter any of the .in files, you only need to run config.status as opposed to the whole configure script. This speeds things up greatly when debugging.

Using the shell logic

Run the following configure script and examine config.log:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC
AC_CHECK_LIB(m, cos)
AC_CHECK_HEADER(unistd.h)
AC_CHECK_HEADER(b0rk.h)
You should find the following section:
ac_cv_c_compiler_gnu=yes
ac_cv_env_CC_set=
ac_cv_env_CC_value=
ac_cv_env_CFLAGS_set=
ac_cv_env_CFLAGS_value=
ac_cv_env_CPPFLAGS_set=
ac_cv_env_CPPFLAGS_value=
ac_cv_env_CPP_set=
ac_cv_env_CPP_value=
ac_cv_env_LDFLAGS_set=
ac_cv_env_LDFLAGS_value=
ac_cv_env_build_alias_set=
ac_cv_env_build_alias_value=
ac_cv_env_host_alias_set=
ac_cv_env_host_alias_value=
ac_cv_env_target_alias_set=
ac_cv_env_target_alias_value=
ac_cv_exeext=
ac_cv_header_b0rk_h=no
ac_cv_header_inttypes_h=yes
ac_cv_header_memory_h=yes
ac_cv_header_stdc=yes
ac_cv_header_stdint_h=yes
ac_cv_header_stdlib_h=yes
ac_cv_header_string_h=yes
ac_cv_header_strings_h=yes
ac_cv_header_sys_stat_h=yes
ac_cv_header_sys_types_h=yes
ac_cv_header_unistd_h=yes
ac_cv_lib_m_cos=yes
ac_cv_objext=o
ac_cv_prog_CPP='gcc -E'
ac_cv_prog_ac_ct_CC=gcc
ac_cv_prog_cc_g=yes
ac_cv_prog_cc_stdc=
ac_cv_prog_egrep='grep -E'
Some of these will be relevant to the tests, such as ac_cv_lib_m_cos=yes. These are all defined shell variables (not environment variables), and can be used in the following manner:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC
AC_CHECK_LIB(m, cos)
AC_CHECK_HEADER(unistd.h)
AC_CHECK_HEADER(b0rk.h)

if test "$ac_cv_header_b0rk_h" == no
then
	AC_MSG_WARN([Error, the b0rk header is missing!])
fi
This script should print a warning message if b0rk.h is missing (which it really should be). You can test this further by creating an empty file called b0rk.h and running:
./configure CPPFLAGS="-I ."
The warning should now be absent. The variables set by autoconf are rather long and cumbersome, but there is a way around this. All of the AC_CHECK_* macros take optional arguments for success and failure:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC
AC_CHECK_HEADER(b0rk.h, [echo "OK!!"], [echo "Failed!!"])
test this as above. This can lead to the following idiom which is useful if a feature depends on many things at once:
AC_INIT(myconfig, version-0.1)

AC_PROG_CC

a=1
AC_MSG_NOTICE([Checking for b0rk and its requirements.])
AC_CHECK_LIB(m, cos, [], [a=0])
AC_CHECK_HEADER(sys/ioctl.h, [], [a=0])
AC_CHECK_LIB(c, vsnprintf, [], [a=0])
AC_CHECK_HEADER(b0rk.h, [], [a=0])

if test $a == 0
then
	AC_MSG_NOTICE([Feature b0rk is missing.])
else
	AC_MSG_NOTICE([Feature b0rk is present.])
fi
If any one of the tests fails, then the test for b0rk fails too. You may wish to print out a helpful error message here. Of course, you can use all of the other shell logic constructs (NB don't forget to use test as opposed to [ , otherwise you will get some very strange errors). At this point it is well worth reading "Portable Shell Programming" in the Autoconf documentation, otherwise your configure script may be of less use than you think.

More tests

There are many more tests than the ones covered here: If you wish to test for something, it is worth seeing if Autoconf will do it for you, before trying to do it yourself. Bear in mind that the tests often have optional arguments which allow you to fine-tune their behavior.

--with-foo

Command line arguments of a standard form can are common in configure scripts. Here is an example of how to use them:
AC_INIT(myconfig, version-0.1)

AC_ARG_WITH(stuff, [  --with-stuff            enable stuff])

if test "$with_stuff" == "yes"
then
	AC_MSG_NOTICE([You enabled stuff. Good for you.])
elif test "$with_stuff" != ""
then
	AC_MSG_NOTICE([Your specific kind of stuff is $with_stuff])
else
	AC_MSG_NOTICE([No stuff for you.])
fi
Try running the script with --with-stuff and --with-stuff=hello. Also run ./configure --help and see your argument appear. You need to use this macro to perform --without-* tests as well, but you have to invert your shell logic afterward.

Communicating with the compiler

This one is easy. Just append stuff to CFLAGS (for the C compiler), CPPFLAGS (for the C preprocessor, C and C++ compilers), CXXFLAGS (for the C++ compiler) and LIBS (for the linker). Whatever is appended to these flags will be exported as a substitution if the configure script terminates correctly. It is common to do that when you allow the user to specify paths to optional libraries. If you really need to, you can run the compiler directly with $CC.

Performing custom tests

If there is really nothing available to test for a particular feature, you can build tests by hand. Here is an example of checking for a specific processor model (on a Linux system):
AC_INIT(myconfig, version-0.1)

AC_MSG_CHECKING([for a Pentium III processor])
if grep -q "Pentium III" /proc/cpuinfo
then
	AC_MSG_RESULT([yes])
else
	AC_MSG_RESULT([no])
	AC_MSG_ERROR([I can't work on anything but a PIII processor.])
fi
This performs a completely nonstandard test, but it looks like a standard one, from the outside. The script will fail with an error if the test fails.

Care needs to be taken in order to ensure that the tests are portable, or at lease to not fail catastrophically on different systems.

Configuring for compatibility

Configure can also be used to check for missing functions on old, broken or plain nonstandard systems. You will have to consult the official Autoconf documentation for that, since I have no experience in performing and using these kinds of tests.

Host specific stuff

In order to detect the host type properly, you will need a shell script called config.guess to be distributed with your program. This will should be installed somewhere as part of your autoconf installation.

Updated September 14th 2016, 04:23