4. Finding Libraries

4.1. Searching For System Libraries

When using autoconf for portability, it's necessary to consider that some functions, even standard system ones, are often in different libraries on different operating systems. For instance, the dlopen() function is in libdl on GNU/Linux, and in the standard C library on FreeBSD and other BSD-derived systems.

This is one of the most common things to test, but the commonly-documented solution to this, which involves the use of the AC_CHECK_LIB macro, leads to either wrong solutions, or over-engineered ones. This macro is used to check for the presence of a known (usually, third-party) library but it does not work that well when you have a list of alternatives to check.

The correct macro to use for this task is AC_SEARCH_LIBS, which is designed keeping into consideration at least two important points:

  • There might be no need for further libraries to be added for the function to be available. This may either be because the function is in the C library or because the library that it's found in is already present in the LIBS variable's list. This does not mean the function is present in a library called libc;

  • Only one library carrying the function is needed, so testing should stop at the first hit. Testing further libraries might very well lead to false positives and will certainly slow down the configuration step.

Other than these configurations, the interface of the macro is nothing special for autoconf:

AC_SEARCH_LIBS(function, libraries-list, action-if-found, action-if-not-found, extra-libraries)
function

The name of the symbol to look for in the libraries.

libraries-list

A whitespace-separated list of libraries, in library-name format, that have to be searched for the function listed before. This list does not require the C library to be specified, as the first test will be done with just the libraries present in the LIBS variable.

action-if-found, action-if-not-found

As usual, the macro supports expanding code for success and failure. In this instance, each will be called at most once, and the default action-if-found code, adding the library to the LIBS variables, is always executed, even if a parameter is passed.

extra-libraries

Technically, on some if not most operating systems, it is possible for libraries to have undefined symbols that require other libraries to be linked in to satisfy. This is the case for most static libraries, but it can also happen for some shared libraries.

To make it possible to search in the libraries, the macro provides this parameter. There is an implicit value for the parameter: the LIBS variable, which is always passed at link-time after the value of this parameter. This list is not added to the variable even on success.

It is important to note that if you were to find the symbol in one of these libraries, you'd be hitting the same case as if the symbol is already available in the libraries listed in LIBS.

Using this macro it's possible to ignore in the Makefile the different libraries that are used to provide the functions on different operating systems. The LIBS variable is set up to list all the libraries, hiding the need for anything besides the standard library.

Example 1.3. Looking for two common system libraries with AC_SEARCH_LIBS

dnl The dlopen() function is in the C library for *BSD and in
dnl libdl on GLIBC-based systems
AC_SEARCH_LIBS([dlopen], [dl dld], [], [
  AC_MSG_ERROR([unable to find the dlopen() function])
])

dnl Haiku does not use libm for the math functions, they are part
dnl of the C library
AC_SEARCH_LIBS([cos], [m], [], [
  AC_MSG_ERROR([unable to find the cos() function])
])


4.2. Checking For Headers

The header files describe the public interface of a C (or C++) library. To use a library you need its public headers, so to make sure a library is available, you have to ensure that its headers are available.

Especially in the Unix world, where a shared library already defines its public binary interface or ABI, the presence of headers can tell you whether the development packages needed to build against that library are present.

To make sure that headers can be found properly, autoconf provides two macros: AC_CHECK_HEADER to find a single header or AC_CHECK_HEADERS to look for more than one header at a time (either replacement headers or independent headers).

4.2.1. Checking for one out of Multiple Headers

It's not unlikely that we have to look for one out of a series of possible replacement headers in our software. That's the case when we've got to support libraries that might be installed at top level, in subdirectories, or when older non-standard headers are replaced with new equivalent ones.

A very common case is looking for either one of stdint.h, inttypes.h or sys/types.h headers to declare the proper standard integer types (such as uint32_t) for backward compatibility with older non-standard C libraries.

In this situation, the order used above is also a priority order; since the first is the preferred header and the third is the least favourite one. It doesn't make sense to test for any other headers once the first is found. There also has to be an error message if none at all of those is available.

The macro AC_CHECK_HEADERS provides all the needed parameters to implement just that. The first parameter of the macro is a sh-compatible list, rather than an M4 list, and it's argument to a for call in the configure file and there is an action executed once the header is found (actually, there is one also when the header is not found, but we're not going to use that one.

AC_CHECK_HEADERS([stdint.h inttypes.h sys/types.h],
        [mypj_found_int_headers=yes; break;])

AS_IF([test "x$mypj_found_int_headers" != "xyes"],
        [AC_MSG_ERROR([Unable to find the standard integers headers])])

In this case, the configure script will check for the headers in sequence, stopping at the first it finds (thanks to the break instruction). This reduces the amount of work needed for the best case scenario (a modern operating system providing the standard header stdint.h).

Since exactly one of the actions (found or not found) is executed per each header tested, we cannot use the 'not found' action to error out of the script, otherwise any system lacking stdint.h (the first header tested) will be unable to complete the step. To solve this, we just set a convenience variable once a header is found and test that it has been set.

4.2.2. Headers Present But Cannot Be Compiled

Since August 2001, autoconf has been warning users about headers that are present in the system but couldn't be compiled, when testing for them with either of the two macros explained previously.

Before that time, the macros only checked if the header was present by asking the preprocessor for it. While this did find the whereabouts of headers, it included no information regarding their usability. Without checking if the headers compiled, it was impossible to say if the header was valid for the language variety chosen (like C99, or C++), or if it required unknown dependencies.

To solve the problem, it was decided to use the actual compiler for the currently selected language to look for the headers, but for compatibility with the previous version at the moment of writing, both tests are performed for each header checked, and the header present but cannot be compiled warning is reported if the preprocessor accepts a header while the compiler refuses it.

There are multiple causes for this problem, and thus to solve this warning, you have to identify the actual cause of it. For reference, you can find some examples here.

Example 1.4. header present but cannot be compiled: wrong language dialect

Note

This example is based on an actual mistake in the KDE 3.5.10 build system for the kdepim package.

One situation where the header files are present but cannot be compiled is when they are not designed to work with a particular language variety. For instance, a header might not be compatible with the C99 dialect, or with C++, or on the other hand it might require C99 or C++.

If that particular language is selected, though, and the header is tested for, the behaviour of rejecting the header is indeed what the developers are expecting. On the other hand, it's possible that the test is being done with a different language than the one where the header is going to be used.

For instance, take the following snippet, that tries to look for the bluetooth/bluetooth.h header from bluez-libs, using a strict C compiler:

dnl SPDX-FileCopyrightText: 2009- Diego Elio Pettenò
dnl
dnl SPDX-License-Identifier: MIT

AC_INIT

CFLAGS="-std=iso9899:1990"

AC_CHECK_HEADERS([bluetooth/bluetooth.h])

AC_OUTPUT

This will issue the warning discussed above when running configure:

checking bluetooth/bluetooth.h usability... no
checking bluetooth/bluetooth.h presence... yes
configure: WARNING: bluetooth/bluetooth.h: present but cannot be compiled
configure: WARNING: bluetooth/bluetooth.h:     check for missing prerequisite headers?
configure: WARNING: bluetooth/bluetooth.h: see the Autoconf documentation
configure: WARNING: bluetooth/bluetooth.h:     section "Present But Cannot Be Compiled"
configure: WARNING: bluetooth/bluetooth.h: proceeding with the preprocessor's result
configure: WARNING: bluetooth/bluetooth.h: in the future, the compiler will take precedence
checking for bluetooth/bluetooth.h... yes

The reason for the above warnings can be found by looking at the config.log file that the configure script writes:

configure:3338: checking bluetooth/bluetooth.h usability
configure:3355: gcc -c -std=iso9899:1990  conftest.c >&5
In file included from conftest.c:51:
/usr/include/bluetooth/bluetooth.h:117: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'int'
/usr/include/bluetooth/bluetooth.h:121: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'void'
configure:3362: $? = 1

Looking at those lines in the header file shows that it is using the inline keyword, which is not supported by the C90 language (without extensions). If the header is checked for, though, it means it's going to be used, and even if it does not work with the selected C90 dialect, it might work with the C++ language used by the actual program.

The configure.ac file can then be fixed by changing it to the following code:

dnl SPDX-FileCopyrightText: 2009- Diego Elio Pettenò
dnl
dnl SPDX-License-Identifier: MIT

AC_INIT

CFLAGS="-std=iso9899:1990"

AC_LANG_PUSH([C++])
AC_CHECK_HEADERS([bluetooth/bluetooth.h])
AC_LANG_POP

AC_OUTPUT