zoukankan      html  css  js  c++  java
  • 使用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。

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

    以上就是生成一个完整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

     

     

  • 相关阅读:
    正向代理和反向代理的区别
    response对象和request对象详解
    非controller层获取response和request对象
    Java中的Lock与synchronized
    如何解决jeecgBoot前端运行项目之后无法获取验证码的问题
    怎么定义一个自己的vue组件
    前端集成方案——理论(二)
    javascript基础-ajax
    网页中文乱码
    javascript基础-事件2
  • 原文地址:https://www.cnblogs.com/softidea/p/4623093.html
Copyright © 2011-2022 走看看