GNU Automake 版本(version 1.16.1, 26 February 2018)
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License,
Version 1.3 or any later version published by the Free Software
Foundation; with no Invariant Sections, with no Front-Cover texts,
and with no Back-Cover Texts. A copy of the license is included in
the section entitled “GNU Free Documentation License.”
2 Autotools 简介
如果以前没有接触过Automake,你可能大概也许知道它是 Autotools 工具集的一部分。
如果你已经开始研究诸如 configure,configure.ac,Makefile.in,Makefile.am,aclocal.m
等,那么你可能已经看到他们被声明为 generated by Autoconf or Automake。
整套生成过程异常复杂,本章准备一点点介绍讲解出来。
Alexandre Duret-Lutz 的 Autotools 教程特别好,供参考
(http://www.lrde.epita.fr/~adl/autotools.html)。
2.1 GNU Build System 简介
作为一个开发人员,有一套构建系统是相当有必要的。
在Unix的世界里,一般使用make
。所有的规则都在文件Makefile
中描述。
比如程序prog
链接到文件 main.o,foo.o,bar.o
,文件 main.o
由 main.c
编译而成。
每次执行 make
的时候,它都会读取Makefile
,检查文件的修改日期,来决定哪些文件需要重新构建。
在不同的平台上进行编译的时候,Makefile
可能需要微调。
1991年David J. MacKenzie在厌倦反复修改 Makefile
后,编写了一个支持20个平台的脚本。
这个脚本的文件称为 configure
,可以自动调整 Makefile
。
现在编译软件包简化为 ./configure && make
.
目前GNU项目已经标准化这个过程,安装只需要解压软件包,然后执行 ./configure && make && make install
,即可安装。
Autotools 工具可以帮助我们构建自己的工具包。
Autoconf 主要完成configure
, Automake 完成Makefile
。
2.2 GNU Build System的用例
随automake发布的还有一个示例程序 amhello-1.0.tar.gz ,一般位于PREFIX/share/doc/automake/amhello-1.0.tar.gz
,其中PREFIX为安装路径的前缀,
默认为/usr/local
,还有一些发行版位于/usr
。
2.2.1 基本安装
最常见的安装如下:
1 | ~ % tar zxf amhello-1.0.tar.gz |
我们首先解压文件,然后执行configure,将会创建Makefile文件,此时就可以执行make命令了。
make命令将编译程序、库和相关脚本。
make check会测试程序是否运行,不过需要编写测试用例。
当一切完成后,我们就可以使用make install来安装程序的,默认情况下降安装在目录/usr/local
,比如:
- 可执行程序在
/usr/local/bin
, - 库文件在
/usr/local/lib
2.2.2 标准的 Makefile
目标
前面我们接触了 make
, make check
, make install
, 和 make installcheck
.
这里的 check
, install
和 installcheck
称为目标,而 make
本身是 make all
的缩写。
下面是一些常用的make命令:
make all
:编译程序、库、文档等make install
:安装并拷贝相关文件到系统目录make uninstall
:清除安装的文件(需要在同一个目录执行)make clean
:清除由make生产的编译文件make distclean
:清除由configure创建的文件make check
:运行测试用例make installcheck
:检查安装的程序或库make dist
:从源码制作发布归档文件PACKAGE-VERSION.tar.gz
2.2.3 标准目录变量
GNU的编码标准也指定了安装的目录层,如下所示:
目录变量 | 默认值 |
---|---|
prefix |
/usr/local |
exec_prefix |
${prefix} |
bindir |
${exec_prefix}/bin |
libdir |
${exec_prefix}/lib |
… | |
includedir |
${prefix}/include |
datarootdir |
${prefix}/share |
datadir |
${datarootdir} |
mandir |
${datarootdir}/man |
infodir |
${datarootdir}/info |
docdir |
${datarootdir}/doc/${PACKAGE} |
… |
安装的时候,安装的文件就会按照到上述变量的一个目录中。比如示例 amhello-1.0
,可执行程序 hello
将安装到BINDIR, README
安装到DOCDIR,默认路径为/usr/local/share/doc/amhello
.
如果希望安装到自己的目录,可以使用以下参数:
1 | ~/amhello-1.0 % ./configure --prefix ~/usr |
将安装为 ~/usr/bin/hello
,~/usr/share/doc/amhello/README
.
更多参数可以使用 ./configure --help
.
2.2.4 标准配置变量
GNU的编码标准也指定了编译中一系列的配置变量,如下所示:
变量 | 含义 |
---|---|
CC |
C编译器 |
CFLAGS |
C编译器参数 |
CXX |
C++编译器 |
CXXFLAGS |
C++编译器参数 |
LDFLAGS |
链接参数 |
CPPFLAGS |
C/C++预处理参数 |
… |
configure
一本都会设定好默认的参数值,不过如果希望覆盖默认参数,就需要修改这些值了比如通过 configure
强制设定 gcc-3
作为C编译器,使用 ~/usr/include
的头文件编译,使用 ~/usr/lib
的库进行连接。
1 | ~/amhello-1.0 % ./configure --prefix ~/usr CC=gcc-3 CPPFLAGS=-I$HOME/usr/include LDFLAGS=-L$HOME/usr/lib |
这些变量均可以通过
./configure --help
查看
2.2.5 使用文件config.site
重置默认的配置
在安装多个文件的时候,如果配置参数一致,那么创建一个通用的文件将十分简便。如果文件PREFIX/share/config.site
存在,在configure的时候将自动导入这些参数。
重新调用上一节的命令:
1 | ~/amhello-1.0 % ./configure --prefix ~/usr CC=gcc-3 \ |
此时我们就可以把下面的内容写入文件 ~/usr/share/config.site
test -z "$CC" && CC=gcc-3
test -z "$CPPFLAGS" && CPPFLAGS=-I$HOME/usr/include
test -z "$LDFLAGS" && LDFLAGS=-L$HOME/usr/lib
然后,每次执行 configure
都会load这个文件
1 | ~/amhello-1.0 % ./configure --prefix ~/usr |
2.2.6 并行编译树(Parallel Build Trees) (又名VPATH Builds)
GNU Build System区分两个目录树:源码树和编译树。
源码树包含文件 configure
及源码,编译树位于 configure
运行的目录。一般编译树的布局与源码树类似。
正常情况下,我们可能会在源码树目录直接执行 configure
命令,还有一种情况是如果希望保持源码树的整洁,在其他目录来进行编译,如下所示:
1 | ~ % tar zxf ~/amhello-1.0.tar.gz |
源码树与编译树的目录不同即被称为“parallel builds” 或 “VPATH builds”。
这个源码与编译不在同一个目录的方式对不同的配置参数进行编译特别方便,比如我们通过不同的配置参数来分别编译:
1 | ~ % tar zxf ~/amhello-1.0.tar.gz |
对于网络文件系统,这种方式更方便了,比如我们在两个主机 HOST1
和HOST2
上分别编译,
1 | ~ % cd /nfs/src |
这种情况下,不会对源码目录有任何特殊需求,就算是只读也可编译,这个功能最开始主要是因为当时FSF使用CD-ROM发布,而CD只能读取。
2.2.7 两步安装
GNU编译系统还支持两步安装,什么意思的,也就是区分两种类型的文件,架构相关的和架构无关的,可以通过不同的 Makefile
来完成相关安装。
其实对于我们平时使用的make install
是由两部分组成的:
- 架构相关的
install-exec
- 架构无关的
install-data
从GNU编译系统的角度来看,架构相关与架构无关主要是安装环境及变量不同。所以架构相关的我们使用 make install-exec
来安装,架构无关的直接使用make install-data
来安装。
举个例子来看看:有两台主机需要安装,假定 (1) 程序安装到 /usr
, (2) 目录 /usr/share
共享。
在第一台host上,执行On the first host we would run
1 | [HOST1] ~ % mkdir /tmp/amh && cd /tmp/amh |
On the second host, however, we need only install the
architecture-specific files.
1 | [HOST2] ~ % mkdir /tmp/amh && cd /tmp/amh |
In packages that have installation checks, it would make sense to runmake installcheck
to verify that the
package works correctly despite the apparent partial installation.
2.2.8 交叉编译
这个功能特别有用,我们可以在一个平台编译,而在另外一个平台运行。
其中参数为:
--build=BUILD
:软件包的编译系统--host=HOST
:软件和库将要运行的系统
再加上 --host
参数的时候, configure
就会搜索这个平台的交叉编译套件。交叉编译工具一般在名字前有目标架构作为前缀,比如 MinGW32 编译环境被称为i586-mingw32msvc-gcc
, i586-mingw32msvc-ld
, i586-mingw32msvc-as
等。
接下来的操作介绍如何在GNU/Linux上为 i586-mingw32msvc
编译 amhello-1.0
。
1 | ~/amhello-1.0 % ./configure --build i686-pc-linux-gnu --host i586-mingw32msvc |
2.2.9 安装时重命名程序
可以使用 configure
的编译选项,在安装之前加上特定的前缀或后缀来避免与系统程序冲突。
--program-prefix=PREFIX
:增加前缀--program-suffix=SUFFIX
:增加后缀--program-transform-name=PROGRAM
:安装时运行sed PROGRAM
比如下面的例子将安装 hello
到/usr/local/bin/test-hello
。
1 | ~/amhello-1.0 % ./configure --program-prefix test- |
2.2.10 使用DESTDIR编译二进制包
单纯的GNU Build System的 make install
和 make uninstall
接口并不能完全满足系统管理员的部署,比如我们希望在编译的时候安装指定路径,而在make
的时候因为可能没有root权限暂时放在一个临时目录,然后再部署到其他机器。
比如像下面这个例子就很好的诠释了这个方法。
1 | ~/amhello-1.0 % ./configure --prefix /usr |
然后,我们就可以把 amhello-1.0-i686.tar.gz
解压到大部分主机的 /
目录了。
注意使用
cat ../files.lst
而不是.
是为了避免子目录的归档问题,保证在解压的时候按照预定的目录进行解压。
2.2.11 准备发布
make dist
收集源码及必须的文件创建一个压缩包,名为 PACKAGE-VERSION.tar.gz
。
虽然也能满足大部分的功能,但是还有一个更有用的命令 make distcheck
,这个命令也会像 make dist
一样创建压缩包 PACKAGE-VERSION.tar.gz
,并会做各种各样的检查以确保压缩包没有问题:
• 将运行所有的软件包命令,比如 make
, make check
, make install
和 make installcheck
, 甚至是 make dist
,来确保软件发布没有问题
• 测试只读源码树的VPATH编译
• 确保 make clean
, make distclean
, and make uninstall
不忽略任何文件
• 检查 DESTDIR
安装是否工作
所有的这些操作都在一个临时目录进行,所以并不需要root权限。
如果 make distcheck
失败了,意味着其中的一个场景没有满足,可能不会影响程序的使用,不过最好根据失败的提示进一步改进得到一个完美的发布包。
2.2.12 自动依赖跟踪
Automake自动生成代码来解决依赖的问题。
在 configure
执行的时候,我们可以看到这个过程:
1 | ~/amhello-1.0 % ./configure --prefix /usr |
可以通过下面两个选项来使能或禁用依赖跟踪:
--disable-dependency-tracking
:Speed up one-time builds.--enable-dependency-tracking
:Do not reject slow dependency extractors.
2.2.13 嵌套包
虽然并不推荐刚开始接触Autotools的时候就了解嵌套包,但是最为一个极具特性的功能还是需要了解一下的。
使用Autotools或者相关工具开发的时候可以任意潜逃子模块,比如包A的发布将需要字幕了的一个库,库B拥有自己的一套构建系统,那么包A的 configure
脚本将先运行库B的 configure
脚本,所以安装编译包A的同时也安装编译了库B。生成的包A的发型版也将包含库B。
当然还可以收集更多的包,GCC重度使用这个功能,这样就可以保证每个子模块均可以独立的开发了。
可以使用命令 configure --help=recursive
来显示所有包含包的选项。
2.3 Autotools大有裨益
或许你只是希望完成一些可以通过自行编写 configure和
Makefile来完成的功能:
• 不需要特别多的功能
• 需要考虑移植的代价
• 随着GNU Coding Standards的改变需要及时升级配置文件
而 GNU Autotools 就把这些工作自己悄悄的做掉了,只需要关注你的代码即可。
2.4 经典Hello World小程序
本节中我们首先重新从头创建 amhello-1.0
软件包,然后介绍下configure.ac
和 Makefile.am
的含义。
2.4.1 创建 amhello-1.0.tar.gz
接下来我们从头创建 amhello-1.0.tar.gz
,这个压缩包很简单,我们只需要准备5个文件即可。
在一个空目录中创建以下文件:
• src/main.c
:程序 hello
的源文件,我们把这个文件保存在 src/
子目录,这样方便以后添加诸如man/
和 data/
的文件夹。
1 | ~/amhello % cat src/main.c |
• README
:包含一些说明文档
1 | ~/amhello % cat README |
• Makefile.am
和 src/Makefile.am
包含Automake的指令
1 | ~/amhello % cat src/Makefile.am |
• configure.ac
包含创建 configure
脚本的Autoconf指令
1 | ~/amhello % cat configure.ac |
准备好这些文件就可以运行 Autotools 了,使用 autoreconf
指令:
1 | ~/amhello % autoreconf --install |
搞定,此时就完成了编译工作。
除了提示的添加的文件, autoreconf
还会创建另外4个文件: configure
, config.h.in
,Makefile.in
和 src/Makefile.in
. configure
将使用后面的3个文件来创建 config.h
, Makefile
和 src/Makefile
。
1 | ~/amhello % ./configure |
此时我们就可以看到已经创建了 Makefile
, src/Makefile
和 config.h
。
1 | ~/amhello % make |
autoreconf
只需要运行一次即可,在构建后环境,后续更改了 Makefile.am
及 configure.ac
,在 make
的时候会自动重建需要的依赖。
autoreconf
是 autoconf
, automake
等一些列命令的集合。
其中 autoconf
负责从configure.ac
创建 configure
;
automake
从configure.ac
和Makefile.am
创建 Makefile.in
。
2.4.2 configure.ac
配置详解
看看文件 configure.ac
:
AC_INIT([amhello], [1.0], [bug-automake@gnu.org])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_PROG_CC
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT
这个文件被 autoconf
(创建 configure
) 和automake
(创建 Makefile.in
s)读取。这个文件包含一系列的M4宏。
以 AC_
开头的是Autoconf 宏,以 AM_
开头的是Automake 宏。
configure.ac
的前两行用于初始化 Autoconf 和Automake。 AC_INIT
的参数为软件包,版本及联系邮箱(可以使用 ./configure --help
来看到)。
记得邮箱要使用自己的
AM_INIT_AUTOMAKE
的参数为automake
的选项,比如 -Wall
和 -Werror
为打开所有的警告和错误报告。 foreign
选项告诉Automake这个包不遵从GNU标准。GNU包需要包含ChangeLog
, AUTHORS
等文件。如果我们不希望automake
抱怨就需要加上这个选项。
AC_PROG_CC
可以指定 configure
脚本的C编译器。文件src/Makefile.in
将使用这里定义的编译器来编译生成hello
,当 configure
从 src/Makefile.in
创建src/Makefile
的时候会用到。
选项 AC_CONFIG_HEADERS([config.h])
将使 configure
脚本创建一个文件config.h
用于汇总configure.ac文件中所有定义的宏。在我们的例子中国呢,AC_INIT
宏已经定义了一部分了。比如下面这个例子:
1 | ... |
我们可能注意到文件src/main.c
包含头文件config.h
,这样就可以使用 PACKAGE_STRING
。
AC_CONFIG_FILES
宏定义了configure
需要从 *.in
模版生成的文件。Automake将遍历需要的 Makefile.am
文件,所以如果新增了一个文件夹,一定要记得添加 Makefile
到这个列表。
AC_OUTPUT
行为结束命令。
2.4.3 amhello
的 Makefile.am
配置详解
接下来我们看看文件 src/Makefile.am
,这个文件包含Automake编译安装 hello
的指令。
bin_PROGRAMS = hello
hello_SOURCES = main.c
Makefile.am
与 Makefile
有相同的语法。automake
在处理Makefile.am
的时候,会拷贝整个文件到 Makefile.in
。
以 _PROGRAMS
结尾的变量为 Makefile
s将要编译的程序。还有其他后缀变量,比如 _SCRIPTS
, _DATA
, _LIBRARIES
等,参考后面的章节。
bin_PROGRAMS
的 bin
部分表示 automake
将把该程序安装到BINDIR。
注意此处省略了dir
程序需要从源码编译,所以对于每一个以 _PROGRAMS
结尾的PROG
都需要有一个变量为 PROG_SOURCES
,后面将列出所有编译时需要的源文件。
Automake也会通过这个源码文件在打包 make dist
的时候一起发布。
最后我们看看顶层目录的文件Makefile.am
SUBDIRS = src
dist_doc_DATA = README
SUBDIRS
是一个特殊的变量,用来列出在处理当前目录之前 make
需要遍历的目录。
dist_doc_DATA = README
将会把文件 README
发布和安装到DOCDIR。以 _DATA
结尾的变量默认情况下在 make dist
发布时是不包含的,所以我们新增了前缀dist_
。