DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on • Edited on

Optional Packages & Features

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:

  1. 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.
  2. 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}"
  ])
])
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.])
])
Enter fullscreen mode Exit fullscreen mode

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])]
  )
])
Enter fullscreen mode Exit fullscreen mode

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>
  ])
])
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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])]
  )
])
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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 */
Enter fullscreen mode Exit fullscreen mode

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 ])
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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