尧图精选

Autoconf自动化生成Makefile:从configure.ac到跨平台构建实战

🕒 发布时间:2026/10/2 3:33:34 📁 来源:尧图网络
我以前刚接触Linux下C/C项目的时候被Makefile折腾得够呛。明明就是几个源文件编译链接写出来的Makefile却总是顾此失彼换台机器路径不对了加个编译选项全局要改链接库多两个依赖就绕不清。直到认真啃了一遍Autoconf才明白真正规范的工程应该把Makefile的生成交给工具去处理而我们要维护的只是一份描述项目需求的配置脚本。这篇文章就围绕Autoconf自动生成Makefile这条主线把整个工具链的来龙去脉、配置脚本语法、实战案例和排坑经验完整串一遍给正准备为项目引入自动构建体系的同学提供一个可以直接抄作业的参考。1. 为什么需要Autoconf从手写Makefile到自动生成的演进任何工具的存在都是为了解决某个具体的痛点Autoconf也不例外。要理解它先得知道在没有它的时候一个C项目想把构建脚本写到跨平台可用有多难。1.1 手写Makefile的坑到底有多深很多初学者是从一个Makefile打天下的路子走过来的。单个目录、两三个.c文件、没有外部依赖这种场景下手写Makefile确实够用甚至一两行就能写完hello: hello.c gcc -o hello hello.c但项目一旦扩大事情就立刻变味。首先是跨平台问题Linux下的编译器是gcc但*BSD系统可能是clangSolaris下又要考虑cc有些系统的printf需要额外的链接选项有些系统缺少某个头文件。就算只是从Ubuntu搬到CentOS编译器的默认选项、库的搜索路径都可能不同。这还没算上交叉编译、静态链接、共享库的版本管理这些进阶需求。我最早维护的一个内部工具就是因为在Makefile里硬编码了开发机的头文件路径和库路径导致每次有新同事加入、换一台机器编译都要手动改一遍Makefile才能跑起来。改来改去Makefile里充满了ifdef加平台判断可读性越来越差到最后没人敢碰。这正是手写Makefile最难处理的问题把平台差异、依赖探测、参数适配这些逻辑全部揉进了构建规则里而构建规则本身应该是相对稳定的。1.2 configure脚本带来的思路转变后来读源码包的时候注意到很多知名项目比如核心的gnu工具链、nginx、甚至linux内核的某些子系统都采用同一个发布模式发出来的是一个以configure为核心的文件集用户在拿到源码后先执行 ./configure再执行 make构建就完成了。这个过程之所以好用在于它把平台探测从构建规则里分离出来了。configure脚本的核心任务是在编译之前做一轮体检检查编译器是否存在、检查头文件在不在、检查库函数有没有、确认某些特性是否支持然后把这些检查结果固化下来生成适合当前平台的Makefile。Makefile本身不需要写一堆平台判断它只用关心基于configure告诉我的事实怎么把代码编译出来。这件事的价值是革命性的源码作者只维护一份描述项目需要什么的配置文件而不用为每个平台维护不同的Makefile。用户拿到的Makefile是根据当前环境动态生成的正好贴合这台机器的情况。Autoconf扮演的就是configure脚本的生成器这一角色。1.3 Autotools工具链完整的拼图这里需要把Autotools家族的成员关系说清楚否则很多初学者会混淆。Autoconf本身只负责生成configure脚本但光有configure还不够因为config.status把configure.ac里的配置展开成Makefile时还需要一个模板。这个模板一般不用手写而是由Automake根据Makefile.am生成Makefile.in。所以一个完整的GNU构建体系是这样的configure.ac源码作者维护的输入文件用m4宏语言描述项目需求。autoconf读取configure.ac生成configure脚本。Makefile.am源码作者维护的输入文件用Automake语法描述要构建哪些目标。automake读取Makefile.am和configure.ac中的信息生成Makefile.in模板。configure运行时对系统进行探测根据探测结果把Makefile.in展开成Makefile。config.hconfigure生成的头文件里面定义了各种HAVE_XXX宏供源码文件包含。此外还有libtool专门处理共享库和静态库的构建pkg-config虽然没有被集成在Autotools里但它提供的.pc文件和PKG_CHECK_MODULES宏在查找第三方库时是事实标准。我用一张简单的流程顺序来总结就是autoconf automake把开发期的描述文件编译成发布期的configure和相关模板用户拿到发布包后configure根据当前系统信息最终生成构建真正需要的Makefile和config.h。1.4 为什么说这套体系至今没有过时有人可能会说现在CMake更流行为什么还要学Autoconf这个说法有道理但Autoconf的意义依然扎实。一方面GNU系的大量历史项目tar、grep、coreutils、甚至很多嵌入式的交叉编译工具链至今仍以Autoconf为主要参与这些项目的开发、移植、交叉编译看不懂configure.ac就是寸步难行。另一方面Autoconf的设计思想也就是把环境探测与构建规则解耦本身也是CMake、Meson这些新一代构建系统想要解决的议题。理解了Autoconf再去看CMake的CMakeLists里那些find_package、check_include_file其实思路是相通的。很多嵌入式方向的工作岗位面试题里明确要求解释configure脚本的作用或者给出一个简单的autotools工程让候选人补全Makefile.am。这至少说明一点Autoconf依然是Linux下C/C工程构建的基本功绕不开。2. configure.ac脚本的核心语法与宏详解如果你打开过某个开源项目的configure.ac第一反应大概率是这是什么鬼全是M4宏调用花花绿绿的。别急这些东西说白了就是一堆带参数的模板只要理解了宏的作用分类configure.ac的阅读和编写就顺了。2.1 configure.ac的基本骨架与AC_INIT先看一个最精简的configure.ac长什么样AC_INIT([myproject], [1.0], [bugexample.com]) AC_CONFIG_SRCDIR([src/main.c]) AM_INIT_AUTOMAKE([foreign subdir-objects]) AC_PROG_CC AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT这个文件虽然短但每一个宏都有讲究。AC_INIT是configure.ac的起点它的三个参数分别是项目名、版本号、维护者邮箱。这三个字段会被autoconf写进configure脚本里并在后续的宏调用和生成的Makefile中反复使用。AC_INIT必须放在最前面这是autoconf扫描的硬性条件。AC_CONFIG_SRCDIR的作用是自证身份。它接受一个源文件名作为参数autoconf生成的configure在执行时会先去检查这个文件是否存在。如果用户误在某目录下执行了 configure脚本会立刻报错退出。别小看这个保护我见过有人在源码根目录的子目录里误执行 configure结果生成了一堆错乱的Makefile排错比重新生成还浪费时间。有了这行至少少踩一个坑。AM_INIT_AUTOMAKE是automake的初始化宏。这里的foreign参数代表非严格GNU风格意思是automake不必强制检查AUTHORS、NEWS、ChangeLog等文件个人项目和小团队内部工具建议加这个参数如果有意向开源发布国际标准GNU项目就应该去掉foreign并补齐这些文档文件。subdir-objects是让子目录里的.o文件也输出到对应子目录避免多目录同名的.c文件产生目标文件冲突。2.2 编译器与平台探测宏的选型逻辑工程里最常用的AC_PROG_CC检测的是C编译器。同理还有AC_PROG_CXXC、AC_PROG_F77Fortran等。这些宏做的事情不是简简单单which gcc而是会做一轮编译器的功能测试能不能编译基本程序、支持哪些标准模式、生成的依赖选项是否可用然后把结果变量CC、CFLAGS设置好。这里有个细节值得注意AC_PROG_CC在较新版本的autoconf中会默认尝试编译标准CC99以上而不是老式的KR语法。如果你在维护一个古董代码库需要另一番配置绕开新特性检测。不过这属于极少数情况大多数现代代码直接AC_PROG_CC就对了。当项目同时需要C和C编译器时建议AC_PROG_CC和AC_PROG_CXX都显式调用。仅仅使用了C源文件并不会自动触发C编译器检测因为configure的检查是按需展开的你没写对应的宏它就不知道要探测什么。这是初学Autotools时特别容易掉的坑。探测平台特性用的AC_CANONICAL_HOST宏也很重要它会把当前运行的系统类型host解析出来一般是三元组格式比如x86_64-pc-linux-gnu。做交叉编译时这个三元组就是判断目标平台的核心依据。不过AC_CANONICAL_HOST会触发config.sub和config.guess两个辅助脚本的定位autoconf如果找不到这两个脚本会报错。很多人第一次交叉编译就卡在这里——要么是没安装autotools的配套脚本要么是configure.ac少写了这个宏。2.3 检测头文件、函数和库的宏家族configure脚本最实用的价值就是检测依赖。Autoconf提供了三组最常用的探测宏AC_CHECK_HEADER([foo.h], [action-if-found], [action-if-not-found])检查某个头文件是否存在。AC_CHECK_FUNCS([strdup])检查某个函数定义是否存在。AC_CHECK_LIB([m], [floor])检查某个库里的函数。实际工程里我更推荐使用带复数s的批量版本AC_CHECK_HEADERS、AC_CHECK_FUNCS。它们接受一个空行分隔的列表批量探测后会把结果记录到HAVE_XXX_H或HAVE_XXX类的宏中。例如AC_CHECK_HEADERS([sys/stat.h unistd.h string.h]) AC_CHECK_FUNCS([strdup getline])如果探测通过config.h里就会生成#define HAVE_SYS_STAT_H 1这样的宏定义。源码里就可以这样写#ifdef HAVE_UNISTD_H #include unistd.h #endif这套机制解决的就是同一个源代码在不同系统上包含不同头文件的问题。需要注意的是AC_CHECK_HEADER只检验头文件是否存在并不检验头文件能否独立编译。某些头文件依赖前置头文件的情况需要优先检查前置条件或者使用AC_CHECK_HEADER检查时带上强制包含的选项。这个问题在跨平台时很容易翻车后面章节我会详细展开。2.4 检查第三方库的标准姿势PKG_CHECK_MODULES项目依赖第三方库时如果对方提供pkg-config的.pc文件绝大多数现代库都提供那么强烈建议用PKG_CHECK_MODULES而不是单纯拼库名和头文件路径。PKG_CHECK_MODULES([libcurl], [libcurl 7.58.0], [], [AC_MSG_ERROR([libcurl is required])])这个宏会调用pkg-config查询libcurl项目把编译选项和链接选项分别存到libcurl_CFLAGS和libcurl_LIBS变量中。之后在Makefile.am里这样引用即可myprog_CPPFLAGS $(libcurl_CFLAGS) myprog_LDADD $(libcurl_LIBS)这里有个使用前提就是configure.ac里要提前调用PKG_PROG_PKG_CONFIG。这个宏用来定位pkg-config程序并检查其可用性。虽然某些环境即使不写也能跑通但那属于碰运气不推荐依赖这种运气。2.5 条件编译与可选功能的触发机制大型项目总有一些可选模块开不开某个插件、是否启用debug输出、是否编译示例程序。Autoconf为此提供了AC_ARG_ENABLE和AC_ARG_WITH两个宏。AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [enable debug output])], [enable_debugyes], [enable_debugno])如果用户在configure时传了 --enable-debug那么enable_debug变量会被设置为yes没传则取默认值no。后续配合AM_CONDITIONAL可以在Makefile.am中做条件构建AM_CONDITIONAL([DEBUG_ENABLED], [test x$enable_debug xyes])if DEBUG_ENABLED myprog_CPPFLAGS -DDEBUG_LOG endifAC_ARG_WITH则通常用来指定依赖软件的安装前缀比如--with-ssl/usr/local/ssl。需要注意的是AC_ARG_ENABLE和AC_ARG_WITH本身并不做实际检查它们只是把用户参数解析出来存进变量。真正检查逻辑需要你自己写test判断或者交给后续的AC_CHECK_LIB/AC_CHECK_HEADER去用这也是很多人写出来的configure脚本没反应的原因——光有解析宏没有后续消费。2.6 AC_CONFIG_HEADERS与生成文件的完整配置前面几次提到了AC_CONFIG_HEADERS和AC_CONFIG_FILES这里集中说明一下。AC_CONFIG_HEADERS([config.h])会指示configure在配置阶段生成config.h文件。生成的过程其实不是configure直接写文件而是configure调用config.status由config.status从config.h.in模板中替换得到config.h。config.h.in则由autoheader工具根据configure.ac里的AC_CHECK_HEADERS等宏生成。AC_CONFIG_FILES([Makefile src/Makefile])则决定哪些Makefile.in会被展开成Makefile。automake执行时会根据Makefile.am生成对应的Makefile.in。如果目录里没有做出对应的Makefile.amautomake就会报错因为它不知道从哪生成Makefile.in。最后用AC_OUTPUT做收尾。这个宏是生成流程的触发点它会要求config.status执行所有已经登记的文件生成操作。有些项目用AC_CONFIG_FILES之后不写AC_OUTPUT然后发现configure跑到最后不生成Makefile排查了半天才想起来AC_OUTPUT没写。这个倒不是最坑的更隐蔽的问题是AC_OUTPUT写在了一些条件分支里某些系统配置下根本不执行导致构建产物缺失。我的建议是AC_OUTPUT永远放在configure.ac的最后一行简单粗暴不搞条件。3. 从configure.ac到Makefile的完整实操一个最小可复制的项目前面讲了很多宏的原理这一章我们用一个小项目把Autotools完整跑一遍直接把每个命令、每个生成产物展示出来。这个例子的代码量不大但覆盖了C语言项目最典型的构建场景一个主程序、一个静态库、一个头文件。3.1 项目结构与源文件准备先在临时目录创建项目骨架mkdir -p demo/src demo/include cd demo源码结构如下demo/ ├── configure.ac ├── Makefile.am ├── src/ │ ├── Makefile.am │ ├── main.c │ └── log.c └── include/ └── log.hlog.h的内容#ifndef LOG_H #define LOG_H void log_message(const char *msg); #endiflog.c的内容#include stdio.h #include log.h void log_message(const char *msg) { printf([LOG] %s\n, msg); }main.c的内容#include stdio.h #include log.h int main(void) { log_message(autotools demo); return 0; }这个项目编译后应该生成一个名为demo的可执行文件。头文件放在include目录源文件放在src目录库目录先不做等演示完基本流程再扩展。3.2 编写configure.ac与Makefile.amconfigure.ac写为AC_INIT([demo], [1.0], [devexample.com]) AC_CONFIG_SRCDIR([src/main.c]) AM_INIT_AUTOMAKE([foreign subdir-objects]) AC_PROG_CC AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT这里只检测了C编译器没有额外的依赖检查够用就行。注意AC_CONFIG_HEADERS生成的是根目录的config.hsrc目录下的源文件要包含它需要用相对路径或-I假设的路径。Autotools的惯例是configure会自动给编译规则加-I$(top_builddir)所以src/main.c里可以直接#include config.h。为了让Automake知道每个子目录的存在Automake要求每个包含Makefile的地方都有一个Makefile.am所以还有根目录Makefile.amSUBDIRS src以及src/Makefile.ambin_PROGRAMS demo demo_SOURCES main.c log.c demo_CPPFLAGS -I$(top_srcdir)/include demo_LDADD $(top_builddir)/src/liblog.a等一下LDADD引用的是还没建出来的liblog.a。为了让项目结构更清晰我把log.c单独编成静态库这样main.c只依赖库即可。调整后的src/Makefile.ambin_PROGRAMS demo demo_SOURCES main.c demo_CPPFLAGS -I$(top_srcdir)/include demo_LDADD liblog.a noinst_LIBRARIES liblog.a liblog_a_SOURCES log.c liblog_a_CPPFLAGS -I$(top_srcdir)/include关键点说明bin_PROGRAMS声明了要构建的可执行目标Automake会为每个程序生成对应的变量规则。demo_SOURCES列出该程序的所有源文件。Automake自动推导头文件依赖但这里为了简单没有在SOURCES里列头文件Automake也支持把头文件写进去更利于make dist打包。noinst_LIBRARIES声明一个不安装到系统目录的静态库。它的命名规范要求下划线比如liblog_a就是liblog.a对应的Automake变量前置模板。这一点非常容易记错——Automake把库名里的点号替换成下划线来构造SOURCES变量名如果你写liblog.a的SOURCES变量为liblog_a_SOURCES那Automake会报错找不到源文件定义。top_srcdir和top_builddir是两个Automake内置变量分别指向源码根目录和构建根目录。当在源码目录内编译时两者相同如果是out-of-source构建在另一个目录运行configuretop_builddir就是你执行configure的目录。用变量而不是硬编码路径是保证构建系统可移植的关键。还要注意root目录的Makefile.am里SUBDIRS src这样make时才会进入src目录构建demo。如果不写SUBDIRSautomake会认定根目录就是唯一目录src目录里的内容根本不会被构建。3.3 执行autoreconf并解释每一条命令的产物传统流程是先调用aclocal、autoheader、autoconf、automake但实际工程中推荐直接用autoreconf一条命令搞定cd demo autoreconf -ivf这条命令会依次执行aclocal、autoheader、autoconf、automake。参数含义-i安装项目缺失的辅助文件比如compile、config.guess、config.sub、install-sh。-v输出详细信息方便观察每一步执行了什么。-f强制重新生成所有文件哪怕时间戳看起来没有过期。执行完之后当前目录会多出这些文件configure Makefile.in src/Makefile.in config.h.in aclocal.m4 autom4te.cache/ compile config.guess config.sub depcomp install-sh missing逐个说明一下aclocal.m4存放autoconf宏展开时需要的额外m4宏定义。如果你用了PKG_CHECK_MODULESaclocal会尝试把pkg.m4纳入进来。configure最终的用户可执行脚本。Makefile.in和src/Makefile.inautomake根据Makefile.am生成的模板。in文件里有很多VAR占位符包括CC、CFLAGS等这些占位符在configure运行时会被替换成检测到的实际值。config.h.inautoheader生成的头文件模板。源码里包含config.h时configure会本着模板生成最终版config.h。autom4te.cacheautoconf内部的缓存目录可以删但留着能加速二次预编译。那几个辅助脚本是automake --add-missing自动复制过来的。如果autoreconf执行时提示缺少Makefile.in通常是你没有在AC_CONFIG_FILES里加上对应的Makefile或者该目录缺少Makefile.am。检查一下这两处基本能解决。3.4 运行configure并查看生成结果接着运行./configure这个输出一般会走半天核心信息包括checking for gcc... gcc checking whether the C compiler works... yes checking for C compiler default output file name... a.out checking for suffix of executables... checking for suffix of object files... o checking whether we are using the GNU C compiler... yes ... configure: creating ./config.status config.status: creating Makefile config.status: creating src/Makefile config.status: creating config.h最后三行意味着Makefile和config.h已经成功生成。打开config.h看一下/* config.h. Generated from config.h.in by configure. */ #define HAVE_DECL_STRDUP 1 #define HAVE_STRING_H 1这些宏就是configure对你当前系统状态的现场记录。将来代码里要判断某些特性时直接查config.h就好。这也是为什么源码开发时要把config.h.in纳入版本控制而实际生成的config.h不要提交。然后执行makemake看到编译输出Making all in src make[1]: Entering directory /home/user/demo/src gcc -DHAVE_CONFIG_H -I. -I.. -I../include -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c main.c -o main.o gcc -DHAVE_CONFIG_H -I. -I.. -I../include -g -O2 -MT log.o -MD -MP -MF .deps/log.Tpo -c log.c -o log.o ar cru liblog.a log.o ranlib liblog.a gcc -g -O2 -o demo main.o liblog.a这里可以看到Automake自动加了-g -O2默认编译选项还生成了.deps目录做依赖跟踪。如果修改了log.hmake会自动重新编译依赖它的文件这个能力是Automake内置的不需要手动在Makefile里写-MMD之类的规则。运行一下./demo [LOG] autotools demo基本流程就通了。3.5 make install和make dist的扩展用法构建体系不只要能编译还得能安装、能打包。Autotools把这几个目标全内置好了。make install默认把demo安装到当前prefix的bin目录。prefix默认为/usr/local如果想装到别的目录configure时加--prefix即可./configure --prefix/opt/myapp make make installAutomake还会自动生成make dist和make distcheck。make dist会把所有源码文件打包成demo-1.0.tar.gzdistcheck则在干净目录里重新构建一遍已验证包可编译。这个target是发布工程的必备流程能在打包时暴露少了某个文件没一起发出去这类低级失误。make dist ls demo-1.0.tar.gz对这个demo项目来说Makefile.am里没列头文件也不影响编译但make dist的默认文件清单由Makefile.am的SOURCES决定。我建议把include/log.h写进liblog_a_SOURCES里这样dist包会带上它能确保用户拿到源码包后可以正常编译。4. 常见问题与排查技巧实录讲了半天成功路径实际工程里踩的坑往往比教程多。这里记录几类我经常遇到的问题按频率从高到低排列。4.1 autoreconf找不到宏或宏展开报错你有没有遇到过这种提示configure.ac:5: warning: macro AM_INIT_AUTOMAKE not found in library或者aclocal: warning: couldnt find macro PKG_CHECK_MODULES先说崩溃背后的机制autoreconf执行aclocal时aclocal会扫描configure.ac里的宏调用去系统的aclocal目录以及包含在ACLOCAL_PATH中的目录里找这些宏的m4定义。如果找不到宏调用就会以一个未被展开的状态留给后续的autoconf最终导致错误。有一个非常常见的元凶是meta包未安装。在Debian/Ubuntu系里跑autotools的工具链需要三个包autoconf、automake、libtool。这三个包装全了基本宏不会缺。PKG_CHECK_MODULES所在宏文件pkg.m4通常由pkg-config包提供但有些发行版把宏文件放在pkg-config的独立包里需要用ACLOCAL_PATH把pkg.m4的目录指出来或者直接把pkg.m4复制到项目的m4目录并让ACLOCAL能扫到。在configure.ac里加入如下内容才是正规做法m4_ifndef([PKG_CHECK_MODULES], [ m4_fatal([pkg-config macros not found. Install pkg-config and re-run autoreconf]) ])这样一旦宏缺失autoreconf就能直接报出明确错误而不是让你在几百行m4报错后靠猜。4.2 只改了Makefile.am却忘了重新生成Makefile.in这个坑出现频率极高而且隐蔽。比如在src/Makefile.am里新增了一个源文件然后直接make发现Automake完全没有识别新文件。原因是Makefile.in还是旧版本configure把它展开成的Makefile里依旧没有新文件的规则。当Makefile.am或configure.ac发生变化时必须重新跑autoreconf或至少automake再重新运行configure。在大型项目里很多人习惯用一条命令刷新全部autoreconf -ivf ./configure make我自己因为忘跑这步遇到过多次改了源文件没反应的诡异问题最后发现都是Makefile.in没更新。建议在新手阶段每次同步执行这三步等熟练了再考虑增量刷新。4.3 跨平台的头文件检测假阳性AC_CHECK_HEADER([stdint.h])在很多Unix系统上没问题但如果你在Windows的MSVC环境里用Autotools比如通过MSYS2或者在某些嵌入式系统的libc里stdint.h可能存在于系统目录但是不能独立编译或者需要先包含另一个头文件。AC_CHECK_HEADER默认做法只是#include该文件如果依赖其他宏未定义它可能会编译失败返回找不到导致代码里没定义HAVE_STDINT_H。应对策略是分两步先用AC_CHECK_HEADERS检查基础的头文件体系比如sys/types.h、stddef.h再检查具体功能头文件必要时用AC_CHECK_HEADER的第四个参数指定包含前提。AC_CHECK_HEADER([linux/input.h], [], [], [#include sys/types.h ])这能大幅减少假阴性。做嵌入式Linux开发时目标平台的头文件经常依赖顺序这个点在目标板上反复configure不如在configure.ac里把前提声明清楚省事得多。4.4 交叉编译时configure把宿主环境当成目标环境做交叉编译时最常见的错误之一是configure在编译宿主机上执行的所有探测却错误地把宿主机信息当作目标平台信息。比如用x86_64机器为ARM开发板编译程序configure检测到编译器是arm-linux-gnueabihf-gcc按理没问题但如果你没有在configure前正确设置hostAC_CANONICAL_HOST会给出错误的值find-library检测会找宿主机的库而不是目标板的库。规范做法是./configure --hostarm-linux-gnueabihf --prefix/opt/arm-rootfs/usr同时要确保PKG_CONFIG_LIBDIR指向目标板的.pc文件目录否则pkg-config会把宿主机库路径塞进CFLAGS/LIBS里。这个细节我踩过不少次交叉编译环境里光改PATH是不够的PKG_CONFIG_PATH、CPPFLAGS、LDFLAGS全都要重设。如果项目里有可执行工具需要在构建过程运行还涉及build与host的区隔一般用AC_CANONICAL_BUILD和AC_CANONICAL_HOST两个宏同时定义。Autotools对三段式构建build/host/target的支持是完整成熟的但前提是configure.ac不能漏写AC_CANONICAL_HOST。4.5 版本太老或太新的兼容问题autoconf 2.69是很多老项目的基底版本而2.71之后某些宏的提示方式变了。比如AC_PROG_CC在2.70里默认开启C99在老版本里需要显式AC_PROG_CC_C99。如果你的代码是基于老版本autoconf写的在新环境跑autoreconf可能会爆出更多warning但多数不影响生成。反过来当你拿着新版本autoconf生成configure发布到老系统的用户手里时老系统可能无法执行。因为configure本质是个shell脚本但它依赖特定版本的autoconf生成辅助脚本和shell特性。如果要让用户不用安装autotools就能configure最好在发布前用autoreconf生成完整的configure把configure脚本提交到发布包里而不是发一堆.ac和.am让别人自己生成。这也是GNU项目发布源码包一贯做法包里直接带configure和Makefile.in用户只需有make和shell即可。4.6 调试Configure脚本的小技巧很多configure脚本被包裹得严严实实出错信息却含含糊糊。排查configure问题有个传统技巧看生成的config.log。config.log是configure的日志文件记录了每一次编译测试的完整命令和输出。如果某个检测失败config.log里往往直接给了原因比如undefined reference to floor、链接器找不到-lm等等。另一个技巧是给configure传环境变量。AC_PROG_CC默认会找gcc但如果你想用clang不用改configure.ac直接CCclang ./configure运行时再决定编译器是configure脚本的固有设计。交叉编译时也是通过CC和AR这些变量来覆盖默认值。不过要注意设置了CCclang时configure可能仍然因为AC_PROG_CC里的cache变量残留而继续使用gcc。遇到这种问题删除config.cache文件或使用新构建目录即可。5. 进阶心得把Autoconf当构建文档来维护前面已经覆盖了完整的流程和排错这一章聊点我个人的使用心得。Autoconf体系学习曲线陡峭但摸透之后维护起来非常顺。5.1 configure.ac当作项目需求的活文档我认为configure.ac的价值不只是生成构建脚本它更是一份机器可读的项目需求说明书这个项目叫什么、版本多少需要什么编译器、检查哪些头文件、链接哪些库、提供了哪些可选功能。新人接手项目与其读一堆文档不如先读configure.ac往往几分钟就能知道这个项目的依赖边界。所以写configure.ac时我会养成给它添加清晰注释的习惯。比如# Check for libssl, required for HTTPS support PKG_CHECK_MODULES([libssl], [openssl 1.1.0])这样autoreconf能跑人也能读。Autotools虽然本身是一种元构建工具但调试时读configure.ac比读生成的configure快太多倍。5.2 组合使用pkg-config与自己探测现在很多新库都提供.pc文件用pkg-config是一件顺理成章的事。但老库、内部私有库未必有。我的经验是优先尝试PKG_CHECK_MODULES如果库没有.pc文件就用AC_PATH_PROG定位库配置程序或者用AC_CHECK_LIB配合手动传入的CFLAGS/LIBS。两种方案结合起来既现代又兼容老环境。另外LAAS的用户还遇到过一个坑库有.pc文件但.pc文件里把CFLAGS写成了-I/usr/include这在64位环境没问题而在某些多版本库共存机器上会把错误版本的头文件暴露出来。此时可以在configure.ac里通过条件判断过滤掉指定版本的include路径。这种边缘问题极少了解即可。5.3 不要手写Makefile但也不要完全丢掉Makefile知识Autotools简化了Makefile的编写但它生成出来的仍是Makefile。当make编译报错或者你想给某个目标加一条自定义规则时还是需要具备直接读Makefile的能力。Automake提供了很多便捷的target比如clean-local、install-data-local允许你在Generated Makefile框架里插入自定义规则。这些man也不难核心是localselfclean-local属于在clean时追加动作install-data-local会在安装数据阶段执行你的额外命令。用得好能处理不少自动化需求。我的建议是花一天掌握常用Autotools宏再用一天跑通一个小项目之后遇到问题去查GNU Automake手册。这比一开始就去啃几百页宏列表高效得多。6. 写在最后的经验小结这个demo项目虽然小但是一套完整的Autotools构建体系从无到有的过程已经全在里面了configure.ac写清需求Makefile.am声明构建目标autoreconf一键生成所有胶水文件configure完成运行时探测最终make得到产物。这一路走过来最深刻的体会有三点。第一构建体系是工程资产不是临时脚本值得花时间维护写清楚configure.ac项目以后的每一次跨平台移植、依赖升级、新同事上手都会省下大量沟通和排查成本。第二Autotools报错信息虽然晦涩但好在它留有config.log这个黑匣子绝大多数问题都能从里面找到线索关键是别慌一查到底。第三也是最重要的一点Autoconf的设计思想即把环境探测与构建规则分离是无论用什么构建系统都值得遵循的。即使是CMake项目也依然要面对这个库在不在、这个编译器支持什么标准、这个系统上有没有某个头文件这些问题。理解了Autoconf你对构建系统的理解就上了一个台阶再去学任何新工具都会快很多。如果以后要在自己的项目里引入自动构建建议直接从这个最小demo开始先跑通再逐步加功能。构建系统这种东西落到实处远比看完几百页文档有用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →