Saturday, December 29, 2012

Slaying the automake/autoconf dragon

After quite a few years of avoiding it, I decided today was the day to get a project compiling with autoconf/automake. The project is a new C++ JSON parser I've written. It consists of a shared library and a test suite that is based on cppunit. Notably, the project is based on flex (lex) and bison (yacc), which presented certain problems with automake (more on that below).

The big "ahah" moment for me was discovering "autoscan", which does the work of generating a configure.ac file automatically based on a source directory. 

Here are the steps.

First, I created a directory for all my project

$ mkdir myproject
$ cd myproject
$ mkdir src
$ mkdir test

I then copied all the source and header files for the shared library into src, and the two source files (one c++, the other a header) into test.

In addition to c++ sources, the src dir also contains a c++ bison source (with a .ypp suffix) and a flex source (with a .lpp suffix). Using ypp and lpp cause automake to generate c++ source files, which is important for my project.

Here's what the directories looked like after copying srcs:

./src/jsonapi.cpp
./src/json.ypp
./src/jsonobj.cpp
./src/lex.lpp
./src/context.cpp
./src/yyerror.cpp
./src/jsonapi.h
./src/context.h
./src/jsonparse.cpp
./src/jsonparse.h
./src/jsonobj.h
./test/jsonapitest.cpp
./test/jsonapitest.h

Next step involved using a tool to scan the above directory and generate some skeleton automake/autoconf files. While in myproject:

$ autoscan
$ mv configure.scan configure.ac

Then, I edited configure.ac, so that it looked like the following:

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.68])
AC_INIT([jsonapi], [1.0], [xxx@gmail.com])
AC_CONFIG_SRCDIR([src/jsonapi.cpp])
AC_CONFIG_HEADERS([config.h])

AM_INIT_AUTOMAKE(jsonapi, 1.0)
LT_INIT

# Checks for programs.
AC_PROG_CXX
AC_PROG_LEX
AC_PROG_YACC

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([memory.h string.h])

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

# Checks for library functions.
AC_CHECK_FUNCS([strstr])

AC_CONFIG_FILES([Makefile src/Makefile test/Makefile])
AC_OUTPUT

Next, I typed:

$ aclocal
$ autoconf

This generates a script called configure, which will be used later.

Next, I created a Makefile.am file in the myproject directory:

AUTOMAKE_OPTIONS = foreign
SUBDIRS = src test

I also created one in src:

AM_CXXFLAGS = --pedantic -Wall -O2 -I ../src
AM_LDFLAGS =

# deal with bug with generating header file from flex.

BUILT_SOURCES = json.cpp lex.cpp json.h lex.h
CLEANFILES = lex.h
lex.h : lex.lpp
        $(LEX) --header-file=$@ -o /dev/null $<
AM_YFLAGS = -d


lib_LTLIBRARIES = libjsonparse.la
libjsonparse_la_SOURCES = json.ypp lex.lpp context.cpp context.h \
                          jsonapi.cpp jsonapi.h \
                          jsonobj.cpp jsonobj.h \
                          jsonparse.cpp jsonparse.h \
                          yyerror.cpp


and one in test:

AM_CXXFLAGS = --pedantic -Wall -O2 -I ../src
AM_LDFLAGS = -lcppunit -ljsonparse

bin_PROGRAMS = jsonapitest
jsonapitest_SOURCES = jsonapitest.cpp  jsonapitest.h

You'll notice in the src Makefile.am I had to go to extra trouble to get flex to generate a header file that is needed by other sources. This should be fixed in versions of autoconf late 2012 (my version of autoconf that came with Ubuntu 12.04 did not have the fix). The fix will allow you to specify the --header-file argument using an AM_LFLAGS macro (similar to how AM_YFLAGS is used to pass -d to bison).

The following commands were used to generate the Makefiles:

$ autoreconf -i
$ automake --add-missing
$ ./configure

Finally, I issued the following command to build and install my library, and to generate a test program:

$ sudo make install