7
1
2016
3

autotools 教程:用 automake 搞出来实际可用的玩意

在开始之前

这里我们用 Vala 语言和 GTK+ 来展示如何使用 Automake。

首先创建 src/sp-gui.vala:

using Gtk;

int main(string[] args)
{
	Gtk.init(ref args);
	var win = new Gtk.Window(WindowType.TOPLEVEL);
	win.destroy.connect(Gtk.main_quit);
	win.title = "Salamat Pagi";
	win.show_all();
	Gtk.main();
	return 0;
}

这段代码会生成一个标题为 Salamat Pagi 的空白窗口。这段代码可以使用以下命令测试:

> valac sp-gui.vala --pkg gtk+-3.0
> ./sp-gui

开始折腾 Automake

首先创建 Makefile.am 文件:

SUBDIRS = src

然后创建 src/Makefile.am 文件:

bin_PROGRAMS = sp-gui
sp_gui_SOURCES = sp-gui.vala
# 很遗憾我还没找到有什么好办法把这个 VALAFLAGS 消除掉
sp_gui_VALAFLAGS = --pkg gtk+-3.0
sp_gui_CFLAGS = $(GTK_CFLAGS)
sp_gui_LDADD = $(GTK_LIBS)

我们已经知道,Automake 是把 Makefile.am 转换成 Makefile.in 的东西,理论上来说 automake 是增强 autoconf 功能的程序,所以我们不得不把 autoconf 和 automake 放在一起来讲。不信的话,你现在试试,automake 会报错 configure.ac 缺少一些命令。

现在我们把 configure.ac 改成下面的样子:

AC_INIT([SalamatPagi], [0.1], [bug-report@address])
AM_INIT_AUTOMAKE
AM_PROG_VALAC
AC_PROG_CC
PKG_CHECK_MODULES([GTK], [gtk+-3.0])
AC_SUBST([GTK_CFLAGS])
AC_SUBST([GTK_LIBS])
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

然后依次执行 aclocal, autoconf, automake -a --foreign。值得注意的是,这里必须要有 AC_PROG_CC,因为 Vala 实际上是将代码编译成 C 代码的,所以 C 语言需要的配置这里也要有(这正是我选择 Vala 为示例的原因,不用 Vala 的话,完全可以自己修改为 C语言版本),如果你以后要用 Vala 编程,应该注意。

automake 的参数

--add-missing (-a) 可以为你自动安装一些必须的脚本。

--foreign 则不强求你安装一些无聊的文档文件,比如 README,INSTALL,NEWS 之类的。如果你不想每次都这么做,可以把代码改成 AM_INIT_AUTOMAKE([foreign])

现在你可以试试 make 和 make clean 了。

同时生成多个可执行文件

我们这里提供一个小型 CLI 版本 Salamat Pagi 程序,创建文件 src/sp-cli.c:

#include <stdio.h>
int main(int argc, char *argv[])
{
	printf("Salamat Pagi!\n");
	return 0;
}

按着上面的办法,我们在 src/Makefile.am 中添加:

bin_PROGRAMS += sp-cli
sp_cli_SOURCES = sp-cli.c

响应 configure 参数

这里把 CLI 作为可选项。configure.ac 中添加:

AC_ARG_WITH([cli],
[  --with-cli        Command line interface],
[case "${withval}" in
  yes) cli=true ;;
  no)  cli=false ;;
  *) AC_MSG_ERROR([bad value ${withval} for --with-cli]) ;;
esac],[cli=false])
AM_CONDITIONAL([WITH_CLI], [test x$cli = xtrue])

--with-xx 的 value 是 withval,那么 --enable-xx 的 value 就是 enableval。

Makefile.am 中原有 sp-cli 的内容改为:

if WITH_CLI
bin_PROGRAMS += sp-cli
sp_cli_SOURCES = sp-cli.c
endif

现在,要编译 CLI 版本,就不得不 configure --with-cli=yes 了。

如果你想让 GUI 版本成为可选,想想应该修改哪些文件?没错,正是 configure.ac 和 src/Makefile.am。首先 configure.ac 中应该按照需求进行 PKG_CHECK_MODULES,然后再在 src/Makefile.am 中按需要编译 GUI 版本程序,甚至你可以让 AM_PROG_VALAC 按需求调用!

编译命令很长很烦?

automake 有 silent rules,可以把很长的命令缩短到 CC xx.c!有两个办法,一个是在 configure 的时候提供 --enable-silent-rules,另一个是在 configure.ac 里面提供 AM_SILENT_RULES([yes])。

一些测试程序?

有些程序是我们用来测试的,并不打算被 make install 安装到系统中,那么我们这时会使用 noinst_PROGRAMS 而不是 bin_PROGRAMS。以 noinst 前缀为 target 的文件,全部不会被安装。

安装其他文件

现在我们已经基本完成了大致的程序构造,make install 和 make uninstall 已经可以实作了!那么假如我们想要安装一些别的文件怎么办呢?比如说 Glade 生成的 UI 描述文件,我们一般希望这些东西安装到 ${prefix}/share/ProgramName 目录里面。我们创建 common/somethingRequired.txt:

This text file is required by Salamat Pagi!

为了让 automake 能管理到 common 目录,我们需要修改 Makefile.am:

SUBDIRS = common src

同时创建 common/Makefile.am:

commondir = $(datadir)/$(PACKAGE)
dist_common_DATA = somethingRequired.txt

另外别忘了修改 configure.ac。

这些文件一般在项目的 data 文件夹里面,不过因为之前已经创建好了 common,这里用就是了。

此外需要注意的是,一定不要在 Makefile.am 中使用绝对路径!

这里 datadir 会展开为 $(prefix)/share。现在我们就能试试了。安装后在 /usr/local/share/salamatpagi 下看看?另外值得注意的是,make uninstall 不会删掉目录!所以最终还是建议打包成 rpm 或者 deb。

回顾操作流程与自定义目标

我们现在已经知道 automake 可以将 Makefile.am 转换成 Makefile.in 交给 configure 具体化。那么自定义 target 又如何呢?很简单,直接在 Makefile.am 里面写就行了。

我们在 Makefile.am 里面添加:

greet:
	@echo yo!

现在 make greet 就能显示出来一个 yo! 了!想想看为什么这样可以?

Category: 技术 | Tags: autotools-tut
7
1
2016
0

autotools 教程:现在再谈 autoconf

很好的开端……

你应该已经学习了本部教程的 m4 章节,那么恭喜你,这是一个非常好的开端。过去你可能已经多次尝试入门 autoconf 但是不得要领,但是只要你学会了 m4 的基础知识,autoconf 对你来说就是一套预先定制好的 m4 宏而已,你只需学习其用法即可。读完本章,你就会发现 autoconf 是多么简单!

configure.ac 和 autoconf

要使用 autoconf,你需要一个叫做 configure.ac 的文件,当然,搞得这么花哨,其实就是一 m4 文件罢了。因为讲解 autoconf 时不宜引入 automake 之类的乱七八糟的东西,避免影响我们的教授流程,所以这里不会给出一个实际例子。这里你只需要知道,我们的目标程序名字叫做 SalamatPagi,使用 C 语言开发。

我们创建一个非常典型的开源项目文件系统:

mkdir -p SalamatPagi/src
mkdir -p SalamatPagi/lib
mkdir -p SalamatPagi/common
cd SalamatPagi

然后我们创建 configure.ac:

AC_INIT([SalamatPagi], [0.1], [bug-report@address])
AC_PROG_CC

此处,AC_INIT 初始化 autoconf,AC_PROG_CC 引入 C 编译器,AC_OUTPUT 则指示写出 configure。在这个目录下运行 autoconf,就可以得到 configure 文件,与此同时也会生成一个叫做 autom4te.cache 的目录(看到 m4 了吗?)。我们运行 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 whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed

会生成一个 config.log,打开之,可看到 configure 到底做了些什么。

其他编译器

除了 AC_PROG_CC,autoconf 还原生支持 AC_PROG_{CXX,OBJC,OBJCXX,GO} 等。但是注意,Vala 支持是 Automake 提供的,命令叫做 AM_PROG_VALAC。

什么是归 autoconf/configure 管的?

从上面的 configure.ac、configure 的运行输出和 config.log,我们可以清楚地看到:编译器调用、可移植性检测、用于程序内部的常量定义均归 autoconf 管辖范畴。除此之外,一些自定义校验、依赖库是否存在的检验和includedir/libdir定义也是属于 autoconf 的范围的。

现在可移植性问题少了,就代表 configure 可以消失了吗?

事实并非如此,因为 configure 还有另外一个非常重要的用途,即将一些模板文件具体化。直接这么说,可能还有点难以理解,我们就以具体例子来说吧。

创建一个叫做 pathdefs.in 的文件:

According to your settings:
The binaries will be in ${bindir} and the libraries will be in ${libdir}.

然后在 configure.ac 下面添加两行:

AC_CONFIG_FILES([pathdefs])
AC_OUTPUT

AC_CONFIG_FILES 是什么意思呢?也就是在 autoconf 这里注册某个文件(文件名这里不能有 .in,所以你自己创建的模板文件名必须以 .in 结尾),这个文件后来会被具体化。如果有多个文件,则应在 [ ] 中以空格分隔,如 [file1 file2 file3]。

AC_OUTPUT 则会创建一个叫 config.status 的脚本文件,实际上是这个文件在做模板具体化的事情。

在下一章,你将会看到 automake 实际上是将 Makefile.am 文件转化为 Makefile.in,然后交给 configure 处理的。

在当今时代,这可能是 configure 最重要的功能,而第二重要的功能则是提供一系列方便好用的参数以供配置时修改,这一点可以大大方便各种发行版的打包(如果你做过某个发行版的开发者肯定为一些不提供 autotools 的软件头痛过);此外更可以自己添加配置选项。

如何自己添加选项?

让 configure 拥有一个类似 --with-foo 的参数:

AC_ARG_WITH([foo], 
	[AS_HELP_STRING([--with-foo], 
		[get fancy features of foo @<:@default=no@:>@])], 
	[], [with_foo=no])

其实第一个参数是 with-xxx 中的 xxx,而 AS_HELP_STRING 则为我们格式化漂亮的帮助文档。第三个参数是如果给定了该参数的行为,第四个是如果没给定时的行为(也就是默认行为)。这里默认时 with-foo 设为 no。这里的 @<:@ 是转义 [,因为 [ 被用为 quote 了。当我们设置了 --with-foo 参数时,就会在 shell 脚本层面定义一个 with_foo 变量。

但是这有什么用呢?我们还需要对其进行处理。这里需要使用 AS_IF。AS_IF 和 ifelse 的用法差不多,但是其并非对两个字符串进行比较,而是运行一段 shell 脚本,如果脚本返回 0,那么代表验证成功,即执行 true-clause。

AS_IF([test "x$with_foo" = xyes], [echo yes!!!], [echo no!!!])

autoconf 后再运行 configure --with-foo=yes 我们就能看到 yes!!!

与 --with-xxx 类似,我们还能定义 --{enable,disable,without}-xxx,其命名均为AC_ARG_{WITHOUT,ENABLE,DISABLE},故不再赘述。一般来说,enable 的是某个外部库引入的功能,而 with 的是本身程序提供的功能。

这里实际上还有另一种更方便的处理方法,详见 automake 章。

如何使变量对 .in 可见?

默认情况下,configure 里面的变量是作为 shell 的内部变量不对 .in 可见的,如果要让我们的变量对 .in 可见,需要调用 AC_SUBST 命令。这里我们把 pathdefs.in 修改为:

--with-foo=@with_foo@

并在 configure.ac 中添加一行 AC_SUBST([with_foo]),我们就能看到效果了。注意,这里的变量替换用的是 @variable@ 格式。

如何检查程序依赖的库?

AC_CHECK_{LIB,HEADERS,FUNCS} 等如今已经很少用,这里从简,详见文档。

但是现代编程又经常使用 pkg-config,我们看看如何使用之。

PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= 3.0])

注意,这里 >= 和两边应有空格,且重新生成 configure 时应先用 aclocal,因为引入了一个新的 m4 命令 PKG_CHECK_MODULES。使用 PKG_CHECK_MODULES,可以毫无压力地解决LDFLAGS、CFLAGS 等麻烦事,并且还能自动把变量传下去,如这里会创建一个 GTK_CFLAGS 和 GTK_LIBS 以供 Makefile 使用。

谈谈 config.h

我们有时候需要用 config.h 给程序提供一些常变的信息,如版本号。我们当然希望这些信息是集中的,比如说集中在 configure.ac 里面,然后通过 config.h 传递给代码。我们选择把 config.h 放在 common 里面。

在 configure.ac 中添加:

AC_CONFIG_HEADER([common/config.h])

然后创建 common/config.h.in,现在你应该驾轻就熟了吧,和普通的 .in 一样!填进去自
己感兴趣的东西即可。

写在 SCons、CMake 等工具的旁边

上回书说道,m4 可以当作一个相当蹩脚的编程语言来用,但是 m4 的强项并不在于控制程序逻辑,而是把一堆乱七八糟的东西不断展开再展开,所以可以用一些简单的宏生成一堆复杂的代码,也就是 configure 脚本。

而一些更现代化的构建工具,比如说 CMake,它的控制脚本叫做 CMakeLists.txt,其实也是一种编程语言,但是与 autotools 不同,要生成代码,你必须安装好 cmake。而 SCons 则更为激进,它直接使用 Python 作为脚本语言。

你可能会问,为什么那么 autotools 相对这些工具还有什么优势?

  1. 正如之前所说,GNOME 系的库全线支持 autotools,简单方便。
  2. autotools 原生支持 make uninstall,这在 cmake 和 scons 中都是要手动完成的!

多絮叨几句

实际上本章已经介绍了不少 configure.ac 构造技术了,借助搜索引擎和文档,你可以写出更大更复杂的 configure.ac。你可能会疑惑我们怎么没有生成出来一个有效的可执行文件,但是这已经不在 configure 范畴内了!下一章我们会看如何在 automake 的帮助下构
建出来可执行文件。

当然,实际上 autoconf 不单单可用于构建程序,试着想想看 autotools 还能用在哪儿?:-)

Category: 技术 | Tags: autotools-tut
7
1
2016
0

autotools 教程:从 m4 说起

m4 是什么?

Autoconf 的基础是一个叫做 m4 的东西。那么 m4 是什么呢?说白了,它类似于 C 语言的预处理器,用来处理宏的。

要看看这玩意是怎么工作的?

kai@debian:~$ m4
define(`OS', `operating system')
⇒
OS
⇒operating system

注意,我输入的是 `' (第一个字符不是半角单引号,而是 ~ 键不按 Shift)而不是 ''。

很简单吧?它将 OS 直接展开成了 operating system,而 C 语言 #define 就差不多:

#define OS operating system
OS /* 展开成 operating system */

m4 对我们有什么用?

要玩转 autoconf,我们不得不了解一下 m4。虽然 configure.ac 看上去很简单,但是里面一些高端玩法都是和 m4 有关的,要进行深度定制也离不开 m4。但是我们有必要学得很深入么?当然没有必要啦,学学跟我们可能有关的就行了。

m4 玩法举例

引用

前引用符号 ` 和后引用符号 ' 扩起来的,就是一个单纯的引用(不知道怎么解释比较好,文档里称 Quotation,注意不是 Reference)。

m4 在遇到 quotation 时不会进行宏展开,这类似于 Lisp,Lisp 遇到 quote 时不会对变量求值。如果一个关键字可以被宏展开,那么 m4 会反复将其展开到不能再展开为止。

注意,因为 m4 有前缀和后缀,所以一般来说,你在其他编程语言中有关 " " 的经验是失效的。比如 ``abc'' 是嵌套引用,应用上述展开规则,首先展开为 `abc',再展开为`abc'。

定义变量

define(`variable', `字符串')
define(`varInt', 100)
define(`DO', `define(`varInt', incr(varInt))Hello $1 varInt')

从鸭子编程(我生造的词laugh)的角度来看,这确实是定义变量,虽然官方声称它是宏。

调用宏

define(`COUNT', 0)
define(`DO', `define(`COUNT', incr(COUNT))Hello $1 varInt')

你看得懂 DO 吗?展开后很易懂,每次调用 DO 会将 COUNT 重新定义为 COUNT + 1,并输出一段字符串 Hello $1 varInt(这里是varInt的值),$1 代表 DO 的第一个参数,如:

DO(hi)
⇒Hello hi 1
DO(mike)
⇒Hello mike 2

有多个参数,用逗号分隔,如果一个宏没有参数,那么依然要加上括号:

macro(arg1, agr2, ..., argn)
macro()

根据你自己的尝试和上面的一些例子,你能否猜出下面的代码是什么意思呢?

define(`foo', `one')
define(foo, `two')

嗯,其实第二句就相当于 define(`one', `two'),如果你会 Tcl,应该很好理解:

set foo one
set $foo two

注释

dnl 这是注释
dnl 这还是注释
dnl 一共三行注释
dnl 上面是骗你的

一些常见的控制结构

测试是否定义宏:

ifdef(`foo', ``foo' is defined', ``foo' is not defined')
⇒`foo' is not defined
define(`foo', `bar')
⇒
ifdef(`foo', ``foo' is defined', ``foo' is not defined')
⇒`foo' is defined

通用的 if 结构:

ifelse(string-1, string-2, equal-1, ..., [not-equal])
[ ] 表示可选参数

什么意思呢?看这个例子:

ifelse(foo, `bar', `$`foo' == bar', `$`foo' != bar')
⇒$foo != bar
define(`foo', `bar')
⇒
ifelse(foo, `bar', `$`foo' == bar', `$`foo' != bar')
⇒$foo == bar

也就是 string-1 和 string-2 相等时,该宏展开成 equal-1,以此类推,如果所有判断都是假,就展开成 not-equal。

循环?m4 是没有的。记得 m4 的求值规则吗?我们完全可以使用递归!这里我们利用 shift 函数(把一个列表的起始项移走)。

shift(1,2,3)
⇒2,3
shift(2,3)
⇒3
shift(3)
⇒
shift()
⇒
define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'',
                          `reverse(shift($@)),`$1'')')
⇒
reverse(1,2,3)
⇒3,2,1

这个例子是从文档里面抄出来的,很恶心吧?那就最好别用。

包含一个文件

include(`xxx.m4')

宏的参数

  • $1 第一个参数
  • $2 第二个参数
  • $3 第三个参数
  • ……
  • $n 第n个参数
  • $@ 所有参数

注意,GNU m4 实现支持任意个参数,但这与 POSIX 不兼容,因此不建议那样使用。

我们再看一个有关引用的小问题

define(`active', `ACT, IVE')
⇒
define(`show', `$1 $1')
⇒
show(active)
⇒ACT ACT
show(`active')
⇒ACT, IVE ACT, IVE
show(``active'')
⇒active active

你能解释上述结果吗?

写在 autoconf 边上

m4 提供了一个叫 changequote 的功能,可以修改 quotation 的前缀后缀,autoconf 就用了这个功能,即前缀后缀分别修改成了 [ ],显得更加清晰简洁。

我们在本章中介绍的 m4 其实已经涵盖了 m4 的核心功能,这也是我们接下来使用 autoconf 的一个坚实基础。如果你对 m4 还有兴趣,可以参考 info m4 阅读官方文档。

其他碎碎念

m4 其实完全可以当作一个编程语言来用,但是其奇妙的用法实在让人难以接受,所以官方将其定位为 macro preprocessor 其实是非常准确的。从编程语言角度来看,相比 Tcl,m4 是一个更为贯彻「一切皆为字符串」信条的,比如上面那个引用小问题……Tcl 是不可能有这种问题的。

Category: 技术 | Tags: autotools-tut
7
1
2016
1

autotools 教程:GNU 构建系统简介

谈谈这个系列

我以前写过一个 10 分钟入门 Autotools 的文章,但是弄丢了,而且困于俗务,又好久没有敲打键盘,因此自己也都不会用 Autotools 了,便计划重新系统地学习一遍。这部教程打算作为学习的产物,以飨大众。计划完整涵盖 autotools 的常见用例,包括 m4, autoconf, automake, intltool, libtool 及多语言混合编程、单元测试,共 7 篇。

这个系列的撰写对我个人而言意义会更大,因为我会尝试在撰写中使用较多自由软件技术,点亮更多技能点。

在该系列中,我会力求阐明 GNU 构建系统的来龙去脉。我的机器使用 Debian Jessie 系统,autotools 各组件版本如下:

  • autoconf 2.69
  • automake 1.14.1
  • libtool 2.4.2
  • intltool 0.50.2

此外,该系列也会尽量从现代视角出发,争取不牵扯到无聊的历史细节中去,使知识可以立即转化为技能。

这部教程假定读者对 Linux 桌面编程有初步的认知,即会使用 C 语言、了解 C 语言程序和库的编译流程、会编写简单的 Makefile、知道程序国际化和本地化的流程和基本工具。

另外,这套教程建议读者先通读一遍,此后再按需查阅,因为其中的例子是随着行文逐渐发展的,如果不通读一遍可能不知道我在写什么。

什么是 GNU 构建系统?

如今,我们已经渐渐遗忘了历史上兵荒马乱的 UNIX 战国时代。在那个时代,各 UNIX 实现有不少出入,要编写可移植程序实属困难,故许多程序存在一个 configure 脚本生成进行一些预先配置。

而现在,我们几乎只需考虑 *BSD(含 Mac OS X)、Linux® 和 Microsoft® Windows®,而前两者又差别不大,因此现在 configure 脚本存在的意义已经不像以前那么大了。

熟悉 *NIX 系统的诸位,不可能对以下流程感到陌生:

./configure --prefix=/usr
make
sudo make install

那么,我们如何在自己的程序中用到这个呢?又如何对其进行定制,使之符合我们自己的心意呢?GNU 构建系统——Autotools 就是自动化该流程的一整套工具。

Autotools 包括以下工具:

  • autoconf —— 生成 configure 脚本
  • automake —— 生成 Makefile
  • libtool —— 处理静态库、动态库之事宜
  • intltool —— 将你的程序国际化(英文叫做 internationalization,因为太长所以缩
    写为 i18n)

另外,还有 autoscan 之类的小工具,因为用处不大,所以本教程不予使用。

我们将通过例子,一个一个考察这些工具。下面是一幅 Autotools 关系图:

(该图形由 GraphViz 绘制)

说真的,autotools 到底有什么用?

这个要列出来,真的还挺多的。

  1. 用户熟悉的操作流程
  2. 完善的国际化支持
  3. 完善的动态与静态库支持
  4. 肉眼可读、易于编辑的文件格式(与之相对,没有太好的 IDE 支持)
  5. 可以极简单地实现交叉编译

如果你是 GNOME 开发者,你应该知道,GNOME 的所有程序都是 Autotools 构建的,所有库都对 Autotools 有完善的支持,用起来非常方便。

其他声明

  • 「Linux®」是林纳斯·托瓦兹的注册商标。
  • 「Microsoft®」和「Windows®」是微软公司的注册商标。
Category: 技术 | Tags: autotools-tut

Powered by Chito | Hosted at is-Programmer | Theme: Aeros 2.0 by TheBuckmaker.com