GNU工具链

在上个世纪八十年代,计算机都是奢侈品,操作系统里最著名的是Unix家族,当时还没有Windows、Linux之类的,Unix系统都是商业软件,里面的应用软件也是商业软件,全是封闭的环境。
系统程序员Richard M.Stallman(RMS)在此环境下创立了与众不同的GNU项目(GNU’s Not Unix),以及推进自由软件发展的Free Software Foundation(FSF)自由软件基金会。GNU项目是为了创建自由的类Unix系统,也因此开发出来很多开源的系统工具,其中非常著名的就是GCC(GNU Compiler Collection,GNU编译器套件)。
GNU工具链(GNU toolchain)是一个包含了由GNU项目所产生的各种开发工具的集合,这些工具形成了一条工具链(串行使用的一组工具)。GNU工具链在针对嵌入式系统的Linux内核、BSD及其它软件的开发中起着至关重要的作用,其中的部分工具也被Solaris、Mac OS X、Microsoft Windows(via Cygwin和MinGW/MSYS)and Sony PlayStation3等其它平台直接使用或进行了移植。
在GNU工具集里面,开发时常见到的几个罗列如下(这些工具通常位于Linux或Unix系统里的/usr/bin/目录):

  • GNU M4:m4宏语言处理器,POSIX标准的一部分
  • GNU Compiler Collection(GNU编译器套件)
    • cpp:c语言预处理器
    • gcc:GNU c语言编译器(注意是GCC中的gcc)
    • g++:GNU c++语言编译器
    • 注意gcc和g++本质是compiler驱动程序,通过这些程序可以调用cc1、cc1plus、as、ld等程序进行编译(或者说类似于接口?)
  • GNU Binutils(GNU二进制工具集)
    • as:GNU汇编器
    • ld:GNU链接器
    • ar:将可重定位目标文件打包成静态库
  • GNU Debugger:调试器,用于调试可执行程序
  • GNU Make:生成器,可以根据makefile文件自动编译链接生成可执行程序或库文件
  • GNU Autotools:自动构建工具集

GNU :项目主页
FSF :基金会主页

MinGW

原本GNU工具只在Linux/Unix系统里才有,随着Windows系统的广泛使用,为了在Windows系统里可以使用GNU工具,诞生了MinGW(Minimalist GNU for Windows)项目,利用MinGW就可以生成Windows里面的exe程序和dll链接库。需要注意的是,MinGW与Linux/Unix系统里GNU的工具集有些区别:

  • MinGW里面工具带有扩展名.exe,Linux/Unix 系统里工具通常都是没有扩展名的。
  • MinGW里面的生成器文件名为mingw32-make.exe,Linux/Unix系统里就叫make。
  • MinGW在链接时是链接到*.a库引用文件,生成的可执行程序运行时依赖*.dll,而Linux/Unix系统里链接时和运行时都是使用*.so 。
  • MinGW里也没有ldd工具,因为Windows不使用.so共享库文件。如果要查看Windows里可执行文件的依赖库,需要使用微软自家的Dependency Walker工具。
    • Windows里面动态库扩展名为.dll,MinGW可以通过dlltool来生成用于创建和使用动态链接库需要的文件,如.def和.lib。

MinGW原本是用于生成32位程序的,随着64位系统流行起来,从MinGW分离出来了MinGW-w64项目,该项目同时支持生成64位和32位程序。

另外提一下,由于MinGW本身主要就是编译链接等工具和头文件、库文件,并不包含系统管理、文件操作之类的Shell环境,这对希望用类Unix命令的开发者来说还是不够用的。所以MinGW官方又推出了MSYS(Minimal SYStem),相当于是一个部署在Windows系统里面的小型Unix系统环境,移植了很多Unix/Linux命令行工具和配置文件等等,是对MinGW的扩展。
MSYS对于熟悉Unix/Linux系统环境或者要尝试学习Unix/Linux系统的人都是一种便利。MSYS和MinGW的安装升级都是通过其官方的mingw-get工具实现,二者是统一下载安装管理的。对于MinGW-w64项目,它对应的小型系统环境叫MSYS2(Minimal SYStem 2),MSYS2是MSYS的衍生版,不仅支持64位系统和32位系统,还有自己的独特的软件包管理工具,它从Arch Linux系统里移植了pacman软件管理工具,所以装了MSYS2之后,可以直接通过pacman来下载安装软件,而且可以自动解决依赖关系、方便系统升级等。装了MSYS2之后,不需要自己去下载MinGW-w64,可以直接用pacman命令安装编译链接工具和git工具等。

MinGW :项目主页(含 MSYS)
MinGW-w64 : 项目主页
MSYS2 : 项目主页

Unix-GNU程序的构建和部署过程

在Unix-GNU环境下,从源代码编译生成一个可执行的GNU程序的3步骤(经典三部曲)是:

./configure
make
make install

不止是Unix用户对这几行命令很熟悉,如果要开发一款针对Homebrew、Linux或者BSD包管理器的应用,最终在构建部署时都离不开这三条命令。这就是GNU构建系统,是利用脚本和make程序在特定的平台上构建项目的过程。从用户视角来说,这个过程的三条命令分别对应于三个程序构建部署阶段:配置、构建和安装,下面逐一展开介绍。

配置阶段

通过Shell执行一个configure脚本,负责在你使用的系统上准备好项目的构建环境,确保接下来的构建和安装过程所需要的依赖已经准备好,并且搞清楚使用这些依赖需要的东西。比如,Unix程序一般是用c语言写的,所以通常都需要一个c编译器来构建它们,因此configure要做的一件事就是确保系统中有c编译器并确定它的名字和路径,然后生成一个makefile(以及其它一些配置文件,典型的是大型项目中的config.h头文件,其中的宏有助于项目通过预编译来改变自身代码,来适应目标平台某些特殊性)以供下一个阶段使用。
一般下载的源码包一般没有一个最终的makefile文件,只有类似makefile.in的一些模板文件,configure则根据系统参数和这些模板文件生成一个定制化的makefile文件。有些小型的项目可能已经包含了makefile,那么这个时候实际就不需要configure了。
configure主要检查当前目标平台的程序,库、头文件,函数等的兼容性。这些结果将作用于config.h和makefile文件的生成从而影响最终的编译。用户可以通过configure配置参数,来定制需要包含或者不需要包含的组件,安装路径等。参数大概可以分为五组:

  • 安装路径相关
  • 程序名配置
  • 跨平台编译
  • 动静态库选项
  • 程序组件

configure在执行过程中,除了生成Makefile外,还会生成,并且不限于以下文件:

  • config.log:日志文件
  • config.cache:缓存文件,提高下一次configure的速度,由-C指定
  • config.status:实际调用编译工具构建项目的shell脚本
  • 如果项目通过libtool构建,还会生成libtool脚本(主要解决库依赖关系和库的跨平台问题)

构建阶段

当configure脚本配置执行完毕后,可以使用make命令对其生成的makefile执行构建,通过makefile文件中定义的一系列任务将项目源代码编译成可执行文件。关于makefile的知识具体可以参考我的makefile介绍系列。

安装阶段

通过makefile内容可以借助编译器(包括汇编、链接器等)生成可执行文件,也就是说现在项目已经被构建好并且可以执行,接下来要做的就是将可执行文件复制到最终的路径。make install命令(make执行makefile中的install伪目标)就是将可执行文件、第三方依赖包和文档复制到正确的路径。这通常意味着,可执行文件被复制到某个环境变量PATH包含的路径,程序的调用文档被复制到某个MANPATH环境变量包含的路径,还有程序依赖的文件也会被存放在合适的路径。因为安装这一步也是被定义在makefile中,所以程序安装的路径可以通过configure命令的参数指定,或者 configure通过系统参数决定。另外如果要将可执行文件安装在系统路径,执行这步需要赋予相应的权限,一般是通过sudo命令。

GNU Autotools(GNU Build System)

如果尝试着打开configure或者makefile.in文件,通常会在其中发现超长而且复杂的shell脚本和其他语言,有时候这些脚本代码甚至比要安装的源程序还要长。要手动创建一个这样的 configure脚本文件是非常困难的,好消息是这些脚本可以通过工具生成。GNU Autotools就这样一种工具集,使用其中工具可以使得维护项目生命周期变得很容易,甚至开发人员可以通过编写20或30行安装代码从而免费获得其他4000行的效果。
Autotools主要由三个组件构成,这几个组件分别是:

  • autoconf:其中包括autoconf、autoscan、autoheader、autoreconfig、ifnames等工具
  • automake:其中包括automake、aclocal等工具
  • libtool:较为独立,可以在不同平台上创建并调用动态库

需要注意的是这几个组件可能在一个安装包中,但也有可能是在不同的安装包中,因此两个独立的包中的路径不同使得组件在协作时可能会存在找不到对方的依赖项类似的问题。
Autotools具有如此强大的生成能力,部分原因在于其采用了高度定制化的GNU项目规范,因而可以采用简短的语法(但肯定是无二义性的)来描述复杂的规则。如果项目采用Autotools工具集来生成makefile的话,它倾向于项目符合GNU的一套预期规范,比如源文件本身应该位于一个名为src的子目录中。此外有些文件是必需的:

  • NEWS
  • README
  • AUTHORS
  • ChangeLog

可以不必主动使用这些文件(空),甚至它们可以是包含所有信息的单个汇总文档(如README.md)的符号链接,但它们必须存在。另外如果用automake来生成makefile模板,那么其只支持三种目录层次:

  • 平行层次flat:全部文件都位于同一个目录中
  • 浅层次shallow:主要源文件在顶层目录,其他一些功能模块位于各自不同的目录
  • 深层次deep:全部源文件都被储存在子目录中,顶层目录主要包括配置信息

Autotools是GNU工具链上的一个环节,其他经常与之配套使用的相关工具包括GNU的make程序、GNU gettext、pkg-config等等。
下面简要介绍一下Autotools中工具以及对应的脚本文件的用途和格式,并通过一个简单的例子来进行演示。系统学习可参考官方文档:
GNU Autoconf - Creating Automatic Configuration Scripts
GNU Automake
Autoconf 中文手册
Automake 中文手册

autoconf

一般是没有人直接写configure脚本文件的,而是通过创建一个描述文件configure.ac来描述configure需要做的事情。configure.ac使用m4sh写,m4sh是m4宏命令和shell脚本的组合,用configure.ac生成configure脚本的工具就是autoconf。简单来说,autoconf预定义了大量的、用户检查系统可移植性的m4宏,这些宏展开后就是大量的shell脚本。在configure.ac中使用的m4宏一般以AC_开头,一般的configure.ac文件布局为:

AC_INIT(…)
AM_INIT_AUTOMAKE
测试程序、函数库、头文件、类型定义、结构、编译器特性、库函数、系统调用等等
AC_CONFIG_FILES([makefiles])
AC_OUTPUT

首先声明一些autoconf的版本信息,紧接着正文以AC_INIT初始化autoconf并配置一些关于项目的基本信息(名称、版本、报告错误的电子邮件地址、项目 URL以及可选的源TAR文件名称等参数)。接下来还要初始化automake的信息,AM_开头的m4宏表示automake的拓展宏,一般与automaker一起安装,通过宏AM_INIT_AUTOMAKE,然后就可以指定autoconf进行具体的配置工作,包括寻找路径、依赖等等。AC_CONFIG_FILES是用来指定configure脚本生成makefile的行为,参数为makefile文件路径。最后AC_OUTPUT用来指定最后的生成文件。

autoscan

如果觉得编写configure.ac还是很麻烦的话,Autotools还提供了一个工具autoscan,通过autoscan命令扫描整个项目,可以得到一个初始化的configure.scan文件,这就相当于是一个configure.ac的雏形文件(果然计算机问题都可以通过中间层来解决),接下来直接新建一个.ac文件或者直接将.scan文件重命名为.ac文件后在其中修改、定制项目的配置参数即可。

autoheader

configure脚本除了生成makefile外还有一个重要任务是生成config.h,Autotools对此也提供了一个工具autoheader用以生成config.h.in文件,并且和makefile一样可以在configure.ac中通过宏来指定生成config.h.in的路径。(AC_CONFIG_HEADER([config.h]))。

automake

makefile.in也是一样的道理,在有高效工具的前提下是没有必要一步步手写的。可以创建一个makefile.am脚本,再借助之前在configure.ac中初始化的automake工具来生成makefile.in文件。makefile.am使用与makefile相同的语法(额外指定的变量看上去可能是环境变量),通常其只需要几个变量定义来指示要构建的文件以及它们的安装位置即可。
makefile.am的编写主要注意以下几点:

  • automake默认项目的结构是标准GNU结构,也就是源文件位于名为src的目录中
    • 如果项目使用了其他结构进行布局,则必须告知automake接受来自外部源的代码:AUTOMAKE_OPTION=foreign,表示只检测必须的依赖文件
    • automake提供了三种等级:foreign、gnu、gnits
  • makefile.am中以_PROGRAMS结尾的变量标识了要构建的目标文件,类似的还有其他后缀如_SCRIPTS、_DATA、_LIBRARIES等代表构成项目的其他常见部分。如果项目在构建过程中需要实际编译,那么可以用bin_PROGRAMS变量标记项目的目标可执行文件,其中bin指的是automake生成的文件将被放在${bindir}目录下(该变量为系统定义,也可以自定义),然后使用该程序名称作为变量前缀引用构建它所依赖的源代码(_SOURCE)的任何部分(这些部分可能是将被编译和链接在一起的一个或多个文件)
  • 如果项目是一个不需要编译的脚本文件,那么直接定义一个_SCRIPTS变量指定生成文件即可
  • 可以在makefile.am中创建任何自定义的makefile规则,它们将逐字复制到生成的makefile.in中

aclocal

configure.ac实际是依靠宏展开来得到configure,因此能否成功生成取决于m4宏定义是否能够找到。autoconf会从自身安装路径下寻找事先定义好的宏,然而实际的configure.ac中可能还需要用户自定义的宏、第三方扩展宏等(考虑到autoconf和automake可能不在一个安装路径,autoconf或许连automake的扩展宏都找不到),这些autoconf便无从知晓。此时需要aclocal在configure.ac的目录下将项目所需的m4宏收集起来生成aclocal.m4文件。

autoreconf

autoreconf可以更新已经生成的配置文件,与make一样默认只重建比依赖文件更老的目标文件,autoreconfig能够自动按照合理的顺序调用autoconf,automake,aclocal等程序:

在早期的GNU Build System中没有autoreconf工具,开发者需要自己编写脚本autogen.sh来调用autotools的工具,其达到的效果与autoreconf是相似的。

生成文件

编写完configure.ac和makefile.am文件后,就可以通过Autotools工具生成configure脚本和makefile.in文件了,不过在此之前还是应该了解构建环境中各个组成部分间的依赖关系是否都得到了满足,在发生错误时定位错误和查询解决方案。下面这张图体现了Autotools工具项目构建中各个工具执行间的依赖文件:

  • 首先是两个(可能)完全不需要依赖生成的文件
    • makefile.am是需要开发者提供的类makefile文件,不依赖于其他文件
    • configure.ac可以通过autoscan生成的configure.scan生成,也可以由开发者提供
  • 执行aclocal命令的依赖文件
    • aclocal命令依赖于configure.ac文件,通过该命令可以生成aclocal.m4文件
  • 执行autoconf、autoheader命令的依赖文件
    • autoconf命令依赖于configure.ac和aclocal.m4文件,通过该命令可以生成configure脚本
    • autoheader的依赖与autoconf相同,通过该命令可以生成config.h.in文件
  • 执行automake命令的依赖文件
    • 执行automake命令依赖于autoconf和autoheader输出的configure.ac文件(但我实际测试发现还依赖于config.h.in文件)
    • 执行automake命令还依赖于makefile.am文件
    • 通过该命令和依赖文件可以生成makefile.in文件
  • configure脚本的依赖文件
    • configure脚本负责生成makefile,其依赖于makefile.in和config.h.in文件
  • 执行make命令的依赖文件
    • make通过makefile完成项目的编译生成

整个构建过程如下:

最后用一个例子来模拟一下Autotools的构建和部署过程:

  • 首先创建一个项目目录test和其中的两个文件test.c和defs.h
  • automake支持的目录层次有三种,如果把上述两个文件放在主目录下就是flat层次
/********/test/defs.h*********/
#ifndef _DEFS_H_
#define _DEFS_H_
#define NAME "makefile"
#endif
/*****************************/
/********/test/test.c*********/
#include <stdio.h>
#include "defs.h"
int main(int argc, char* argv[]) {
    printf("%s\n",NAME);
    return 0;
}
/*****************************/
  • 然后将需要的Autotools的工具准备就绪
  • 使用autoscan扫描项目,生成一个空的autoscan.log文件和configure.scan文件
    • configure.scan的内容如下:
    • AC_PREREQ([2.69])表示autoconf版本
    • AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])初始化configure信息
    • AC_CONFIG_SRCDIR([test.c])是autoscan扫描到的源文件
    • AC_CONFIG_HEADERS([config.h])是计划生成的config.h文件
    • AC_PROG_CC用来指定编译器,默认为gcc
    • #中的文字表示要进行检查项目,下面空着一行是留着用来写具体执行的宏语句
    • AC_OUTPUT用来设定configure所要产生的文件,如果是makefile的话会把它检查出来的结果带入makefile.in文件
  • 下一步可以以configure.scan为模板编写configure.ac文件
    • 首先将其重命名
    • 在其中填写初始化autoconf和automake的宏
    • 指定最终的生成文件名makefile
#######################/test/configure.ac#########################
#                                               -*- Autoconf -*-
AC_PREREQ([2.69])
AC_INIT([TEST], [0.01], [xxxxxxxxx@email.com])
AC_CONFIG_SRCDIR([test.c])
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h])
AC_PROG_CC
AC_CONFIG_FILES([makefile])
AC_OUTPUT
##################################################################
  • 接下来用aclocal生成configure.ac所需要的m4环境,然后就可以生成configure脚本了
    • 当然也可以将makefile.am编辑好后一并处理
  • 然后开始编写makefile.am文件,用automake生成makefile.in文件
######/test/makefile.am######
AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=target
target_SOURCES=test.c
#############################
  • 如果是第一次进行构建的话,可能会缺少./compile、./install-sh、./missing、./depcomp等依赖文件
  • 此时系统会提示你automake --add-missing命令可以安装这些依赖文件,所以照做就行了
  • 另外,因为configure脚本是需要config.h文件的,如果你在这时还没有生成的话系统会提示你缺少该文件,所以用autoheader生成该文件

到这里项目构建过程就完成了,接下来就可以对项目进行经典三部曲以及打包测试等环节:

最后如果想重新进行构建可以使用autoreconf(前提是你已经改过源文件,否则什么也不会做)。整个过程总结下来步骤如下:
(比较固定,所以可以考虑编写shell脚本来完成这些操作,不过麻烦点在于改写文件,所以就不做展开了,现在暂时只是了解这些工具的基本使用,剩下的等日后有机会再补充吧)

#####################test.sh#######################
#!/bin/sh
cd ~/test
mkdir test && mv test.c defs.h configure.ac makefile.am test
autoconf
aclocal
autoheader
automake --add-missing
./configure
make
make dist check
make install
###################################################
chmod +x test.sh
sudo ./test.sh