René Nyffenegger's collection of things on the web
René Nyffenegger on Oracle - Most wanted - Feedback
 

The GNU build system

I'd say that more than 90% of the folks who have ever compiled an open source program, have followed this simple recipe steps:
  1. Download
  2. unzip/untar
  3. ./configure
  4. make
  5. make install
I'd also say, that less than 10% of these people know how to assemble a package so that it can be compiled and installed with this recipe. Well, I certainly didn't, so I investigated what is known as the GNU build system.
I have tested the following on Windows with Cygwin. The necessary tools (like automake, autoconf, a shell and so on) need of course be installed.

The program

First, I developped a very simple program that displays yesterday's date.
main.c
#include "yesterday.h"
#include "printdate.h"

int main() {
  time_t y = yesterday();
  printdate(y);
  return 0;
}
printdate.c
#include "printdate.h"

void printdate(time_t d) {
  struct tm*   t;
  char buf[100];

  t=localtime(&d);

  strftime(buf, sizeof(buf), "Yesterday was: %Y-%m-%d\n", t);

  printf(buf);
}
printdate.h
#ifndef PRINTDATE_H
#define PRINTDATE_H

#include <time.h>

void printdate();

#endif
yesterday.c
#include "yesterday.h"

time_t yesterday() {
  time_t today = time(NULL);
  return today - (time_t)(24*60*60);
}
yesterday.h
#ifndef YESTERDAY_H
#define YESTERDAY_H

#include <time.h>

time_t yesterday();

#endif
Of course, this program is so simple that it could be compiled with a
gcc *.c
But that's not what I am going to do.

configure.ac

A configure.ac is needed. autoscan helps creating one.
For some reason (or maybe another?), autoscan requires the presence of a configure.ac file which can be empty. So I create one:
$ touch configure.ac
Now, I can run autoscan:
$ autoscan
configure.ac: warning: missing AC_FUNC_STRFTIME wanted by: printdate.c:9
configure.ac: warning: missing AC_PREREQ wanted by: autoscan
configure.ac: warning: missing AC_PROG_CC wanted by: main.c
configure.ac: warning: missing AC_STRUCT_TM wanted by: printdate.c:4
autoscan also checks for portability issues. In my case, it found two: strftime and the tm struct are not 100% portable.
autoscan has created a configure.scan:
configure.scan
$ less configure.scan
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.
AC_STRUCT_TM

# Checks for library functions.
AC_FUNC_STRFTIME
AC_OUTPUT
This file can now be used as a skeletton for the real configure.ac:
$ cp configure.scan configure.ac
I have changed this configure.ac:
  • Added a package name and version (yesterday and 1.0 respectively) in AC_INIT. Took the bug report address totally out.
  • Also added AM_INIT_AUTOMAKE because I will be using automake later.
  • Finally, I have also added a AC_CONFIG_FILES.
configure.ac
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(yesterday, 1.0)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.
AC_STRUCT_TM

# Checks for library functions.
AC_FUNC_STRFTIME
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

aclocal

aclocal must be run in order to create aclocal.m4. As far as I can see, the idea is to put all m4 macros used for deployment into aclocal.m4 so that the machines, on which a program is going to be installed, are not dependent on automake and the like.
$ aclocal

Makefile.am

It was now time to create a Makefile.am.
Makefile.am
bin_PROGRAMS = yesterday

yesterday_SOURCES = main.c yesterday.c yesterday.h printdate.c printdate.h

Autoheader

autoheader is used to create a config.h.in file.
$ autoheader

Makefile.in

The tool to create Makefile.in is automake.
Automake requires that the following files are present when it is invoked:
  • install-sh
  • missing
  • INSTALL
  • NEWS
  • README
  • AUTHORS
  • ChangeLog
  • COPYING
  • depcomp
If automake is invoked with the -a option, it creates install-sh, missing, INSTALL, COPYING and depcomp. The rest must be created manually. For brevity's sake, I just touched them:
$ touch NEWS README AUTHORS ChangeLog
I was now ready to invoke automake, which created Makefile.in.
$ automake -a
configure.ac: installing `./install-sh'
configure.ac: installing `./missing'
Makefile.am: installing `./INSTALL'
Makefile.am: installing `./COPYING'
Makefile.am: installing `./depcomp'

configure

I am now creating the infamous configure script with autoconf.
$ autoconf

Testing

Testing the created configure script by just running it:
./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output file name... a.exe
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables... .exe
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 ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking whether struct tm is in sys/time.h or time.h... time.h
checking for strftime... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
As can be seen from the output, it checks if strftime and struct tm is available. This happens because I have specified this with the AC_FUNC_STRFTIME and AC_STRUCT_TM macros.

Creating a deliverable package:

Now, that I have a Makefile, I can use this to create a distributable tar.gz file. This file is created through the dist-gzip target:
$ make dist-gzip
This creates a yesterday-1.0.tar.gz. Note, that it automatically appended the version that I specified with the AC_INIT macro.
So, let's copy this file to a different (clean) directory and test it:
$ mkdir ../yesterday_test
$ mv yesterday-1.0.tar.gz ../yesterday_test/
$ cd ../yesterday_test/
$ tar xfvz yesterday-1.0.tar.gz
$ cd yesterday-1.0
So far, so good. It extracted some files and upon first inspection, all source files seem to be here as well as the configure script.
$ ./configure
$ make
$ make install
Ok, no errors. Let's go to yet another directory and see if yesterday can be called from there:
$ cd /tmp
$ yesterday
Yesterday was: 2004-11-30
Yeah! Hurray!

Links