当前位置: 首页 > 科技观察

从用户和开发者的角度详细讲解如何创建GNU兼容的构建系统

时间:2023-03-13 17:16:21 科技观察

经常使用Linux的开发者或运维人员可能对configure->make->makeinstall相当熟悉。实际上,这就是所谓的GNU构建系统,它使用脚本和make程序在特定平台上构建软件。这种方法已经成为一种习惯,被广泛使用。本文从用户和开发者的角度,详细解释了这种构建方式的细节,以及开发者如何使用autoconf和automake(autotools)等工具来创建兼容GNU构建系统的项目。为了简化便携构建的难度,早期有一套autotools工具来帮助程序员构建软件。我们熟悉的configure->make->makeinstall三部曲大部分都是基于autotools构建的。autotools是GNU程序的标准构建系统,所以我们实际上经常使用trilogy。有些程序虽然也是三部曲,但不是用autotools实现的。比如nginx的源码就是作者自己写的构建程序。从用户的角度,用户可以通过configure->make->makeinstall来安装基于源代码的软件。但是,大多数用户可能不知道这个过程是做什么的。配置脚本是由软件开发人员维护并分发给用户的shell脚本。这个脚本的作用是检测系统环境,最终目的是生成Makefile和config.h。make通过读取Makefile开始构建软件。而makeinstall可以将软件安装到需要安装的位置。如上图所示,开发者在分发源代码包时,除了源代码(.c.h...)外,还有很多用于支持软件构建的文件和工具。最重要的文件是Makefile.in和config.h.in。configure脚本执行成功后,会将每个*.in文件处理成对应的非*.in文件。大多数情况下,只生成Makefile和config.h,因为Makefile是make程序用来识别和构建软件的,而config.h中定义的宏帮助软件通过预编译改变自己的代码以适应某些目标平台。特殊性。有些软件在configure阶段还可以生成其他文件,这完全取决于软件本身。configure运行configure时,您将看到类似于以下的系统检查。这些检查的多少取决于软件本身的需要,即由软件开发人员定义和编写。检查BSD兼容安装.../usr/bin/install-ccheckingwhetherbuildenvironmentissane...yescheckingforathread-safemkdir-p.../bin/mkdir-pcheckingforgawk...gawkcheckingwhethermakesets$(MAKE)...yescheckingforgcc...gcccheckingforCcompilerdefaultoutputfilename...a.out...一般来说,configure主要检查当前目标平台的程序、库、头文件、函数等的兼容性。这些检查结果将作用于config.h和Makefile文件的生成。从而影响最终的编译。用户还可以通过configure配置参数,自定义软件需要包含或不包含的组件行为、安装路径等。这些参数分为5组,可以通过执行./configure--help查看,软件提供了哪些配置参数:*安装路径相关配置。最常见的是--prefix。*程序名称配置。例如--program-suffix可以用来为生成的程序添加后缀。*跨平台编译。不是很常用。*动态库静态库选项。用于控制是否生成某种类型的库文件。程序组件选项。用于配置程序是否将某个函数编译到程序中,一般为--with-xxx的形式。这可能是最常见的配置,由软件开发人员定义。(*表示这是几乎所有软件都支持的配置,因为默认情况下autotool生成的configure脚本都支持这些配置。)configure在执行过程中,除了生成Makefile外,还会生成包括但不包括在内的文件限于:配置。log日志文件config.cache被缓存,以提高下次configure的速度。生成config.status需要用-C指定。实际调用编译工具构建软件的shell脚本。如果软件是通过libtool构建的,也会生成一个libtool脚本。如何生成libtool脚本,请看开发者视角。configure经常会中途失败,这通常是因为当前平台没有构建软件所必需的依赖(库、函数、头文件、程序……)。此时,不要惊慌,仔细查看输出,并解决这些依赖关系。开发人员的视角除了编写软件本身的代码外,开发人员还负责生成构建软件所需的文件和工具。当我接触到autotools时,我发现尽管有了工具的帮助,这件事还是很复杂。对于C或C++程序员来说,构建跨平台应用程序在早期是相当繁琐的,对于没有经验的程序员来说甚至是困难的。因为构建可移植程序的必要前提是对各个平台有足够的了解,而这往往需要长时间的积累。Unix系统的分支复杂度非常高,不同的商业版本或开源版本或多或少会有不同。这些差异主要体现在:系统组件、系统调用。我们主要把Unix分为以下几类:IBM-AIXHP-UXApple-DARWINSolarisLinuxFreeBSD。Unix分支百科全书因此,对于开发者来说,不得不自己编写脚本进行构建,这往往需要极其扎实的shell能力和平台熟悉度。另一种选择是部分依赖工具。autoconf和automake就是这样的工具。为了让autoreconf生成configure脚本和Makefile.in文件,开发者需要创建和维护一个configure.ac文件(早期一般叫configure.in文件,虽然没有区别,但是强烈推荐使用.ac,因为.in文件往往意味着configure脚本将其识别为模板文件并生成直接参与最终构建的文件,configure.in在命名上有歧义),以及一系列的Makefile。是。autoreconf程序可以按合理顺序自动调用autoconfautomakeaclocal等程序。configure.acconfigure.ac用于生成配置脚本。autoconf工具用于执行此操作。下面是configure.ac的例子:AC_PREREQ([2.63])AC_INIT([st],[1.0],[zhoupingtkbjb@163.com])AC_CONFIG_SRCDIR([src/main.c])AC_CONFIG_HEADERS([src/config.h])AM_INIT_AUTOMAKE([foreign])#Checksforprograms.AC_PROG_CCAC_PROG_LIBTOOL#Checksforlibraries.#Checksforheaderfiles.#Checksfortypedefs,structures,andcompilercharacteristics.#Checksforlibraryfunctions.AC_CONFIG_FILES([Makefilesrc/Makefilesrc/a/Makefilesrc/b/Makefile])AC_OUTPUTAC_全部开头看起来像是函数调用的代码,其实是一些叫做“宏”的调用。这里的宏类似于C中的宏概念,会被替换和扩展。m4是一个经典的宏工具,而autoconf是建立在m4之上的。可以理解为autoconf预先实现了大量用于检测系统可移植性的宏。这些宏是大量扩展后的shell脚本。因此,编写configure.ac需要熟练掌握这些宏并合理调用。有时,您甚至可以自己实现自己的宏。autoscan和configure.scan可以通过调用autoscan命令得到一个初始化的configure.scan文件,然后重命名为configure.ac,然后在此基础上编辑configure.ac。autoscan扫描源代码并生成一些常用的宏调用、输入声明和输出声明。autoscan虽然很方便,但是没有人能完全在build之前写好代码,所以通常使用autoscan来初始化configure.ac。autoheader和config.hautoheader命令扫描configure.ac的内容并确定需要如何生成config.h.in。每当configure.ac发生变化时,可以通过再次执行autoheader来更新config.h.in。在configure.ac中通过AC_CONFIG_HEADERS([config.h])告诉autoheader应该生成config.h.in的路径。在实际编译阶段,生成的编译命令会加上-DHAVE_CONFIG_H来定义宏,所以在代码中,我们可以通过下面的代码放心的引用config.h。/bin/sh../../libtool--tag=CC--mode=compilegcc-DHAVE_CONFIG_H...#ifdefHAVE_CONFIG_H#include#endifconfig.h包含大量的宏定义,包括包程序可以直接使用这些宏;更重要的是,程序可以根据与目标平台的可移植性相关的宏,通过条件编译动态调整编译行为。automake和Makfile.am手写一个Makefile是比较麻烦的,如果工程复杂的话,会越来越难写。因此,automake工具应运而生。我们可以像下面这样写Makefile.am,依赖automake生成Makefile.in:.la这里通过SUBDIRS声明了两个子目录,子目录中的构建需要通过a/Makefile.am和b/Makefile.am进行,这样组织多个目录就方便多了。bin_PROGRAMS声明了一个可执行文件target,st_SOURCES指定了这个target所依赖的源代码文件。另外st_LDADD声明了可执行文件链接时需要依赖的libtool库文件。这个Makefile.am文件生成的Makefile.in文件比较大,不方便贴出来,但是可以想象Makefile.in要比我们手写的Makefile文件复杂很多。automake出现的时间晚于autoconf,所以automake是作为autoconf的扩展来实现的。通过在configure.ac中声明AM_INIT_AUTOMAKE告诉autoconf配置和调用automake。上面aclocal提到,configure.ac其实是依赖宏展开来获取configure的。因此能否成功生成取决于能否找到宏定义。autoconf将从它自己的安装路径中查找预定义的宏。然而,对于诸如automake、libtool和gettext之类的第三方扩展宏,甚至是开发人员自己编写的宏,我们一无所知。于是就有了aclocal这个工具,它会在configure.ac的同级目录下生成aclocal.m4,在扫描configure.ac的过程中复制开发者自己编写的第三方扩展和宏定义。这样当autoconf遇到未知的宏时,就会从aclocal.m4开始搜索。下图更详细地显示了整个工具链是如何组合在一起的。libtoollibtool试图解决不同平台上库文件的差异。Libtool实际上是一个shell脚本。在实际工作过程中,调用目标平台的cc编译器和链接器,并给出适当的命令行参数。libtool可以单独使用,这里只介绍与autotools集成使用相关的内容。automake支持libtool构建语句。在Makefile.am中,常见的库文件目标写为xxx_LIBRARIES:noinst_LIBRARIES=liba.aliba_SOURCES=ao1.cao2.cao3.c而对于libtool目标,写为xxx_LTLIBRARIES并以.la为后缀声明库文件。noinst_LTLIBRARIES=liba.laliba_la_SOURCES=ao1.cao2.cao3.c需要在configure.ac中声明LT_INIT:...AM_INIT_AUTOMAKE([foreign])LT_INIT...有时候,如果需要使用libtool中的一些宏,建议将这些宏复制到项目中。首先,使用AC_CONFIG_MACRO_DIR([m4])指定使用m4目录存放第三方宏;然后在最外层的Makefile.am中加入ACLOCAL_AMFLAGS=-Im4。All-in-one在上面讨论了很多关于autoreconf的细节。事实上,现在我们可以直接调用autoreconf--install来自动调用上面提到的所有子命令。这里的--install参数尝试将辅助脚本和宏复制到当前项目目录。以下是执行过程中的输出:autoreconf:Enteringdirectory`.'autoreconf:configure.ac:notusingGettextautoreconf:running:aclocalautoreconf:configure.ac:tracingautoreconf:running:libtoolize--copylibtoolize:puttingauxiliaryfilesin`.'.libtoolize:copyingfile`./ltmain.sh'libtoolize:考虑将`AC_CONFIG_MACRO_DIR([m4])'添加到配置中。/usr/bin/autoheaderautoreconf:running:automake--add-missing--copy--no-forceconfigure.ac:10:installing`./config.猜测'configure.ac:10:installing`./config.sub'configure.ac:9:installing`./install-sh'configure.ac:9:installing`./missing'src/Makefile.am:installing`./depcomp'autoreconf:Leavingdirectory`.'当我们使用--install参数运行时,会调用libtoolize--copy,这将使ltmain.sh被复制进去;然后分别执行autoconf和autoheader;automake的参数是--add-missing--copy--no-force,这将导致将几个辅助脚本和文件安装到目录中。默认情况下,这些辅助文件安装在与configure.ac相同的目录中。如果你想使用另一个目录来存放它们,你可以配置AC_CONFIG_AUX_DIR,比如AC_CONFIG_AUX_DIR([build-aux])将使用build-aux目录存放辅助文件。如果不使用--install参数,辅助文件要么不复制,要么以软链接的形式创建。推荐使用--install,因为这样其他软件维护可以避免构建工具版本不一致导致的问题。辅助文件GNU构建系统开发的软件除了源代码外还有很多辅助文件,有的是脚本文件,有的是文本文件。下面将对这几个文件一一进行说明:aclocal.m4:如前所述,这个宏定义文件包含了autoconf扩展configure.ac的第三方宏定义NEWSREADMEAUTHORSChangeLog:这些文件是GNU软件的标准配置,但是不一定需要加入项目。如果项目中没有这些文件,autoreconf每次都会提示文件丢失,但不影响。如果你不想看到这些错误信息,你可以使用AM_INIT_AUTOMAKE([foreign])来配置automake。foreign参数是告诉automake不要那么认真:)config.guessconfig.sub:automake生成的,目标平台检测的两个脚本depcompinstall-sh:automake生成的,用来完成编译安装的脚本missing:generated通过automakegeneratesltmain.sh:generatedbylibtoolize,用于在configure阶段配置生成可以在目标平台上运行的libtool脚本。sh:早期没有autoreconf,软件开发者往往需要自己编写脚本依次调用autoconf、autoheaderautomake等工具。这个文件就是这样一个脚本。这样的名字可能是一种习惯性的概括。本文的一般概念解释了autotool系列工具的工作原理。与如今现成的IDE相比,GNU构建系统其实非常难用,学习成本也比较高。笔者认为,最高效的方法就是学习开源程序,慢慢体验和吸收。另外,推荐几本资料:《GNU Autoconf Automake and Libtool》AutotoolsTutorialGNU网站关于autoconfautomake和libtool作者也有一些相关的笔记,后面会另写一篇总结。