Looking for Libraries

The most common use for config.m4 scripts is to check if dependent libraries have been installed. Extensions such as MySQL, LDAP, GMP, and others are designed to be a simple glue layer between the world of PHP userspace and the C libraries that implement their functionality. If these dependent libraries aren't installed, or if the installed version is too old, either compilation would fail, or the resulting binary would be unable to run.

Scanning for Headers

The simplest step in searching for a dependent library is to look for the include files that your script will use when linking against it. Listing 17.1 attempts to find zlib.h in a number of common locations.

Listing 17.1. A config.m4 File That Checks for libz

PHP_ARG_WITH(zlib,[for zlib Support] [ with-zlib Include ZLIB Support]) if test "$PHP_ZLIB" != "no"; then for i in /usr /usr/local /opt; do if test -f $i/include/zlib/zlib.h; then ZLIB_DIR=$i fi done if test -z "$ZLIB_DIR"; then AC_MSG_ERROR([zlib not installed (http://www.zlib.org)]) fi PHP_ADD_LIBRARY_WITH_PATH(z,$ZLIB_DIR/lib, ZLIB_SHARED_LIBADD) PHP_ADD_INCLUDE($ZLIB_DIR/include) AC_MSG_RESULT([found in $ZLIB_DIR]) AC_DEFINE(HAVE_ZLIB,1,[libz found and included]) PHP_NEW_EXTENSION(zlib, zlib.c, $ext_shared) PHP_SUBST(ZLIB_SHARED_LIBADD) fi

This config.m4 file is noticeably larger than those you've worked with up till now. Fortunately, the syntax is fairly straightforward and even familiar if you've done bourne shell scripting.

The file begins with the PHP_ARG_WITH() macro that was first mentioned in Chapter 5, "Your First Extension." This macro behaves the same way as the PHP_ARG_ENABLE() macro you've been using except that the resulting ./configure option becomes with-extname / without-extname rather than enable-extname / disable-extname.

Recall that these macros are functionally identical, and differ only to provide a hint to the end user of your package. You're free to choose either one for any private extension you create. However, if you plan to release it to the public you should bear in mind that PHP's formal coding standards dictate enable/disable for use with extensions that do not link against external libraries, and with/without for extensions that do.

Because this hypothetical extension will be linking against the zlib library, your config.m4 script begins by trying to find the zlib.h header that will be included by the extensions source code files. This is accomplished by checking a few standard locations/usr, /usr/local, and /optfor any file named zlib.h located two folders below these locations in include/zlib.

If it finds zlib.h, it places the base path into a temporary variable: ZLIB_DIR. Once the loop completes, the config.m4 script checks that ZLIB_DIR actually contains somethingindicating that it found zlib.h somewhere. If it doesn't, a meaningful error is produced letting the user know why ./configure can't continue.

At this point, the script assumes that if the header file exists, the corresponding library must be there as well so it uses the next two lines to modify the build environment, ultimately adding -lz -L$ZLIB_DIR/lib to LDFLAGS and -I$ZLIB_DIR/include to CFLAGS.

Finally, a confirmation message is output stating that a zlib installation was found, and what location will be used during compilation. The remaining lines should already be familiar from your earlier work with config.m4. Declare a #define for config.h, declare an extension and specify its source files, and identify a variable substitution to finish tying it to the build system.

Testing for Functionality

So far, this config.m4 example only looks for the necessary header files. Although this is sufficient for compilation, it doesn't ensure that the resulting binary will link properly because it's possible that the matching library file doesn't exist, ormore likelyis the wrong version.

The simplest way to test for the presence of libz.sothe library file that corresponds to zlib.hmight be to simply test that the file exists:

if ! test -f $ZLIB_DIR/lib/libz.so; then AC_MSG_ERROR([zlib.h found, but libz.so not present!]) fi

Of course, that only covers half of the question. What if, for example, another identically named library was installed, but it's incompatible with the library you're looking for? The best way to test that your extension will successfully compile against this found library will be to actually compile something against it. The way you'll do this is through a new config.m4 macro placed right before the call to PHP_ADD_LIBRARY_WITH_PATH:

PHP_CHECK_LIBRARY(z, deflateInit,,[ AC_MSG_ERROR([Invalid zlib extension, gzInit() not found]) ],-L$ZLIB_DIR/lib)

This utility macro will expand out to an entire program that ./configure will attempt to compile. If compilation succeeds, it means that the symbol defined by the second parameter was found in the library named by the first parameter. On success, any autoconf script located in the third parameter would be executed; on failure, the autoconf script located in the fourth parameter is run. In this example, the third (success) parameter was left empty because no news is good news. The fifth and final parameter is used to specify additional compiler and linker flags, in this case, a -L indicating an additional location to look for libraries.

Optional Functionality

So now you've got a bead on a matching set of library and header files, but depending on what version of that library is installed, you may want to include or exclude additional functionality. Because these kinds of version changes often involve the introduction or removal of a particular procedure entry point, you can reuse the PHP_CHECK_LIBRARY() macro you just used to get a finer grain read on the library's capabilities.

PHP_CHECK_LIBRARY(z, gzgets,[ AC_DEFINE(HAVE_ZLIB_GETS,1,[Having gzgets indicates zlib >= 1.0.9]) ],[ AC_MSG_WARN([zlib < 1.0.9 installed, gzgets() will not be available]) ],-L$ZLIB_DIR/lib)

 

Testing Actual Behavior

It might not be enough to simply know that a symbol exists and that your code will compile successfully; some libraries have bugs in specific versions that can only be spottedand subsequently worked aroundby running some test code against them.

The AC_TRY_RUN() macro will compile a small source file to an executable program and let it run. Depending on the return code, which is passed up through ./configure, your script can then set optional #define statements or just bail out with a message requesting an upgrade if the bug cannot be worked around. Consider the following excerpt from ext/standard/config.m4:

AC_TRY_RUN([ #include double somefn(double n) { return floor(n*pow(10,2) + 0.5); } int main() { return somefn(0.045)/10.0 != 0.5; } ],[ PHP_ROUND_FUZZ=0.5 AC_MSG_RESULT(yes) ],[ PHP_ROUND_FUZZ=0.50000000001 AC_MSG_RESULT(no) ],[ PHP_ROUND_FUZZ=0.50000000001 AC_MSG_RESULT(cross compile) ]) AC_DEFINE_UNQUOTED(PHP_ROUND_FUZZ, $PHP_ROUND_FUZZ, [Is double precision imprecise?])

As you can see, the first parameter to AC_TRY_RUN() is a block of literal C code that will be compiled and executed. If the exit code of this block is zero, the autoconf script located in the second parameter will be executed, in this case indicating that round() functions as expected and splits on precisely 0.5.

If the code block returns a nonzero value, the autoconf script located in the third parameter will be executed instead. The fourth and final parameter is a default used when PHP is being cross-compiled. In this case, any attempts to run sample code will be pointless because the target platform is different from the platform on which the extension will be compiled.

Категории