Automake

Let Automake do the work :)

Table of Contents

Introduction

As we have discussed about the Automake in the Autotools, it is seems very tempting to start generate Makefiles for your projects and generate a Makefile for each module with a master Makefile at the top of project directory structure. However, there is a very good and informative paper by Peter Miller regarding “Recursive Makefiles are Considered Harmful”. As per this paper, projects with Recursive Makefiles build system are not very optimal for building projects, especially which are very large in size. But, writing a single Makefile for a very large project or even generating it using Automake is not very manageable, which is good argument. So, a blog by Karel Zak suggest that we can create a single resulting Makefile from bunch of include files which are included in main Makefile.am. Therefore, our first goal is to create a build system which is optimal.
image: e_8906a2ec3701_project-tree.png
Figure 1.1: Project structure

Enabling Automake in configure.ac

Just for the introduction, lets briefly understand what’s configure.ac is?

configure.ac is the input file which is used by Autoconf to generate configure which is used to setup the environment for project compilation. In other word, this script allows us to configure our project build system. Detailed discussion over this is done in Autoconf tutorial.

Much of the configure.ac script can be generated automatically based on the existing project tree or even updated based on the the current status. This automatic generation of configure.ac script is done using autoscan script. Note, that as the project structure grows, we don’t have to create new configure.ac every time. Instead, autoscan will read the existing configure.ac and improve upon it. The resulting output of the autoscan is configure.scan and autoscan.log. configure.scan can be change to configure.ac to use by autoconf for configuration of the project.

The output of autoscan looks like:


Listing 1: configure.ac generated from autoscan.
# Process this file with autoconf to produce a configure script. -*- Autoconf -*- AC_PREREQ([2.71]) AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS]) AC_CONFIG_SRCDIR([foo/myprog.c]) AC_CONFIG_HEADERS([config.h]) # Checks for programs. # This below is added only for C++ files. AC_PROG_CXX AC_PROG_CC # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. AC_CHECK_HEADER_STDBOOL # Checks for library functions. AC_CONFIG_FILES([Makefile]) AC_OUTPUT


Note that the generated script is not enabled to use Automake tool the generate the Makefiles from Makefile.am input files. Oh wait! Didn’t we just say that Automake generates Makefiles automatically for the project. Well, lets address that in the next section.

Now, to enable the Automake in this build system, we need to add one single line onto the above script.


Listing 2: Automake enabled
AC_PREREQ([2.71]) AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS]) AC_INIT_AUTOMAKE AC_CONFIG_SRCDIR([creational-pattern/src/MazeGame.cpp]) AC_CONFIG_HEADERS([config.h])


Executing the autoreconf -i command will now make the build system of project, Automake enabled.

What is a Makefile.am file?

It is just like a regular Makefile, but with comparatively less Makefile code and some syntactically specific to Automake. But, why Automake needs some input file at the first place to generate a output Makefile. Well, the main intent of the Automake tool is to generate a Makefile which mostly comprises of boilerplate code. Since GCS enforce lot of standard rules, this leads to lot of code begin boilerplate which makes it very redundant to write it by hand and also making sure that it is logically correct. Here, Automake comes in handy as it generates a “complete” Makefile which is compliant to GCS. Another benefit of using Automake is, it has automatic dependency tracking capability which allows the tracking of header files changes which are included in the source files. It is annoying problem where change in header files are often not reflected in the compiled build due to missing dependency checks and Automake handles it very gracefully to ensure the dependency checks are efficiently managed.

Preparing the structure

Since our goal is to create a non-recursive build system of Makefiles using Automake, all the targets rules should appear in the top-makefile which may tell you that we may need to write a big Makefile.am for a project which is at the top of project directory. Fortunately, Automake can still create a single Makefile from bunch of input *.am files. This makes it super manageable. Writing bunch of “Makemodule.am” for each sub-directories of the project and including them in top Makefile.am will help us in completing our goal.


Listing 3: Makefile.am in root directory.
bin_PROGRAMS = man_MANS = include foo/Makemodule.am include bar/Makemodule.am


Here, we can see that defining the variable at the top of top Makefile.am and then using it in the Makemodule.am in the sub-directories.
For ex, in foo/Makemodule.am:


Listing 4: Makemodule.am in foo directory of project.
bin_PROGRAMS += myprog # PLV man_MANS += foo/myprog.8 myprog_SOURCES = foo/myprog.c \ # PSV foo/myprog-utils.c


Here in Makefile.am and Makemodule.am, we are using two important types of variables. These are Product List Variable and Product Source Variable.

4.1 Product List Variable:

Products of the build systems are specified to the Makefile.am using Product List variable (PLV). These are class of variables which have special meaning to Automake. They tell what are the resultant output of the Makefiles or build system that will be generated after the successful completion of the build process. This variable consist of white-space-separated list of products and the variable (as you can see in above example) consist of two major parts separated by a underscore.
These two components are:
The bin portion of the PLV represent a installation location prefix. This is used to define the installation location for the list of products which are mentioned in the PLV. The GCS defines many installation locations. However, make variable ending with dir , whose value is a file-system location, is a viable installation location and can be used as prefix in a Automake PLV.
What we mean here is that any make variable ending with dir, for example $(bindir), with a installation location value can be used by Automake to produce install rule which will install the list of products to the $(bindir) location. However, only those products will be installed that are mentioned in the PLV which have prefix bin as mentioned in the above example (bin_PROGRAMS) and PROGRAMS is termed as Primary which represent the type of product that is expected to be produced, which in this case is the list of binary files.
Automake by default recognizes 4 type of variable which they can be treated as installation locations and they have a prefix pkg. These variables are:
These pkg version of standard libdir, includedir, datadir and libexecdir indicates that the installation of the products listed in the PLV equivalent variable should be installed in the sub-directory of these location mentioned in these standard make variable where the sub-directory is named after the package. For example, our Project which is named as Project in our Figure 1, will have binary files (like myprog) installed in the Project sub-directory of the bin system directory. And yes, we can change the installation directory from bin to any other directory by overriding the value of the $(bindir) variable in Makefile.
We can also provide our own PLV custom prefix which represent a variable, having the location for installation of some list of products. For example, for installation of some XML files to a custom location, we can define a xmldir variable having value for the installation path and a PLV xml_DATA having value a list of xml files which will be installed in the installation path.


Listing 5: Custom PLV with variable in Makefile.am
xmldir = $(datadir)/xml xml_DATA = file1.xml file2.xml file3.xml ...


Installation location can default values which are either defined by Automake or by user itself. However, these default value can be overridden using the argument passed while running the configure script or make command. If we don’t want to install certain products to the installation location, Automake generated rules ensures to generate rules to handle such situations. For example, if we don’t want to install binary and library files and only want to install the data files, we can pass the command make bindir=’’ libdir=’’ install.

4.2 Prefix Not Associated with Installation

Sometimes, we don’t want certain products to installed anywhere. That’s why we have certain prefix which are not associated with any installation location. There are 3 main such prefix:

Listing 6: A shell script code in configure.ac for building optprog product conditionally.
AC_INIT(...) ... optional_programs= AC_SUBST([optional_programs]) ... if test "x$(build_opt_prog)" = xyes; then optional_programs=$(optional_programs) optprog ### 1 ### fi ...



Listing 7: Using the EXTRA prefix to conditionally build products in Makefile.am
EXTRA_PROGRAMS = optprog ### 2 ### bin_PROGRAMS = myprog $(optional_programs) ### 3 ###


In Listing 6 ### 1 ###, optprog is appended to an Autoconf substitution variable called optional_program. The EXTRA_PROGRAMS variable at ### 2 ### in Listing 7 list optprog, as a product that may or may not be build. This redundant information of EXTRA_PROGRAMS in Makefile.am make it ensure that Automake knows that optprog is a optional product and make the resultant Makefile with appropriate script. This make sure that even if optprog is not mentioned in $(optional_programs) variable in some build.

4.3 Primaries

Primaries represent type of product which is generated by this build system. That’s it! What are these type of files?
These files can be executable, libraries, config files in terms of xml, json and event normal txt files. Wait, normal txt files? Well, what it means is that some products (not exactly) of build system are not actually generated by the build system. These are part of the project and we will need these files while executing the software in our system after the build process of that software. For example, C or C++ language have the prototype of the function or API written in header files (.h or .hpp) which allows the user of the library to make there software aware of these API before using it and enables in linking the implementation to the function calls while in the linking process of the build system. So, the main intent of this long boring example is to provide a context on why we have different kind of primaries as this helps in differentiating the type of product to Automake and hence generate rules in resultant Makefile accordingly. Interestingly, we can define our own primaries,but beware, we need to write the rules for those on our own in Makefile.am.
Let us see the default primaries supported by Automake.
There are also other types of primaries that Automake supports, but we will currently discuss about the ones which are used the most. The rest of the commonly used primaries are MANS, TEXTINFOS, PYTHON, JAVA, etc.

4.4 Product Source Variable

If you notice, the second line in Listing 7 is an example of Automake product source variable (PSV).

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


Just like PLV which represent list of products, PSV represent list of files on which the product is dependent on. These are the source files which are used to build the products of the projects. Only characters that are allowed in make variables (letters, numbers, and the at sign) are allowed in the product tag of a PSV. So, Automake converst illegal characters into underscores.

Listing 8: Illegal make variable characters are converted to underscores in product tags.
lib_LIBRARIES = libc++.a libc___a_SOURCES = ...

4.5 PLV and PSV Modifiers

Modifiers are optional prefix added to PLV or PSV to modify the behavior of these Automake variable which would have behaved differently if not added. It is better to understand this with examples:

Unit Testing: Automake support for make check

Check the example below first:

Listing 12: The check target
... check: all ./myprog | grep “Hello from .*myprog!” @echo “*** ALL TESTS PASSED ***” ...


As we can see to test the sanity of the executable produced by the build system, in this case is a Makefile is used for testing. Here, it is executing the executable and using grep to check the output is as expected as shown above. Automake can also automate this by using variables specific to this functionality.

Listing 13: src/Makemodule.am: Additional code required to support the check target
bin_PROGRAMS += myprog myprog_SOURCES += myprog.c check_SCRIPTS = greptest.sh @1 TESTS = $(check_SCRIPTS) @2 greptest.sh: @3 echo ’./myprog | grep “Hello from .*jupiter!”’ > greptest.sh chmod +x greptest.sh CLEANFILES = greptest.sh @4


The check_SCRIPTS (@1) is a PLV which refers to a script that is generated at build time. This script is only generated when make check command is executed. Also, we need to keep in mind that the generated script should be cleaned (or deleted) as they are not files which should be distributed. So, CLEANFILES(@4) variable takes care about the cleaning part.
The TESTS (@2) line is the important one in Listing 13. It tells which targets should be executed when make check is executed and since the check_SCRIPTS have complete list of these target, hence it is referenced. However, since TESTS variable ensures that appropriate rules are generated so that the test scripts are build and executed successfully. Therefore, check_SCRIPTS is redundant and can be skipped by replacing it with the contents of it. check_* PLVs becomes important only when helper scripts needs to build which are not executed directly but are executed by script which are listed in TESTS variable. Note, TESTS is actually for executing the script and Automake always build the script before executing if it is already not build.

Convenience Libraries: Static libraries.


Listing 14: common/Makemodule.am: To build the temporary library.
noinst_LIBRARIES += libcommon.a libcomman_a_SOURCES = common.h print.c


The first line defines what library should be build and as you have noticed, one noinst modifier is used so that the build library is not installed into the system directory after the project is build and installed. The goal of such library is to compile common code in one static library and used in many different section of the projects. Here, libcommon.a is like a archive file which is given to compiler for compilation of executable. The only difference between .tar and .a files is that the compiler know which object files to use to compile the executable which is present inside the static library.

6.1 Product Option Variables

In order for the source files which will be used to build the executable to find the relevant code from the static library and also find the correct corresponding header files, we need to add some additional information to the Makefile.am (more specifically Makemodule.am)of the src directory.
Listing 15: src/Makemodule.am: Adding compiler and linker directives to Makefile.am files.
bin_PROGRAMS += myprog myprog_SOURCES = main.c myprog_CPPFLAGS = -I$(top_srcdir)/common myprog_LDADD = ../common/libcommon.a


The two new variable added here (myprog_CPPFLAGS and myprog_LDADD) are product option variables (POVs) are used to specify product-specific options to tools that are used to build products from source code. The myprog_CPPFLAGS variable adds product-specific C-preprocessor flags to the compiler command line for all source files that are compiled for the myprog program. This add reference to the list of location that the compiler will look for header file when the source files are used for compilation. The myprog_LDADD variable is used to add libraries to the linker command line. The path ../common/libcommon.a is added to the linker command line while building the myprog product so that the relevant object files can be used to link and build the product. The variables program_LDADD and program_LIBADD is only necessary for libraries that are built as part of our own package. If our project requires libraries which are already a part of user’s system, then all we need is a macro call of AC_CHECK_LIB or AC_SEARCH_LIB in configure.ac file. This will generate appropriate script in configure that will add the references to these libraries in linker command via LIBS variable.
The set of POVs supported by Automake are listed below:
We can use the last three option variables in the list to pass lists of order-dependent static and shared library references to the linker. These variable can also be used to pass -L and -l options.

6.1.1 Per-Makefile Option Variables

Some of the preprocessor flags are common for all the products that is to be build by the Makefile. So, instead of repeatedly mentioning it on product variables in .am files, these can be mentioned in the per-makefile option variable as shown below:

Listing 16: per-product and per-makefile flags.
AM_CFLAG = ... some flags ... ... prog1_CFLAGS = ... more flags ... $(AM_CFLAGS) @1


The first line above is a per-makefile flag while the next flag at (@1) is a per-product flag. Note, that after adding the per-makefile flag it is appended as variable to the per-product flag because the per-product flags overrides the value of per-makefile flag. In order to include both, the former is added to the latter in order to include all the mentioned flags for the products.

Why then per-makefile are needed in the first place? Can’t we directly add to the per-products flag?
Per-makefile flags generates relatively compact Makefile as compare what per-product flag generates. This is because per-product flags makes Automake to generate per-product rules instead of most of the redundant part which can be used as suffix.
Note that these variables specific to Automake don’t modify the actual CFLAGS of Makefiles. These variables are exclusively reserved for the end user to tinker with. All the flags are defined above are appended to these flags or to the Command when executed for building the product.

6.2 Building the New Library

Now the newly added static library can be build by adding the SUBDIR to the top-level makefile. This is for the case when we opt for recursive Build system. However, our intention is to create a Non-Recursive Build system for reason we have already discussed. For that we declare the PLVs on the top-level Makefile.am and we include the products list in the Makemodules.am which are in the sub-directories, which we have been doing till now.
In the case of Recursive-Makfiles, we would have to add the Makefiles of the sub-directories into the macro AC_CONFIG_FILES in configure.ac:

Listing 17: For the case of Recursive-Makefile build system.
... AC_CONFIG_FILES([Makefile common/Makefile src/Makefile]) ...


However, for Non-recursive case we don’t need such inclusion as there is only one Makefile which would be generated by the Automake, i.e. the top-level makefile.
On running the autoreconf -i, the updated build system will be checked and enhancements will be suggested by the Autotools scripts.

autoreconf -i configure.ac:6: installing ’./install-sh’ configure.ac:6: installing ’./missing’ Makefile.am:2: error: library used but ’RANLIB’ is undefined Makefile.am:2: The usual way to define ’RANLIB’ is to add ’AC_PROG_RANLIB’ Makefile.am:2: to ’configure.ac’ and run ’autoconf’ again. Makefile.am: installing ’./depcomp’ autoreconf: error: automake failed with exit status: 1


As we can see, it install some scripts and mention one error where RANLIB is not added in configure.ac file to for building the project. This error can be removed by adding AC_PROG_RANLIB to configure.ac. The inclusion of RANLIB is only for making the build system portable. It is not usually not needed in modern systems.

What Goes into a Distribution?

Automake is quite smart enough to know what are the roles of each file in the build process and hence determine which files and directory needs to be included in the distribution created by executing make dist. This is only possible because we have mentioned every file to Automake using PLV and PSV. Be warned that don’t try to use some bash shell shortcuts to write one line to include all the source files, etc. Thats not how Automake will behave properly. Trying to hack a will be more pain than listing all the files.
Also, Automake EXTRA_DIST variable can be used to mention files or directories that also need to including while creating the distribution package.

Listing 18: directory Config to be included in distribution package.
EXTRA_DIST = Config


Note that the directories might have .git, .svn or .CVS status directories and we don’t want it to be installed with the distribution created. Fortunately, Automake handles this automatically.

Maintainer Mode

This is where we make our project ready to use for our user. One situation might be created that the timestamp of the source in the distribution package might be newer relative to the system clock. Because of this, when the end-user execute make command, it might see that the configuration scripts and all the tools related to Autotools are modified and the entire build system needed to be build including these configuration script, Makefile.in from Makefile.am. The problem is that the user might not have these Autotools. But, it doesn’t meant that they cannot build the project. To handle such situation, Automake has a mode called Maintainer Mode where Makefile rules are generated conditionally on whether the Autotools related generated scripts need to be rebuild. This is done via AM_MAINTAINER_MODE macro in configure.ac which disable maintainer-level make rules that we discussed just now.
For maintainer, to rebuild the entire build-system including the Autotools related script, it should use command-line option (--enable-maintainer-mode) with configure scripts that tells to generate Makefile.in templates that contains rules and commands to execute the Autotools as necessary. This is to enable the end-user to not worry about Autotools behavior and can build the project without it.

Suppressing Noise

Some user might want to print only relevant things while building the project and don’t want entire command-line output for building the project. This can be achieved by in two ways and either can be used:
The user can set the verbosity by using argument --enable-silent-rules or --disable-silent-rules on the configure command line. What it does it actually it set the V (verbosity) of make output by setting it to either 1 or 0, respectively.

Ending

And thats it, we have covered hopefully enough to at least create a moderately good enough build-system using Autoconf and Automake. Mostly, we have discussed about Automake and very slightly about Autoconf. But, next write-up will be about Autoconf and how we can make the build-system configurable for end-user based on the requirement of the user and the capability of the user’s system. Farewell!