Monday, January 21, 2013

A config.m4 generator (in m4)

When you create a PHP extension you need to provide a config.m4 file.
There are plenty of examples how a config.m4 should look like and one can (and does) mimic these examples.

Since they are all somewhat similar I wanted to take a step back and find a way how to automatically create a valid config.m4 by using

  • a set of definitions (name of the extension, name of C file etc.)
  • a config.m4 template

    Of course there are plenty of ways to do this and I decided to explore the m4 way i.e. writing an m4 file which after being parsed by m4 would generate the config.m4 file.

    The m4 file consists of three parts (aside from documenation).

  • The first part lists some variables which need to be changed by the user according to his needs.
    The user needs to provide:
    • The name of the extension (which will name the shared library)
    • The title of the extension (more a descriptive name for humans)
    • The C file
    • The name of the external library
      The path will be provided at configuration time via --with-PHP_EXT=DIR
  • The second part creates a set of derived variables (derived from part 1)
    These variables will replace the respective content in the template.
  • The third part is the PHP template.
    The template contains code to generate a PHP interface to an external application whose library should be linked in.

    The m4 file

    I named it preconfig.m4
    divert(-1)
    
    dnl This is a generalized config.m4 file which defines all settings at the beginning
    dnl in order to simplify the building of php extensions
    dnl Run as:
    dnl    m4 preconfig.m4 > config.m4
    dnl    phpize
    dnl    ./configure --with-PHP_EXT=DIR
    dnl    make
    dnl where PHP_EXT is the name of the extension and DIR is the directory of the external package
    dnl which should contain an include and a lib directory
    
    dnl ----------------------------------
    dnl A set of definitions for the new php extension to be built incl. the C file(s)
    dnl     1) the name of the shared library  e.g.  foo
    dnl     2) a nickname for the share library e.g. Foo bar
    dnl     3) the name of the C file e.g.           foo.c
    dnl     4) the name of the library to be linked into e.g.  bar
    dnl        (the corresponding libbar.a must be found in DIR/lib)
    dnl     Note: DIR/include should contain the corresponding .h file
    dnl           which needs to be called in foo.c
    dnl ----------------------------------
    
    dnl  The name of the php extension
    define(`PHP_EXT',`foo')
    
    dnl  A name of the extension (for humans)
    define(`EXT_NAME',`Foo bar')
    
    dnl  The corresponding C file (there should be an include file too like php_config.h)
    define(`PHP_C_FILES',`foo.c')
    
    dnl  To be linked as  -lbar  with a corresponding  libbar.a  in DIR/lib
    define(`LIBNAME',`bar')
    
    
    dnl ----------------------------------
    dnl Everything below here is derived from the settings above
    dnl ----------------------------------
    
    dnl  The name of the extension in capitals
    define(`PHP_EXT_UPP',translit( PHP_EXT, [a-z], [A-Z]))
    
    dnl  The internal php variable to define the existance of the extension
    define(`PHP_VAR',`PHP_'PHP_EXT_UPP)
    
    dnl  The directory of lib/libbar.a
    define(`PHP_DIR',PHP_EXT_UPP`_DIR')
    
    dnl  An autoconfig variable
    define(`HAVE_EXT',`HAVE_'PHP_EXT_UPP)
    
    dnl  An autoconfig variable
    define(`EXT_SHARED_ADD',PHP_EXT_UPP`_SHARED_LIBADD')
    
    dnl  An autoconfig variable
    define(`HAVE_EXTLIB',`HAVE_'PHP_EXT_UPP`LIB')
    
    dnl  The filename of the library  bar -> libbar.a
    define(`LIBS',`lib'LIBNAME`.a')
    
    dnl  The default paths for libbar.a
    define(`LIB_PATHS',`/usr/local /usr')
    
    dnl ----------------------------------
    dnl Here starts the actual config.m4 code
    dnl ----------------------------------
    divert()
    `dnl This is the config.m4 file for extension 'EXT_NAME` ('PHP_EXT`)'
    
    `dnl The extension should be built with   --with-'PHP_EXT`=DIR'
    PHP_ARG_WITH( PHP_EXT, whether to enable EXT_NAME support,
    [ --with-PHP_EXT=[DIR]   Enable EXT_NAME support])
    
    `dnl Check whether the extension is enabled at all'
    if test "$PHP_VAR" != "no"; then
    
      AC_DEFINE(HAVE_EXT, 1, [Whether you have EXT_NAME])
    
      `dnl Add include path'
      PHP_ADD_INCLUDE($PHP_VAR/include)
    
      `dnl Check whether the lib exists in the lib directory'
      AC_MSG_CHECKING(for LIBS in $PHP_VAR and LIB_PATHS )
      for i in $PHP_VAR LIB_PATHS; do
          if test -r $i/lib/LIBS; then
            PHP_DIR=$i
            AC_MSG_RESULT(found in $i)
          fi
      done
    
      if test -z "$PHP_DIR"; then
        AC_MSG_RESULT(not found)
        AC_MSG_ERROR(Please reinstall the LIBS distribution - includes should be
                     in /include and LIBS should be in /lib)
      fi
    
      `dnl Add lib path'
      PHP_ADD_LIBRARY_WITH_PATH( LIBNAME, $PHP_VAR/lib, EXT_SHARED_ADD)
      AC_DEFINE( HAVE_EXTLIB, 1, [Whether EXT_NAME support is present and requested])
    
      `dnl Finally, tell the build system about the extension and what files are needed'
      PHP_SUBST(EXT_SHARED_ADD)
      PHP_NEW_EXTENSION( PHP_EXT, PHP_C_FILES, $ext_shared)
    fi
    

    You should run

    m4 preconfig.m4 >config.m4
    
    which will report
    m4:../s1/preconfig.m4:65: empty string treated as 0 in builtin `divert'
    
    which can be ignored, it is about ommitting a set of superfluous empty lines.

    The resulting config.m4

    What you can see is that all references to PHP_VAR, PHP_EXT etc. have been replaced by the proper 'FOO' versions.

    
    dnl This is the config.m4 file for extension Foo bar (foo)
    
    dnl The extension should be built with   --with-foo=DIR
    PHP_ARG_WITH( foo, whether to enable Foo bar support,
    [ --with-foo=[DIR]   Enable Foo bar support])
    
    dnl Check whether the extension is enabled at all
    if test "$PHP_FOO" != "no"; then
    
      AC_DEFINE(HAVE_FOO, 1, [Whether you have Foo bar])
    
      dnl Add include path
      PHP_ADD_INCLUDE($PHP_FOO/include)
      
      dnl Check whether the lib exists in the lib directory
      AC_MSG_CHECKING(for libfoo.a in $PHP_FOO and /usr/local /usr )
      for i in $PHP_FOO /usr/local /usr; do
          if test -r $i/lib/libfoo.a; then
            FOO_DIR=$i
            AC_MSG_RESULT(found in $i)
          fi
      done
    
      if test -z "$FOO_DIR"; then
        AC_MSG_RESULT(not found)
        AC_MSG_ERROR(Please reinstall the libfoo.a distribution - includes should be
                     in /include and libfoo.a should be in /lib)
      fi
    
      dnl Add lib path
      PHP_ADD_LIBRARY_WITH_PATH( libfoo.a, $PHP_FOO/lib, FOO_SHARED_LIBADD)
      AC_DEFINE( HAVE_FOOLIB, 1, [Whether Foo bar support is present and requested])
      
      dnl Finally, tell the build system about the extension and what files are needed
      PHP_SUBST(FOO_SHARED_LIBADD)
      PHP_NEW_EXTENSION( foo, foo.c, $ext_shared)
    fi
    

    This file will run nicely with phpize (I tested with PHP5). This particular example will of course end with a configuration error but should run ok for a properly existing and installed library.

    ./configure --with-foo=DIR
    ...
    ...
    checking whether to enable Foo bar support... yes, shared
    checking for libfoo.a in /Users/bar and /usr/local /usr ... not found
    configure: error: Please reinstall the libfoo.a distribution - includes should be
                     in /include and libfoo.a should be in /lib
    
    

    Of course the template could be extended to include other config.m4 stuff (debugging, check the validity of the library etc.) but it works nicely for me.

  • No comments:

    Post a Comment