How To Use Autotools

The autotools infrastructure can be extremely overwhelming to newcomers. I intend here to give a brief tutorial on how to write a C or C++ project that uses autools and automake. I will also cover libtool, which you'll need if you want to ship shared libraries (i.e. .so files.).

Preparing configure.ac

By convention you should put your C or C++ code in a directory called src/. Do not put your code in the top level of your project or you will make things a lot harder for yourself.

Once you've got your code basically working, you should run the command autoscan from the root of your project. This tool will automatically scan all of the code in your project and try to come up with a recommended configure.ac template for you. Typically this will generate two files, autoscan.log and configure.scan. The one you're interested in is configure.scan.

Here's an example of the file it generates in a C++ project I'm currently working on, although your version will almost certainly be different:

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/readelf.h])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CXX
AC_PROG_AWK
AC_PROG_CC
AC_PROG_CPP
AC_PROG_INSTALL
AC_PROG_LN_S
AC_PROG_MAKE_SET
AC_PROG_RANLIB

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([limits.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_C_INLINE
AC_TYPE_PID_T
AC_TYPE_SIZE_T
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT64_T
AC_TYPE_UINT8_T

# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MMAP
AC_CHECK_FUNCS([getcwd getpagesize memmove strerror strtol])

AC_CONFIG_FILES([Makefile
                 src/Makefile])
AC_OUTPUT

Rename this file to configure.ac and then modify it as necessary. At the very minimum, you will have to update the AC_INIT field to fill in the package name, version, and an email address.

One other thing to be aware of is that AC_PREREQ line. That specifies the minimum version of autotools you have to have. Older distrbutions may be on older versions. In particular, a lot of people are still using the Ubuntu 12.04 LTS release ("Precise"), and that has autotools 2.68. So if you want to support older distros you should try running autoscan on an older distro and see what you get. Another thing to be aware of here is that autotools 2.69 is likely to generate a line that says AC_CHECK_HEADER_STDBOOL, and that directive is not supported by autotools 2.68, so you may want to delete it.

I strongly recommend also using the AC_CONFIG_AUX_DIR directive. Here's why. When you run autoreconf it's going to pollute your projefct with a ton of weird scripts that you don't want to check in. If you use AC_CONFIG_AUX_DIR it will instead put all of those weird scripts in the directory you specify, and then you can just add that directory to your .gitignore. The line should look like this, and should immediately follow the AC_CONFIG_SRCDIR line:

AC_CONFIG_AUX_DIR([build-aux])

Now autoreconf will put all of its weird files in a directory that it will create (if necessary) called ./build-aux.

I strongly recommend using automake. This is pretty easy. You add the following line to your configure.ac towards the end (I put mine right above the AC_CONFIG_FILES line):

AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])

Obviously you can change the compiler flags here if you want different ones.

If you want to generate shared libraries there is one more line you need to add. You will need to add a line like this:

LT_INIT([shared disable-static])

This tells autoconf that you want to build shared libraries (and not to build static libraries; that's up to you though).

I strongly recommend adding a file called autogen.sh at the root level of your project that will automatically re-generate your ./configure script from configure.ac. A minimal version of that script will look like this:

#!/bin/sh
autoreconf --install

One last thing. You will periodically want to re-run autoscan as your program changes, as autoscan can report errors in your existing configure.ac file and suggest fixes. For instance, if you start using a new header then you might not have the right macro to detect its presence in your configure.ac script, and running autoscan will detect this for you and suggest that you add it. I recommend diffing your existing configure.ac and the newly generated configure.scan and picking the suggested changes that you think make sense.

Library Checking

The autoscan tool doesn't always do a good job of knowing what libraries you need. Therefore you may need to add some of your own checks to your configure.ac file to check for libraries. Here's an example from a project I'm working on:

# Checks for libraries.
AC_SEARCH_LIBS([dlopen], [dl], [], [
  AC_MSG_ERROR([unable to find dlopen()])
])
AC_SEARCH_LIBS([cs_disasm_ex], [capstone], [], [
  AC_MSG_ERROR([unable to find libcapstone2])
])

The first says "check that there is a library called -ldl that has a method named dlopen()". The second says "check that there is a library called -lcapstone that contains a method called cs_disasm_ex()". If your library can be in multiple places you can put multiple parameters in the second option. For instance, some Unixes put dlopen() in -ldld instead of -ldl, so you could check for that like this:

AC_SEARCH_LIBS([dlopen], [dl dld], [], [
  AC_MSG_ERROR([unable to find dlopen()])
])

In this case it will make your ./configure script check both of those libraries and your code will automatically link against the right one..

Header Checking

If you need certain headers to be present to compile your code then you need to add directives to configure.ac to check this. You can do this like so:

AC_CHECK_HEADERS([python2.7/Python.h], [], [
  AC_MSG_ERROR([unable to find header Python.h])
])

This will cause the ./configure script to fail if Python.h isn't found, with an error like this:

...
checking python2.7/Python.h usability... no
checking python2.7/Python.h presence... no
checking for python2.7/Python.h... no
configure: error: unable to find header Python.h

You have to be pretty careful when doing this because different operating systems will put headers in different places. For instance, some distributions might put Python.h directly in /usr/include instead of /usr/include/python2.7/ as in this example. If you want to handle all of these cases the code can get pretty complex. I would recommend using Google or GitHub search to search for configure.ac files containing the header you're looking for, and then use someone else's code to get an idea of what you should do.

Configuring Automake (and Libtool)

You're almost done! Now you just need to make two Makefile.am files. One will go at the root of your project, the other will go in your src directory.

The Makefile.am at the root of your project will look like this:

SUBDIRS = src
EXTRA_DIST = autogen.sh

There's more stuff you can put in here, but that's beyond the scope of this post.

The file src/Makefile.am is where your real logic will be. It might look something like this for a very simple C++ project.

AM_CXXFLAGS = -g -std=c++11 -Wall -fPIC

lib_LTLIBRARIES = libpymemtrace.la
libpymemtrace_la_SOURCES = libpymemtrace.cc

bin_PROGRAMS = pymemtrace
pymemtrace_SOURCES = pymemtrace.cc

This says we want to build a libtool shared library called libpymemtrace.so, and we also want to build a binary executable called pymemtrace. If you're not interested in using libtool then omit those lines.

In the _SOURCES lines you need to list all of the source files, just like a regular Makefile. If you have a complex project that reuses multiple files for multiple targets you can create variables to simplify things, just like in regular Makefiles.

If you're using libtool, there's one more idiosyncrasy you need to be aware of. If you have source files that are used both by the library you're building and the executable you're using then you'll get an error when running autoreconf (which is run by your configure.ac script). You'll see an error line like this:

src/Makefile.am: error: object 'util.$(OBJEXT)' created both with libtool and without

This is really easy to fix, there's a page in the automake manual about this topic that I'll refer you to. Basically you just need to add a line like this for a C project:

prog_CFLAGS = $(AM_CFLAGS)

or

prog_CXXFLAGS = $(AM_CXXFLAGS)

for a C++ project. Replace prog with the actual program you're using.

Building Your Project

If you've made it this far you should be able to build your project like this:

./autogen.sh
./configure
make

Binary programs will be generated in src/, libtool libraries will be generated in src/.libs/. If your binary links against the libtool library, the "binary" you get in src/ will actually be a stub shell script that sets up LD_LIBRARY_PATH and whatnot and then runs the true binary which is in src/.libs/. If you're not linking against the libtool library you'll just get a regular executable in src/. Normally this will be totally transparent to you, but I mention it because if you want to examine core dumps in GDB you'll need to give GDB the true program which will be something like src/.lib/myprog.

Distributing Source Tarballs

I recommend not checking in any of the autogenerated files in your project. There's going to be a lot of them, they're just boilerplate, and they're going to change when you update autoconf or automake. You do need to ship them when if you are distributing a source tarball intended for end users who want to compile your project, but that's easy. You run:

make dist

And it will create a file named programname-version.tar.gz that has all of the files required for end users to build your project.

Update: 2016-02-23

I've since learned how to use PKG_CHECK_MODULES which I strongly recommend for packages that are configured for usage with pkg-config. This mean's you'll need a file named foo.pc on your computer for libfoo. Here's what I'm using in a current work project:

# GOOD
PKG_CHECK_MODULES(CAPSTONE, capstone)
PKG_CHECK_MODULES(GFLAGS, libgflags)
PKG_CHECK_MODULES(GLOG, libglog)
PKG_CHECK_MODULES(PYTHON, python)

You would use this instead of the previous pattern that I had suggested (in the case of Capstone):

# BAD
AC_SEARCH_LIBS([cs_disasm_ex], [capstone], [], [
  AC_MSG_ERROR([unable to find libcapstone2])
])
AC_CHECK_HEADERS([capstone/capstone.h], [], [
  AC_MSG_ERROR([unable to find header capstone.h])
])