使用 Imake 构建项目

作者:Otto Hammersmith

Imake 是一款用于为不同平台和编译器配置 X Window 系统及其组件的工具。Imake 允许您创建项目构建方式的通用描述,并将系统特定的细节留给集中的配置文件。

简而言之,Imake 构建于将 C 预处理器应用于 Makefile 而不是 C 程序的思想之上。常见 Makefile 中的许多冗余已被转换为预处理器宏,这些宏具有与 C 程序中宏相同的效果——使事情变得更容易。

Imake 命令和配置

Imake 应该已随您的 X 安装一起提供。如果您运行常见的 Linux 发行版之一,则需要检查是否安装了一些“开发”软件包。例如,我在我的系统上运行 Red Hat 4.0,而 Imake 系统包含在 XFree86-devel RPM 中。

如果您不确定是否已安装 Imake 或是否已正确配置它,则很容易检查。只需运行以下命令

touch Imakefile
xmkmf

现在当前目录中应该有一个名为 Makefile 的新文件。新的 Makefile 中有很多“东西”,主要是从配置文件生成的通用规则和信息。

如果您已走到这一步,则 Imake 已安装并配置良好。现在,您应该能够运行 make 并获得输出。例如,在我的 Red Hat 4.2 系统上,我键入 make 并获得以下输出

make: Nothing to be done for 'emptyrule'.
支持程序

除了包含许多程序之外,还有几个程序是 Imake 正常运行所依赖的。但是,它们大多数是基本的东西,例如 bashmvrm 等等。如果这些基本程序丢失,那么您的问题比 Imake 是否正常工作更严重。尽管如此,仍然有一些主要组件对于工作的 Linux 系统来说不是至关重要的,但对于 Imake 来说至关重要。

make

make 对于 Imake 的功能来说不一定是至关重要的,但是生成的 Makefile 本身是绝对无用的。如果您没有 make,则应安装 GNU make。对于 Imake 的更高级用法,最好彻底了解 make 的工作原理。本文末尾有一些关于 make 的更多信息的参考资料。

GCC

显然,对于 Makefile 生成程序来说,要有用,您需要一个编译器。但是,这并不是安装 GCC 的唯一原因。Imake 依赖于默认安装在 /lib/cpp 中的外部 C 预处理器。如果您已安装并配置了 GCC,则还应安装 cpp(C 预处理器)。

如果您没有 /lib/cpp 文件,解决问题的最简单方法是从它到 GCC 的 cpp 可执行文件创建一个符号(软)链接(使用 ln)。例如,在我的系统上,/lib/cpp 是指向 /usr/lib/gcc-lib/i486-linux/2.7.2/cpp 的符号链接。

Imakefile 内容,快速入门

Imake 系统的主要用户可见组件是 Imakefile 文件,其中保留了 Imake 构建项目所需的项目特定信息。

在基本级别上,Imakefile 由规则调用、变量定义和变量调用组成。第一个,规则调用,类似于 C 函数调用。第二个类似于 C 变量赋值。对于任何在 C 代码中使用过宏的人来说,这两者都应该很熟悉。另一方面,变量调用与它们的 C 对应物完全不同,但对于任何使用普通 make 进行过工作的人来说都应该很熟悉。这是一个简单的例子

VARIABLE = value
ARule(arg1,arg2,$(VARIABLE))

与一般的编程一样,Imake 也存在许多语法陷阱。以下两节介绍了一些需要牢记的问题。

参数警告

Imake(实际上是底层的 C 预处理器)对各种规则的调用方式相当严格。首先,在参数之间的逗号周围绝对不能有多余的空格。例如,此规则是 不正确的

ARule(arg1, arg2, arg3)

而这个版本是 正确的

ARule(arg1,arg2,arg3)
更令人困惑的是,在某些情况下,参数具有空格是有效的。例如,许多规则将文件列表作为参数之一,其中每个文件都用空格分隔。通常,这非常直观,并且本文后面的几个示例显示了最常见的情况。

Imake 中参数传递的第二个问题是空参数。并非所有规则都必须使用所有参数,但如果省略参数或参数没有值,C 预处理器会中断。为了解决这个问题,Imake 提供了宏 NullParameter 以允许规则中的空参数。例如

ARule(arg1,NullParameter,arg3)
注释警告

在 Imakefile 中包含注释有两种不同的方法。第一种是标准的 C 风格注释,以 /* 字符开头,以 */ 字符结尾。两者之间的任何内容都会被完全忽略。第二种注释样式使用字符串 XCOMM 开始注释,并以换行符结束。

两者之间的基本区别在于最终生成的 Makefile 中出现的内容。C 风格的注释根本不会出现在生成的 Makefile 中,而 XCOMM 注释则会。简而言之,XCOMM 的实例被替换为 # 字符。关于 XCOMM 注释,需要记住的一点是,由于 C 预处理器不知道忽略它们,因此它会很乐意处理它在 XCOMM 注释中看到的任何宏。换句话说,看起来被注释掉的无效规则调用可能会导致 Makefile 生成失败。

(常用)规则

Imake 配置文件提供了许多规则。此处描述了一些最常见和最有用的规则。请注意,所提供的示例中使用的规则不一定彼此兼容。不要混合使用来自不同示例的规则。

规则 1. SimpleProgramTarget()

顾名思义,此规则是 Imake 提供的最简单的规则。它生成一个简单的 Makefile,该 Makefile 从单个源文件编译单个程序。假设您有一个源文件 prog.c,并且希望将其编译为名为 prog 的可执行文件。您只需要在 Imakefile 中添加一行,例如这一行

SimpleProgramTarget(prog)

要试用它,请用 C 编写一个简短的“hello world”类型程序和一个合适的 Imakefile。然后,运行 xmkmf 和 make。您的“hello world”程序应该可以顺利编译。

规则 2. ComplexProgramTarget()

由于仅使用单个源文件很难编写复杂的程序,因此创建此规则是为了允许从多个源文件构建单个可执行文件。但是,随着灵活性的增加,也带来了成本,您必须添加有关要使用哪些源文件的信息。例如,如果您有一个包含 foo.c 和 bar.c 的项目,该项目将创建一个名为 foobar 的可执行文件,则您的 Imakefile 应如下所示

SRCS = foo.c bar.c
OBJS = foo.o bar.o
ComplexProgramTarget(foobar)

还有三个规则与 ComplexProgramTarget() 类似,但又不完全相同。它们被称为 ComplexProgramTarget_1()、ComplexProgramTarget_2() 和 ComplexProgramTarget_3()。这些规则背后的基本思想是允许单个 Imakefile 为多个程序生成目标。

尽管这些规则与 ComplexProgramTarget() 类似,但仍存在一些细微的差异。第一个也是最明显的区别是指定源文件的方式不同。您可以使用 SRCS1、OBJS1、SRCS2、OBJS2、SRCS3 和 OBJS3,而不是设置 SRCS 和 OBJS。第二个是规则采用不同的参数。这些规则采用另外两个参数。第二个和第三个参数是一种指定必须链接到最终可执行文件中的本地库和系统库的方法。以下是如何使用这些规则的示例

PROGRAMS = foobar1 foobar2 foobar3
SRCS1 = foo1.c bar1.c
OBJS1 = foo1.c bar1.c
SRCS2 = foo2.c bar2.c
OBJS2 = foo2.c bar2.c
SRCS3 = foo3.c bar3.c
OBJS3 = foo3.c bar3.c
ComplexProgramTarget_1(foobar1,NullParameter,\
        NullParameter)
ComplexProgramTarget_2(foobar2,NullParameter,\
        NullParameter)
ComplexProgramTarget_3(foobar3,NullParameter,\
        NullParameter)

请注意,如果没有第一个规则,则最后两个规则将无法正常运行。如果没有 ComplexProgramTarget_1(),则不能有 ComplexProgramTarget_2() 或 ComplexProgramTarget_3()。其原因与规则定义的内部结构有关。要了解原因,请阅读 Resources 中提到的 O'Reilly Imake 书籍。

规则 3. NormalProgramTarget()

到目前为止,此规则是最有用的。它可以任意次数使用,并且可以使用任意数量的源文件来构建可执行文件。同样,随着灵活性的增加,复杂性也随之增加。Imake.rules 给出的此规则的用法如下

NormalProgramTarget(
        locallibs,syslibs)

以下是参数的定义方式

  • program:正在构建的可执行文件的名称

  • objects:要从源文件构建的目标文件的名称。此参数取代了各种 ComplexProgramTarget() 规则中 OBJS 变量的功能。

  • deplibs:可执行文件需要的库

  • locallibs:要链接的本地(项目)库

  • syslibs:要链接的系统库

请注意,在最后三个参数中存在一些冗余。deplibs 参数是程序需要的库文件名的列表。Imake 使用此列表来构建正确的 Makefile,如果这些文件中的任何一个发生更改,它将重建可执行文件。最后两个参数 locallibssyslibs 是相同的库,但表示为命令行选项,以告知编译器链接这些库。自然,这样做是为了让 Imake 知道如何使用所有必需的库正确构建可执行文件。

列表 1 使用 NormalProgramTarget() 来重现 ComplexProgramTarget() 示例的功能。当然,优势在于可以添加或删除程序目标,而不会相互影响。

请注意,变量 SRCS1、OBJS1 等的用法只是为了方便。与 ComplexProgramTarget() 不同,这些变量未在规则的定义中使用。当需要更改源文件和对象列表时,在 Imakefile 的开头以变量定义的形式找到它们比在规则调用中搜索所有实例更容易。

另一方面,变量 SRCS 是一种不同的情况。请注意两个新规则:DependTarget() 和 LintTarget()。这两个规则分别创建目标 dependlint。为了能够完成它们的工作,这些规则需要将变量 SRCS 设置为项目中源文件的列表。请注意,在前面的示例中,依赖项和 lint 目标是自动创建的;这些目标的功能将在稍后解释。

此示例中还有一个新规则 AllTarget()。同样,由于 NormalProgramTarget() 的工作量少于其他规则,因此您必须做更多的工作。很简单,AllTarget() 的每个实例都会向最终 Makefile 中的“all”目标添加另一个依赖项。默认情况下,将为要构建的每个程序调用 AllTarget()。

文件

Imake 构建系统涉及各种文件;大多数文件都位于 Imake 配置目录中。尽管只有一个文件 Imakefile 对于 Imake 的简单用途来说真正重要,但这些描述被包含进来,作为更深入了解 Imake 工作原理的路线图。

  • Imakefile 是用于生成 Makefile 的文件。此文件包含您的项目特定信息。

  • Makefile 是最终输出文件。有很多参考资料可以帮助您了解 Makefile 的内容。最容易获得的可能是关于 GNU make 的 GNU Info 页面,它随 GNU make 发行版一起提供。有关 make 的可用信息的详细信息,请参阅参考资料部分。

  • Imake.tmpl 是一个通用模板,用于填写 Imakefile 未提供的信息。大多数模板文件都是为需要自定义 Imake 超出 Imakefile 可能范围的大型项目创建的。(以 FVWM 源代码为例。)

  • Imake.rules 和其他 .rules 文件保存构成 Imake 规则主体的 C 预处理器“定义”。SimpleProgramTarget() 和 DependTarget() 之类的东西在此文件中定义。

  • .cf 文件包含系统特定的配置信息。Imake.cf 是“选择”正确的平台特定 .cf 文件的文件。

开始使用 Imake

编写基本 Imakefile 后,就该开始使用 Imake 了。幸运的是,困难的部分已经结束。只需键入以下两个命令

xmkmf -a
make

如果您正确编写了 Imakefile,您的项目将顺利构建。

xmkmf 是一个 Bourne shell 脚本,它包装了 imake 命令。它使用方便的默认参数调用 imake,并进行一些清理,例如将旧的 Makefile 复制到 Makefile.bak。此程序从您的 Imakefile 生成 Makefile。

imake 是一个几乎不需要手动运行的程序。它是处理运行 C 预处理器的大部分工作以及一些小的调整(即,XCOMM 注释)以与 make 兼容的程序。

不带任何参数,xmkmf 仅保存旧的 Makefile 并运行 imake 以从 Imakefile 生成新的 Makefile。如果您给 xmkmf -a 开关,它将按给定顺序运行以下命令

make Makefile
make includes
make depend

有关 xmkmf 功能的更多详细信息,请参阅 xmkmf(1) 手册页。

目标

以下是 Imake 生成的 Makefile 中包含的常见 make 目标的列表。确切哪些目标最终会出现在您的 Makefile 中取决于您在 Imakefile 中使用的规则,这是理所当然的。给定目标始终执行相同的操作,而与项目无关。(也就是说,如果 Imakefile 已正确编写。)实际上,这些目标是 Imake 之外的标准。例如,《GNU 编码标准》指定了大致相同的目标。

  • all:构建整个项目。通常是默认目标。这是 AllTarget() 规则向其添加依赖项的目标。

  • clean:删除可构建的文件和备份文件。包括所有目标文件、可执行文件、备份 Makefile 和编辑器常用作备份文件的文件 (*~)。

  • Makefiles:使用 Imake 重新生成所有 Makefile。

  • depend:生成源文件 (*.c) 和头文件 (*.h) 之间的依赖关系,并确保重建所有需要重新编译的目标文件。

  • tags:构建 vi 和 Emacs 使用的标签数据库。标签数据库只是 C 语言符号及其在源代码中位置的列表。它允许快速自动地定位函数定义和其他此类对象。请参阅 etags(1)、ctags(1)、vi(1) 的手册页以及 Emacs 附带的信息文档。

  • install:将最终二进制文件安装到主文件系统中。默认为 /usr/X11R6/ 下的目录,具体取决于每种组件的类型。例如,可执行文件进入 /bin;手册页进入 /man 下的正确部分目录。

  • lint:此目标在 Linux 系统上不是特别有用,因为没有常用的 lint 程序。lint 是一个检查 C 代码中常见样式问题和其他非语法错误的程序。GNU 编译器已将 lint 的大部分功能移至编译器中,使用 -Wall 标志集编译代码,您将拥有 lint 的大部分功能。在系统上安装 lint 程序后,您可以使用此 make 目标在项目的所有源文件上运行 lint。

自定义 Imake 规则

在某些情况下,编写您自己的自定义规则来生成 Makefile 非常有用。Imake 系统可以轻松指定一组与通常的 /usr/X11R6/lib/X11/config 不同的配置文件。这种类型的自定义超出了本文的范围,但 O'Reilly 出版了一本关于 Imake 的优秀书籍,其中有几章专门讨论此主题。有关该书的更多信息,请参阅参考资料。

参考资料

Otto 是 Red Hat Software 的开发人员,目前正忙于 Red Hat Linux 的下一个版本。可以通过电子邮件 otto@redhat.com 与他联系

加载 Disqus 评论