Autotools for humans (part 1)
Autotools fills a strange niche in the *nix software world. People love to hate it — perhaps with good reason — yet there’s a good chance that the source tarball for your favourite open source project contains an Autotools-generated configure script.
Others claim that Autotools as a solution is broken, and suggest that we should instead use SCons, CMake or waf. Others still decide to roll their own solution.
I’m still on the fence, but one thing is clear: Autotools persists as a solution despite its technical shortcomings, limited documentation and unclear examples scattered across the internet. It’s hard to work with C & C on *nix systems without running into Autotools again & again. For this reason, I’ve spent a little time trying to get to know Autotools a little more intimately for some personal projects of my own.
I’m no expert on the topic & I’m still learning my way around — but I want to try & document the basics of using Autotools both my own benefit & to show that it’s not nearly as mysterious as it might first appear, though there’s a fair bit of guff involved. With any luck, it won’t be just another painful experience for you to work through. :)
Since this is “Part 1″, let’s take a look at a really simple example: compiling a simple C program using autotools. This is complete overkill for the autotools toolchain & though you’ll struggle to see any benefits, it should be easy to grasp the basics of how autoconf, automake, et al fit together.
First, the files in the project, to give you some context:
Makefile.am
autogen.sh
configure.ac
main.c
configure.ac
The first thing I usually do on a new project is write the configure.ac script, so let’s start there. This script serves as an input to autoconf, and describes tests for all the features you require of the host on which your project is being built, in addition to giving you a little opportunity to specify things like your project’s name & version number.
Describing itself as a “Shell Script Compiler”, autoconf theoretically makes it easier for you to generate configure scripts that test for features in portable ways across multiple platforms. Autoconf scripts are written in a language called m4, a macro language supported by most modern versions of UNIX.
You’ll often see an interesting mix of shell and m4 code in configure.ac scripts, but if you’re familiar with shell scripting it shouldn’t be particularly hard to follow along once you grasp the fundamentals.
Note that because these macros are essentially generating shell code, order can be important to some degree. How & when this is a problem is hard to determine, but I’ve yet to be badly bitten by an issue relating to ordering. Still, I’m sure it’s possible to get caught out!
AC_INIT([my-project],[0.0.1],[you@your.com])
AM_INIT_AUTOMAKE([1.9 foreign])
AC_PROG_CC
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
Let’s look at this line by line:
Line 1, AC_INIT is an m4 macro that initializes Autoconf & allows you to set the name, version and a contact email address for this project. Notice the square brackets? These make it easier for m4 to delineate where its arguments begin and end. I tend to use square brackets around all macro arguments. Feel free to tweak these to suit your own project.
Line 2, AM_INIT_AUTOMAKE is another m4 macro. Here we pass a single argument with the Automake options we require. 1.9 indicates we require at least version 1.9 of automake. foreign is simply to disable the default assumption that this project is a GNU project, suppressing some annoying warnings that are irrelevant to our example here. Again, feel free to experiment with these options later on.
Note that this macro will also call the AC_DEFINE macro which will pass your package name and version along to your compiler, which is handy for keeping your C source DRY.
Line 4, AM_PROG_CC is yet another m4 macro which generates code to try and find a C compiler on the build host. The documentation does a pretty good job of describing the functionality of AC_PROG_CC, but all you really need to know here is that this macro is the one that sets your CC (“C Compiler”) environment variable.
Line 6, AC_CONFIG_FILES specifies the output files generated by running the configure script. Remember that autoconf generates the configure script, so the configure scriptwill generate the files specified in AC_CONFIG_FILES on the build host — not autoconf.
If you read the documentation closely, you’ll that when we run the configure script, the code generated by AC_CONFIG_FILES([Makefile]) will be looking for an input file called Makefile.in. This file will be generated for us by Automake, which we’ll look at shortly.
Line 8, AC_OUTPUT generates code to write the output files specified in AC_CONFIG_FILES. Again, this won’t actually happen until we run the configure script.
Makefile.am
You may have noticed that when we call the AC_CONFIG_FILES macro in configure.ac, we’re indicating that we should generate a file called Makefile. Looking closely at the documentation for AC_CONFIG_FILES, you’ll see that the configure script will attempt to locate a file called Makefile.in to use as a template when generating Makefile. Although you could theoretically write Makefile.in yourself, we’re going to take full advantage of Autotools and generate Makefile.in from a file called Makefile.am & running it through Automake. We want to do this because Automake scripts are a lot simpler and provide automatic dependency management support.
Note that Makefile.am has more in common with traditional Makefiles than with the weird & wonderful m4 code we were dealing with in configure.ac, but the goal is the same: we write some simple code and automake generates a full-featured Makefile.in on our behalf. Keep that in mind as we look at our own Makefile.am:
bin_PROGRAMS = my-project
my_project_SOURCES = main.c
my_project_CFLAGS = -Wall
my_project_LDFLAGS =
Again, line by line:
Line 1, bin_PROGRAMS tells us several things:
- my-project is the name of the program we wish to build. You can specify more than one program here.
- bin tells us that when we run “make install”, these files should be installed in $bindir (e.g. /usr/local/bin or /usr/bin).
- PROGRAMS — which seems to be known as a “primary” in automake parlance — indicates that the files named are executable programs (and not libraries/scripts), &that they need to be compiled from sources & linked with other objects and/or libraries.
You can get a fair bit of interesting stuff from the documentation relating to the PROGRAMS primary. It’s probably also interesting to note that you can riff on bin_PROGRAMS in interesting ways. For example, changing it to sbin_PROGRAMS will see my-project installed in your $sbindir (like $bindir, this can be set when you run configure, but is usually /usr/local/sbin by default).
Another interesting one to try is check_PROGRAMS — these are programs that get built when you run “make check” in preparation for running tests. The “check” target appears to be named after the check unit testing framework for C.
Line 2 my_project_SOURCES is the variable that bin_PROGRAMS will use to determine what source files to use when building my-project (note that this variable name is derived from our program name — the hyphen is replaced by an underscore).
Line 3 my_project_CFLAGS is the variable that bin_PROGRAMS will use to determine extra CFLAGS to be passed to the C compiler when building my-project (again, this variable name is derived from the program name). Here we’re indicating that we want to see compile-time warnings.
Line 4 my_project_LDFLAGS indicates the variable that bin_PROGRAMS will use to determine extra LDFLAGS to be passed to the linker when linking my-project (yep, variable name derived from the program name). We’re not passing any LDFLAGS to the linker here.
main.c
Okay, let’s write a little C program to prove that all this stuff works:
#include
int
main (int argc, char **argv)
{
return 0;
}
Hopefully you know enough C to understand what’s going on here. :)
autogen.sh
Okay, now to bring it all together. It’s pretty typical for folks to write an autogen.sh script when dealing with Autotools projects. Running autogen.sh will run the various autotools programs in the appropriate order. Let’s look at a simple autogen.sh:
#!/bin/sh
aclocal --install -I m4 &&
autoconf &&
automake --add-missing --copy &&
./configure "$@"
Once again, let’s go line by line:
Line 1 Shebang line, nothing to see here.
Line 3 aclocal is an automake tool that generates aclocal.m4, which supplies a bunch of the evil necessary for automake integration with autoconf including the AM_INIT_AUTOMAKE macro. This file will be automatically picked up and incorporated by autoconf in the next step. Refer to the documentation for the options we pass here — in practice the options don’t do anything interesting for this simple little project. All we care about is aclocal.m4.
Line 4 we run autoconf. This generates a configure shell script from the macros we added to configure.ac. Recall that our configure script requires Makefile.in to be present. We can’t run it yet.
Line 5 we run automake to generate Makefile.in. Once again, check the automake documentation for the full details of the options here. Basically we’re indicating that we want to add in files necessary for the correct operation of automake, and that these files should be copied in rather than symlinked to a directory on the build system.
Line 6 we (finally!) run configure. This will ultimately generate a Makefile to build our project.
Push the Big Red button
Now you’re ready to chmod 755 autogen.sh, then run it! ./autogen.sh you should see a bunch of output and (hopefully) no errors. If you’ve got a configure script and a Makefile at the end of it, things probably worked!
From here, it’s a simple matter of running “make” and you’ll get a binary called my-project compiled & linked from main.c as per your Makefile.am.
Experiment!
If you’re eager to play around more, try the following:
Run a make install to install my-project to your system. This is usually /usr/local/bin by default. Run make uninstall to remove it from your system.
Run the configure script with –prefix=/usr, then make install again to install my-project to /usr/bin. Again, you can make uninstall to get rid of it.
Run make clean to delete intermediate build files.
Run a make maintainer-clean, which will perform a clean and then blow away many of the temporary files generated by the autogen process. I use make maintainer-clean prior pulling my source code into git for the first time.
Run make dist to generate a versioned tarball of your source code.
The End
And that’s it for this first post about Autotools! I’ve put the code from this tutorial up on github so that you can play with a pristine version of everything we discussed here. Feel free to get in touch if you have any problems getting it working. Good luck!
(By the way, I’ve been working with autoconf 2.69 and automake 1.11.6. If you see any weird behaviour, ensure you’re running something reasonably close to this — but don’t be afraid to reach out if you need a hand)
UPDATE: Part 2 is here.