Linux——从C代码编译出软件

从C代码编译出软件

Linux系统上,几乎所有的东西都有它的源代码——从内核、C库,到网页浏览器等等。你甚至可以使用源代码来(重新)安装你系统的某部分,来更新和加强你的系统。但是,你不应该让所有东西都从源代码构建并安装,除非你真的很享受这个过程或者某些特殊原因使然。

Linux发行版的核心部分,如/bin中的程序,一般都不难更新,而且,Linux的安全问题通常都修复得很快。然而,不要期望你的发行版能为你提供一切。以下这些原因就解释了为何你需要自行安装某些包。

  • 你可按自己的需要进行设置。
  • 你可自己决定安装位置。甚至你还可以安装同一个包的不同版本。
  • 你可自行控制版本。发行版不一定会自带所有包的最新版本,尤其是那些附属的软件包(如Python的库)。
  • 你可了解该包如何运作。

软件的构建系统

Linux上的编程环境有很多种:从传统的C语言到如Python的解释型脚本语言。它们各自都有至少一套区别于Linux发行版自带工具的构建和安装系统。

从C代码安装一个软件,通常涉及这些步骤:

  • 将源代码的归档解包;
  • 对包进行设置;
  • 运行make来构建程序;
  • 运行make install或者发行版特定的安装命令来安装该包。

解开C源码包

一个软件包的源码包,通常是一个.tar.gz、.tar.bz2或.tar.xz文件,不过在此之前,还是先用tar tvf或tar ztvf来检查下里面的内容,因为有些包解开时不会建立自己的目录。

如果输出是这样,那就可以解开它了:

package-1.23/Makefile.in
package-1.23/README
package-1.23/main.c
package-1.23/bar.c
--snip--

不过,它也有可能没有将文件放在一个目录(如上例的package-1.23)里:

Makefile
README
main.c
--snip--

将上面这种包直接解包的话,会使你的当前目录变得一团乱。为了避免这种情况,你要先创建一个目录,并cd进去,再在里面进行解包。最后一点,你还要留意那些包含绝对路径名的文件。

从哪里开始

当你解包完,并看到一堆文件出现时,请先去找找README和INSTALL文件。无论如何,README文件都是要首先看的,因为里面含有关于该包的描述、手册、安装提示,以及其他有用的信息。很多包都会提供INSTALL文件,里面是编译和安装软件的指令。请注意其中特别的编译器选项和定义。

除了README和INSTALL,你还会看到以下三类文件。

  • 与make系统相关的文件,例如Makefile、Makefile.in、configure、CMakeList.txt等。有些旧
    的软件的Makefile可能需要你自己去改,但现在大多都会用GNU autoconf或CMake之类的配置工具。这些工具有其脚本或配置文件(如configure或CMakeList.txt),可根据你的系统设定和配置选项来帮你从Makefile.in中生成Makefile。
  • 以.c、.h或.cc结尾的源码文件。包里到处都会有C源码文件。而C++源码文件则通常以.cc、.C或.cxx结尾。
  • 以.o结尾的对象文件,或者二进制文件。一般来说,源码包中是没有对象文件的,但如果该包的维护者无权释放源代码而只能提供对象文件的话,那你就要自己处理它们了。大多数情况下,源码包里含有对象文件(或可执行的二进制文件),就意味着该软件打包有问题,你需要运行make clean来进行全新的编译。

GNU autoconf

虽然C代码一般都是可移植的,但我们还是难以只靠一个Makefile来适应各平台的差异。早期的解决方法是为不同的操作系统提供不同的Makefile,或者是提供一个易于修改的Makefile。这种做法后来演变成了使用脚本来分析系统(这种分析以往只用于构建软件包),再产生出Makefile。

GNU autoconf就是一套流行的、用于自动产生Makefile的系统。使用此套系统的包都会带有configure、Makefile.in和config.h.in文件。其中.in文件是模板。它的做法是,运行configure脚本来分析你系统的特性,然后在.in文件的基础上做一些替换,最后创建出真正的构建文件。对于终端用户来说,这个过程是简单的,只需要像下面这样执行configure就能从Makefile.in产生出Makefile:

$ ./configure

因为该脚本会先检查你的系统,所以它会输出一大堆诊断信息。如果一切正常,configure就会创建出一个或多个Makefile、一个config.h文件以及一个缓存文件config.cache。缓存文件能使你下次configure时不必再做一次系统检查。

现在你可以运行make来编译那个包了。虽然configure成功不代表make也能成功,但它作为第一步来说还是很重要的。

一个autoconf的例子

你需要先把GNU coreutils包安装在自己的root目录里(以确保不会影响整个系统)。该包可在http://ftp.gnu.org/gnu/coreutils/(通常最新的就是最好的)获取,然后解开它,进入它的目录中,进行以下配置:

$ ./configure --prefix=$HOME/mycoreutils
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
--snip--
config.status: executing po-directories commands
config.status: creating po/POTFILES
config.status: creating po/Makefile

接着make它

$ make
GEN lib/alloca.h
GEN lib/c++defs.h
--snip--
make[2]: Leaving directory '/home/juser/coreutils-8.22/gnulib-tests'
make[1]: Leaving directory '/home/juser/coreutils-8.22'

下一步试着运行某个刚刚创建出的可执行文件,如./src/ls,再试着运行make check,来对该包进行一系列的检查。(这会花一些时间,但是很有趣。)

最后,可以安装该包了。先用-n选项空跑一次,看看它准备安装些什么:

$ make -n install

检查一下输出,如果没有什么异常(例如它准备安装在mycoreutils以外的地方),就实施安装:

$ make install

使用打包工具来安装

大部分Linux发行版都带有软件打包工具,它创建出的安装包能对由该包安装的软件进行后期维护。基于Debian的发行版(如Ubuntu)或许是最简单的,就像下面这样,使用checkinstall,而不单是使用make install:

# checkinstall make install

你可以用--pkgname=name选项来指定新包的名称。创建RPM包会更复杂一点,因为你得先创建一个目录树以便制作包。你可以使用rpmdev-setuptree命令来实现,然后再用rpmbuild工具来完成剩下的工作。

configure脚本的选项

刚才你已看到了configure脚本最有用的选项之一:使用–prefix来指定安装位置。autoconf默认生成的Makefile 中的install 目标都是使用/usr/local 作为前缀: 二进制程序会去到/usr/local/bin,库会去到/usr/local/lib,等等。更改前缀可执行如下命令:

$ ./configure --prefix=new_prefix

configure的大多数版本都有–help选项,可以列出其他配置选项。不幸的是,该列表实在太长了,难以得知哪些是重点。

  • --bindir=directory:将可执行程序装在directory目录。
  • --sbindir=directory:将系统级的可执行程序装在directory目录。
  • --libdir=directory:将库装在directory目录。
  • --disable-shared:不构建共享库(要看具体是什么库)。不构建的话,或许能避免一些后续的麻烦
  • --with-package=directory:告诉configure需要用到directory目录的包。当某个库不在标准位置时,这个选项是比较好用的。但不幸的是,并非所有的configure脚本都能识别这个选项,而且,它的语法不明确。

使用不同的构建目录

首先在任意一个地方新建一个目录,然后在新目录运行原目录的configure脚本。这样所产生的Makefile将会依然使用原目录的源码,但make出的东西却留在新目录。(有些开发者更希望你这么做,因为这样不仅不会在原目录产生新的东西,而且还有利于使用相同的源码为不同平台或使用不同配置选项进
行构建。)

环境变量

你可以通过修改一些会被configure脚本当成make变量的环境变量来影响configure的行为。最重要的环境变量是CPPFLAGS、CFLAGS和LDFLAGS。但要小心的是,configure对环境变量是很挑剔的。比如说,对于头文件目录,你应该使用CPPFLAGS而不是CFLAGS,因为configure经常会单独运行预处理器。

在bash中,为configure设置环境变量的最简单的做法就是在./configure前放置变量的声明。

$ CPPFLAGS=-DDEBUG ./configure

也可以用选型的形式来传递变量。

$ ./configure CPPFLAGS=-DDEBUG

使用环境变量来指引configure查找第三方include文件和库也是很方便的

$ CPPFLAGS=-Iinclude_dir ./configure

要使连接器到lib_dir里查找

$ LDFLAGS=-Llib_dir ./configure

autoconf的目标

若configure成功执行,你会发现它所生成的Makefile里除了有标准的all和install,还包含如下所列的一些有用的目标。

  • make clean:它会清除所有对象文件、可执行程序和库。
  • make distclean:它与make clean很像,只不过它清除的是所有自动产生的东西,包括Makefile、config.h、config.log等等。也就是说,它使整个源码目录就像刚解包出来一样。
  • make check:有些包会自带一些用于检查所编译出的程序是否正确的测验;而make check就运行这些测验。
  • make install-strip:它与make install很像,只不过是在安装时,它会将可执行程序和库内的符号表及其他调试信息都移除掉。移除之后,程序占的空间会少很多。

autoconf的日志文件

如果configure的执行过程出了错,但你却看不出哪里有错,那么你可以检查一下config.log。

pkg-config

第三方的库有很多,如果都放在同一个地方,那会显得很乱。但是,如果各自放在单独的地方,那么在连接时又会出现麻烦。例如,假设你要编译OpenSSH,它需要用到OpenSSL库,那么你该怎样将OpenSSL库的位置告知OpenSSH的configure呢?

现在很多库都用pkg-config来解决问题,它不仅可以公告include文件和库的位置,还可以用于明确指定编译和连接的选项。其语法如下:

$ pkg-config options package1 package2 ...

查找OpneSSL所需的库,可用以下命令:

$ pkg-config --libs openssl

其输出大概是这样:

-lssl -lcrypto

想查看pkg_config所知的全部库,用以下命令:

$ pkg-config --list-all

pkg-config的运作方式

pkg_config是通过读取.pc配置文件来获取包信息的。

例如,以下是Ubuntu中OpenSSL套接字库的openssl.pc(在/usr/lib/i386-linux-gnu/pkgconfig中):

prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib/i386-linux-gnu
includedir=${prefix}/include
Name: OpenSSL
Description: Secure Sockets Layer and cryptography libraries and tools
Version: 1.0.1
Requires:
Libs: -L${libdir} -lssl -lcrypto
Libs.private: -ldl -lz
Cflags: -I${includedir} exec_prefix=${prefix}

你可以修改这个文件,例如,给库选项增加-Wl,-rpath=${libdir},以指定运行时动态连接的路径。

使用标准位置以外的pkg-config文件

pkg-config不会在标准位置以外的地方查找.pc文件。如果一个.pc文件的所在位置不标准,如/opt/openssl/lib/pkgconfig/openssl.pc,那么常规安装的pkg-config是不会读取到它的。以下是两个基本的解决方法。

  • 将.pc文件的符号链接(或副本)集中到pkgconfig目录中。
  • 使环境变量PKG_CONFIG_PATH包含那些另外的pkgconfig目录。环境变量只在本shell及子shell内有效。

编译和安装的问题排查

如果你懂得区分编译器错误、编译器警告、连接器错误和共享库问题,那你应该能应付构建软件时出现的很多问题。本节会介绍一些常见的问题。虽然用autoconf的话是不太可能遇到这些问题的,但了解一下也无妨。

在进入细节之前,先确认自己能读懂几种make输出。学会区分错误与被忽略的错误是很重要的。以下就是一个需要你检查的真正的错误:

make: *** [target] Error 1

有些错误提示则是Makefile怀疑出错了,但即使出错也无害。像这类提示你可以忽略

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

大型的包中经常会出现GNUmake多次调用自身的情况。这时,每次make都会带有一个[N],其中N是个数字。通常你能够很快地在编译出错的提示中找到错误所在

[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

具体错误

以下列举了一些你可能会遇到的常见构建错误及其解释与修复途径。

编译器错误信息:

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:4: pkg.h: No such file or directory
(long list of errors follows)

编译器对src.c运行C预处理器时,找不到include文件pkg.h。可能是有个库没安装好,或者include文件在非常规位置,需要你指明。通常,你只需要为预处理器选项(CPPFLAGS)加上-I这一include路径选项。(你可能还需要一个连接器选项-L。)

如果出错原因不是找不到库,那还有一种可能,即该操作系统不支持这个代码。你可以查看一下Makefile和README中关于平台的要求。

如果你的发行版是基于Debian的,试试用apt-file命令来查找头文件:

$ apt-file search pkg.h

它可能会帮你找到该包的开发版。而对于有yum的系统,则可以这样做:

$ yum provides */pkg.h

make错误信息:

make: prog: Command not found

你要有prog程序才能构建该软件。如果prog是cc、gcc或ld之类,那就说明你系统上没有安装开发工具。而如果你觉得你已安装了,那就试试在Makefile中指明prog的完整路径。

还有一种不常见的情况是make即时构建并使用prog。如果你的$PATH包含当前目录(.)的话,那这是可以做到的。而如果你的$PATH不包含当前目录,你可以将Makefile的prog改成./prog,或者暂时将.加到$PATH中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章