Makefile好难写
曾经也总结了一篇关于Makefile的文章《make和makefile的简单学习》。但是,总结完以后,发现写Makefile真的是一件非常痛苦的事情,的确非常痛苦。而更痛苦的是当需要将代码移植到别的系统上时,这就够你喝一壶的。再说了,作为程序员的我们,是不是更应该投入更多的精力到业务逻辑的编写与处理中呢,而并不是和Makefile纠结呢。
他们是怎么做到的?
不知道你是否阅读过一些Linux平台上开源的C或者C++项目,当你编译这些项目的时候,只需./configure
、 make
和make 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文件。具体的步骤如下:
- 运行autoscan命令
扫描源代码以搜寻普通的可移植性问题,比如检查编译器、库、头文件等,生成文件configure.scan,它是configure.ac的一个雏形。 - 将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 - 执行aclocal命令
aclocal是一个perl 脚本程序。aclocal根据configure.ac文件的内容,自动生成aclocal.m4文件。 - 执行autoheader命令
该命令生成config.h.in文件。该命令通常会从acconfig.h文件中复制用户附加的符号定义。 - 执行autoconf命令
有了configure.ac和aclocal.m4 两个文件以后,我们就可以使用autoconf来产生configure文件了。configure脚本能独立于autoconf运行,且在运行的过程中,不需要用户的干预。 - 在Project目录下新建Makefile.am文件
- 运行automake命令
automake会根据Makefile.am文件产生一些文件,其中最重要的是Makefile.in文件。 - 执行configure生成Makefile。
这些命令之间的关系如下图所示:
以上就是生成一个完整makefile的主要步骤。当然了,在实际项目中,会根据需要进行微调。下面我就拿最经典的Hello World
程序进行一个简单的演示。
从Hello World开始
测试代码:HelloWorld.cpp
源码如下:
#include <iostream>
using namespace std;
int main()
{
cout<<"Hello World"<<endl;
return 0;
}
- 运行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 - 重命名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 - 执行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 - 执行autoheader命令,生成config.h.in文件
- 执行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
文件。 - 在Project目录下新建Makefile.am文件,Makefile.am文件的内容如下:
AUTOMARK_OPTIONS=foreign
noinst_PROGRAMS=HelloWorld
HelloWorld_SOURCES=HelloWorld.cpp关于如何编写Makefile.am文件,我在下一节总结。
- 运行automake命令,就会得到Makefile.in文件
执行automake命令时,提示有些文件不存在,我们直接touch
即可。touch NEWS README ChangeLog AUTHORS
- 执行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.hmake 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