Introduction
The first article in this series introduced GNU Autotools, a higher-level build system for programs enabling portability. Continuing in this series, this article is about using Autotools to allow the user to enable or disable use of third-party packages and program features.
Packages vs. Features
To be clear, there are two similar, yet different things being discussed:
- Compiling your program with third-party software packages. For example, your program might either require such a library to compile, or simply provide more features if available. Common such libraries include GNU Readline, libcurl, or OpenSSL.
- Compiling your program having features enabled or disabled. For example, your program might include optional features such as extra debugging information or compiling with clang’s address sanitizer.
However, they’re sufficiently similar that it warrants discussing them together.
Packages
For an example, cdecl
uses Readline to allow command-line editing. It’s so useful, that it’s on by default. However, if it’s not available on a particular system, the user can compile cdecl
without it and cdecl
will fall back to using just getline
. Here’s a snippet that goes into configure.ac
:
AC_ARG_WITH([readline],
AS_HELP_STRING([--without-readline], [disable readline])
)
AS_IF([test "x$with_readline" != xno], [
AC_DEFINE([WITH_READLINE], [1],
[Define to 1 if readline support is enabled.])
AS_IF([test "x$withval" != xyes], [
CPPFLAGS="-I${withval}/include ${CPPFLAGS}"
LDFLAGS="-L${withval}/lib ${LDFLAGS}"
])
])
AC_ARG_WITH
is an Autoconf macro that takes the name of the third-party package (here, readline
) and a help string that’s displayed in response to the --help
option being given to configure
.
AC_ARG_WITH
sets the variables with_
name where name is the name used in its first argument that is one of no
, yes
, or, the optional value specified by the user that is also put into withval
. The optional value allows the user to specify where on the filesystem the package is installed if in a non-standard location. For example, the user could do one of:
$ ./configure --without-readline
$ ./configure --with-readline
$ ./configure --with-readline=/opt/local
In the first case, with_readline
is set to no
; in the second, it’s set to yes
; in the third, it’s set to /opt/local
.
AS_HELP_STRING
takes the name of the command-line option to add to configure
and what that option does. As stated, we want use of Readline to be on by default, hence the option is --without-readline
to disable it.
AS_IF
is Autconf’s if-else statement. Typically, the shell command test
is used to perform the test. In this case, we test that with_readline
is not equal to no
. Since we want the default to be to build with Readline, we want only the value of no
to disable it.
The use of the leading
x
is a common technique in Shell programming to avoid possible problems with shells misinterpreting empty arguments as missing arguments. This is a problem in Shell (or any programming language) that uses whitespace to separate arguments rather than commas used in most programming languages.Similarly, another common technique is always to enclose variable expansions within
""
(double quotes) so if a value contains whitespace, it’ll still be treated as a single argument.
At this point, if Readline has not been disabled, then the second AS_IF
checks withval
: if it’s not yes, then the user specified an optional argument: the path, so we add that to both CPPFLAGS
(for where the package’s header files are to be included) and LDFLAGS
(for where the package’s library is to link against).
Features
For an example, cdecl
allows compiling with clang’s address sanitizer. Here’s a snippet that goes into configure.ac
:
AC_ARG_ENABLE([asan],
AS_HELP_STRING([--enable-asan],
[enable Address Sanitizer for debugging])
)
AS_IF([test "x$enable_asan" = xyes], [
AC_DEFINE([ENABLE_ASAN], [1],
[Define to 1 if Address Sanitizer is enabled.])
])
AC_ARG_ENABLE
is an Autoconf macro that takes the name of a program’s feature either to enable or disable. Other than that, it works exactly like AC_ARG_WITH
. Note that in this case, the feature is disabled by default, hence the option is --enable-asan
.
AC_DEFINE
defines a preprocessor symbol with a given value. (The third argument is a comment.) The symbol gets defined in config.h
.
Third-Party Headers
Just because use of a third-party package is enabled by default doesn’t mean the package’s headers are actually installed. You need to check whether they are:
AS_IF([test "x$with_readline" != xno], [
AC_CHECK_HEADERS([readline/readline.h readline/history.h],
[],
[AC_MSG_ERROR([readline.h header not found; use --without-readline])]
)
])
AC_CHECK_HEADERS
checks for the existence of one or more headers. For Readline, both of the aforementioned headers must exist, so for the action-if-not-found (third) argument, AC_MSG_ERROR
both prints an error message and aborts configure
.
AC_CHECK_HEADERS
, if successful, also defines symbols of the form HAVE_
header where header is the name of the header converted to uppercase and non-identifier characters replaced by _
(underscore), so the above would define HAVE_READLINE_READLINE_H
and HAVE_READLINE_HISTORY_H
.
As an alternative to printing an error message and aborting if a header isn’t found, you could choose simply not to compile code that makes use of that header via #ifdef
. For example, if readline.h
isn’t found, cdecl
could, as mentioned, simply fall back to using getline
.
I personally think it’s a mistake to disable functionality silently. For cdecl
, if readline.h
weren’t found and it simply fell back to using getline
, then it would compile just fine yet not have the functionality that Readline provides (such as autocompletion). Unless the user just so happened to notice the single line of output from configure
that readline.h
was not found (which is easy to miss), the user might think something is wrong with cdecl
because autocompletion isn’t working. I therefore think it’s better to error-out and force the user explicitly to disable Readline via --without-readline
if it’s not available.
Third-Party Functions
Similarly to headers, just because a third-party package’s headers are installed doesn’t mean a particular function is available in case there are multiple versions of a package. You can check whether specific functions are declared:
AS_IF([test "x$with_readline" != xno], [
AC_CHECK_DECLS([rl_ding], [], [],
[#include <stdio.h>
#include <readline/readline.h>
])
])
AC_CHECK_DECLS
checks whether a particular symbol is declared (first argument) after including the given file(s) (fourth argument). If declared, Autotools will define HAVE_DECL_
symbol only if it’s available where symbol is the name of the symbol converted to uppercase.
In cdecl
’s case, it uses the result of that check to define its own version of rl_ding
if not available:
#if !HAVE_DECL_RL_DING
/**
* Partial implementation of GNU readline's `rl_ding()`
* for systems that don't have it.
*/
static inline void rl_ding( void ) {
fputc( '\a', stderr );
}
#endif /* !HAVE_DECL_RL_DING */
Third-Party Libraries
Similarly to headers, just because a third-party package’s headers are installed doesn’t mean the package’s libraries are actually installed. You need to check whether they are:
AS_IF([test "x$with_readline" != xno], [
AC_SEARCH_LIBS([readline],[readline], [],
[AC_MSG_ERROR([readline library not found; use --without-readline])]
)
])
AC_SEARCH_LIBS
searches for a particular function (first argument) in the given library (second argument). If not found, action-if-not-found (fourth argument) is performed.
config.h
One of the things Autoconf does is to generate a config.h
header that contains the results of running configure
, i.e., which packages are included, which features are enabled or disabled, etc. To instruct Autoconf to generate the file requires something like this near the bottom of your configure.ac
file just before AC_CONFIG_FILES
:
AH_TOP([#ifndef cdecl_config_H
#define cdecl_config_H])
AH_BOTTOM([#endif /* cdecl_config_H */])
AC_CONFIG_HEADERS([src/config.h])
AH_TOP
puts the given text verbatim (newlines included) at the top of config.h
; AH_BOTTOM
put the given text at the bottom. Typically, this is used to include an #include
guard.
AC_CONFIG_HEADERS
actually causes the header to be generated in the given path. (You can name the header anything you want, but config.h
is conventional.) The header would then include:
#ifndef cdecl_config_H
#define cdecl_config_H
...
/* Define to 1 if readline support is enabled. */
#define WITH_READLINE 1
/* Define to 1 if you have the <readline/readline.h> header file. */
#define HAVE_READLINE_READLINE_H 1
/* Define to 1 if you have the declaration of 'rl_ding',
and to 0 if you don't. */
#define HAVE_DECL_RL_DING 0
/* Define to 1 if Address Sanitizer is enabled. */
/* #undef ENABLE_ASAN */
...
#endif /* cdecl_config_H */
These preprocessor symbols can then be used throughout your program to compile code conditionally via #ifdef
.
Makefile Conditionals
The symbols defined by Autoconf can be used only within configure.ac
and in your code via config.h
. If you want to use them in any Makefile.am
file, then in configure.ac
, you need to do add makefile conditionals:
AM_CONDITIONAL([ENABLE_ASAN], [test "x$enable_asan" = xyes])
AM_CONDITIONAL([WITH_READLINE], [test "x$with_readline" != xno ])
AM_CONDITIONAL
defines a symbol with the name of the first argument only if the second argument is true. You can then use if
in makefiles. For example, in src/Makefile.am
:
AM_CFLAGS = $(CDECL_CFLAGS)
if ENABLE_ASAN
AM_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
endif
AM_CFLAGS
(and other conventional variables prefixed by AM_
) are Automake’s version of those variables to distinguish them from those supplied by the user.
Anyway, Automake understands if
, else
, and endif
with a few caveats:
- While they may nest, they should not be indented.
- An
if
may test only a single variable — no expressions.
When configure
generates the final Makefile
, the if
s will be removed and either replaced, or not, with their content depending on whether the variable is defined.
Conclusion
Autotools provides AS_ARG_WITH
and AS_ARG_ENABLE
to allow the user to specify which third-party packages to compile with and which optional program features are either enabled or disabled. Autotools also provides AC_CHECK_HEADERS
for checking for the existence of specific headers.
There are more parts to come!
Top comments (0)