一个 mini Autotool 项目实践

本贴最后更新于 1920 天前,其中的信息可能已经时移世异

****************

用户提供的输入文件

在这个项目中我们需要用户仅仅提供两个文件. 剩下的全部都交给 Autotools 去处理和生成:

  • "Makefile.am" 是 automake 的输入文件
  • "configure.in" 是 autoconf 的输入文件
    我喜欢把"Makefile.am"想象成一个项目构建要求的最基本的规范: 需要编译什么, 以及它安装在哪? 这可能是 Automake 的最大优势 - 描述尽可能简单, 但最终产品是一个带有一系列方便 make target 的"Makefile".
    "configure.in"是宏调用和 shell 代码片段的模板, "autoconf"使用它来生成"configure"脚本. "autoconf"将"configure.in"的内容复制到"configure"中, 并且扩展输入中出现的宏. 其他文本是逐字复制的.
    首先来看看用户提供的"Makfile.am":
## Makefile.am -- Process this file with automake to produce Makefile.in
     bin_PROGRAMS = foonly
     foonly_SOURCES = main.c foo.c foo.h nly.c scanner.l parser.y
     foonly_LDADD = @LEXLIB@

这个"Makefile.am"指定我们当运行 make install 时在"bin"目录中编译和安装一个名为"foonly"的程序. 用于编译"foonly"的源文件是 C 源文件'main.c', 'foo.c', 'nly.c'和'foo.h', 'scanner.l'中的 lex 程序和 'parser.y'中的 yacc 语法. 这也引出了 Automake 非常好的一个方面: 因为 lex 和 yacc 都从它们的输入文件生成中间 C 程序, Automake 知道如何构建这样的中间文件并将它们链接到最终的可执行文件中. 最后, 我们必须记住链接一个合适的 lex 库.
接下来是"configure.in":

dnl Process this file with autoconf to produce a configure script.
     AC_INIT(main.c)
     AM_INIT_AUTOMAKE(foonly, 1.0)
     AC_PROG_CC 
     AM_PROG_LEX
     AC_PROG_YACC
     AC_OUTPUT(Makefile)

这个"configure.in"调用一些强制的 Autoconf 和 Automake 初始化宏, 然后调用 AC_PROG 系列的一些 Autoconf 宏来找到合适的 C 编译器, lex 和 yacc 程序. 最后, AC_OUTPUT 宏用于使生成的"configure"脚本输出"Makefile" - 但是来自哪里呢? 它是从"Makefile.in"处理的, "Automake"根据你的"Makefile.am"而生成. 也就是说"Makefile.in"是输入给 configure 的, 然后生成"Makefile".

graph TD; makefile.am --> AUTOMAKE; AUTOMAKE --> makefile.in; configure.in --> AUTOCONF; AUTOCONF --> configure; makefile.in --> configure; configure --> Makefile;

生成输出文件

在之后的文章中会介绍到从输入文件到输出文件需要什么命令去执行. 在这里我们先说"configure"的生成:

$ aclocal
$ autoconf

因为"configure.in"所包含的宏调用好多是 Autoconf 自身不认识的(因为前面提到了, configure.in 会调用一些 Autoconf 和 Automake 强制初始化宏)--"AM_INIT_AUTOMAKE"就是一个典型的例子. 所以很有必要为 Autoconf 收集所有的宏定义, 让它可以在生成"configure"的时候用. 这个就是通过 aclocal 这个程序完成的. 名字的由来是由于它生成了"aclocal.m4". 如果你看生成出来的"aclocal.m4", 就会发现"AM_INIT_AUTOMAKE"的定义.
在运行 autoconf 之后, 你就会发现"configure"生成了出来, 但是先运行 aclocal 是很重要的, 因为"automake"依赖于"configure.in"和"aclocal.m4". 关系是不是有些凌乱了, 的确有点恶心. 其实各个文件的出场顺序是这样的:

autoscan 生成 configure.ac --> 调整 configure.ac 中的一些参数 --> autoconf 先生成一波 autom4te 和 configure --> 然后编辑 Makefile.am --> 再次编辑 configure.ac, 比如引入 AUTOMAKE, OUTPUT 等的宏 --> aclocal --> automake --add-missing 这一步是需要有根据之前配置好的 configure.ac, 因为里面写了再哪些字目录中有 Makefile.am --> 最后 autoconf 生成最终版 configure --> ./configure 生成出 Makefile.
这里我写了一篇 autoconf&automake 实战性指导, 具体可以帮忙理清楚它们之间的关系, 穿插在这里是因为我在学习这里的时候也是凌乱了. 于是在这里阅读链接文章比较舒服.

我们在看看 Automake:

$ automake --add-missing
     automake: configure.in: installing ./install-sh
     automake: configure.in: installing ./mkinstalldirs
     automake: configure.in: installing ./missing
     automake: Makefile.am: installing ./INSTALL
     automake: Makefile.am: required file ./NEWS not found
     automake: Makefile.am: required file ./README not found
     automake: Makefile.am: installing ./COPYING
     automake: Makefile.am: required file ./AUTHORS not found
     automake: Makefile.am: required file ./ChangeLog not found

"--add-missing"会将一些样板文件从 Automake 的安装中拷贝到当前目录. "COPYING"文件包含 GPL 规则, 并且变化是不频繁的, 所以可以不需要用户介入的生成. 一系列的脚本文件也会生成, 它们都是为了给生成的"Makefile"使用的. 尤其是给"install" target. 但是可以看到一些 Requred 的文件仍然是丢失的:
NEWS
用户可见的包更改记录. 格式不严格, 但对最新版本的更改应显示在文件的顶部.
README
用户首先要查看包的目的, 以及可能的特殊安装说明.
AUTHORS
维护者的邮箱
ChangeLog
这个格式是比较严格的, 这也是相当重要的一个文件. 记录了这个包的变化.
我们先来欺骗一下 Automake:

touch NEWS README AUTHORS ChangeLog
automake --add-missing

到目前为止, 目录的内容看起来相当完整, 可以让你联想到你之前安装的 GNU 项目的顶级目录情况:

AUTHORS    INSTALL      NEWS        install-sh    mkinstalldirs
COPYING    Makefile.am  README      configure     missing
ChangeLog  Makefile.in  aclocal.m4  configure.in

接下来就可以将这些所有文件打包到一个 tar 文件中, 然后给其他人在他们自己的系统上进行安装. 在由 Automake 生成的"Makefile.in"中有个 make 的 target 会很容易的帮助生成不同的发行版. 关于这块儿的东西, 在以后会单独谈到. 当用户拿到后, 只需要打开 tar 包, 然后:

$ ./configure
     creating cache ./config.cache
     checking for a BSD compatible install... /usr/bin/install -c
     checking whether build environment is sane... yes
     checking whether make sets ${MAKE}... yes
     checking for working aclocal... found
     checking for working autoconf... found
     checking for working automake... found
     checking for working autoheader... found
     checking for working makeinfo... found
     checking for gcc... gcc
     checking whether the C compiler (gcc  ) works... yes
     checking whether the C compiler (gcc  ) is a cross-compiler... no
     checking whether we are using GNU C... yes
     checking whether gcc accepts -g... yes
     checking how to run the C preprocessor... gcc -E
     checking for flex... flex
     checking for flex... (cached) flex
     checking for yywrap in -lfl... yes
     checking lex output file root... lex.yy
     checking whether yytext is a pointer... yes
     checking for bison... bison -y
     updating cache ./config.cache
     creating ./config.status
     creating Makefile

 $ make all
     gcc -DPACKAGE=\"foonly\" -DVERSION=\"1.0\" -DYYTEXT_POINTER=1  -I. -I. \ 

 -g -O2 -c main.c
     gcc -DPACKAGE=\"foonly\" -DVERSION=\"1.0\" -DYYTEXT_POINTER=1  -I. -I. \ 

 -g -O2 -c foo.c
     flex   scanner.l && mv lex.yy.c scanner.c
     gcc -DPACKAGE=\"foonly\" -DVERSION=\"1.0\" -DYYTEXT_POINTER=1  -I. -I. \ 

 -g -O2 -c scanner.c
     bison -y   parser.y && mv y.tab.c parser.c
     if test -f y.tab.h; then \ 

 if cmp -s y.tab.h parser.h; then rm -f y.tab.h; \ 

 else mv y.tab.h parser.h; fi; \
     else :; fi
     gcc -DPACKAGE=\"foonly\" -DVERSION=\"1.0\" -DYYTEXT_POINTER=1  -I. -I. \ 

 -g -O2 -c parser.c
     gcc  -g -O2  -o foonly  main.o foo.o scanner.o parser.o -lfl

关于输入文件的维护

如果在项目中更改了 GNU Autotool 中的任何一个输入文件, 那么必须重新生成由机器生成的这些文件才能使得改动生效.
当然我们也可以通过一次运行一个所需的工具来重新生成这些文件. 但是, 正如我们上面所看到的, 计算依赖关系可能很困难 - 比如说特定的更改是否需要运行 aclocal? 特定更改是否需要运行 autoconf? 针对于这个问题有两种解决方案:

  • 第一种解决方案是使用 autoreconf命令 此工具通过以正确的顺序重新运行所有必需的工具来重新生成所有派生文件. 它有点像一个暴力的解决方案, 但它工作得很好, 特别是如果你不想容纳其他维护者, 或定期维护会很麻烦.
  • 另一种选择是 Automake 的"维护者模式" 通过从"configure.in"中调用 AM_MAINTAINER_ MODE 宏, "automake"将在"configure"中激活 --enable-maintainer-mode 选项. 关于这个功能的细节, 再以后会说到.

打包生成的文件

关于如何处理生成的文件的争论是在相关的邮件列表上争论的热火朝天的. 归纳总结下基本有两种观点:

  • 一个论点是生成的文件不应该包含在包中, 而应该只包含源代码的"首选形式" 根据这个定义, "configure"是一个派生文件, 就像一个目标文件, 它不应该包含在包中. 因此, 用户应该在构建软件包之前使用 GNU Autotools 来自我引导. 我相信这种纯粹的方法有一些优点, 因为它不鼓励打包衍生文件的做法.
  • 另一个论点是提供这些文件的优点远远超过了上面提到的对良好软件工程实践的违背 通过包括生成的文件, 用户可以方便地无需关注和使用的所有不同版本的工具保持同步. 对于 Autoconf 尤其如此, 因为"configure"脚本通常由使用本地修改版本的 autoconf 和本地安装的宏的维护者生成. 如果用户重新生成"configure", 则结果可能与预期的不同. 当然, 这是一种糟糕的做法, 但它恰好反映了现实.
    我相信答案是当包将要分发给广泛的用户时, 在包中包含生成的文件. 对于内部包, 前一个论点可能更有意义, 因为这些工具也可以在版本控制下进行.

记录和 changelog

在"ChangeLog"中记录更改时, 一个条目是由一个人完成的. 逻辑更改组合在一起, 而逻辑上不同的更改(即"更改集")由单个空行分隔. 以下是 Automake 自己的"ChangeLog"的示例:

2019-09-02  Yuan Ren  <reyren179@gmail.com> 

 * automake.in (finish_languages): Only generate suffix rule
             when not doing dependency tracking. 

 * m4/init.m4 (AM_INIT_AUTOMAKE): Use AM_MISSING_INSTALL_SH.
             * m4/missing.m4 (AM_MISSING_INSTALL_SH): New macro. 

 * depend2.am: Use @SOURCE@, @OBJ@, @LTOBJ@, @OBJOBJ@,
             and @BASE@.  Always use -o.

关于"ChangeLog"条目的另一个要点是它们应该言简意骇. 条目没有必要详细解释为什么要进行更改. GNU 编码标准提供了一整套保存"ChangeLog"的指南. 尽管可以使用任何文本编辑器创建 ChangeLog 条目, 但 Emacs 提供了一种主模式来帮助编写它们.

OK, 这一篇信息量不小. 接下来会深入这里涉及的没个文件进行细节描述.


相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...