编译

既然该软件已正确配置,所剩的就是编译了。这个阶段通常比较简单,并且不会出现什么严重的问题。

Make

自由软件社区中编译源代码最得宠的工具是 make。它具有两个优点:

  • 帮助开发者有效地管理项目的编译过程而节省了他们的时间。

  • 对于最终用户而言,他们只需键入几行命令就能够编译并安装软件,即使他们从来没有开发的经验。

编译源代码的各个步骤常存储于 MakefileGNUMakefile 文件中。当调用 make 时,它会从当前目录读取该文件(如果该文件存在的话)。如果它不存在,可以通过 make-f 选项指定其他文件。

规则

make 按照依赖性进行操作,因此为了编译某个二进制文件(“目标”)需要依次执行几个步骤(“依赖”)。例如,(假定)要创建 glloq 这个二进制文件,必需编译并链接 main.oinit.o 这两个目标文件(编译过程的中间文件)。而这两个目标文件同样也是目标,它们分别依赖于各自相应的源文件。

本文仅能对纷繁芜杂的 make 用法作一简略介绍。更详尽的文档,请参考使用 Make 管理项目,第二版,O'ReillyAndrew OramSteve Talbott 著。

开始执行!

通常,使用 make 需要遵循一些约定。例如:

  • 不加参数执行 make 表示仅编译程序,而不安装。

  • make install 编译程序(不是一定的),并随后将文件安装到文件系统的正确位置。某些文件常常无法正确安装(比如 maninfo),可能会需要用户自己手动复制。有时候,需要在子目录中再次执行 make install。通常这是由于包含了第三方开发的模块。

  • make clean 清除编译产生的所有临时文件,大多数情况下还会删除可执行文件。

第一个阶段是编译程序,因此请键入(假设的例子):

$ make
gcc -c glloq.c -o glloq.o
gcc -c init.c -o init.o
gcc -c main.c -o main.o
gcc -lgtk -lgdk -lglib -lXext -lX11 -lm glloq.o init.o main.o -o glloq

好,二进制文件已经正确编译完成。我们可以进入下一个阶段 -- 安装该发行版的文件了(二进制文件、数据文件等)。参见“安装”一节

解释

如果您曾好奇地查看过 Makefile 文件,您会发现一些已知的命令(rmmvcp 等),以及一些诸如 $(CFLAGS) 的奇怪字符串。

它们是变量,通常它们在 Makefile 文件开始处被设定。然后,在它们出现的地方会被相应的值所取代。当您需要一直使用同样的编译选项时,这会非常有用。

例如,要使用 make all 在屏幕上显示字符串“foo”:

TEST = foo
all:
        echo $(TEST)

以下变量经常被设置:

  1. CC:编译器。通常为 cc,在大多数自由系统中是 gcc 的代名词。如果不能肯定,可将其设为 gcc

  2. LD:该程序用以确保编译的最后一个阶段(参见“编译的四个步骤”一节)。默认为 ld

  3. CFLAGS:它将在编译的第一个阶段为编译器提供额外的参数。其中有:

    • -I<路径>:通知编译器到哪里寻找额外的头文件(比如:-I/usr/X11R6/include 表示可以直接包含存放于 /usr/X11R6/include 中的头文件)。

    • -D<符号>:定义额外的符号,对依赖已定义的符号进行编译的程序(比如:当定义了 HAVE_STRING_H 后将使用 string.h 文件)很有用。

    编译的命令行常如:

    $(CC) $(CFLAGS) -c foo.c -o foo.o
  4. LDFLAGS (或 LFLAGS):在编译的最后阶段使用的参数。其中有:

    • -L<路径>:指定搜索库文件的额外路径(比如:-L/usr/X11R6/lib)。

    • -l<库>:指定在编译的最后阶段使用的额外库文件。

是什么原因它不起作用?

不要惊慌,谁都可能犯错。其中最常见的是:

  1. glloq.c:16: decl.h: No such file or directory

    编译器不能找到对应的头文件。不过,在软件配置阶段就可能已经发现该错误了。解决方法是:

    • 请检查该头文件确实已经存在于以下某个目录中:/usr/include/usr/local/include/usr/X11R6/include 或它们的某个子目录。如果没有,请在整个磁盘上查找它(可用 findlocate)。如果还是没有,请检查您是否已经安装了该头文件对应的库了。您可以在 findlocate 命令各自的手册页面中分别找到它们的示例。

    • 请检查该头文件确实可以读取(键入 less <路径>/<文件>.h 来测试)。

    • 如果它在 /usr/local/include/usr/X11R6/include 目录中,您有时可能需要为您的编译器添加额外的参数。用您常用的文本编辑器(EmacsVi 等)打开对应的 Makefile (请注意,您得打开编译出错的那个目录中的文件[33])。查找到出错的那一行,并紧接着调用编译器(gcc,有时是 $(CC))的地方添加字符串 -I<路径>(其中<路径>是包含该头文件的路径)。如果您不清楚要把该选项加到哪里,请把它添加到文件开始处 CFLAGS=<什么什么的> 后面或是 CC=<什么什么的> 后面。

    • 再次运行 make,如果它还是不起作用,请检查这个选项(见前文)确实被添加于编译过程中出错的地方。

    • 如果还是不起作用,请向您周围的高手求助,或向自由软件社区求助(参见“技术支持”一节)。

  2. glloq.c:28: `struct foo' undeclared (first use this function)

    结构几乎是所有程序都会使用的一种特殊类型。系统在头文件中定义了许多。这表示该问题很可能是由于找不到头文件,或误用头文件造成。解决该问题的正确步骤是:

    • 试着查一查出问题的结构是否已由程序或系统定义。比如用 grep 命令查看该结构是否已经在某个头文件中定义了。

      例如,当您在该软件源程序的根目录中时:

      $ find . -name '*.h'| xargs grep 'struct foo' | less

      在屏幕上可能会出现许多行(例如,每一次某个函数使用该类型的结构定义一个实例)。如果它们存在,找到头文件中 grep 指出的那一行。

      结构的定义应该是:

      struct foo {
          <结构内容>
      };
      

      请检查它是否就是您所要的。如果是,则说明该头文件未包含于出错的 .c 文件中。有两个解决方法:

      • 在出错的 .c 开始处添加 #include "<文件名>.h" 一行。

      • 或者将该结构的定义复制-粘贴到该文件的开始处(不是很美观,不过至少能起作用)。

    • 如果没有,在系统头文件中(通常位于 /usr/include/usr/X11R6/include/usr/local/include)再次查找。不过这一次,您得添加 #include <<文件名>.h>

    • 如果该结构还是不存在,请试着找找它应该在哪个库(即保存许多函数的一个软件包)中定义(请查看 INSTALLREADME 文件以确定该程序使用哪些库以及它们的版本)。如果该程序需要的版本不是您系统上安装的那一个,您就需要更新该库了。

    • 如果依然不起作用,请检查该程序是否真能在您的架构上运行(某些程序还没有移植到 UNIX® 系统上)。并请检查您是否已经为您的架构正确配置了该程序了(比如在执行 configure 的时候)。

  3. parse error

    这个问题解决起来较为复杂。因为编译器常会在真正出错的地方之后报错。有时候,它仅仅是由于某个数据类型未定义。如果您碰上如下的错误信息:

    main.c:1: parse error before `glloq_t
    main.c:1: warning: data definition has no type or storage class
    

    那么很可能是 glloq_t 类型未曾定义。解决方式同前一个问题类似。

    [Note]注意

    如果我没有记错的话,老版本的 curses 库中就存在一个 parse error

  4. no space left on device

    这个问题解决起来较为简单:磁盘上已经没有足够的空间从源文件生成二进制文件了。您可以通过释放安装目录所在分区的一些空间来解决(删除临时文件或源文件,卸载某些您已经不用的程序)。如果您解压到 /tmp 而不是 /usr/local/src 目录中,它会阻止对 /tmp 分区不必要的填充。请检查磁盘上是否有 core 文件[34]。如果有,请删除它们;如果它们属于别的用户,请让他们删除这些文件。

  5. /usr/bin/ld: cannot open -lglloq: No such file or directory

    这表示 ld 程序(在编译的最后阶段由 gcc 调用)无法找到某个库。为了包含某个库,ld 将会搜索文件名由 -l<库> 选项指定的库文件。相应文件为 lib<库>.so。如果 ld 找不到,它将给出一条错误信息。要解决该问题,请如下操作:

    1. 请用 locate 命令检查该文件是否在硬盘上。通常,图形库在 /usr/X11R6/lib 目录中。例如:

      $ locate libglloq

      如果上述查找没有结果,请使用 find 命令查找(比如 find /usr -name "libglloq.so*")。如果您还是找不到,您就需要安装它了。

    2. 一旦找到了该库,请检查它是否能被 ld 访问:/etc/ld.so.conf 文件指定寻找这些库文件的目录。把该库的路径添加到该文件末尾(您可能需要重启您的计算机以使改动起作用)。您也可以把该目录添加到环境变量 LD_LIBRARY_PATH 中。例如,如果要添加 /usr/X11R6/lib 目录,请键入:

      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/X11R6/lib

      (如果您使用 bash 作为您的 shell)。

    3. 如果还是不行,请用 file 命令检查该库文件是否为一个可执行文件(或 ELF)。如果它是一个符号链接,请检查该链接完好且没有指向不存在的文件(例如,可用 nm libglloq.so 检查)。而且,该库文件的权限可能是错误的(例如,如果您不是 root 且该库文件不允许读)。

  6. glloq.c(.text+0x34): undefined reference to `glloq_init'

    该问题是由于在编译的最后阶段某个符号找不到。通常,这是因为某个库出问题了。可能的问题有:

    • 首先,请查找该符号是否应该在某个库文件中。比如,如果该符号以 gtk 开头,它就应该属于 gtk 库。如果这个库能够容易地找到(frobnicate_foobar),您可以用 nm 命令列出该库中的符号。例如,

      $ nm libglloq.so
      0000000000109df0 d glloq_message_func
      000000000010a984 b glloq_msg
      0000000000008a58 t glloq_nearest_pow
      0000000000109dd8 d glloq_free_list
      0000000000109cf8 d glloq_mem_chunk
      

      使用 nm 时添加 -o 选项让您分别在不同行中显示库中的名称,从而使搜索变得更为简单。假定我们要搜索符号 bulgroz_max,您可以:

      $ nm /usr/lib/lib*.so | grep bulgroz_max
      $ nm /usr/X11R6/lib/lib*.so | grep bulgroz_max
      $ nm /usr/local/lib/lib*.so | grep bulgroz_max
      /usr/local/lib/libfrobnicate.so:000000000004d848 T bulgroz_max
      

      好极了!该符号 bulgroz_max 定义于 frobnicate 库中(其名称前有一个大写字符 T)。然后,您只需要编辑 Makefile 文件以在编译命令行中添加字符串 -lfrobnicate:请将其添加于定义 LDFLAGSLFGLAGS (或至少 CC)的那一行的末尾,或是在创建相应二进制文件的那一行处。

    • 编译中使用的库文件不是该软件需要的。请阅读该发行版的 READMEINSTALL 文件,以查看需要哪个版本。

    • 该发行版的目标文件没有全部正确链接。缺少了定义该函数的文件。请键入 nm -o *.o 以查看应该是哪个文件,然后将相应的 .o 文件添加到对应的编译命令行上。

    • 出错的函数或变量可能并不存在。请试试删除它:编辑出问题的源文件(它的名字会出现于出错信息的开始处)。这是没有办法的办法了,而且它将导致程序执行混乱,以及会在启动时出现segfault(段错误),等错误。

  7. Segmentation fault (core dumped)

    有时候,编译器立即挂起,并产生该错误信息。除了建议您安装一个更新版本的编译器,就没有更好的办法了。

  8. no space on /tmp

    在不同的阶段,编译器需要临时工作空间。如果它不能申请到这些空间的话,它将出错。因此,您需要清理分区,不过请尽量小心,因为删除某些文件会导致某些正在执行的程序(X 服务器、管道等)挂起。您必需能够清楚知道您在做什么!如果 /tmp 所在的分区并不仅仅包含该目录(比如根目录),请搜索并删除 core 文件。

  9. make/configure 死循环

    在您的系统上,这常常只是一个时间上的问题。make 确实需要了解计算机时间和它检查的文件的时间。它比较这两个时间,并根据结果确定某个目标是否已过时。

    某些日期问题可能导致 make 不停地编译(或不停地递归编译某个子目录)。在这种情况下,touch (其作用是将有问题的文件的时间设定为当前时间)通常会解决该问题。

    举例说明:

    $ touch *

    或是(更简单有效些):

    $ find . | xargs touch


[33] 请详细查看 make 返回的出错信息。通常,最后几行会包含某个目录(比如像是 make[1]: Leaving directory `/home/zhang/Project/foo')。找出其中序号最大的那个。要确定是否正是那个目录,请进入该子目录,并再次运行 make,您应该会得到同样的错误信息。

[34] 当某个进程试图访问未被允许的内存时,系统将生成该文件。这些文件用以分析这种行为,并改正这一问题。