15、Autotools M4宏:AC_ARG_ENABLE/AC_ARG_WITH、AC_CHECK_HEADER/AC_CHECK_LIB、AC_SUBST/AC_DEFINE、自定义M4宏
聊到Autotools,很多人第一反应是「又老又复杂」。说实话,我当年刚接触时也是这个感觉。但用久了你会发现,它那套M4宏体系,其实是一套非常精巧的配置描述语言。今天我们就来拆解几个最核心的宏,看看它们到底在干什么。
15.1 AC_ARG_ENABLE 与 AC_ARG_WITH:给用户选择权
这两个宏,说白了就是给你的configure脚本添加命令行选项。AC_ARG_WITH用来指定外部依赖的路径,AC_ARG_ENABLE用来开关某个特性。
举个例子,你写了一个库,想支持可选的SSL加密:
AC_ARG_WITH([ssl],
[AS_HELP_STRING([--with-ssl=DIR],
[指定OpenSSL安装目录])],
[ssl_dir=$withval],
[ssl_dir=""])
用户编译时就可以这样:./configure --with-ssl=/usr/local/ssl。我在项目中遇到过好几次,用户把OpenSSL装到了奇怪的地方,没有这个选项他们就得改Makefile,那叫一个痛苦。
再看AC_ARG_ENABLE:
AC_ARG_ENABLE([debug],
[AS_HELP_STRING([--enable-debug],
[启用调试日志])],
[enable_debug=$enableval],
[enable_debug=no])
嗯,这里要注意:$enableval的值是用户传进来的字符串,通常是yes或no。我个人习惯在后续代码里统一用test "x$enable_debug" = xyes来判断,这样最安全,不会因为变量为空而出错。
15.2 AC_CHECK_HEADER 与 AC_CHECK_LIB:自动探测依赖
这两个宏是Autotools的「侦察兵」。它们帮你检查系统上有没有某个头文件或库,然后设置相应的变量。
先看头文件检查:
AC_CHECK_HEADER([zlib.h], [],
[AC_MSG_ERROR([找不到zlib.h,请安装zlib开发包])])
如果找不到,直接报错退出。但有时候头文件是可选的,你可以这样:
AC_CHECK_HEADER([readline/readline.h],
[have_readline=yes],
[have_readline=no])
库的检查类似:
AC_CHECK_LIB([pthread], [pthread_create], [],
[AC_MSG_ERROR([需要pthread库])])
这里有个细节:第二个参数是函数名,不是库名。你想想看,系统上可能有多个版本的库,但函数名是唯一的标识。我刚开始写时总搞混,把库名当函数名传进去,结果检查永远失败。
15.3 AC_SUBST 与 AC_DEFINE:传递配置结果
这两个宏负责把探测结果「传递」出去。AC_SUBST把变量传给Makefile,AC_DEFINE把定义传给C代码。
AC_SUBST的用法很直接:
AC_SUBST([SSL_LIBS])
AC_SUBST([SSL_CFLAGS])
然后在Makefile.am里用@SSL_LIBS@和@SSL_CFLAGS@引用。说白了,它就是Autotools版的「模板变量替换」。
AC_DEFINE则更底层一些:
AC_DEFINE([HAVE_OPENSSL], [1], [是否支持OpenSSL])
这会在生成的config.h里产生一行:#define HAVE_OPENSSL 1。你的C代码就可以用#ifdef HAVE_OPENSSL来做条件编译。
我个人习惯把AC_DEFINE和前面的检查宏配合使用:
AC_CHECK_HEADER([openssl/ssl.h], [
AC_CHECK_LIB([ssl], [SSL_new], [
AC_DEFINE([HAVE_OPENSSL], [1], [启用OpenSSL支持])
SSL_LIBS="-lssl -lcrypto"
])
])
AC_SUBST([SSL_LIBS])
这样一套组合拳下来,既能自动探测,又能把结果传递给构建系统。我在做一个跨平台项目时,这套模式帮我省了大量手工配置的时间。
15.4 自定义M4宏:封装你的配置逻辑
当你发现同样的检查逻辑在多个项目里重复出现时,就该考虑自定义M4宏了。说白了,就是把一组AC_*宏打包成一个新宏。
假设你经常需要检查某个库,可以这样写:
AC_DEFUN([AX_CHECK_SSL], [
AC_ARG_WITH([ssl],
[AS_HELP_STRING([--with-ssl=DIR], [OpenSSL路径])],
[ssl_dir=$withval],
[ssl_dir="/usr"])
save_CFLAGS=$CFLAGS
save_LIBS=$LIBS
CFLAGS="$CFLAGS -I$ssl_dir/include"
LIBS="$LIBS -L$ssl_dir/lib"
AC_CHECK_HEADER([openssl/ssl.h], [
AC_CHECK_LIB([ssl], [SSL_new], [
AC_DEFINE([HAVE_OPENSSL], [1], [启用SSL])
SSL_LIBS="-L$ssl_dir/lib -lssl -lcrypto"
SSL_CFLAGS="-I$ssl_dir/include"
], [
AC_MSG_ERROR([找不到libssl])
])
], [
AC_MSG_ERROR([找不到openssl/ssl.h])
])
CFLAGS=$save_CFLAGS
LIBS=$save_LIBS
AC_SUBST([SSL_LIBS])
AC_SUBST([SSL_CFLAGS])
])
然后在configure.ac里直接调用:AX_CHECK_SSL。注意我用了save_CFLAGS和save_LIBS来保存和恢复环境变量——这是一个好习惯,避免宏的副作用影响到后续检查。
自定义宏的文件通常放在m4/目录下,命名遵循ax_*.m4的惯例。你可以在configure.ac里用AC_CONFIG_MACRO_DIR([m4])告诉Autotools去哪里找。
ax_check_ssl_xxx而不是简单的xxx。否则很容易和用户项目的变量冲突,调试起来非常痛苦。我曾经因为这个bug查了整整一个下午。
知识体系总览
下面这张图总结了本章的核心逻辑:
从这张图可以看得很清楚:Autotools的M4宏体系本质上是一个三层架构。最上层是用户交互和自动探测,中间层是逻辑处理,最下层是结果输出。每一层都有对应的宏来完成工作。
说实话,这套设计虽然老,但思路非常清晰。它把「配置」这件事从代码里剥离出来,变成了一套可组合、可复用的宏语言。你想想看,如果没有Autotools,你要手动写一堆shell脚本来做同样的事,那维护成本得多高。
好了,这一章的内容就到这里。记住:AC_ARG_ENABLE/AC_ARG_WITH是给用户看的,AC_CHECK_HEADER/AC_CHECK_LIB是给系统看的,AC_SUBST/AC_DEFINE是给构建和代码看的。把这几个角色分清楚,你就能驾驭Autotools的配置体系了。
公众号:蓝海资料掘金营,微信deep3321