分类:
2008-03-19 16:48:15
如果您的 UNIX 系统中缺少某种所需的工具,那么您也许可以在可联机获取的各种各样的软件中找到适当的解决方案。这个月,让我们来学习如何从源代码构建软件。
UNIX 系统中提供了数百个实用工具应用程序或者命令。其中一些命令可以操作文件系统,而其他的命令则用于查询并控制操作系统本身。大量的命令提供了连接性,并且还有更丰富的命令可用于生成、交换、修改、筛选和分析数据。由于 UNIX 具有悠久而丰富的历史,所以您也许能够找到恰好合适的工具,以用于手头的任务。
此外,当一个实用工具不能满足需求时,您可以通过各种各样的方式结合任何数量的 UNIX 实用工具,以创建您自己的工具。正如您在前面的部分中所看到的,可以利用管道、重定向和条件,直接在命令行中构建即时可用的工具,并且 Shell 脚本将小型的、易于学习的编程语言的强大功能与 UNIX 命令结合在一起,以构建可重用工具。
当然,在很多情况下,仅依靠命令行和 Shell 脚本是不够的。例如,如果您必须部署一个新的守护进程以提供新的网络服务,那么您可以使用一种表达能力更强的语言,如 C 或者 Python,以便自己编写应用程序。并且,因为 Internet 上有许多应用程序是免费的(免费 意味着无需支付任何费用、得到自由条款的许可,或者两者都有),所以您还可以下载、编译并安装适当的、有效的解决方案,以满足您的需求。
UNIX(以及 Linux)的许多版本都提供一种称为包管理器 的特殊工具,用以在系统中添加、删除和维护软件。包管理器通常可以维护本地安装的所有软件的详细目录,以及一个或者多个远程存储库 中所有可用软件的目录。您可以使用包管理器在存储库中搜索您所需要的软件。如果存储库中包含您正在寻找的软件,那么您只需要使用一个命令或者点击几下鼠标,就可以在您的系统中安装一个新的包。
包管理器是非常有价值的。使用它,您可以删除全部的包、更新现有的包,以及为任何包自动地检测并实现任何先决条件。例如,如果您选择了操作图像的软件,如可靠的 ImageMagick,但是您的系统中缺少处理 JPEG 图像的库,那么包管理器将在安装您所选择的包之前检测并安装缺少的内容。
然而,也可能存在这样的情况,即您所需要的软件是可获得的,但它却不包含于任何存储库中。由于包管理方式具有显著的优势,所以大多数软件都提供了可以使用包管理器进行下载并安装的形式。然而,因为 UNIX 的版本和风格非常之多,所以很难针对每种特定的变体,以各种包管理器的格式提供每个应用程序。如果您的 UNIX 安装是主流的,并受到大量拥护者的喜爱,那么您将更有可能找到预先构建的并且可供使用的软件。否则,您就需要挽起袖子准备自己动手构建软件了。
是的,年轻的绝地武士(《星球大战》中的武士),是使用源代码的时候了。
如同从沼泽中升起一架 X 翼战斗机一样,从源代码构建软件乍看起来可能是令人生畏的,特别当您不是软件开发人员的时候。事实上,在大多数情况下,整个构建过程仅仅只需要少数几条命令,其余的工作都是自动完成的。
当然,某些程序构建起来是非常复杂的(或者需要花费数小时来进行构建),并且在构建过程中需要进行人工介入。然而,即使这些程序通常是由一些容易构建的较小的块构造而得到的,依赖关系的数量和构造的顺序也会使构建过程变得复杂。一些程序还有许多您并不一定希望拥有的特性。例如,您可以构建 PHP,以便与新的网际协议版本 6 (IPv6) Internet 寻址方案进行互操作。如果您的网络尚未采用 IPv6,则不需要包括这个特性。对大量选项进行的审查将使构建过程变得更加麻烦。
这个月,让我们来研究如何构建一个典型的 UNIX 软件应用程序。在继续学习后面的内容之前,请确保系统中安装了 C 编译器,如 GNU 编译器套装(GNU Compiler Collection,GCC),以及常见的 UNIX 软件开发工具套装,包括 make、m4、pkg-config 和 awk。此外,请确保在您的 PATH 环境变量中包含了所有的开发工具。
软件包中有价值的内容
作为一个说明性的和典型的示例,让我们配置、构建并安装 SQLite——一个实现结构化查询语言(Structured Query Language,SQL)数据库引擎的小型的库。SQLite 不需要进行任何配置即可使用,并且可以完整地嵌入到任何应用程序中,而数据库则包含在单个文件中。许多编程语言都可以调用 SQLite 以实现数据的持久化。SQLite 还包括一种用于管理 SQLite 数据库的、名为 sqlite3 的命令行实用工具。
要开始学习这部分内容,首先下载 SQLite(请参见参考资料)。选择最新的源代码包,并将其下载到您的计算机中。(在撰写本文时,SQLite 的最新版本是版本 3.3.17,于 2007 年4 月 25 日发布。)这个示例使用了 中存储的文件。
在您获得了该文件之后,请对其进行解压缩。.tar.gz 扩展反映了该存档文件是如何构造的。在这个示例中,它是一个压缩了的 tar 存档文件。后面的扩展 .gz,表示 gzip(压缩);前面的扩展 .tar,表示 tar(一种存档格式)。要提取该存档文件的内容,只需要对其进行反向处理即可,也就是首先解压缩,然后打开该存档文件:
$ gunzip sqlite-3.3.17.tar.gz
$ tar xvf sqlite-3.3.17.tar
这两个命令在一个名为 sqlite-3.3.17 的新目录中创建了原始源代码的一个副本。顺便说明一下,.tar.gz 文件格式是非常常见的(称为 tarball),并且您可以使用 tar 命令直接解压缩 tarball 文件:
$ tar xzvf sqlite-3.3.17.tar.gz
这一个命令和前面的两个命令是等价的。
接下来,将目录更改为 sqlite-3.3.17,并使用 ls,以列出其中的内容。您应该看到与清单 1 所示类似的清单:
清单 1. SQLite 包的清单
$ ls
Makefile.in contrib publish.sh
Makefile.linux-gcc doc spec.template
README ext sqlite.pc.in
VERSION install-sh sqlite3.1
aclocal.m4 ltmain.sh sqlite3.pc.in
addopcodes.awk main.mk src
art mkdll.sh tclinstaller.tcl
config.guess mkopcodec.awk test
config.sub mkopcodeh.awk tool
configure mkso.sh www
configure.ac notes
其中的源代码和 SQLite 补充文件经过了很好组织,并且模拟了大部分的软件项目分发源代码的方式:
README 文件对该项目进行了描述,并且通常用于说明如何构建该软件。(README 文件还详细地介绍了使用条款,或者许可证、适用情况。许多项目的许可证代码都符合 GNU 公共许可版本 2 中的条款,即所谓的“copyleft”许可证。在许可证与您打算如何使用该软件之间可能存在一定的冲突,如果您对此有任何疑问,最好请教一下合适的法律顾问。)
src 目录中包含了相关的代码。
test 目录中包含了一组测试,以验证该软件的操作是否正确。在开始构建或者进行了任何修改之后,请运行这些测试,这样可以增加对该软件的信心。
contrib 目录中包含核心 SQLite 开发团队所没有提供的附加软件。对于像 SQLite 这样的库,contrib 中可能包含一些常用语言(如 C、Perl、PHP 和 Python)的编程接口。它可能还包括图形用户界面(GUI)包装,以及更多的内容。
在其他文件中,Makefile.in、configure、configure.ac 和 aclocal.m4 用于生成在您的 UNIX 版本中编译 SQLite 软件的脚本和规则。如果这个软件足够简单,那么要编译其代码,可能只需要一条简单的编译命令即可。但是,因为存在如此之多的 UNIX 变种(Mac OS X、Solaris、Linux、IBM AIX 和 HP/UX 等等),所以必须对宿主计算机进行分析,以确定它的功能及其实现。例如,邮件阅读应用程序可能会尝试确定本地系统是如何存储邮箱的,并包含对该格式的支持。
集中精力感受源代码
下一个步骤是探查系统并配置该软件,以便进行正确地构建。(您可以将这个步骤想象为裁剪一件衣服:这件衣服大小基本合适,但是需要进行一定的修改以使其更加时尚。)您需要进行自定义工作,并为使用 ./configure 本地脚本进行编译做好准备。在命令行提示处,键入: $ ./configure
这个配置脚本进行了几项测试,以证明您的系统是合格的。例如,在一台 Apple MacBook 计算机上(其中运行着 FreeBSD UNIX 的一个变种)运行 ./configure,将产生以下结果(请参见清单 2):
清单 2. 在 Mac OS X 上运行 ./configure 的结果
checking build system type... i386-apple-darwin8.9.1
checking host system type... i386-apple-darwin8.9.1
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
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
checking for a sed that does not truncate output... /usr/bin/sed
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ld used by gcc... /usr/bin/ld
...
这里,./configure 可以确定编译和主机系统的类型(如果您采用交叉编译的话,它可能不同),证实是否已经安装了 GNU C 编译器(GCC),并找到其余构建过程可能需要使用的实用工具的路径。您可以浏览一下其余的输出,但是您将看到一个较长的诊断信息列表,该列表从成功构造 SQLite 的角度分析了您的系统的特点。
注意: ./configure 命令可能 会失败,特别是在无法找到一个先决条件的情况下(比如一个系统库或者关键的系统实用工具)。
浏览 ./configure 的输出,寻找其中不正常之处,如命令的专用或者本地版本,它可能并不适合于构建通用的应用程序,如 SQLite。作为一个示例,如果您的系统管理员安装了 GCC 的 alpha 版本,并且 configure 工具首选使用该版本,那么您可以选择手动地改写这个选择。要查看您可以改写的选项的列表(该列表通常很长),可以键入 ./configure --help,如清单 3 中所示:
清单 3. 用于 ./configure 脚本的通用选项
$ ./configure --help
...
By default, `make install' will install all the files in
`/usr/local/bin', `/usr/local/lib' etc. You can specify
an installation prefix other than `/usr/local' using `--prefix',
for instance `--prefix=$HOME'.
For better control, use the options below.
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sbindir=DIR system admin executables [EPREFIX/sbin]
--libexecdir=DIR program executables [EPREFIX/libexec]
...
./configure --help 的输出中包括配置系统使用的通用选项,以及仅与您正在构建的软件相关的特定选项。要查看后者(较短)的列表,可以键入 ./configure --help=short(请参见清单 4):
清单 4. 要构建的软件包所特定的选项
$ ./configure --help=short
Optional Features:
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-shared[=PKGS] build shared libraries [default=yes]
--enable-static[=PKGS] build static libraries [default=yes]
--enable-fast-install[=PKGS]
optimize for fast installation [default=yes]
--disable-libtool-lock avoid locking (might break parallel builds)
--enable-threadsafe Support threadsafe operation
--enable-cross-thread-connections
Allow connection sharing across threads
--enable-threads-override-locks
Threads can override each others locks
--enable-releasemode Support libtool link to release mode
--enable-tempstore Use an in-ram database for temporary tables
(never,no,yes,always)
--disable-tcl do not build TCL extension
--disable-readline disable readline support [default=detect]
--enable-debug enable debugging & verbose explain
返回到 ./configure --help,最顶部的输出显示了可执行文件的缺省安装目录是 /usr/local/bin,库文件的缺省安装目录是 /usr/local/lib,等等。许多系统使用一个替代的层次结构来存储非核心软件。
例如,许多系统管理员选择使用 /opt 而不是 /usr/local 存储本地添加的或者在本地进行了修改的软件。如果您希望将 SQLite 安装到与缺省目录不同的其他目录中,可以使用 --prefix= 选项指定该目录。一种可行的方法(也是一种常见的方法,如果只有您一个人使用这个软件包,或者如果您没有 root 访问权限以便在全局的范围内安装该软件)是将该软件安装到您的 home 目录中的层次结构中: $ ./configure --prefix=$HOME/sw
使用这个命令,构建过程的安装部分将在 $HOME/sw 中(比如 $HOME/sw/bin、$HOME/sw/lib、$HOME/sw/etc、$HOME/sw/man,以及其他所需的目录中)重新创建该软件的层次结构。为了简单起见,这个示例在缺省目标处安装其代码。
编译代码
./configure 的结果是一个与您的 UNIX 版本兼容的 Makefile。名为 make 的开发实用工具将使用这个 Makefile,以执行编译所需的步骤,并将代码链接到一个可执行文件。您可以打开这个 Makefile 对其进行检查,但不要对它进行编辑,因为如果您再次运行 ./configure,它将列出您所做的任何修改。
这个 Makefile 中包含需要编译的源文件的列表,并且它还包括启用或者禁用并选择 SQLite 包中的某些代码片段的常数。例如,如果 configure 工具检测到了系统中合适的芯片,那么它可能会启用 64位处理器特定的代码。这个 Makefile 还说明了源文件之间的依赖关系,因此在一个非常重要的头文件 (.h) 中进行的一项更改,可能会导致重新编译所有的 C 源代码。
您的下一个步骤是运行 make,以构建该软件(请参见清单 5):
清单 5. 运行 make
$ make
sed -e s/--VERS--/3.3.17/ ./src/sqlite.h.in | \
sed -e s/--VERSION-NUMBER--/3003017/ >sqlite3.h
gcc -g -O2 -o lemon ./tool/lemon.c
cp ./tool/lempar.c .
cp ./src/parse.y .
./lemon parse.y
mv parse.h parse.h.temp
awk -f ./addopcodes.awk parse.h.temp >parse.h
cat parse.h ./src/vdbe.c | awk -f ./mkopcodeh.awk >opcodes.h
./libtool --mode=compile --tag=CC gcc -g -O2 -I. -I./src \
-DNDEBUG -I/System/Lib rary/Frameworks/Tcl.framework/Versions/8.4/Headers \
-DTHREADSAFE=0 -DSQLITE_THREA D_OVERRIDE_LOCK=-1 \
-DSQLITE_OMIT_LOAD_EXTENSION=1 -c ./src/alter.c
mkdir .libs
gcc -g -O2 -I. -I./src -DNDEBUG \
-I/System/Library/Frameworks/Tcl.framework/Vers ions/8.4/Headers \
-DTHREADSAFE=0 -DSQLITE_THREAD_OVERRIDE_LOCK=-1 \
-DSQLITE_OMIT_L OAD_EXTENSION=1 -c ./src/alter.c -fno-common \
-DPIC -o .libs/alter.o
...
ranlib .libs/libtclsqlite3.a
creating libtclsqlite3.la
注意: 在上面的输出中,添加了一些空白行,以便更好地突出显示 make 发起的每个步骤。
make 实用工具检查文件(头文件、源代码、数据文件和目标文件)的修改日期,并编译合适的 C 源文件。最初,make 将重新编译所有内容,因为不存在任何目标文件或者编译目标。正如您可以看到的,用于编译目标的规则还包括一些中间步骤,其中使用了一些相关的工具,如 sed 和 awk,以产生在后续的步骤中将要使用的头文件。
执行 make 命令所得到的结果是一个完成的库和 sqlite3 实用工具。
最好对您刚编译的软件进行测试,尽管这在每个包中并不是强制的,也没有提供相应的内容。即便成功地构建 了您的软件,也不一定就表示该软件能够正确地运行。
要测试您的软件,可以再次运行 make,并使用 test 选项(请参见清单 6):
清单 6. 对软件进行测试
$ make test
...
alter-1.1... Ok
alter-1.2... Ok
alter-1.3... Ok
alter-1.3.1... Ok
alter-1.4... Ok
...
Thread-specific data deallocated properly
0 errors out of 28093 tests
Failures on these tests:
成功了!该软件构建成功,并且工作正常。如果其中一个或者多个测试用例失败了,那么底部的总结(这里,它是空白的)将向您报告哪一项测试或者哪几项测试需要进一步研究。
完成后的产品
如果您的软件工作正常,那么最后一个步骤是将它安装到您的系统中。同样,使用 make,并指定 install 目标。要将软件添加到 /usr/local,通常需要由 sudo 所提供的超级用户(root)权限(请参见清单 7):
清单 7. 在您的本地系统中安装软件
$ sudo make install
tclsh ./tclinstaller.tcl 3.3
/usr/bin/install -c -d /usr/local/lib
./libtool --mode=install /usr/bin/install
-c libsqlite3.la /usr/local/lib /usr/bin/install
-c .libs/libsqlite3.0.8.6.dylib /usr/local/lib/libsqlite3.0.8.6 .dylib
...
/usr/bin/install -c .libs/libsqlite3.lai /usr/local/lib/libsqlite3.la
/usr/bin/install -c .libs/libsqlite3.a /usr/local/lib/libsqlite3.a
chmod 644 /usr/local/lib/libsqlite3.a
ranlib /usr/local/lib/libsqlite3.a
...
/usr/bin/install -c -d /usr/local/bin
./libtool --mode=install /usr/bin/install -c sqlite3 /usr/local/bin
/usr/bin/install -c .libs/sqlite3 /usr/local/bin/sqlite3
/usr/bin/install -c -d /usr/local/include
/usr/bin/install -c -m 0644 sqlite3.h /usr/local/include
/usr/bin/install -c -m 0644 ./src/sqlite3ext.h /usr/local/include
/usr/bin/install -c -d /usr/local/lib/pkgconfig;
/usr/bin/install -c -m 0644 sqlite3.pc /usr/local/lib/pkgconfig;
make install 过程创建了所需的目录(如果它们都不存在的话),将文件复制到目标,并运行 ranlib,以便为应用程序准备好要使用的库。它还将 sqlite3 实用工具复制到 /usr/local/bin,复制开发人员使用 SQLite 库构建软件时所需的头文件,并将文档复制到该层次结构中适当的位置。
假定 /usr/local/bin 在您的 PATH 变量中,那么您现在就可以运行 sqlite3(请参见清单 8):
清单 8. SQLite 已经可以使用
$ which sqlite3
/usr/local/bin/sqlite3
$ sqlite3
SQLite version 3.3.17
Enter ".help" for instructions
sqlite>
对初学者的建议?
与 SQLite 一样,大多数软件包构建起来都非常容易。实际上,您通常只需要使用一个命令就可以配置、构建和安装该软件:
$ ./configure && make && sudo make install
仅当前面的命令正确完成时,&& 操作符才会运行后面的命令。因此,以上的命令相当于“运行 ./configure,如果它运行成功的话,那么运行 make,如果运行成功的话,运行 sudo make install。”这条命令构建了一个无人参与的包。启动它,然后去喝杯咖啡、来份三明治,或者享用一份套餐,这取决于您正在构建的包的大小和复杂程度。
对于从源代码构建软件,还有一些其他有价值的技巧:
如果您正在构建的软件包使用典型的 ./configure && make && sudo make install 还不够,那么请记录下编译代码所采取的步骤。如果您必须重新编译相同的代码,或者编译该代码更新的版本,那么就可以借助您的记录,回忆起所需采取的步骤。将这个记录存储在与该包的 README 文件相同的目录中。您甚至可以为该记录的文件名采用一种约定,这将使您可以更容易地识别以前所编译的内容。
然而更有价值的是,如果构建该软件所需的步骤是无需手工干预就可重复执行的,那么可以在 Shell 脚本描述这个过程。稍后,如果您必须重新编译相同的代码,只需要运行这个 Shell 脚本即可。如果该代码有更新的版本可供使用,那么您可以根据需要对这个脚本进行修改,以添加、更改或者删除其中的步骤。
在安装完软件之后,通过使用 make clean,您就可以收回磁盘空间。这个规则通常会删除目标文件和任何中间文件,但是它将完整地保留重新启动该过程所需的文件。另一个规则 make distclean,可以删除 Makefile 和生成的其他文件。
分开保存相同代码的不同版本源代码。这种方法允许您对不同的发布版本进行比较,除此之外,它还允许您恢复软件的特定版本。将源代码组织到一个本地存储库中,比如 $HOME/src 或者 /usr/local/src,这取决于您的使用范围(个人或者全局)以及您的本地约定。
而且,您可以通过使得源代码在全局范围内成为只读的,来防止意外地删除或者覆盖。更改到您希望进行保护的源代码目录,并运行 chmod -R a-w * 命令(递归地运行 chmod,关闭所有的写权限)。
最后,可能出现这样的情况,源代码无法在您的系统中进行编译。如前所述,最常出现的问题是缺少某些先决条件。请仔细阅读错误消息或者相关的消息,可以清楚地看到究竟什么地方出了错。
如果您无法找到错误原因,那么可以在 Google 中键入确切的错误消息,以及您正在尝试编译的包的名字。其他的人很有可能也碰到并解决过相同的问题。(事实上,在 Internet 中搜索错误消息可能是非常具有启发性的,尽管您可能需要花费少许时间以挖掘出真正有用的信息。)
如果您遇到困难,可以查看该软件的主页,获取有关参考资料的链接,如 IRC 频道、新闻组、邮件列表或者 FAQ。本地系统管理员也同样具有丰富的经验。
源代码的功能是非常强大的
如果系统中缺少您所需要的某种工具,那么您可以随意地在命令行中创建一个,您可以编写 Shell 脚本,也可以编写您自己的程序,并且还可以借鉴联机查找的大量代码。您可以像我一样,不断地锻炼绝地武士的意识技能。