This is the second post of the autoconf tutorial series. In this post I’ll cover some fundamental units in autoconf and automake, and an example cross platform X11 program that uses the concepts in this post. After reading this post, you should be able to write your own build script for small scope projects.
Autoconf
Autoconf is part of the GNU Autotools build system. Autotools is a collection of three main packages: autoconf, automake, and libtools. Each of the package has smaller sub-packages including autoheader, aclocal, autoscan etc. I won’t cover the details of all the packages; instead I’ll only focus on how autoconf plays its role in the build chain.
Autoconf is mainly used to generate the configure
script. configure
is a
shell script that detects the build environment, and output proper build
flags to the Makefile, and preprocessor macros (like HAVE_ALLOCA_H
) to
config.h
. However, writing a good portable, extensible shell script isn’t
easy. This is where the gnu m4 macro comes in. Gnu m4 macro is an
implementation of the traditional UNIX macro processor. By using m4, you can
easily create portable shell script, include different pre-defined macros, and
define your own extensions easily.
In short, autoconf syntax is shell script wrapped by gnu m4 macro.
In the early days, writing portable shell scripts wasn’t that easy. For example
not all the mkdir
support -p
option, not all the shells are bash
compatible, etc. Using the m4 macro to perform the regular shell logics, like
AS_IF
instead if if [[ ]]; then...
, AS_MKDIR_P
instead of mkdir -p
,
AS_CASE
instead of case ... esac
makes your configure script works better on
all unix/unix-like environment, and more conventional. Most of the time you’ll
be using macros instead of bare bone shell script, but keep in mind that behind
the scene your final output is still shell script.
M4 Macro Basics
Though the first look at M4 macros is very alien and unfriendly, but it only consist two basic concepts:
- Macro expansion
- Quoting
You can define a macro like so:
1 2 3 4 5 6 7 |
|
It’s pretty much similar to C macro or Lisp macro. The macro expands at compile
time (configure.ac
=> configure
). You can define a macro MY_MACRO
that
expands to a snippet of shell script. Here we just expands it to ABC
, which
doesn’t have any meaning in shell script and can trigger an error.
Every symbol in your script is expandable. For example if you simply write ABC
in your script, is it a shell symbol, or is it a m4 symbol that needs to expand?
The m4 system uses quoting to differentiate the two. The default quoting in
autoconf is square brackets [
, ]
. Though you can change it, but it is highly
unrecommended.
1 2 |
|
Why does it matter? Consider these two examples
1 2 3 4 5 6 7 8 9 |
|
This is the base of all m4 macros. To recap, always quote the arguments for the macros, including symbols, expressions, or body statements. (I skipped some edge cases that requires double quoting or escapes, for the curious please check the autoconf language).
Printing Messages
Now we know the basic syntax of m4, let’s see what are the functions it
provides. In the configure script, if you invoke echo
directly the output
would be redirected to different places. The convention to print message in
autoconf, is to use AC_MSG_*
macros. Here are the two macros that is most
commonly used:
1 2 3 4 5 |
|
For the more curious, check the Printing Messages section in autoconf manual.
If-condition
To write an if condition in autoconf, simply invoke
AS_IF(test-1, [run-if-true-1], ..., [run-if-false])
.
The best way to see how it works is by looking an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Note that we don’t use common shell test operator [[
and ]]
, instead we use
test
because the square bracket is preserved for macro expansion. The
recommended way to invoke test is test "X$variable" = "Xvalue"
. This is how we
avoid null cases of the shell variable.
Another common branching function is AS_CASE(word, [pattern1], [if-matched1], ..., [default])
the logic is pretty much the same.
That all the basics we need to know for autoconf, let’s take a break and switch to automake.
Automake
Like autoconf, automake is additional semantics on top of another existing language – the Makefile syntax. Unlike autoconf, it’s not using m4 to extend the syntax. It uses a naming convention that converts to the actual logic. Most of the time, we only need to use the following two rules, which we’ll discuss in detail.
where_PRIMARY = targets
target_SECONDARY = inputs
where_PRIMARY = targets
This syntax has three parts, targets
, type PRIMARY
, and where to install
where
. Some examples shown as below:
1 2 3 4 5 |
|
The targets
is a list of targets with the type PRIMARY
. Depending on what
PRIMARY
is, it can be a program, a library, a shell script, or whatever
PRIMARY
supports. The current primary names are “PROGRAMS”, “LIBRARIES”,
“LTLIBRARIES”, “LISP”, “PYTHON”, “JAVA”, “SCRIPTS”, “DATA”, “HEADERS”, “MANS”,
and “TEXINFOS”.
There are three possible type of variables you can put into the where
clause.
-
GNU standard directory variables (bindir, sbindir, includedir, etc.) omitting the suffix “dir”. See GNU Coding Standard - Directory Variables for list of predefined directories. Automake extends this list with
pkgdatadir
,pkgincludedir
,pkglibdir
, andpkglibexecdir
Automake will check if your target is valid to install the directory you specified. -
Self-defined directories. You can hack around automake default type check by defining your own directories. Do not do this unless you have a good reason!
1 2 3 4 5 6 |
|
- Special prefixes
noinst_
,check_
,dist_
,nodist_
,nobase_
, andnotrans_
.noinst_
indicates the targets that you don’t want to install;check_
is used for unit tests. For the others are less common, please check the automake manual for detail.
target_SECONDARY = inputs
Depending on what your PRIMARY
type is, there are different SECONDARY
types
you can use for further logic. The common SECONDARY
types are
_SOURCES
defines the source for primary type_PROGRAMS
or_LIBRARIES
_CFLAGS
,_LDFLAGS
, etc. compiler flags used for primary type_PROGRAMES
or_LIBRARIES
Note that the invalid character in target
name will get substituted with
underscore. The following example illustrate all the above:
1 2 3 4 |
|
The example above requires libtool. You need to declare
AC_PROG_LIBTOOL
in your configure.ac
for it to work.
Wraps it up - A X11 example program
With everything we learnt so far, let’s write a more complicated autoconf
program. This is a very simple X11 program that aims to be portable on all
existing platforms with valid X11 installed. To test if X11 is installed, we use
the macro AC_PATH_XTRA
, the manual for this macro is defined in
autoconf existing test for system services.
The manual says: An enhanced version of AC_PATH_X
. It adds the C compiler flags
that X needs to output variable X_CFLAGS
, and the X linker flags to X_LIBS
.
Define X_DISPLAY_MISSING
if X is not available. And in the AC_PATH_X
it
states “If this method fails to find the X Window System … set the shell
variable no_x to ‘yes’; otherwise set it to the empty string”. We can use the
logic and write our configure.ac
script as following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Note that the AC_PATH_XTRA
export variables X_CFLAGS
and X_LIBS
. To use
these variables in Makefile.am
, just surround it with @
.
1 2 3 4 5 6 7 |
|
That all we need to build a platform independent X11 program! Check the full source on github. The X11 example program was written by Brian Hammond 2/9/96. He generously released this to public for any use.
This program can easily work on Linux. I’ll use OSX as an example of how cross platform works. Before you run the example, make sure you have XQuartz installed.
1 2 3 4 5 |
|
Change the --x-includes
and --x-libraries
to proper directory if you
installed the xquartz to a different location.
I only introduced very little syntax for autoconf (if-else, print message) and
automake (primary/secondary rules, use of export variables by @
). But just
using these basic component is already very sufficient for writing conventional
build scripts. How to do it? Check the [existing tests provided by
autoconf][exsisting test]. Here are some of the most commonly used existing
checks:
- Library checks:
AC_CHECK_LIB
AC_SEARCH_LIBS
. library documentation. - Header checks:
AC_CHECK_HEADER[S]
. header documentation. - Compiler characteristics.
For the checks that are not included in the default autoconf package, it probably exists in the extended package autoconf archive, which I’ll cover in the next post.