UP | HOME

Part II: Automake

Table of Contents

This page contains a tutorial on the usage GNU Automake. This is the second part of the Autotools tutorial.

1 Introduction

Using a simplified syntax, Automake produces functional Makefiles which are consistent with the GNU coding standards. This implies that the Makefiles use standard build targets (make install, make clean, make dist, …) and standard installation paths (/usr/bin, /usr/share/, /usr/include, /usr/man, …). To avoid struggling too much with Automake, it is important to understand the usual build, test and install philosophies common to GNU projects, as well as the underlying infrastructure managed by Autotools.

autotools.png

You have seen in the Autoconf tutorial how to write configure.ac and Makefile.in. Here, we introduce Automake which will produce the Makefile.in from a high-level specification file Makefile.am. The produced file will automatically add extra functionalities to the Makefile, using standard target names.

2 GNU coding standards

Some aspects of the GNU coding standards (GCS) are well known to users. If you use them, your users will be grateful because it will avoid them to read your installation instructions! In addition, it will make the packaging of your code much simpler if you want to make it available as a Linux distribution package (.deb, .rpm, …), or as a package for Homebrew, Guix, Conda, Spack, EasyBuild, …

2.1 Standard targets

The GCS defines standard target names to be included in Makefiles, such that the behavior of make becomes uniform among multiple packages. Here is a short list of the main targets:

make all
Builds all the targets sufficient to declare the package built.
make clean
Removes all derived files.
make check
Runs unit tests provided by the package.
make install
Installs the package
make uninstall
Uninstalls the package
make html
Builds the documentation in HTML format
make check
Performs self-tests

2.2 Filesystem Hierarchy Standard

The Filesystem Hierarchy Standard (FHS) defines the directory structure and contents in Linux distributions. All Unix-like systems follow more or less the FHS, which defines standard places for files like:

Path Description
/bin Essential binaries
/lib Essential libraries
/etc Editable Text Configuration files
/sbin Essential system binaries
/share Platform-independent files
/tmp Directory for temporary files
/usr Secondary hierarchy for user data
/usr/bin Non-essential binaries
/usr/lib Libraries for non-essential binaries
/usr/include Standard include files
/usr/local Tertiary hierarchy for local data

It is important to know what these directories contain, because it will let you know where to install your software and the extra files that are distributed with it such as the documentation or the configuration files.

2.3 Standard Makefile.in variables

Installation directories are named by standard variables, so it gives the user the flexibility to install the package in a non-standard place. For example, this feature is required for packaging where the software is installed in a temporary directory.

The main variables are:

Variable Default Description
prefix /usr/local Root of the hierarchy
exec_prefix $(prefix) Root of the hierarchy of executable files
bindir $(exec_prefix)/bin Where to install the binaries
libdir $(exec_prefix)/lib Where to install the libraries
includedir $(prefix)/include Where to install include files
datarootdir $(prefix)/share The root of the platform-independent files
datadir $(datarootdir) Where to install platform-independent files
mandir $(datarootdir)/man Where to install platform-independent files
sysconfdir $(prefix)/etc Where to install configuration files
docdir $(datarootdir)/doc/$(package) Where to install the documentation
srcdir Set by configure The directory of the sources being compiled

In the Makefile.in, these variables may be used between @ symbols, like @bindir@ for instance.

These variables can take different values by specifying them as arguments of the configure script:

./configure --prefix=$HOME/install --sysconfdir=$HOME/.config/

2.4 Standard environment variables

The GCS also defines a set of user-defined environment variables allowing to change the compilation behavior of the package.

Variable Description
CC C compiler
FC Fortran compiler
CXX C++ compiler
CPP C/C++ preprocessor
CFLAGS C compiler flags
FCFLAGS Fortran compiler flags
CXXFLAGS C++ compiler flags
CPPFLAGS C/C++ preprocessor flags
LDFLAGS Linker flags
LIBS Libraries to add to the linker

To use alternate compiler flags without changing the Makefile, you can set these variables on the command line as arguments to make. For example:

make FC=ifort FCFLAGS="-xAVX -O2"

You can also do it at configuration time:

./configure FC=ifort FCFLAGS="-xAVX -O2"

To see the complete list of user variables, you can run ./configure --help

3 Writing portable Makefiles

The Makefiles produced by Automake are made to be portable among Linux and Unix systems. Before diving into Automake, we will present a few techniques to write portable Makefiles.

The most popular implementation of make is GNU Make. GNU Make is much more evolved than the POSIX standard, so Makefiles designed for GNU Make are not guaranteed to work with alternate implementations of make, such as BSD make for instance. If you want your software to be really portable, you should stick to the POSIX standard in your Makefiles by including the line

.POSIX:

at the top of your Makefile.

3.1 What you will lose by sticking to the POSIX standard

  • Pattern rules, such as

    %_test.o: %_test.c
            $(CC) $< -o $@
    
  • Special functions $(wildcard *.c), $(patsubst %.f, %.o, $(SRC)), …
  • The ability to have one rule with multiple output files

3.2 Guidelines that will make everything more portable

  • Don't use any other automatic variable than $@ (the name of the current target)
  • Write your scripts with _bash{/bin/sh} instead of /bin/bash
  • Don't put your compiled files (.o) in a separate location than the source files
  • Give up writing your Makefiles by hand. Use scripts (Bash, Python) to write them for you.
  • Install bmake as an alternative to GNU make to check the portability of your Makefiles.
  • Use $(MAKE) in your Makefiles instead of make for recursive builds
  • Similarly, use POSIX constructs for your shell scripts or shell constructs in configure.ac (tests with a single square bracket).

4 Automake basics

Automake reads a file named Makefile.am and produces the Makefile.in file. The produced file may contain thousands of lines, and is guaranteed to have the basic functionalities (make all, make clean, …) in a portable Makefile. Makefile.am contains portable make syntax, and the code it contains will be inserted at the proper location in the Makefile.in file.

Automake enables the use of the += operator, which is an extension of the POSIX standard.

We will now create the minimal working example for the code presented in the Autoconf section. First, inform Autoconf that you will be using Automake by inserting the AC_INIT_AUTOMAKE macro in configure.ac:

AC_INIT([sherman-morrison], [0.0.1], [])
AM_INIT_AUTOMAKE([foreign subdir-objects silent-rules])

The foreign flag informs Autotools that you are not building a GNU project, otherwise you will be warned that file files NEWS, README, AUTHORS, ChangeLog and COPYING should be present in the project.

The silent-rules option causes Automake to generate Makefiles that don't display the complete command line of the actions it performs, but a less verbose output similar to CMake.

The subdir-objects option informs Automake that source files can be located in subdirectories. It is the case here because all the source files are located in the src/ and tests/ directories.

Autoconf macros start with AC_ and Automake macros start with AM_.

Then, create the file Makefile.am with the following content:

bin_PROGRAMS = test_h5

test_h5_SOURCES = $(SOURCES)

SOURCES = src/SM_Maponi.cpp \
          src/SM_Standard.cpp \
          src/Woodbury.cpp \
          src/SMWB.cpp \
          src/Helpers.cpp \
          tests/test_h5.cpp

The syntax of this file will be explained in the next section.

In the definition of the SOURCES variable, all the required files are explicitly written. As Automake produces portable Makefiles, it is not possible to benefit from the GNU make syntax such as $(wildcard src/*.cpp).

Automake will overwrite Makefile.in. Before running Automake the first time, don't forget to make a backup copy of your Makefile.in file.

We can now create the Makefile.in and the Makefile by running

$ autoreconf
$ ./configure

and build the project

$ make

You can remark that many standard targets are available. make clean cleans the compiled files, make maintainer-clean removes more files, including the Makefile and config.status.

Automake also provides make install and make uninstall. To test it, you can create a directory named _install in the current directory and configure the package to be installed in this particular directory:

$ mkdir _install
$ ./configure --prefix=$PWD/_install
[...]
$ make install
make[1]: Entering directory '/home/test/Sherman-Morrison-0.1-demo'
 /bin/mkdir -p '/home/test/Sherman-Morrison-0.1-demo/_install/bin'
  /usr/bin/install -c test_h5 '/home/test/Sherman-Morrison-0.1-demo/_install/bin'
make[1]: Nothing to be done for 'install-data-am'.
make[1]: Leaving directory '/home/test/Sherman-Morrison-0.1-demo'
$ ls _install/
bin
$ ls _install/bin/
test_h5
$ make uninstall
 ( cd '/home/test/Sherman-Morrison-0.1-demo/_install/bin' && rm -f test_h5 )
$ ls _install/bin/
$

5 Creating a distributed tarball

5.1 Maintainer mode

There are two types of people who will try to compile the source code:

  1. The end-users having downloaded a tarball with sources
  2. The developers of the code, who produce the source distribution of the code

These two profiles should be differentiated, because their system requirements are usually different. The first difference is that the code developers will need to install Autotools as they will produce the configure script, but Autotools will not be needed by the end-users.

A more critical example is when complex Makefiles are produced by a script. The script generating the Makefiles should be stored in the version control system for developers, as opposed to the generated Makefiles. For the end-user's point of view, only the generated Makefiles are needed for the compilation of the sources, and the script should not be present in the source distribution.

To handle these two types of profiles, Autotools provides the maintainer mode. For instance, in maintainer mode the configure script is re-generated when configure.ac has changed, as opposed to standard mode. All these rules are activated in the generated Makefiles by running

./configure --enable-maintainer-mode

5.2 Source distribution targets

Automake provides automatically the dist target. make dist creates a .tar.gz file which contains all the files necessary to install the package: installation instructions, configure script, source code, documentation, man pages, unit tests, etc.

$ make dist
[...]
$ tar --list -f sherman-morrison-0.0.1.tar.gz
sherman-morrison-0.0.1/
sherman-morrison-0.0.1/m4/
sherman-morrison-0.0.1/m4/ax_lib_hdf5.m4
sherman-morrison-0.0.1/src/
sherman-morrison-0.0.1/src/SM_Maponi.cpp
sherman-morrison-0.0.1/src/SM_Standard.cpp
sherman-morrison-0.0.1/src/Woodbury.cpp
sherman-morrison-0.0.1/src/SMWB.cpp
sherman-morrison-0.0.1/src/Helpers.cpp
sherman-morrison-0.0.1/tests/
sherman-morrison-0.0.1/tests/test_h5.cpp
sherman-morrison-0.0.1/Makefile.am
sherman-morrison-0.0.1/configure
sherman-morrison-0.0.1/configure.ac
sherman-morrison-0.0.1/aclocal.m4
sherman-morrison-0.0.1/Makefile.in
sherman-morrison-0.0.1/compile
sherman-morrison-0.0.1/depcomp
sherman-morrison-0.0.1/install-sh
sherman-morrison-0.0.1/missing

Can you figure out if this archive is OK?

To answer this question, we can use the power of Automake and run the distcheck target:

$ make distcheck
[...]
../../src/SM_Maponi.cpp:4:10: fatal error: SM_Maponi.hpp: No such file or directory
 #include "SM_Maponi.hpp"
          ^~~~~~~~~~~~~~~
compilation terminated.
make[1]: *** [Makefile:449: src/SM_Maponi.o] Error 1
make[1]: Leaving directory '/home/test/Sherman-Morrison-0.1-demo/sherman-morrison-0.0.1/_build/sub'
make: *** [Makefile:613: distcheck] Error 1

Oh no! This archive is not valid. By inspecting the content of the archive, we notice we forgot to add the header files in the sources. Although the project builds on our platform, the Makefile is incomplete because it lacks some dependencies.

We can update the SOURCES variables as

SOURCES = src/SM_Maponi.cpp       \
          src/SM_Standard.cpp     \
          src/Woodbury.cpp        \
          src/SMWB.cpp            \
          src/Helpers.cpp         \
          tests/test_h5.cpp       \
          include/Helpers.hpp     \
          include/SM_Maponi.hpp   \
          include/SM_Standard.hpp \
          include/SMWB.hpp        \
          include/Woodbury.hpp

and run make dist again to rebuild the source distribution. Note that the current Makefile detects that Makefile.am has changed, so it re-runs Automake and also re-generates the Makefile using config.status before producing the updated archive.

$ tar --list -f sherman-morrison-0.0.1.tar.gz
sherman-morrison-0.0.1/
sherman-morrison-0.0.1/include/
sherman-morrison-0.0.1/include/Helpers.hpp
sherman-morrison-0.0.1/include/SM_Maponi.hpp
sherman-morrison-0.0.1/include/SM_Standard.hpp
sherman-morrison-0.0.1/include/SMWB.hpp
sherman-morrison-0.0.1/include/Woodbury.hpp
sherman-morrison-0.0.1/m4/
sherman-morrison-0.0.1/m4/ax_lib_hdf5.m4
sherman-morrison-0.0.1/src/
sherman-morrison-0.0.1/src/SM_Maponi.cpp
sherman-morrison-0.0.1/src/SM_Standard.cpp
sherman-morrison-0.0.1/src/Woodbury.cpp
sherman-morrison-0.0.1/src/SMWB.cpp
sherman-morrison-0.0.1/src/Helpers.cpp
sherman-morrison-0.0.1/tests/
sherman-morrison-0.0.1/tests/test_h5.cpp
sherman-morrison-0.0.1/Makefile.am
sherman-morrison-0.0.1/configure
sherman-morrison-0.0.1/configure.ac
sherman-morrison-0.0.1/aclocal.m4
sherman-morrison-0.0.1/Makefile.in
sherman-morrison-0.0.1/compile
sherman-morrison-0.0.1/depcomp
sherman-morrison-0.0.1/install-sh
sherman-morrison-0.0.1/missing

We can now test again the source distribution:

$ make distcheck
[...]
make[1]: Entering directory '/home/test/Sherman-Morrison-0.1-demo/sherman-morrison-0.0.1/_build/sub'
depbase=`echo src/SM_Maponi.o | sed 's|[^/]*$|.deps/&|;s|\.o$||'`;\
g++ -DPACKAGE_NAME=\"sherman-morrison\" -DPACKAGE_TARNAME=\"sherman-morrison\" -DPACKAGE_VERSION=\"0.0.1\" -DPACKAGE_STRING=\"sherman-morrison\ 0.0.1\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"sherman-morrison\" -DVERSION=\"0.0.1\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_HDF5=1 -I. -I../..   -I/usr/include -I/usr/include/hdf5/serial  -I./include/  -g -O2 -std=c++11 -MT src/SM_Maponi.o -MD -MP -MF $depbase.Tpo -c -o src/SM_Maponi.o ../../src/SM_Maponi.cpp &&\
mv -f $depbase.Tpo $depbase.Po
../../src/SM_Maponi.cpp:4:10: fatal error: SM_Maponi.hpp: No such file or directory
 #include "SM_Maponi.hpp"
          ^~~~~~~~~~~~~~~
compilation terminated.
make[1]: *** [Makefile:449: src/SM_Maponi.o] Error 1
make[1]: Leaving directory '/home/test/Sherman-Morrison-0.1-demo/sherman-morrison-0.0.1/_build/sub'
make: *** [Makefile:613: distcheck] Error 1

Oh no! Still the same error! But now we know that the header file is present.

Can you figure out what is wrong now?

Inspecting the compilation line, we notice that the compilation takes place in the sherman-morrison-0.0.1/_build/sub directory. Automake is now trying to make an out of source build: building the package outside of the source directory. In the previous tutorial, we have introduced -I./include to the CPPFLAGS variable in the configure.ac file. Using such a relative path implies that we are running make in the source directory, which is not desirable if you want to be able to make RPM packages for instance.

So now we need to move this definition into the Makefile.am file. configure.ac becomes:

CPPFLAGS="${HDF5_CPPFLAGS} ${CPPFLAGS}"

and Makefile.am has the extra line:

AM_CPPFLAGS =  -I$(srcdir)/include/

You can remark that we have now introduced the srcdir variable which points the the root of the source. For in source builds, srcdir is . but for out of source builds it is set to the relative path to the root. If for some reason you need the absolute path, you can use abs_srcdir instead.

Now, let us test our source distribution:

$ make distcheck
[...]
========================================================
sherman-morrison-0.0.1 archives ready for distribution:
sherman-morrison-0.0.1.tar.gz
========================================================

Success! the distribution compiles and we now have something that we can give to our users!

You can specify which tarball format to use with make dist by adding the dist-zip, dist-bzip2, dist-xz, … option to the AM_INIT_AUTOMAKE macro in configure.ac

6 Automake Syntax

6.1 Variables

Here is the list of the main variables that are used in Makefile.am:

AM_CPPFLAGS = -I/dir1 -I/dir2 -I$(srcdir)/include ...
List the -I flags that need to be passed to the compiler to find the include files
AM_LDFLAGS = -L/dir1 -L/dir2 ...
Flags to be passed to the linker
LDADD = file.o $(top_builddir)/dir1/libmylib.la -lmylib ...
Object files and built libraries to be linked with all executables
CLEANFILES = my_file.mod ...
List of extra files to remove with make clean
EXTRA_DIST

Additional files to be shipped with the binary distribution.

User variables such as CFLAGS or CPPFLAGS should never be overwritten in a Makefile.am. This explains why these variables have the AM_ prefix.

6.2 Product list variables

In the previous section, we have seen two special variables bin_PROGRAMS and test_h5_SOURCES.

bin_PROGRAMS contains the names of the programs we are building, and these will be installed in $(exec_prefix)/bin upon invocation of make install.

Product list variables (PLV) conform to the following template:

[modifier-list]prefix_PRIMARY = product1 product2 ... productN

6.2.1 Installation location prefixes

The prefix can be an installation location prefix, as in for example bin_PROGRAMS or lib_LIBRARIES. The prefix corresponds to a variable defining a directory chopping off the dir suffix. For example, the bin prefix corresponds to the $(bindir) variable. You can make your own prefix, for example:

gpudir = $(exec_prefix)/gpu
gpu_PROGRAMS = cublas_test

6.2.2 Other prefixes

noinst
Products that should not be installed
check
Products that should be built for testing
EXTRA
Products that are built conditionally

6.2.3 Primaries

A primary is a class of products. This defines the rules to create in the Makefile to satisfy the needs of the product.

PROGRAMS
Create rules to build the binary executables
LIBRARIES
Create rules to build the libraries
LTLIBRARIES
Create rules to build the libraries using Libtool
SCRIPTS
Create rules to install the scripts
DATA
Arbitrary files (documentation, test data, etc)
HEADERS
Header files to be installed

6.3 Product source variables

In our example, the name of the program is test_h5, so by defining test_h5_SOURCES we can list the source files needed to build test_h5. Automake will then do everything necessary in the Makefile.in to build test_h5.

Product source variables (PSV) conform to the following template:

[modifier-list]product_SOURCES = file1 file2 ... fileN

product refers to the built product. If the product contains special characters, these should be replaced by underscores. For example:

lib_LIBRARIES = libc++.a
libc___a_SOURCES = ...

6.4 PLV and PSV modifiers

Modifiers change the default behavior of the variable. The main modifiers are dist and nodist, which indicate if the files should be part of the distributed package or not.

Modify the Makefile.am to add the LICENSE file to the source distribution package, and to install the README.md file into the documentation directory.

7 Testing

Automake provides a framework for testing your program. The TEST variable contains a list of tests to execute when the user runs make check. The tests should be written such that success corresponds to an exit code equal to 0. An exit status of 77 means that the test should be ignored. For all other exit codes, the test failed.

Once you have added the TEST variable to Makefile.am, you need to run autoreconf -i. It will detect that you will need the testing framework and install it in your package.

  1. Provide a dummy test for the current package which always succeeds, and another one which always fails.
  2. Add the tests to the Makefile.am
  3. Run make check

The prefix check is used for products that should only be built when make check is run.

In Makefile.am, we add

TESTS = tests/success tests/failure
check_PROGRAMS = $(TESTS)

tests_success_SOURCES = tests/success.cpp
tests_failure_SOURCES = tests/failure.cpp

We can now test the package by running

$ make check
[...]
PASS: tests/success
FAIL: tests/failure
============================================================================
Testsuite summary for sherman-morrison 0.0.1
============================================================================
# TOTAL: 2
# PASS:  1
# SKIP:  0
# XFAIL: 0
# FAIL:  1
# XPASS: 0
# ERROR: 0
============================================================================
See ./test-suite.log
============================================================================

which results in an error.

Sometimes, you know that a test fails, but you don't have time to fix it right away. You can add this test to the XFAIL_TESTS variable, which contains the list of tests that are expected to fail. Similarly, the XPASS_TESTS are tests that are not supposed to pass.

TESTS = tests/success tests/failure
XFAIL_TESTS = tests/failure
check_PROGRAMS = $(TESTS)

tests_success_SOURCES = tests/success.cpp
tests_failure_SOURCES = tests/failure.cpp
PASS: tests/success
XFAIL: tests/failure
============================================================================
Testsuite summary for sherman-morrison 0.0.1
============================================================================
# TOTAL: 2
# PASS:  1
# SKIP:  0
# XFAIL: 1
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================

Now, when we run make distcheck, the tests will also be run when building the source distribution package.

8 Using pkg-config

When your software depends on external libraries and you try to compile it on an unknown system, you need to figure out what are the correct flags to access the include files of the library and how to link it. `configure` will try to find the libraries in the standard locations, but if the configuration is a bit exotic, this will require some extra knowledge.

At the moment you install a library on a system, you have all the knowledge required to use this library in other software: what compiler options to add. All this information can be saved in a file with the suffix .pc stored in a standard location such as /usr/lib/pkgconfig, and this information can be retrieved later on with the pkg-config command.

Let's assume you have compiled and installed the QMCkl library. The library will produce the system-specific file /usr/local/lib/pkgconfig/qmckl.pc:

prefix=/usr/local
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${exec_prefix}/lib


Name: qmckl
Description: Quantum Monte Carlo kernel library
URL: https://github.com/trex-coe/qmckl
Version: 0.1.1
Cflags:  -I${includedir}
Libs: -L${libdir} -lqmckl
Libs.private:  -L/home/scemama/TREX/trexio/_install/lib -L/usr/lib/x86_64-linux-gnu/hdf5/serial -ltrexio -lhdf5 -lpthread -lm

This file tells you that you need to include the flag -I/usr/local/include to the C preprocessor (the CPPFLAGS variable), and the -L/usr/local/lib -lqmckl flag at the link stage (the LDFLAGS variable) if you want to use the dynamically linked library.

This information can be accessed using the pkg-config command:

$ pkg-config --cflags qmckl
-I/usr/local/include/

$ pkg-config --libs qmckl
-L/usr/local/lib -lqmckl

In your configure.ac file, you can simply use

PKG_CHECK_MODULES([qmckl])
CFLAGS="${CFLAGS} `pkg-config --cflags qmckl`"
LDFLAGS="${LDFLAGS} `pkg-config --libs qmckl`"

to include the appropriate flags. The PKG_CHECK_MODULE macro will fail if pkg-config is not installed on the system, or if the qmckl.pc file is not found.

The pkg-config files are searched in the directories defined by the PKG_CONFIG_PATH environment variable.

9 Back to Autoconf

9.1 Searching for dependencies

9.1.1 C definitions

If you are using the C/C++ preprocessor, you can enable conditional compilation in your code. This is particularly frequent when you use MPI. For example:

#ifdef HAVE_MPI
  rc = mpi_reduce(...)
#endif

allows you to activate the MPI calls only when the -DHAVE_MPI option is passed in the command line, or if an included header file defines the HAVE_MPI variable.

In the configure.ac file, the AC_CONFIG_HEADERS macro produces a configuration header file which will enable the definition of C preprocessor variables. The AC_DEFINE macro in configure.ac allows to create C definitions.

AC_CONFIG_HEADERS([include/config.h])

case $CC in
    *icc*)
       AC_DEFINE([HAVE_INTEL], [], [Define if using the Intel compiler]) ;;
    *) ;;
esac

Will produce the following in include/config.h if the Intel compiler is used:

/* Define if using the Intel compiler */
#define HAVE_INTEL /**/

and this code if not

/* Define if using the Intel compiler */
/* #undef HAVE_INTEL */

Similarly, you can give values to the definitions, to pass values determined at configuration time, like the installation prefix to locate the configuration files for instance:

AC_DEFINE_UNQUOTED([PREFIX_DIR], ["`echo $prefix`"], [Installation prefix])
$ ./configure --prefix=/usr/local
/* Installation prefix */
#define PREFIX_DIR "/usr/local"

9.1.2 Checking for headers

You may need to check if some headers are available. For this, you can use the AC_CHECK_HEADERS macros:

AC_CHECK_HEADERS ([header-file ...], [action-if-found], [action-if-
not-found], [includes])

For example, one can remark in our example that the include/Helpers.hpp requires the mkl_lapacke.h header which is not standard. We can introduce a check for it, and force the failure of configure if it is not found.

AC_CHECK_HEADERS([mkl_lapacke.h], [],
                [AC_MSG_ERROR([Unable to find mkl_lapacke.h])])

If later on in configure.ac you need to remember if you have or not the header, the result is stored in ac_cv_header_<header_file>.

9.1.3 Checking for libraries

As we have seen in Part I, we can look in the archive of Autoconf macros to see if we find a macro for searching a particular library. If not, then we can use the AC_SEARCH_LIBS macro:

AC_SEARCH_LIBS (function, search-libs, [action-if-found],
                [action-if-not-found], [other-libraries])

function is a function provided by the library, and search-libs is a list of libraries that can provide the function we search for (for example [mpich openmpi]). other-libraries contains the list of other libraries to link in the test that checks if the requested library is found.

If the library is found, it will be inserted into the LIBS variable.

If later on in configure.ac you need to remember if you have or not the library, the result is stored in ac_cv_search_<function>.

9.1.4 Checking for external programs

You package might need some other programs to be functional. For example, you might need a Python interpreter for some scripts. The presence of other programs can be checked with the AC_CHECK_PROGS macro:

AC_CHECK_PROGS(variable, progs-to-check-for, [value-if-not-found], [path = ‘$PATH’])

The following exampl checks for a Python interpreter and saves the result in the PYTHON variable:

AC_CHECK_PROGS(PYTHON, [python3 python], [not_found])

9.2 Printing

The following macros will be useful to display messages to the users running configure:

AC_MSG_CHECKING (feature-description) Inform the user that configure is checking something
AC_MSG_RESULT (result-description) Return to the user the results of a check
AC_MSG_ERROR (error-description, [exit-status = $?/1]) Make configure fail with an error message
AC_MSG_NOTICE(message) Print the message
AC_MSG_WARN (problem-description) Inform the user of a possible problem

9.3 Using external software

The usage of an external software can be controlled by the user by specifying some options to the configure script: --with-<package>=<arg> or --without-<package>.

Let us now add the possibility to compile our program with or without MPI. To do that, we start by adding to the configure.ac file:

AC_ARG_WITH(mpi, [AS_HELP_STRING([--with-mpi=yes/no], [Activate MPI])])

After regenerating the configure script, we see that now configure --help shows the --with-mpi option. If the --with-mpi is absent of the command line, the with_mpi variable will be empty. Otherwise, it will contain yes.

AC_ARG_WITH(mpi, [AS_HELP_STRING([--with-mpi], [Activate MPI])])
AS_IF([test "x$with_mpi" == "xyes"], [
  AC_DEFINE([HAVE_MPI], [], [MPI is activated])
  AC_MSG_NOTICE([MPI is configured])
  AC_CHECK_PROGS(MPICC, [mpicc])
  AC_CHECK_PROGS(MPICXX, [mpicxx])
  CC=$MPICC
  CXX=$MPICXX
])

10 Autoscan

Autoscan scans the source tree and tries to fix vulnerabilities encountered in the configure.ac.

$ autoscan
configure.ac: warning: missing AC_CHECK_FUNCS([memset]) wanted by: include/Helpers.hpp:86
configure.ac: warning: missing AC_CHECK_FUNCS([pow]) wanted by: include/Helpers.hpp:148
configure.ac: warning: missing AC_CHECK_FUNCS([sqrt]) wanted by: include/Helpers.hpp:262
configure.ac: warning: missing AC_CHECK_HEADER_STDBOOL wanted by: include/Helpers.hpp:192

Here, Autoscan has detected that we are using the memset, pow and sqrt functions. It is safer to check that these functions are available and functional. We simply need to follow the instructions given by Autoscan.

If you do it at the very beginning of your transition to Autotools, autoscan will provide you a file named configure.scan which is a good starting point for creating the configure.ac file.

11 More about maintainer mode

11.1 Automatic maintainer/user switching

It is convenient to define a variable that allows to know if the configuration should be for a maintainer or for an end user. The simplest recipe is to check for the existence of the .git directory at the root of the project. If it exists, we are not in the source distribution but in a directory obtained by git clone.

AC_CHECK_FILE([.git], [IS_MAINTAINER="yes"], [IS_MAINTAINER="no"])

Then, the commands specific to maintainer mode can be executed in configure.ac by checking the value of this variable:

AS_IF([test "$IS_MAINTAINER" == "yes"],
      [AC_MSG_NOTICE([Maintainer configuration])],
      [AC_MSG_NOTICE([User configuration])])

To propagate this knowledge to Makefile.am, you can use in configure.ac

AM_CONDITIONAL([IS_MAINTAINER], [test "$IS_MAINTAINER" == "yes"])

and then, in Makefile.am

if IS_MAINTAINER
[...]
endif

11.2 Example: Saving the Git Hash

Inside configure.ac, it is rather simple to get the git hash and store it into a defined variable:

GIT_HASH=`git log | head -1 | cut -d ' ' -f 2`

When the distribution package is made, the information relative to the Git repository is not stored in the tarball. So it will not be possible to get the Git hash by executing this command for the end user. The Git hash is available only for maintainers and needs to be stored somewhere in the distribution package to be propagated to the users.

In configure.ac:

AS_IF([test "$IS_MAINTAINER" = "yes"],
      [GIT_HASH=`git log | head -1 | cut -d ' ' -f 2 | tee ${srcdir}/.git_hash`],
      [GIT_HASH=`cat ${srcdir}/.git_hash`])

AC_DEFINE_UNQUOTED(GIT_HASH, ["${GIT_HASH}"], [Git SHA1 Hash])

In maintainer mode, we fetch the Git hash and store it in .git_hash. In user mode, we read the content of .git_hash. Then, we propagate this variable to config.h with AC_DEFINE_UNQUOTED.

In Makefile.am, we add the new file to the source distribution tarball:

EXTRA_DIST += .git_hash

and we add rules to always regenerate it in case we make a commit:

if IS_MAINTAINER

CLEANFILES = .git_hash

.git_hash: FORCE
        git log | head -1 | cut -d ' ' -f 2 > .git_hash

all: .git_hash

.PHONY: FORCE
endif

12 Useful links


Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Author: Evgeny Posenitskiy, Anthony Scemama

Created: 2021-11-09 Tue 17:47

Validate