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])
])