原文地址:Advanced GTK Techniques。
这篇教程中你将学会:
- 生成指定那些文件参加编译的
Makefile
文件;
- 检查程序所需库。
这篇文章是《如何开始一个真正的 GTK 项目》的一部分,如果你不想回看之前的章节,可以直接下载教学示例程序
app-skeleton1
。你也可以从头开始。
现在我们把注意力放在 Automake
上,这是编译系统中另一个重要成员。我们配置好 Automake
后,就可以开始编写代码。
Make
是一个程序,它从文件 Makefile
中读取如何编译源代码的指令,然后将代码转换成可执行文件。Makefile
的内容取决于使用何种编译器,何种系统,以及很多其它事项。如果你希望实现在之前章节描述的所有标准 make target
内容,那么 Makefile
将会变得很长。
这便是引入 Automake
的原因。Automake
可以让这一切更加简洁、抽象,并且不受平台的限制。Automake
会寻找一个名为 Automake.am
的文件,将其转译为一个类似于 Makefile
的文件:Makefile.in
。随后,在运行 configure
时,它会最终转化为 Makefile
。
创建 Makefile.am
我们首先完整拷贝一份 app-skeleton1
目录,将它命名为 app-skeleton2
,或者直接重命名目录亦可。我们当前只需要在 Makefile.am
中添加一行:
# app-skeleton2/Makefile.am
SUBDIRS = src
SUBDIRS
变量告诉 Automake
需要在子目录 src
中查找另外一个 Makefile.am
。最终生成的 Makefile
也会相应地在 src
中查找另外一个 Makefile
。
Make 的递归
Peter Miller 曾有一篇著名的文章给出了对编写递归
Makefile
的看法:《递归编写 Make 是有害的》。建议阅读一下这篇文章,也可以听取其中一些你认为有用的观点。他的实现方法并不比本文的方法简单多少。事实上,在实际应用中,大多数工程使用了递归式的Make
,所以至少你应当熟悉这种做法。
我们已经告知了 Automake
进入 src
目录,那么我们也应该在目录中准备一些让它能用得上的东西。创建一个 Makefile.am
是肯定的,此外还需要一些更重要的东西 —— 源代码文件。在下一章节我们才会开始正式的编程工作,现在可以先找个差不多的文件凑数。从 GTK 官方教程中拿来 “Hello World” 的例程不失为简单快捷的办法,我们不需要从网页中手动复制,只需要 cd src
后拷贝一份即可:
wget http://git.gnome.org/browse/gtk+/plain/examples/hello-world.c
现在我们在 src
中创建 Makefile.am
,并在文件中输入以下内容:
#app-skeleton2/src/Makefile.am
bin_PROGRAMS = app-skeleton
app_skeleton_SOURCES = hello-world.c
Automake
的主要工作是设定变量,SUBDIRS
是其中一个案例。很多 Automake
变量的名称由两部分组成,举个例子,一个名为 something_PROGRAMS
的变量表示一个列表,包含了需要由 Automake
生成的可执行文件的名称。something
表示运行 make install
时程序的安装位置,所以 bin_PROGRAMS = app-skeleton
说明了 Automake
生成的 Makefile
将把 app-skeleton
安装在 /usr/local/bin
中(你也可以把 bin
换成其它名称,Makefile
可通过 configure
灵活定制,这一点可参阅之前的教程)。
顺着这种命名的思路,app_skeleton_SOURCES = hello-world.c
表示名为 app-skeleton
的程序编译依赖的源文件为 hello-world.c
,也就是我们刚刚下载的文件(如果程序名包含了变量不允许的字符,Automake
将把它变为下划线)。
我们还需要让 configure
帮我们把刚才编写好的文件转换成一个新的 Makefile
。我们把 configure.ac
中的 AC_CONFIG_FILES
替换成:
#app-skeleton2/configure.ac
AC_CONFIG_FILES([
Makefile
src/Makefile
])
最后,我们还需要指派 configure
去找一个合适的 C 编译器,在 AM_INIT_AUTOMAKE
后面加上一句 AC_PROG_CC
即可(CC
表示 C Compiler,Linux 中一般为为 gcc
)。
现在,运行 autoreconf
和 ./configure
。configure
的输出将比之前的略长(你可以看到它在寻找一个 C 编译器),并且会有更多文件产生。到目前为止,一切运行良好。不过当你运行 make
时,会收到满屏的错误:所有的 GTK 函数都未定义,编译器也找不到 gtk/gtk.h
头文件。
引入 GTK 库
我们需要指定哪些库将被程序使用。这个工作在 configure.ac
中完成,然后 configure
会去查找这些库并将它们的变量放入 Makefile
中。借此机会,我们可以重新组织 configure.ac
,还能学习一些新的宏。
现在将 configure.ac
分成四个部分:
初始化:完成初始化编译系统的准备工作;
工具箱:告诉
Autoconf
我们需要使用哪些工具。configure
会帮我们查找这些工具,如果没有找到便会报错;库:在此列出需要用到的库。同样地,如果
configure
没有找到,它就会报错;输出:输出上述检测的结果以供
make
使用。
注释
你可以在
configure.ac
或者Makefile.am
中使用注释,在一行的开头输入 “#” 即可。configure.ac
中也可以为在一行开头输入 “dnl”(Delete until New Line)。二者的区别在于 “dnl” 会被Autoconf
完全忽略,而 “#” 会被一同拷贝进configure
文件中。当configure
中出现错误时,注释有助于我们在configure.ac
中快速定位引起错误的部分。
在“初始化”部分,输入:
# app-skeleton2/configure.ac
AC_INIT([App Skeleton], [2], [[email protected]])
AC_CONFIG_SRCDIR([src/hello-world.c])
AM_INIT_AUTOMAKE([-Wall foreign])
AM_SILENT_RULES([yes])
我们将版本号改为 2,此外还有两个新的宏:
-
AC_CONFIG_SRCDIR
这是一个安全行检查,用于确认其所指定位置确有一个
hello-world.c
。我们需要把它的参数设定为项目中一个独一无二的代码文件,这里我们写上目前唯一的代码文件。 -
AM_SILENT_RULES
生成
Makefile
的过程通常产生非常长的信息,我们一般不需要看这么多,而且大量无用的信息刷屏可能会让你忽视夹在其中的警告和错误信息。AM_SILENT_RULES([yes])
将屏蔽这些消息,只输出一些总结信息。一个真正的程序员可能会使用AM_SILENT_RULES([no])
,然后运行./configure --enable-silent-rules
来保证政治正确性。如果你开启了静默模式,但还是希望能检查所有输出信息,可以使用
make V=1
命令(V
表示 Verbose)。
在“工具箱”部分,输入
# app-skeleton2/configure.ac
AC_PROG_CC
PKG_PROG_PKG_CONFIG
这里有一个新的宏 PKG_PROG_PKG_CONFIG
,它会在项目中加入用于检查和引用库的工具 —— pkg-config
。这个宏并非 AC_
或者 AM_
开头,而是 PKG_
,说明这是一个由 pkg-config
提供的功能。
在“库”部分,我们用 pkg-config
引入我们需要的库:
# app-skeleton2/configure.ac
PKG_CHECK_MODULES([APP_SKELETON], [
glib-2.0
gtk+-3.0
])
PKG_CHECK_MODULES
的第一个参数 APP_SKELETON
为程序名,第二个参数列出了一系列 pkg-config 模块
。一个模块代表了 pkg-config
可以识别的一个库。这个宏使用 pkg-config
查找计算机中的库,然后生成两个变量:APP_SKELETON_CFLAGS
—— 包含了库的引用信息(类似于 gcc
中的 -I
参数);APP_SKELETON_LIBS
—— 包含了库的链接信息(类似于 gcc
中的 -L
和 -l
参数)。这些变量可用于 Makefile
的编辑。
这个宏表示我们将使用 glib-2.0
和 gtk+-3.0
两个库。如果你不知道需要用到库的模块名,可以到 /usr/share/pkgconfig
和 /usr/lib/pkgconfig
中查找以 .pc
结尾的文件(译者注:也可以在终端中输入 pkg-config --list-all 查看
)。一些库并不能用 pkg-config
引入,而是需要其它的方法,我们在这里不做介绍。
最后的“输出”部分与之前相同:
# app-skeleton2/configure.ac
AC_CONFIG_FILES([
Makefile
src/Makefile
])
AC_OUTPUT
全部搞定后,我们在 src/Makefile.am
中使用由 pkg-config
生成的 APP_SKELETON_CFLAGS
和 APP_SKELETON_LIBS
变量:
# app-skeleton2/src/Makefile.am
AM_CFLAGS = $(APP_SKELETON_CFLAGS)
bin_PROGRAMS = app-skeleton
app_skeleton_SOURCES = hello-world.c
app_skeleton_LDADD = $(APP_SKELETON_LIBS)
AM_CFLAGS
中的定义将会应用到这个 Makefile.am
文件里的所有编译指令中。相反地,app_skeleton_LDADD
变量只会在链接 app-skeleton
程序时被使用。
译者注:原文作者使用了简便的引用库方法:将所有用到的库绑定到
APP_SKELETON
变量中。其实,更为稳妥的做法应该为将每个库分离对待,这样有利于模块的灵活调用。具体做法为修改configure.ac
中的PKG_CHECK_MODULES
宏:
#app-skeleton2/configure.ac
PKG_CHECK_MODULES([GLIB], [
glib-2.0
])
PKG_CHECK_MODULES([GTK], [
gtk+-3.0
])
然后修改
src/Makefile.am
中相关的变量:
# app-skeleton2/src/Makefile.am
bin_PROGRAMS = app-skeleton
app_skeleton_SOURCES = hello-world.c
app_skeleton_CFLAGS = \
$(GLIB_CFLAGS) \
$(GTK_CFLAGS)
app_skeleton_LDADD = \
$(GLIB_LIBS) \
$(GTK_LIBS)
在大型项目中往往会生成多个可执行文件或库文件,这样也利于分别为每个目标设定
cflags
和libs
标签,更加清晰易读。
现在我们再次编译,这次应该就能正常工作了。注意这次编译我们只需要输入 make
即可,Makefile
(现在是 22 KB 了)会在检测到 configure.ac
被修改后自动执行 ./configure
,同时也生成了新的自己。
你现在可以运行 app-skeleton
来看看程序的效果:一个包含了一个按钮控件的窗口,按钮上写着“Hello World”,按下之后会在终端打印“Hello World”并退出。
现在我们已经有了像模像样的编译系统,接下来还有一些基础工作等待着我们去完成:安装和翻译。
文章许可协议:Attribution-NonCommercial-ShareAlike 3.0 Unported