Thursday, September 04, 2014

A Closer Look at GNU AutoTools

Last time, I mentioned the tools I would be using. After some investigation, I discovered that the documentation for GNU AutoTools was crappy. This is surprising considering how long it has been in use. ☹

So, I'm considering alternatives. I came across a page by Shlomi Fish, who recommended that CMake be used instead. A quick search through the web showed that documentation of it is also crappy.

It seems that all software build systems have crappy documentation, so the final decision will be made when I figure out which is the least crappy. In the mean time, I will discuss creating a simple system using GNU AutoTools.

The system I shall describe is the ubiquitous Hello world. I found a workflow diagram on Wikipedia for creating such a system.

The autoconf-automake process
Figure 1: The autoconf-automake process

Step-by-step Creation of the hello Build System

  1. The first step is to create a directory for it.
      $ mkdir eg-autotools-hello && cd eg-autotools-hello
    
  2. The system will have two directories: one for the source files, src/; and one for the build/.
      $ mdkir src build
      $ ls -F
      build/  src/
    
  3. Create the source files and add their content.
      $ ls -F *
      build:
    
      src:
      hello.c  hello.h
      $ cat src/hello.h 
      #ifndef __HELLO_H__
      #define __HELLO_H__
    
      #define HELLO_MSG "Hello world"
    
      #endif /* __HELLO_H__ */
      $ cat src/hello.c
      #include <stdio.h>
      #include "hello.h"
    
      int main(
      ){
          printf( "%s\n", HELLO_MSG );
      }
      $
    
  4. Now, according to the workflow chart, autoscan is use to create configure.scan.
      $ autoscan
      $ cat configure.scan 
      #                                               -*- Autoconf -*-
      # Process this file with autoconf to produce a configure script.
    
      AC_PREREQ([2.69])
      AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
      AC_CONFIG_SRCDIR([src/hello.h])
      AC_CONFIG_HEADERS([config.h])
    
      # Checks for programs.
      AC_PROG_CC
    
      # Checks for libraries.
    
      # Checks for header files.
    
      # Checks for typedefs, structures, and compiler characteristics.
    
      # Checks for library functions.
    
      AC_OUTPUT
    

    Move the file to configure.ac and edit it.

      $ mv configure.scan configure.ac
      $ cat configure.ac 
      #                                               -*- Autoconf -*-
      # Process this file with autoconf to produce a configure script.
    
      AC_PREREQ([2.69])
      AC_INIT([hello], [1.0])
      AC_CONFIG_SRCDIR([src/hello.c])
      AC_CONFIG_HEADERS([config.h])
      AM_INIT_AUTOMAKE
    
      # Checks for programs.
      AC_PROG_CPP
    
      # Checks for libraries.
    
      # Checks for header files.
    
      # Checks for typedefs, structures, and compiler characteristics.
    
      # Checks for library functions.
    
      AC_CONFIG_FILES([Makefile build/Makefile])
    
      AC_OUTPUT
    

    First, change AC_INIT to the package name, "hello", and the version, "1.0". Then, change AC_CONFIG_SRCDIR to the C file with the main() function. Add AM_INIT_MAKEFILE so that the Makefile.ams can be processed by automake. Change AC_PROG_CC to AC_PROG_CPP. Finally, add AC_CONFIG_FILES([Makefile build/Makefile]) to tell where the Makefile.ams can be found.

    The directories look like this:

      $ ls -AF *
      autoscan.log  configure.ac
    
      build:
    
      src:
      hello.c  hello.h
    
  5. Create the two Makefile.ams: one in the top-level directory and one in the build directory.
      $ cat Makefile.am 
      SUBDIRS = build
    

    The top-level Makefile.am has only one line in it that informs the tools to process build/Makefile.am.

      $ cat build/Makefile.am 
      bin_PROGRAMS = hello
      hello_SOURCES = hello.c
      noinst_HEADERS = hello.h
    
      VPATH = ../src
    

    The bin_PROGRAMS specifies what binary to build, in this case, hello. The hello_SOURCES specifies all the sources files for hello, in this case, just the one. The noinst_HEADERS specifies not to install these H files when running make install. In this case, the list contains only one file, hello.h.

    The VPATH is set so that make can find the source and header files.

    The directories look like this:

      $ ls -AF *
      autoscan.log  configure.ac  Makefile.am
    
      build:
      Makefile.am
    
      src:
      hello.c  hello.h
    
  6. Add the local m4(1) definitions:
      $ aclocal
      $ ls -AF *
      aclocal.m4  autoscan.log  configure.ac  Makefile.am
    
      autom4te.cache:
      output.0  requests  traces.0
    
      build:
      Makefile.am
    
      src:
      hello.c  hello.h
    

    Notice the m4(1) cache.

  7. Create the configuration header file:
      $ autoheader
      $ ls -AF *
      aclocal.m4  autoscan.log  config.h.in  configure.ac  Makefile.am
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      Makefile.am
    
      src:
      hello.c  hello.h
    
  8. Create the Makefile.ins with automake using the --add-missing to add some of the missing files.
      $ automake --add-missing
      configure.ac:11: installing './compile'
      configure.ac:8: installing './install-sh'
      configure.ac:8: installing './missing'
      Makefile.am: installing './INSTALL'
      Makefile.am: error: required file './NEWS' not found
      Makefile.am: error: required file './README' not found
      Makefile.am: error: required file './AUTHORS' not found
      Makefile.am: error: required file './ChangeLog' not found
      Makefile.am: installing './COPYING' using GNU General Public License v3 file
      Makefile.am:     Consider adding the COPYING file to the version control system
      Makefile.am:     for your code, to avoid questions about which license your project uses
      build/Makefile.am: installing './depcomp'
    

    The automake fails because of missing files it can't create. But notice the last line of the output. This says our build system will use the compiler's dependency-tracking functionality. This will save us from doing them by hand, that is, should we had files to the src/.

    This directories so far:

      $ ls -AF *
      aclocal.m4  autoscan.log  compile@  config.h.in  configure.ac  COPYING@  depcomp@  INSTALL@  install-sh@  Makefile.am  missing@
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      Makefile.am
    
      src:
      hello.c  hello.h
    

    Add the missing files automake can't create and run it again. But this time, there's no need to use the --add-missing option.

      $ touch NEWS README AUTHORS ChangeLog
      $ automake
      $ ls -AF *
      aclocal.m4  autoscan.log  compile@     configure.ac  depcomp@  install-sh@  Makefile.in  NEWS
      AUTHORS     ChangeLog     config.h.in  COPYING@      INSTALL@  Makefile.am  missing@     README
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      Makefile.am  Makefile.in
    
      src:
      hello.c  hello.h
    
  9. Create the confiure script:
      $ autoconf
      $ ls -AF *
      aclocal.m4  autoscan.log  compile@     configure*    COPYING@  INSTALL@     Makefile.am  missing@  README
      AUTHORS     ChangeLog     config.h.in  configure.ac  depcomp@  install-sh@  Makefile.in  NEWS
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      Makefile.am  Makefile.in
    
      src:
      hello.c  hello.h
    
  10. We are now through with the developer-only phase. The next steps will be used by the developer and the user to configure the build system.

    Create the configuration for the build system:

      $ ./configure 
      checking for a BSD-compatible install... /usr/bin/install -c
      checking whether build environment is sane... yes
      checking for a thread-safe mkdir -p... /bin/mkdir -p
      checking for gawk... gawk
      checking whether make sets $(MAKE)... yes
      checking whether make supports nested variables... yes
      checking for style of include used by make... GNU
      checking for gcc... gcc
      checking whether the C compiler works... yes
      checking for C compiler default output file name... a.out
      checking for suffix of executables... 
      checking whether we are cross compiling... no
      checking for suffix of object files... o
      checking whether we are using the GNU C compiler... yes
      checking whether gcc accepts -g... yes
      checking for gcc option to accept ISO C89... none needed
      checking whether gcc understands -c and -o together... yes
      checking dependency style of gcc... gcc3
      checking how to run the C preprocessor... gcc -E
      checking that generated files are newer than configure... done
      configure: creating ./config.status
      config.status: creating Makefile
      config.status: creating build/Makefile
      config.status: creating config.h
      config.status: executing depfiles commands
    

    Notice the line: checking dependency style of gcc... gcc3

    This says the build system will be using dependency tracking, so we need not worry about it. ☺

      $ ls -AF *
      aclocal.m4  autoscan.log  compile@  config.h.in  config.status*  configure.ac  depcomp@  install-sh@  Makefile.am  missing@  README
      AUTHORS     ChangeLog     config.h  config.log   configure*      COPYING@      INSTALL@  Makefile     Makefile.in  NEWS      stamp-h1
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      .deps/  Makefile  Makefile.am  Makefile.in
    
      src:
      hello.c  hello.h
    
  11. Finally, the program can be compiled:
      $ make
      make  all-recursive
      make[1]: Entering directory `~/eg-autotools-hello'
      Making all in build
      make[2]: Entering directory `~/eg-autotools-hello/build'
      gcc -DHAVE_CONFIG_H -I. -I..     -g -O2 -MT hello.o -MD -MP -MF .deps/hello.Tpo -c -o hello.o ../src/hello.c
      mv -f .deps/hello.Tpo .deps/hello.Po
      gcc  -g -O2   -o hello hello.o  
      make[2]: Leaving directory `~/eg-autotools-hello/build'
      make[2]: Entering directory `~/eg-autotools-hello'
      make[2]: Leaving directory `~/eg-autotools-hello'
      make[1]: Leaving directory `~/eg-autotools-hello
      $ ls -AF *
      aclocal.m4  autoscan.log  compile@  config.h.in  config.status*  configure.ac  depcomp@  install-sh@  Makefile.am  missing@  README
      AUTHORS     ChangeLog     config.h  config.log   configure*      COPYING@      INSTALL@  Makefile     Makefile.in  NEWS      stamp-h1
    
      autom4te.cache:
      output.0  output.1  requests  traces.0  traces.1
    
      build:
      .deps/  hello*  hello.o  Makefile  Makefile.am  Makefile.in
    
      src:
      hello.c  hello.h
      $ ./build/hello 
      Hello world
    

Yay, it works. ☺

Making Changes

As a developer, to rebuild the program after modifying the sources files, just run make.

But if you add or remove some source files, build/Makefile.am's hello_SOURCES and noinst_HEADERS will have to be changed.

And if you change configure.ac or any of the Makefile.ams, you must run the following successfully for the change to be applied:

  autoreconf && ./configure

Acknowledgements