checkinstall make install


运行此命令会显示与您即将构建的软件包相关的设置,并让您有机会更改它们.当您继续安装时,`checkinstall`会跟踪所有将要安装到系统中的文件,并将它们放入一个`.deb`文件中.然后您可以使用`dpkg`来安装(和删除)新软件包.

创建RPM包稍微复杂一些,因为您首先必须为您的软件包创建一个目录树.您可以使用`rpmdev-setuptree`命令完成此操作;完成后,您可以使用`rpmbuild`实用程序完成其余步骤.最好为此过程参考在线教程.

# 第16章:从C源码编译软件

## 16.3.3 configure 脚本选项

你刚刚看到了 `configure` 脚本最有用的选项之一:使用 `--prefix` 指定安装目录.默认情况下,由 autoconf 生成的 Makefile 中的 `install` 目标使用的前缀是 `/usr/local`——也就是说,二进制程序安装在 `/usr/local/bin`,库安装在 `/usr/local/lib`,以此类推.你通常需要像这样更改前缀:

```bash
$ ./configure --prefix=new_prefix

大多数版本的 configure 都有一个 --help 选项,列出其他配置选项.不幸的是,列表通常非常长,有时很难找出哪些选项是重要的,因此这里列出一些基本选项:

  • --bindir=directory —— 将可执行文件安装到指定目录.
  • --sbindir=directory —— 将系统可执行文件安装到指定目录.
  • --libdir=directory —— 将库安装到指定目录.
  • --disable-shared —— 阻止软件包构建共享库.取决于库的类型,这可以避免日后的麻烦(参见 15.1.3 共享库).
  • --with-package=directory —— 告诉 configure 某个软件包位于指定目录.当某个必需的库位于非标准位置时,这非常有用.不幸的是,并非所有 configure 脚本都识别此类型的选项,且准确语法可能难以确定.

使用分离的构建目录

如果你想试验其中一些选项,可以创建分离的构建目录.为此,在系统上任意位置创建一个新目录,然后在该目录中运行原始软件包源码目录中的 configure 脚本.你会发现 configure 随后在你的新构建目录中创建一个符号链接农场,其中所有链接都指向原始软件包目录中的源码树.(一些开发者偏好以这种方式构建软件包,因为原始源码树从未被修改.如果你希望使用同一源码包为多个平台或配置选项集构建,这也非常有用.)

16.3.4 环境变量

你可以通过 configure 脚本放入 make 变量的环境变量来影响 configure.最重要的环境变量是 CPPFLAGSCFLAGSLDFLAGS.但请注意,configure 对环境变量可能非常挑剔.例如,头文件目录通常应使用 CPPFLAGS 而不是 CFLAGS,因为 configure 经常独立于编译器运行预处理器.

在 bash 中,将环境变量传递给 configure 的最简单方法是在命令行上 ./configure 之前放置变量赋值.例如,要为预处理器定义 DEBUG 宏,使用以下命令:

$ CPPFLAGS=-DDEBUG ./configure

你也可以将变量作为选项传递给 configure,例如:

$ ./configure CPPFLAGS=-DDEBUG

configure 不知道在哪里查找第三方包含文件和库时,环境变量特别方便.例如,要使预处理器在 include_dir 中搜索,执行以下命令:

$ CPPFLAGS=-Iinclude_dir ./configure

如第 15.2.6 节所示,要使链接器在 lib_dir 中查找,使用以下命令:

$ LDFLAGS=-Llib_dir ./configure

如果 lib_dir 包含共享库(参见 15.1.3 共享库),上述命令可能不会设置运行时动态链接器路径.在这种情况下,除了 -L 之外,还需要使用 -rpath 链接器选项:

$ LDFLAGS="-Llib_dir -Wl,-rpath=lib_dir" ./configure

设置变量时要小心.一个小疏忽就可能使编译器出错并导致 configure 失败.例如,假设你忘记了 -I 中的 -,如下所示:

$ CPPFLAGS=Iinclude_dir ./configure

这会产生类似以下的错误:

configure: error: C compiler cannot create executables
See 'config.log' for more details

查阅此失败尝试生成的 config.log,会发现如下内容:

configure:5037: checking whether the C compiler works
configure:5059: gcc  Iinclude_dir  conftest.c  >&5
gcc: error: Iinclude_dir: No such file or directory
configure:5063: $? = 1
configure:5101: result: no

NOTE

设置环境变量时务必小心,一个微小的拼写错误就足以导致配置失败.

16.3.5 Autoconf 目标

一旦 configure 正常工作,你会发现它生成的 Makefile 除了标准的 allinstall 之外,还有几个有用的目标:

  • make clean —— 如第 15 章所述,移除所有目标文件、可执行文件和库.
  • make distclean —— 类似于 make clean,但会移除所有自动生成的文件,包括 Makefile、config.h、config.log 等.其理念是在运行 make distclean 后,源码树应看起来像新解压的发行版.
  • make check —— 某些软件包附带一套测试,用于验证编译后的程序是否正常工作;命令 make check 会运行这些测试.
  • make install-strip —— 类似于 make install,但在安装时剥离可执行文件和库中的符号表及其他调试信息.剥离后的二进制文件占用空间更小.

16.3.6 Autoconf 日志文件

如果 configure 过程中出错且原因不明显,可以检查 config.log 来定位问题.不幸的是,config.log 通常是一个巨大的文件,这使得定位确切的问题根源变得困难.

这种情况下的通用方法是跳到 config.log 的末尾(例如,在 less 中键入大写字母 G),然后向上翻页直到看到问题.然而,末尾仍然有很多内容,因为 configure 会将整个环境转储到那里,包括输出变量、缓存变量和其他定义.因此,与其跳到末尾再向上翻页,不如跳到末尾并向后搜索一个字符串,例如 for more details 或靠近失败 configure 输出末尾的其他文本片段.(记住,你可以在 less 中使用 ? 命令启动反向搜索.)错误很可能就在你的搜索结果正上方.

16.3.7 pkg-config

系统上众多的第三方库意味着将它们全部放在一个公共位置可能很混乱.然而,为每个库使用独立的前缀可能导致构建需要这些第三方库的软件包时出现问题.例如,如果你想编译 OpenSSH,你需要 OpenSSL 库.你如何告诉 OpenSSH 的配置过程 OpenSSL 库的位置以及需要哪些库?

许多库现在使用 pkg-config 程序,不仅用于通告其包含文件和库的位置,还用于指定编译和链接程序所需的精确标志.语法如下:

$ pkg-config options package1 package2 ...

例如,要找出某个流行压缩库所需的库,可以运行以下命令:

$ pkg-config --libs zlib

输出应类似如下内容:

-lz

要查看 pkg-config 已知的所有库,包括每个库的简要描述,运行以下命令:

$ pkg-config --list-all

pkg-config 的工作原理

如果你查看幕后,会发现 pkg-config 通过读取以 .pc 结尾的配置文件来查找软件包信息.例如,以下是 Ubuntu 系统上 OpenSSL 套接字库的 openssl.pc 文件(位于 /usr/lib/x86_64-linux-gnu/pkgconfig):

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include

Name: OpenSSL
Description: Secure Sockets Layer and cryptography libraries and tools
Version: 1.1.1f
Requires: 
Libs: -L${libdir} -lssl -lcrypto
Libs.private: -ldl -lz
Cflags: -I${includedir} exec_prefix=${prefix}

你可以更改此文件,例如在库标志中添加 -Wl,-rpath=${libdir} 以设置运行时库搜索路径.但更大的问题是 pkg-config 最初如何找到 .pc 文件.默认情况下,pkg-config 在其安装前缀的 lib/pkgconfig 目录中查找.例如,安装在 /usr/local 前缀的 pkg-config 会在 /usr/local/lib/pkgconfig 中查找.

NOTE

除非你安装了开发包,否则许多软件包你不会看到 .pc 文件.例如,要在 Ubuntu 系统上获得 openssl.pc,你必须安装 libssl-dev 包.

如何在非标准位置安装 pkg-config 文件

不幸的是,默认情况下,pkg-config 不会读取其安装前缀之外的任何 .pc 文件.这意味着位于非标准位置的 .pc 文件,例如 /opt/openssl/lib/pkgconfig/openssl.pc,将超出任何标准 pkg-config 安装的访问范围.有两种基本方法可以使 .pc 文件在 pkg-config 安装前缀之外可用:

  • 从实际的 .pc 文件创建符号链接(或副本)到中央 pkgconfig 目录.
  • 设置你的 PKG_CONFIG_PATH 环境变量,以包含任何额外的 pkgconfig 目录.此策略在全系统范围内效果不佳.

16.4 安装实践

知道如何构建和安装软件很好,但知道何时以及在哪里安装你自己的软件包更有用.Linux 发行版会尽量在安装时塞入尽可能多的软件,因此你应该始终检查自己安装软件包是否更好.以下是自行安装的优点:

  • 你可以自定义软件包的默认设置.
  • 安装软件包时,你通常会更清楚地了解如何使用它.
  • 你可以控制运行的版本.
  • 备份自定义软件包更容易.
  • 在网络上分发自行安装的软件包更容易(只要架构一致且安装位置相对隔离).

以下是缺点:

  • 如果你想要安装的软件包已安装在系统上,你可能会覆盖重要文件,导致问题.通过使用 /usr/local 安装前缀(稍后描述)可以避免这种情况.即使软件包未安装在系统上,你也应检查发行版是否提供了相关软件包.如果有,你需要记住这一点,以防日后意外安装了发行版软件包.
  • 这需要时间.
  • 自定义软件包不会自动升级.发行版会保持大多数软件包更新,无需你做太多工作.对于与网络交互的软件包,这一点尤其令人担忧,因为你需要确保始终拥有最新的安全更新.
  • 如果你实际上并未使用该软件包,那就是在浪费你的时间.
  • 存在配置错误的可能性.

除非你正在构建一个非常自定义的系统,否则安装诸如本章前面构建的 coreutils 包中的软件包(lscat 等)没有太大意义.另一方面,如果你对网络服务器(如 Apache)有重要兴趣,获得完全控制权的最佳方式是自己安装服务器.

16.4.1 安装位置

GNU autoconf 和许多其他软件包中的默认前缀是 /usr/local,这是本地安装软件的传统目录.操作系统升级会忽略 /usr/local,因此在操作系统升级期间你不会丢失任何安装在那里的内容,对于小型本地软件安装,/usr/local 没问题.唯一的问题是,如果你安装了大量自定义软件,这可能会变得一团糟.成千上万个奇怪的零散文件可能进入 /usr/local 层级结构,而你可能不知道这些文件来自哪里.

如果情况真的开始失控,你应该按照第 16.3.2 节所述创建自己的软件包.

16.5 应用补丁

对软件源代码的大多数更改都以开发者在线源代码版本(如Git仓库)的分支形式提供.然而,有时你可能会得到一个需要应用到源代码上的补丁,以修复错误或添加功能.你可能也会看到术语 diff 被用作补丁的同义词,因为 diff 程序会生成补丁.

补丁的开头看起来像这样:

--- src/file.c.orig     2015-07-17 14:29:12.000000000 +0100
+++ src/file.c   2015-09-18 10:22:17.000000000 +0100
@@ -2,16 +2,12 @@

补丁通常包含对多个文件的更改.在补丁中搜索连续三个破折号 (---) 可以查看哪些文件被更改过,并且始终查看补丁的开头以确定所需的工作目录.注意,前面的例子引用了 src/file.c.因此,在应用补丁之前,你应该切换到包含 src 的目录,而不是 src 目录本身.

要应用补丁,运行 patch 命令:

$ patch -p0 < patch_file

如果一切顺利,patch 会静默退出,给你留下一组更新后的文件.然而,patch 可能会问你这个问题:

File to patch:

这通常意味着你不在正确的目录中,但也可能表明你的源代码与补丁中的源代码不匹配.在这种情况下,你很可能运气不佳.即使你能识别出一些需要修补的文件,其他文件也不会被正确更新,最终你会得到无法编译的源代码.

在某些情况下,你可能会遇到像这样引用软件包版本的补丁:

--- package-3.42/src/file.c.orig     2015-07-17 14:29:12.000000000 +0100
+++ package-3.42/src/file.c   2015-09-18 10:22:17.000000000 +0100

如果你有一个略微不同的版本号(或者只是重命名了目录),你可以告诉 patch 剥离前导路径组件.例如,假设你在包含 src 的目录中(和之前一样).要告诉 patch 忽略路径中的 package-3.42/ 部分(即剥离一个前导路径组件),使用 -p1:

$ patch -p1 < patch_file

16.6 编译和安装故障排除

如果你理解了第15章中描述的编译器错误、编译器警告、链接器错误和共享库问题之间的区别,那么在构建软件时,修复出现的许多小问题应该不会太困难.本节涵盖了一些常见问题.尽管在使用 autoconf 构建时不太可能遇到这些问题,但了解它们的样子总是有好处的.

在讨论具体问题之前,请确保你能读懂某些类型的 make 输出.了解错误和被忽略的错误之间的区别非常重要.以下是一个你需要调查的真正错误:

make: *** [target] Error 1

然而,有些 Makefile 怀疑可能会发生错误情况,但知道这些错误是无害的.你通常可以忽略类似这样的消息:

make: *** [target] Error 1 (ignored)

此外,GNU make 在大型包中通常会多次调用自身,每次 make 实例在错误消息中都会用 [N] 标记,其中 N 是一个数字.你通常可以通过查看编译器错误消息之后紧接着的 make 错误来快速找到错误.例如:

compiler error message involving file.c
make[3]: *** [file.o] Error 1
make[3]: Leaving directory '/home/src/package-5.0/src'
make[2]: *** [all] Error 2
make[2]: Leaving directory '/home/src/package-5.0/src'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory '/home/src/package-5.0/'
make: *** [all] Error 2

这里的前三行给了你需要的信息.问题集中在位于 /home/src/package-5.0/srcfile.c 上.不幸的是,有太多额外的输出,以至于很难发现重要的细节.学会过滤掉后续的 make 错误,对于挖掘出真正的原因大有帮助.

16.6.1 特定错误

以下是一些你可能遇到的常见构建错误.

问题

编译器错误消息:

src.c:22: conflicting types for 'item'
/usr/include/file.h:47: previous declaration of 'item'

解释和修复

程序员在 src.c 的第22行对 item 做了错误的重新声明.通常可以通过移除有问题的行(使用注释、#ifdef 或任何有效的方式)来修复.

问题

编译器错误消息:

src.c:37: 'time_t' undeclared (first use this function)
--snip--
src.c:37: parse error before '...'

解释和修复

程序员遗漏了一个关键的头文件.手册页是找到缺失头文件的最佳途径.首先,查看有问题的行(本例中是 src.c 的第37行).它很可能是一个像这样的变量声明:

time_t v1;

在程序中向前搜索 v1,找到它在函数调用附近的使用位置.例如:

v1 = time(NULL);

现在运行 man 2 timeman 3 time 来查找名为 time() 的系统调用和库调用.在本例中,第2节的手册页有你需要的:

SYNOPSIS
      #include <time.h>
      time_t time(time_t *t);

这意味着 time() 需要 time.h.在 src.c 的开头放置 #include <time.h> 并重试.

问题

编译器(预处理器)错误消息:

src.c:4: pkg.h: No such file or directory
(long list of errors follows)

解释和修复

编译器对 src.c 运行了C预处理器,但找不到 pkg.h 包含文件.源代码很可能依赖一个你需要安装的库,或者你可能只需要为编译器提供非标准包含路径.通常,你只需要在C预处理器标志(CPPFLAGS)中添加一个 -I 包含路径选项.(记住,你可能还需要一个 -L 链接器标志与包含文件配合使用.)

如果看起来不像是缺少库,那么存在一种极小的可能性:你正在尝试为一个该源代码不支持的操作系统进行编译.请查看 Makefile 和 README 文件以了解关于平台的详细信息.

如果你运行的是基于 Debian 的发行版,请对头文件名尝试 apt-file 命令:

$ apt-file search pkg.h

这可能会找到你需要的开发包.对于使用 yum 的发行版,可以尝试:

$ yum provides */pkg.h

问题

make 错误消息:

make: prog: Command not found

解释和修复

要构建该包,你的系统上需要 prog。如果 prog 是诸如 ccgccld 之类的程序,说明你的系统上没有安装开发工具。另一方面,如果你认为 prog 已经安装在你的系统上,请尝试修改 Makefile 以指定 prog 的完整路径。在极少数情况下,对于配置不佳的源代码,make 会构建 prog 然后立即使用 prog,假设当前目录(.)在你的命令路径中。如果你的 $PATH 不包含当前目录,你可以编辑 Makefile 并将 prog 改为 ./prog。或者,你可以临时将 . 追加到你的路径中。

16.7 展望未来

我们只触及了构建软件的基础。当你掌握了自行构建的技巧后,可以尝试以下内容:

  • 学习使用除 autoconf 之外的其他构建系统,例如 CMake 和 SCons。
  • 为你自己的软件设置构建流程。如果你正在编写自己的软件,你需要选择一个构建系统并学习如何使用它。关于 GNU autoconf 打包,可以参考 John Calcote 的《Autotools, 2nd edition》(No Starch Press, 2019)。
  • 编译 Linux 内核。内核的构建系统与其他工具完全不同。它拥有自己的配置系统,专为定制内核和模块而设计。不过,整个过程很直接,如果你了解引导加载程序的工作原理,就不会有问题。但这样做时应该小心;确保始终保留旧内核,以防新内核无法引导。
  • 探索发行版特定的源码包。Linux 发行版以特殊的源码包形式维护自己的软件源代码版本。有时你可以找到有用的补丁,这些补丁可以扩展功能或修复那些否则无人维护的包中的问题。源码包管理系统包含用于自动构建的工具,例如 Debian 的 debuild 和基于 RPM 的 mock

构建软件通常是学习编程和软件开发的一块垫脚石。你在本章和上一章中看到的工具,解开了系统软件来源的神秘面纱。走下去——查看源代码内部、进行修改并创建你自己的软件——并不困难。

NOTE

原文本在第411页附近包含多张图片(Image 2327, 2321, 2325, 2317, 2323, 2316, 2320, 2315, 2319),但原文中未提供对应的描述或说明,因此无法呈现具体内容。这些图片可能与本章内容相关,但此处仅保留引用信息。