Makefile 自动生成 - 三种目录结构

2015年12月22日 14:48:52

这个标题《Makefile 自动生成》 8 号立的,拖到今天刚好两周。ps 2017/8/24 14:04:25 删改了 2/3 的内容,改为现在的标题。

一方面因为最近在忙别的工作,整理数据库表结构的;另一方面,是直到今天,对于自动生成 Makefile 操作依旧懵懵懂懂,在实际项目中无法使用,还是自己手动写 Makefile 文件。下面进入主题。

Makefile 自动生成,实际项目中接触到的工具有:easymake、cmake 和 autotools。 在这里只整理 autotools。

autotools 作为重点学习,所以发现了很多篇不错的帖子。

  1. 编译利器:大型项目如何使用Automake和Autoconf完成编译配置(ver+0.6) 内容很棒,却找不到好的排版,将其摘入自己博客
  2. 概念:GNU构建系统和Autotool实践:GNU构建系统 作者整理的真心不错,专业功底、语言描述、文章结构都很赞;
  3. 使用autotools生成Makefile学习笔记 不能当做一篇学习的帖子,其“常用功能分析”一节可以作为参考手册,认清知识点

引子

无论是在 Linux 还是在 Unix 环境中,make 都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到 make 或 make install。利用 make 工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用 make 和 makefile 工具就可以轻而易举的理顺各个源文件之间纷繁复杂的相互关系。

但是如果通过查阅 make 的帮助文档来手工编写 Makefile,对任何程序员都是一场挑战。幸而有 GNU 提供的 Autoconf + Automake 这两套工具使得编写 makefile 不再是一个难题。

本文将介绍如何利用 GNU Autoconf + Automake 这两套工具来协助我们自动产生 Makefile 文件,并且让开发出来的软件可以像大多数源码包那样,只需”./configure”, “make”,”make install” 就可以把程序安装到系统中。

以下文字转载自:运用 Autoconf 和 Automake 生成 Makefile 的学习之路 ,原作者的几处笔误已改正,除此之外,重写第七、八、十节,新添第十一节 放弃作者第六、七、八、十节。这篇帖子的评论也值得一看。

niel 2017/8/24 8:53:09 补充,再次学习 autotools 时,发现作者写的全都是皮毛。七、八、十节作者纯粹是强行凑字数,估计作者自己就没弄懂——没说到点上不说,好几处地方说的似是而非——初学者看了反而增加学习难度,甚至误入歧途。我在作者基础上重新整理的这三节,当时表述起来就觉得困难,整理完依旧是不清不楚,现在看来根本原因就在于毫无理解,全文都是在介绍表面功夫,一点点原理的东西都没讲。

niel 最让人难以忍受的是,看完作者的帖子,你发现你只会例子,做不到举一反三,稍微复杂点的项目(比如使用动态库、配置文件的存放)就无从下手。因为使用 autotools 的关键——怎么编写 configure.ac 和 Makefile.am——作者根本就没讲(没讲明白和没讲等价),从网上滥竽充数的各种帖子里生拉硬拽凑了第七、八、十节,作者自己都不会写 configure.ac、Makefile.am,给我们讲个毛线啊

网上有很多帖子都参考了 IBM 的例解 autoconf 和 automake 生成 Makefile 文件,但相比上面这篇帖子就显得很一般了,只是平台好罢了!

概念介绍

Makefile

makefile 用来定义整个工程的编译规则。一个工程中的源文件计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
  
makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法。

Autoconf

Autoconf 是一个用于生成可以自动地配置软件源码包,用以适应多种UNIX类系统的 shell 脚本(即 configure)工具。由 Autoconf 生成的配置脚本在运行的时候与 Autoconf 是无关的, 就是说配置脚本的用户并不需要拥有 Autoconf。

对于每个使用了 Autoconf 的软件包,Autoconf 从一个列举了该软件包需要的,或者可以使用的系统特征的列表的模板文件中生成配置脚本。在 shell 代码识别并响应了一个被列出的系统特征之后,Autoconf 允许多个可能使用(或者需要)该特征的软件包共享该特征。 如果后来因为某些原因需要调整 shell 代码,就只要在一个地方进行修改; 所有的配置脚本都将被自动地重新生成以使用更新了的代码。

Automake

Automake 是一个从 “Makefile.am”文件自动生成 “Makefile.in” 的工具。每个“Makefile.am”基本上是一系列 make 的宏定义 (make 规则也会偶尔出现)。生成的“Makefile.in”服从 GNU Makefile 标准。GNU Makefile 标准文档长、复杂,而且会发生改变。Automake 的目的就是解除个人 GNU 维护者维护 Makefile 的负担 (并且让 Automake 的维护者来承担这个负担)。典型的 Automake 输入文件是一系列简单的宏定义。处理所有这样的文件以创建 “Makefile.in”。在一个项目(project)的每个目录中通常包含一个 “Makefile.am”。Automake 在几个方面对一个项目做了限制;例如它假定项目使用 Autoconf 并且对 “configure.in”的内容施加了某些限制。

Automake 支持三种目录层次: “flat”、“shallow”和“deep”。

  • 一个 flat(平)包指的是所有文件都在一个目录中的包。为这类包提供的“Makefile.am” 缺少宏 SUBDIRS。这类包的一个例子是 termutils。
  • 一个deep(深)包指的是所有的源代码都被储存在子目录中的包;顶层 目录主要包含配置信息。GNU cpio 是这类包的一个很好的例子,GNU tar 也是。deep包的顶层“Makefile.am”将包括宏SUBDIRS,但没有其它定义需要创建的对象的宏。
  • 一个shallow(浅)包指的是主要的源代码储存在顶层目录中,而 各个部分(典型的是库)则储存在子目录中的包。Automake 本身就是这类包(GNU make 也是如此,它现在已经不使用 automake)。

其他

libtool 是一款方便生成各种程序库的工具。非必须。

下面,就以这三种目录层次结构分别讲解。

Flat 目录结构:

目录结构:

1
2
3
4
Helloworld 
|-mytest.h
|-mytest.c
|-mymain.c

顶级目录 helloworld,该目录下存在三个文件。mytest.h 头文件声明了 sayhello() 方法;mytest.c 中实现了 sayhello() 方法;mymain.c 中的 main 调用了 sayhello() 方法。

执行步骤:

  1. Autoscan

    在 helloworld 目录下执行 autoscan 命令,其中生成一个 configure.scan 的文件。

  2. 将 configure.scan 文件更名为 configure.in(ps 帖子太早,未与时俱进) configure.ac 文件。

  3. 打开 configure.in configure.ac 文件,修改文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script.

    #AC_INIT([2.68])
    AC_INIT([hello], [1.0], [**@126.com]) # 修改
    AC_CONFIG_SRCDIR([mymain.c])
    #AC_CONFIG_HEADERS([config.h]) # 注释掉了

    #AM_INIT_AUTOMAKE(hello, 1.0) # 新增 ps 这种语法已经不推荐使用了
    AM_INIT_AUTOMAKE # 参考 autoconf 手册

    # Check for programs
    AC_PROG_CC

    # Check for libraries
    # Check for header files
    # Check for typedefs, structures, and compiler characteristics.
    # Check for library functions.

    AC_OUTPUT(Makefile) # 修改
  4. 然后分别执行以下两个命令:

    • aclocal
    • autoconf
    • autoheader(可选,和 AC_CONFIG_HEADERS 宏有关联,此细节【稍后详细描述】【另,注释掉 AC_CONFIG_HEADERS 宏应该是一种不规范的写法】)
  5. 在 helloworld 文件夹下创建一个名为 Makefile.am 的文件,并输入以下内容:

    1
    2
    3
    AUTOMAKE_OPTIONS=foreign
    bin_PROGRAMS=hello
    hello_SOURCES=mymain.c mytest.c mytest.h
  6. 执行命令 automake <--add-missing | -a>,automake 会根据 Makefile.am 文件产生一些文件,其中包含最重要的 Makefile.in。在这里使用选项 <--add-missing | -a> 可以让 automake 自动添加一些必需的脚本文件,如果不带此参数会因为缺失文件而报错。(更多内容参考最后一章:automake 软件等级)

  7. 执行“./configure”命令生成 Makefile 文件

  8. 执行“make”命令来编译 hello.c 程序,从而生成可执行程序 hello。生成可执行程序 hello 后,执行“./hello”。

哈哈,一定看到你想要的结果了吧。ps:更详细介绍《Automake 之 Flat 目录机构》

shallow 目录结构

目录结构

1
2
3
4
5
helloworld 
|-mymain.c
|head
||-mytest.h
||-mytest.c

顶级目录 helloworld,该目录下存在一个主文件 mymain.c 和一个目录 head。head 目录中,mytest.h 头文件声明了 sayhello() 方法;mytest.c 中实现了 sayhello() 方法;mymain.c 中的 main 调用了 sayhello() 方法。

执行步骤:

  1. 在顶层目录下运行 autoscan 产生 configure.scan 文件

  2. 将configure.scan文件更名为 configure.in configure.ac 文件

  3. 打开 configure.in configure.ac 文件,修改文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script.

    #AC_INIT([2.68])
    AC_INIT([hello], [1.0], [**@126.com])
    AC_CONFIG_SRCDIR([mymain.c])
    #AC_CONFIG_HEADERS([config.h])

    AM_INIT_AUTOMAKE(hello, 1.0)

    # Check for programs
    AC_PROG_CC
    #使用静态库编译,需要此宏定义
    AC_PROG_RANLIB

    # Check for libraries
    # Check for header files
    # Check for typedefs, structures, and compiler characteristics.
    # Check for library functions.

    AC_OUTPUT(Makefile head/Makefile)
  4. 然后分别执行以下两个命令:

    • aclocal
    • autoconf
  5. 在 head 文件夹下创建 Makefile.am 文件,内容如下:

    1
    2
    3
    AUTOMAKE_OPTIONS=foreign
    noinst_LIBRARIES=libmytest.a
    libmytest_a_SOURCES=mytest.h mytest.c
  6. 在 helloworld 文件夹下创建 Makefile.am 文件,内容如下:

    1
    2
    3
    4
    5
    AUTOMAKE_OPTIONS=foreign
    SUBDIRS=head
    bin_PROGRAMS=hello
    hello_SOURCES=mymain.c
    hello_LDADD=head/libmytest.a
  7. 执行命令“automake –add-missing”,automake 会根据 Makefile.am 文件产生一些文件,其中包含最重要的 Makefile.in

  8. 执行“./configure”命令生成 Makefile 文件

  9. 执行“make”命令来编译 hello.c 程序,从而生成可执行程序 hello。生成可执行程序 hello 后,执行“./hello”。

哈哈,shallow 的目录结构也搞定了哦。

Deep 目录结构

目录结构

1
2
3
4
5
6
helloworld 
|head
||-mytest.h
||-mytest.c
|src
||-mymain.c

顶级目录 helloworld,该目录下存在两个目录 src 和 head。Head 目录中,mytest.h 头文件声明了 sayhello() 方法;mytest.c 中实现了 sayhello()方法;src 目录中的 mymain.c 中的 main 调用了 sayhello() 方法。

执行步骤

  1. 在顶层目录下运行 autoscan 产生 configure.scan 文件

  2. 将 configure.scan 文件更名为 configure.in configure.ac 文件

  3. 打开 configure.in configure.ac 文件,修改文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script.

    #AC_INIT([2.68])
    AC_INIT([hello], [1.0], [**@126.com])
    AC_CONFIG_SRCDIR([src/mymain.c])
    #AC_CONFIG_HEADERS([config.h])

    AM_INIT_AUTOMAKE(hello, 1.0)

    # Check for programs
    AC_PROG_CC
    #使用静态库编译,需要此宏定义
    AC_PROG_RANLIB

    # Check for libraries
    # Check for header files
    # Check for typedefs, structures, and compiler characteristics.
    # Check for library functions.

    AC_OUTPUT(Makefile head/Makefile src/Makefile)
  4. 然后分别执行以下两个命令:

    • aclocal
    • autoconf
  5. 在 head 文件夹下创建 Makefile.am 文件,内容如下:

    1
    2
    3
    AUTOMAKE_OPTIONS=foreign
    noinst_LIBRARIES=libmytest.a
    libmytest_a_SOURCES=mytest.h mytest.c
  6. 在 src 文件夹下创建 Makefile.am 文件,内容如下:

    1
    2
    3
    4
    AUTOMAKE_OPTIONS=foreign
    bin_PROGRAMS=hello
    hello_SOURCES=mymain.c
    hello_LDADD=../head/libmytest.a
  7. 在 helloworld 文件夹下创建 Makefile.am 文件,内容如下:

    1
    2
    AUTOMAKE_OPTIONS=foreign
    SUBDIRS=head src
  8. 执行命令“automake –add-missing”,automake 会根据 Makefile.am 文件产生一些文件,其中包含最重要的 Makefile.in

  9. 执行“make”命令来编译 hello.c 程序,从而生成可执行程序 hello。生成可执行程序 hello 后,执行“./hello”。

哈哈,deep 目录下的编译与链接也搞定了!

总结

以上 3 种目录层次结构的测试代码在 附件 中。归纳一下以上所有例子的流程:

  1. 在存放源代码的顶层目录下执行 autoscan 命令生成 configure.scan 文件。
  2. 将 configure.scan 文件改名为 configure.in configure.ac,并对其默认配置进行修改。
  3. 执行 aclocal、autoconf 两个命令,分别生成 aclocal.m4、configure 文件。
  4. 在每个目录下创建一个名为 Makefile.am 的文件,并输入相应的内容。 (ps 下文试验证明最好将此步骤放到最前面)
  5. 执行 automake –add-missing,它根据 Makefile.am 文件,生成 Makefile.in。 (ps 此步骤只能在 aclocal -?autoconf-之后)
  6. 执行 ./configure 脚本文件,它根据 Makefile.in 文件,生成最终的 Makefile 文件。
  7. 生成 Makefile 之后,执行“make”编译工程并且生成可执行程序。

流程图

看到这里,你除了跑跑例子什么都不会,只是对于使用 autotools 创建 Makefile 的步骤有了一个模糊的认识,但正如文章开头吐槽的那样,使用 autotools 的关键在于 configure.ac 和 Makefile.am 文件的书写。在 编译利器:大型项目如何使用Automake和Autoconf完成编译配置(ver+0.6) 中着重介绍了这两个文件的书写规则。

附:automake 软件等级【测试……】

在上文的例子中我们看到 Makefile.am 文件的第一行内容就是设置 automake 软件等级。

简单的例子

AUTOMAKE_OPTIONS 为设置 automake 的选项,由于 GNU 对自己发布的软件有严格的规范,比如必须带许可证声明文件等,否则 automake 执行时会出错,automake 提供了 3 种软件等级:foreign、gnu 和 gnits,让用户选择采用,默认等级为 gnu。在本例中使用 foreign 等级,它只检查必须的文件。

gun 等级

automake 软件等级默认 gnu,此等级下执行 automake 需要文件:

1
2
3
4
5
6
7
8
9
* install-sh 
* missing
* INSTALL
* NEWS
* README
* AUTHORS
* ChangeLog
* COPYING
* depcomp

其中,以下文件在执行 automake -a 的时候会自动生成

1
2
3
4
5
* install-sh 
* missing
* INSTALL
* COPYING
* depcomp

所以,接下来手动生成剩下的文件

1
[root@localhost str]# touch NEWS README AUTHORS ChangeLog 

8、执行 automake -a

1
2
3
4
5
6
7
[root@localhost str]# automake -a
configure.ac: installing `./install-sh'
configure.ac: installing `./missing'
Makefile.am: installing `./INSTALL'
Makefile.am: installing `./COPYING'
Makefile.am: installing `./compile'
Makefile.am: installing `./depcomp'