使用autoconf和automake生成Makefile文件(转)

时间:2023-03-09 08:33:52
使用autoconf和automake生成Makefile文件(转)

Makefile好难写

曾经也总结了一篇关于Makefile的文章《make和makefile的简单学习》。但是,总结完以后,发现写Makefile真的是一件非常痛苦的事情,的确非常痛苦。而更痛苦的是当需要将代码移植到别的系统上时,这就够你喝一壶的。再说了,作为程序员的我们,是不是更应该投入更多的精力到业务逻辑的编写与处理中呢,而并不是和Makefile纠结呢。

他们是怎么做到的?

不知道你是否阅读过一些Linux平台上开源的C或者C++项目,当你编译这些项目的时候,只需./configure、 makemake install就可以把程序编译完成并安装到系统中。你是否想过,这些开源的项目的编译和安装怎么如此的干净利落。

我们觉的写Makefile很麻烦,我们羡慕那些开源项目简单的编译安装步骤。那么如何让写Makefile变得简单一些呢?这个时候大师们就编写了一些能够自动根据系统生成Makefile文件的工具。这篇文章,我就对这些工具进行简单的总结,并结合一个简单的例子进行实践。

工具简介

大师们写的工具主要有哪些呢?如下所示:

  • GNU Automake
  • GNU Autoconf
  • GNU m4
  • GNU Libtool

如果你想使用这些工具,就先看看你的系统上有没有正确安装这些工具(我使用的是Ubuntu)。

which autoconf

如果没有安装,则执行以下语句安装就OK了。

sudo apt-get install autoconf

autoconf是一个用于生成可以自动地配置软件源码包,用以适应多种UNIX类系统的shell脚本工具,其中autoconf需要用到m4,便于生成脚本。automake是一个从Makefile.am文件自动生成Makefile.in的工具。为了生成Makefile.in,automake还需用到perl,由于automake创建的发布完全遵循GNU标准,所以在创建中不需要perl。libtool是一款方便生成各种程序库的工具。

对于工具的简单介绍就到此结束,接下来讲讲如何使用这些工具来生成一个Makefile文件。

自动生成makefile的步骤

使用上述的工具生成Makefile文件的步骤基本是死的,只需要在每步按照我们的需求进行适当的配置即可生成一个“漂亮”的Makefile文件。具体的步骤如下:

  1. 运行autoscan命令
    扫描源代码以搜寻普通的可移植性问题,比如检查编译器、库、头文件等,生成文件configure.scan,它是configure.ac的一个雏形。
  2. 将configure.scan文件重命名为configure.ac,并按照需要修改configure.ac文件
    configure.ac文件的内容是一些宏,confiugre.ac调用一系列autoconf宏来测试程序需要的或用到的特性是否存在,以及这些特性的功能。这些宏经过autoconf处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。configure.ac文件中的宏的顺序并没有规定,但是你必须在文件的最前面和最后面分别加上AC_INIT宏和AC_OUTPUT宏。在configure.ac中的一些常用宏定义。
    宏名称 说明
    AC_PREREQ 声明autoconf要求的版本号
    AC_INIT 定义软件名称、版本号、联系方式
    AM_INIT_AUTOMAKE 必须要的,参数为软件名称和版本号
    AC_CONFIG_SCRDIR 用来侦测所指定的源码文件是否存在, 来确定源码目录的有效性
    AC_CONFIG_HEADER 用于生成config.h文件,以便autoheader命令使用
    AC_PROG_CC 指定编译器,默认GCC
    AC_CONFIG_FILES 生成相应的Makefile文件,不同文件夹下的Makefile通过空格分隔。例如:AC_CONFIG_FILES([Makefile, src/Makefile])
    AC_OUTPUT 用来设定configure所要产生的文件,如果是Makefile,configure会把它检查出来的结果带入Makefile.in文件产生合适的Makefile
  3. 执行aclocal命令
    aclocal是一个perl 脚本程序。aclocal根据configure.ac文件的内容,自动生成aclocal.m4文件。
  4. 执行autoheader命令
    该命令生成config.h.in文件。该命令通常会从acconfig.h文件中复制用户附加的符号定义。
  5. 执行autoconf命令
    有了configure.ac和aclocal.m4 两个文件以后,我们就可以使用autoconf来产生configure文件了。configure脚本能独立于autoconf运行,且在运行的过程中,不需要用户的干预。
  6. 在Project目录下新建Makefile.am文件
  7. 运行automake命令
    automake会根据Makefile.am文件产生一些文件,其中最重要的是Makefile.in文件。
  8. 执行configure生成Makefile。

这些命令之间的关系如下图所示:

使用autoconf和automake生成Makefile文件(转)

以上就是生成一个完整makefile的主要步骤。当然了,在实际项目中,会根据需要进行微调。下面我就拿最经典的Hello World程序进行一个简单的演示。

从Hello World开始

测试代码:HelloWorld.cpp
源码如下:

#include <iostream>
using namespace std; int main()
{
cout<<"Hello World"<<endl;
return 0;
}
  1. 运行autoscan命令,生成的文件列表如下:
    -rw-rw-r-- 1 jelly jelly  96 Jun 13 22:05 HelloWorld.cpp
    -rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
    -rw-rw-r-- 1 jelly jelly 475 Jun 13 22:23 configure.scan
  2. 重命名configure.scan文件为configure.ac,修改configure.ac文件为如下样子:
    #                                               -*- Autoconf -*-
    # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69])
    AC_INIT(HelloWorld, 1.0, postmaster@126.com)
    AC_CONFIG_SRCDIR([HelloWorld.cpp])
    AC_CONFIG_HEADERS([config.h])
    AM_INIT_AUTOMAKE(HelloWorld, 1.0) # Checks for programs.
    AC_PROG_CXX # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions.
    AC_CONFIG_FILES([Makefile])
    AC_OUTPUT
  3. 执行aclocal命令,生成的文件列表如下:
    -rw-rw-r-- 1 jelly jelly    96 Jun 13 22:05 HelloWorld.cpp
    -rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
    -rw-rw-r-- 1 jelly jelly 497 Jun 13 22:58 configure.ac
    drwxr-xr-x 2 jelly jelly 4096 Jun 13 23:03 autom4te.cache
    -rw-rw-r-- 1 jelly jelly 39670 Jun 13 23:03 aclocal.m4
  4. 执行autoheader命令,生成config.h.in文件
  5. 执行autoconf命令,生成的文件列表如下:
    -rw-rw-r-- 1 jelly jelly     96 Jun 13 22:05 HelloWorld.cpp
    -rw-rw-r-- 1 jelly jelly 0 Jun 13 22:23 autoscan.log
    -rw-rw-r-- 1 jelly jelly 497 Jun 13 22:58 configure.ac
    -rw-rw-r-- 1 jelly jelly 39670 Jun 13 23:03 aclocal.m4
    drwxr-xr-x 2 jelly jelly 4096 Jun 13 23:08 autom4te.cache
    -rwxrwxr-x 1 jelly jelly 135929 Jun 13 23:08 configure

    可以看到,生成了可执行的configure文件。

  6. 在Project目录下新建Makefile.am文件,Makefile.am文件的内容如下:
    AUTOMARK_OPTIONS=foreign
    noinst_PROGRAMS=HelloWorld
    HelloWorld_SOURCES=HelloWorld.cpp

    关于如何编写Makefile.am文件,我在下一节总结。

  7. 运行automake命令,就会得到Makefile.in文件
    执行automake命令时,提示有些文件不存在,我们直接touch即可。
    touch NEWS README ChangeLog AUTHORS
  8. 执行configure生成Makefile

现在得到我们需要的Makefile文件了,接下来,你应该怎么做了。

如何编写Makefile.am

上面对于一个简单的HelloWorld程序使用autoconf和automake工具成功的生成了Makefile文件,对于编写Makefile.am文件一直没有详细说明,这里就对如何编写Makefile.am文件进行详细的说明。

Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标,它由什么源文件生成,要安装到什么目录等即可。automake会根据我们写的Makefile.am来自动生成Makefile.in。Makefile.am中定义的宏和目标会指导automake生成指定的代码。常见的文件编译类型有下面几种:

  • PROGRAMS;表示可执行文件
  • LIBRARIES;表示库文件
  • LTLIBRARIES;这也是表示库文件,前面的LT表示libtool
  • HEADERS;头文件
  • DATA;数据文件,不能执行。

下面就对编译成可执行文件、动态库文件和静态库文件常用的写法进行简单介绍。

  • 编译可执行文件
    比如这样的一个Makefile.am文件:
    bin_PROGRAMS = client
    
    client_SOURCES = key.c connect.c client.c main.c session.c hash.c
    client_CPPFLAGS = -DCONFIG_DIR=\"$(sysconfdir)\" -DLIBRARY_DIR=\"$(pkglibdir)\"
    client_LDFLAGS = -export-dynamic -lmemcached
    noinst_HEADERS = client.h INCLUDES = -I/usr/local/libmemcached/include/ client_LDADD = $(top_builddir)/sx/libsession.a \
    $(top_builddir)/util/libutil.a

    每个字段具体的含义如下:

    名称 含义
    bin_PROGRAMS 表示指定要生成的可执行应用程序文件,这表示可执行文件在安装时需要被安装到系统中;如果只是想编译,不想被安装到系统中,可以用noinst_PROGRAMS来代替
    client_SOURCES 表示生成可执行应用程序所用的源文件,这里注意,client_是由前面的bin_PROGRAMS指定的,如果前面是生成example,那么这里就是example_SOURCES,其它的类似标识也是一样
    client_CPPFLAGS 这和Makefile文件中一样,表示C语言预处理参数,这里指定了DCONFIG_DIR,以后在程序中,就可以直接使用CONFIG_DIR。不要把这个和另一个CFLAGS混淆,后者表示编译器参数
    client_LDFLAGS 连接的时候所需库文件的标识,这个也就是对应一些如-l,-shared等选项
    noinst_HEADERS 这个表示该头文件只是参加可执行文件的编译,而不用安装到安装目录下。如果需要安装到系统中,可以用include_HEADERS来代替
    INCLUDES 链接时所需要的头文件
    client_LDADD 链接时所需要的库文件,这里表示需要两个库文件的支持
    SUBDIRS 表示在处理本目录之前需要递归处理哪些子目录
  • 编译动态库文件
    想要编译XXX.so文件,需要用_PROGRAMS类型,这里一个关于安装路径要注意的问题是,我们一般希望将动态库安装到lib目录下,只需要写成lib_PROGRAMS就可以了,因为前面的lib表示安装路径(为什么?稍后讲),但是automake不允许这么直接定义,可以采用下面的办法,也是将动态库安装到lib目录下。
    projectlibdir=$(libdir) //新建一个目录,该目录就是lib目录
    projectlib_PROGRAMS=project.so
    project_so_SOURCES=xxx.c
    project_so_LDFLAGS=-shared -fpic //GCC编译动态库的选项
  • 编译静态库文件
    对于下面的一个Makefile.am文件:
    noinst_LTLIBRARIES = libutil.a
    noinst_HEADERS = inaddr.h util.h compat.h pool.h xhash.h url.h device.h
    libutil_a_SOURCES = access.c config.c datetime.c hex.c inaddr.c log.c device.c pool.c rate.c sha1.c stanza.c str.c xhash.c

    对上述Makefile.am文件的解释如下:

    名称 含义
    noinst_LTLIBRARIES 这里要注意用的是LTLIBRARIES,另外还有LIBRARIES,两个都表示库文件。前者表示libtool库,用法上基本是一样的。如果需要安装到系统中的话,用lib_LTLIBRARIES。一般推荐使用libtool库编译目标,因为automake包含libtool,这对于跨平台可移植的库来说,肯定是一个福音。
    libutil_a_LIBADD 静态库编译连接时需要其它的库的话,采用XXXX_LIBADD选项
  • 头文件我们一般需要导入一些*.h的头文件,如果你在Makefile.am中没有标识需要导入的头文件,可能在make dist打包的时候出现问题,头文件可能不会被打进包里面。
    #可以将头文件引入
    include_HEADERS=../include/common.h ../include/sum.h ../include/get.h ../include/val.h

    make install,头文件默认会被安装到linux系统/usr/local/include。

  • 数据文件
    data_DATA = data1 data2

在上面说到一个问题,在编译动态库文件时:

写成lib_PROGRAMS就可以了,因为前面的lib表示安装路径,为什么?

这里涉及到编写Makefile.am文件时,安装路径的问题。

安装路径

在默认的情况下,执行make install命令,则会将文件安装到$(prefix) = /usr/local路径下。我们可以通过./configure --prefix=xxx的方式来进行修改。基于此,系统还定义了以下一些路径变量:

名称
bindir $(prefix)/bin
libdir $(prefix)/lib
datadir $(prefix)/share
sysconfdir $(prefix)/etc
includedir $(prefix)/include

那么现在你就应该明白以下知识点了:

  • bin_PROGRAMS表示将生成的可执行文件安装到$(bindir)目录下
  • lib_LTLIBRARIES表示将静态库安装到$(libdir)目录下
  • 上面说的projectlib_PROGRAMS表示安装到$(projectlibdir)所表示的目录下

如果我们在Makefile.am文件中定义了一个新的路径:

sysdatedir = $(prefix)/sysdate
sysdate_DATA = data1 data2

此时data1和data2就会作为数据文件安装到$(prefix)/sysdate路径下。现在关于安装路径的文件就应该明白了吧。

打包

一切搞定以后,可以正常的生成Makefile文件,编译生成的程序或库也没有任何问题了,此时我们可能需要对源文件进行打包。使用make dist命令就可以完成自动打包任务,自动打包包含的内容如下:

  • 所有源文件
  • 所有的Makefile.am文件
  • configure读取的文件
  • Makefile.am中包含的文件
  • EXTRA_DIST指定的文件
  • 采用dist及nodist指定的文件,如可以将某一源文件指定为不打包:
    nodist_client_SOURCES = client.c

使用make dist命令之后,就会在当前目录下生成一个在AC_INIT中定义的软件名称和版本号的tar.gz压缩包。

总结

总结的好长的一篇文章,文章虽长,但讲的不深,入门足够。更多的相关知识只有通过更多在实际项目中锤炼,阅读更多的相关文档学习了。

为了更好的学习这方面的知识,大伙也可以阅读一些Linux平台的开源代码,看看这些开源代码的Makefile是如何生成的。最后,祝各位好运。

http://www.jellythink.com/archives/1056