工程管理器make及autotools工具
make工程管理器就是一个“自动编译管理器”,“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。用户只需编写一次简单的编译语句即可。它大大提高了项目开发和维护的工作效率,而且几乎所有Linux下的项目编程均会涉及到make管理器。
Makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
Makefile的好处就是“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率,Makefile用来告诉make如何编译和链接一个程序,是make读入的唯一配置文件。
make通过比较对应文件(规则的目标和依赖)的最后修改时间,来决定哪些文件需要更新,哪些文件不需要更新。对需要更新的文件,make就执行数据库中所记录的相应命令(在make读取Makefile以后会建立一个编译过程的描述数据库。此数据库中记录了所有各个文件之间的相互关系,以及它们的关系描述)来重建它,对于不需要重建的文件,make什么也不做。
make也可以通过命令行选项来指定需要重新编译的文件。
当使用make工具进行编译时,工程中以下几种文件在执行make时将会被编译(重新编译)。
如果所有的源文件没有被编译过,则对各个C源文件编译并进行链接,生成最后的可执行程序。
每一个在上次执行make之后修改过的C源代码文件在本次执行make时将会被重新编译。
头文件在上一次执行make之后被修改。则所有包含此头文件的C源文件在本次执行make时将被重新编译。
后两种情况是make只将修改过的C源文件重新编译生成.o文件,对于没有修改的文件不进行任何工作。重新编译过程中,任何一个源文件的修改将产生新对应的.o文件,新的.o文件将和以前的已经存在、此次没有重新编译的.o文件重新链接接生成最后的可执行程序。
Makefile基本结构
在一个Makefile中通常包含如下内容:
a.需要由make工具创建的目标体(target),目标体通常是目标文件、可执行文件或是一个标签
b.要创建的目标体所依赖的文件或目标(dependency_file)
c.创建每个目标体时需要运行的命令(command)
格式为:
target : dependency_files
<tab>command
在Makefile中的每一个command前必须有"tab"符,否则在运行make命令时会出错。
一条makefile规则规定了编译的依赖关系,也就是目标文件依赖于条件,生成规则用命令来描述。
如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile如下:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
反斜杠(\)是换行符,用于增加Makefile可读性。可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。
在这个Makefile中,目标文件(target)包含:可执行文件edit和中间目标文件(*.o),依赖文件(dependency_files)就是冒号后面的那些*.c 文件和*.h文件。每一个.o 文件都有一组依赖文件,而这些.o 文件又是执行文件edit的依赖文件。依赖关系表明目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
在定义好依赖关系后,后面的一行命令定义了如何生成目标文件,这些命令一定要以一个Tab键作为开头。make工程管理器并不关心命令是如何工作的,他只负责执行用户事先定义好的命令。同时,make还会比较目标文件和依赖文件的最后修改日期,如果依赖文件的日期要比目标文件的日期更新,或者目标文件并不存在的话,那么,make就会执行后续定义的命令。
clean不是一个文件,它只不过是一个动作名字,也可称其为标签lable,不依赖于其他任何文件,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包、备份或删除等。
Makefile变量
为了进一步简化Makefile的编写和维护,make允许在Makefile中创建和使用变量。变量时在Makefile中定义的名字,用来代替一个文本字符串,该文本字符串成为该变量的值。
例如:
sunq: kang.o yul.o
gcc kang.o yul.o -o sunq
kang.o: kang.c kang.h
gcc -wall -o -g -c kang.c -o kang.o
yul.o: yul.c yul.h
gcc -wall -o -g -c yul.c -o yul.o
变量的值可以用来代替目标体、依赖文件、命令及Makefile文件中的其他部分。在Makefile中变量的定义有两种方式,一种时递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用该变量时进行替换的,4如果该变量包含了对其他变量的引用,则在引用时该变量时一次性将内嵌的变量全部展开。虽然这种方式能够很好地完成用户的指令,但它也有严重的缺点,如不能再变量后追加内容,在变量扩展过程中可能无穷循环,例如引用自己的时候:CFLAGS = $(CFLAGS) -O
简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除了变量的嵌套引用。
递归展开方式定义格式为:VAR=var。
简单展开方式定义格式为:VAR:=var。
make中变量的使用格式为:$(VAR)。
变量名是不包括“:”、“#”、“=”、结尾空格的任何字符串。同时,变量名中包含字母、数字及下划线意外的情况应尽量避免,因为它们可能在将来被赋予特别的含义。
变量名是大小写敏感的,推荐在Makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
例如
OBJS = kang.o yul.o
CC = gcc
CFLAGS = -wall -o -g
sunq: $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o: kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o: yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
Makefile中变量分为用户自定义变量、预定义变量、自动变量及环境变量。用户自定义变量的值由用户自行设定。预定义变量和自动变量无需定义就可以在Makefile中使用,其中部分有默认值,当然用户也可以对其进行修改。
预定义变量包含了常见编译器、汇编器的名称及编译选项。
命令格式 含义
AR 库文件维护程序的名称,默认值ar
AS 汇编程序的名称,默认值as
CC C编译器的名称,默认值cc
CPP C预编译器的名称,默认值$(CC) -E
CXX C++编译器的名称,默认值g++
RM 文件删除程序的名称,默认值rm -f
ARFLAGS 库文件维护程序的选项
ASFLAGS 汇编程序的选项
CFLAGS C编译器的选项
CPPFLAGS C预编译器的选项
CXXFLAGS C++编译器的选项
由于CC没有采用默认值,因此需要把"CC=gcc"明确列出来。
自动变量看通常可以代表语句中出现的目标文件和依赖文件等,并且具有本地含义(即下一语句出现的相同变量代表的是下一语句的目标文件和依赖文件)。
$* 不包括扩展名的目标文件名称
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$< 第一个依赖文件的名称
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分开
$% 如果目标时归档成员,则该变量表示目标的归档成员名称
例如:
OBJS = kang.o yul.o
CC = gcc
CFLAGS = -wall -o -g
sunq: $(OBJS)
$(CC) $^ -o $@
kang.o: kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o: yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
Makefile在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量。但是如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。
Makefile规则
Makefile规则包括目标体、依赖文件及其间的命令语句,是make进行处理的依据。Makefile中的一条语句就是一个规则。
1隐式规则
隐式规则能够告诉make怎样使用传统的激活素完成任务,这样当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。make会自动搜索隐式规则目录来确定如何生成目标文件。
隐式规则只能查找到相同文件名的不同扩展名文件,如“kang.o”文件必须由“kang.c”文件生成。
常见隐式规则目录:
对应语言后缀名 规则
C编译:.c变为.o $(CC) -c $(CPPFLAGS) $(CFLAGS)
C++编译:.cc或.C变为.o $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
2模式规则
隐式规则仅仅能够用make默认的变量来进行操作。
模式规则不同于隐式规则,是用来定义相同处理规则的多个文件的,模式规则能引入用户自定义变量,为多个文件建立相同的规则,简化Makefile的编写。
模式规则的格式类似于普通规则,这个规则中的相关文件前必须用%标明。
make使用
使用make管理器非常简单,只需在make命令后键入目标名即可建立指定的目标看,如果直接运行make,则建立Makefile中的第一个目标。
make的命令行选项:
命令格式 含义
-C dir 读入指定目录下的Makefile
-f file 读入当前目录下的file文件作为Makefile
-i 忽略所有的命令执行错误
-I dir 指定被包含的Makefile所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示make变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果make在执行过程中改变目录,打印当前目录名
使用autotools生成Makefile
Makefile的确可以帮助make完成它的使命,但要承认的是,编写Makefile确实不是一件轻松的事,尤其对于一个较大的项目而言更是如此。那么可以使用autotools系列工具生成Makefile。它只需用户输入简单的目标文件、依赖文件、文件目录等就可以轻松地生成Makefile,这些工具可以完成系统配置信息的收集,从而可以方便地处理各种移植性的问题。也正是基于此,现在Linux上的软件开发一般都用autotools来制作Makefile。
autotools使用流程
autotools是系列工具,首先要确认系统是否装了以下工具(可以用which命令进行查看)。
- aclocal
- autoscan
- autoconf
- autoheader
- automake
使用autotools主要就是利用各个工具的脚本文件以生成最后的Makefile。其总体流程:
autoscan->aclocal->autoconf-> autoheader->automake->configure
1)使用autoscan生成configure.scan;2)编辑configure.scan,修改相关内容,并将其重命名为configure.in;3)使用aclocal生成aclocal.m4;4)使用autoconf生成configure;5)使用autoheader生成config.in.h;6)编辑Makefile.am;7)使用automake生成Makefile.in;8)使用configure生成Makefile
(1)mkdir./auto
(2)建立hello.c和hello.h
#hello.h
#include stdio.h>
#include "hello.h"
int main()
{
printf("Hello automake!\n");
}
(3)命令:autoscan
用来为软件包创建configure.in文件,它会在以命令行参数中指定的目录为根(如果未给定参数,则以当前目录为根)的目录树中检查源文件。它会搜索源文件以寻找一般的移植性问题并创建一个文件"configure.scan",该文件就是configure.in的前身。如下所示:
[root@localhost auto]# autoscan
autom4te: configure.ac: no such file or directory
autoscan: /usr/bin/autom4te failed with exit status: 1
[root@localhost auto]# ls
autoscan.log configure.scan hello.c hello.h
如上所示,autoscan首先会尝试去读入"configure.ac"(同configure.in的配置文件)文件,此时还没有创建该配置文件,于是它会自动生成一个“configure.in”的原型文件“configure.scan”。
(4)使用vi编辑configure.scan修改并重命名为configure.in
configure.in是autoconf的脚本配置文件,它的原型文件"configure.scan"如下所示:
-*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
#AC_INIT(FULL-PACKAGE-NAME,VERSION,BUG-REPORT-ADDRESS)
AC_INIT(hello,1.0)
AM_INIT_AUTOMAKE(hello,1.0)
AC_CONFIG_SRCDIR([hello.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
每个configure.scan文件都是以AC_INIT开头,以AC_OUTPUT结束
下面对这个脚本文件进行解释。
以“#”号开始的行为注释。
AC_PREREQ 宏声明本文件要求的 autoconf 版本,如本例使用的版本 2.59。
AC_INIT 宏用来定义软件的名称和版本等信息,在本例中省略了 BUG-REPORT-ADDRESS,一般为作者的E-mail。
AM_INIT_AUTOMAKE 是笔者另加的,它是 automake 所必备的宏,也同前面一样。
PACKAGE 是所要产生软件套件的名称,VERSION 是版本编号。
AC_CONFIG_SRCDIR 宏用来侦测所指定的源码文件是否存在,来确定源码目录的有效性。在此处为当前目录下的hello.c。
AC_CONFIG_HEADER 宏用于生成 config.h 文件,以便 autoheader 使用。
AC_CONFIG_FILES 宏用于生成相应的 Makefile 文件。
中间的注释间可以添加分别用户测试程序、测试函数库、测试头文件等宏定义。
保存退出,并重命名为configure.in。
(5)运行:aclocal
[root@localhost auto]# aclocal
生成"configure"可执行文件。
(6)运行:autoconf
[root@localhost auto]# autoconf
并用ls查看是否生成了configure可执行文件
[root@localhost auto]# ls
aclocal.m4 autom4te.cache autoscan.log configure configure.in hello.c hello.h
(7)运行:autoheader
能够产生供configure脚本使用的C #define语句模板文件config.h.in。该工具通常会从"acconfig.h"文件中复制用户附加的符号定义,因为此处没有附加符号定义,所以不需要创建"acconfig.h"文件。
[root@localhost auto]# autoheader
(8)用Vi编辑Makefile.am
automake要用的脚本配置文件是Makefile.am,用户需要自己创建这个文件。之后,automake工具将其转换成Makefile.in。
AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=hello
hello_SOURCES=hello.c hello.h
下面对该脚本文件的对应项进行解释:
其中AUTOMAKE_OPTIONS 为设置 automake 的选项。由于 GNU(对自己发布的软件有严格的规范,比如必须附带许可证声明文件COPYING等,否则automake执行时会报错。automake提供了3种软件等级:foreign、gnu和gnits,让用户选择采用,默认等级为gnu。在本例使用foreign等级,它只检测必需的文件。
bin_PROGRAMS 定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空格隔开。
hello_SOURCES 定义"hello"这个执行程序所需要的原始文件。如果“hello”这个程序是由多个原始文件所产生的,则必须把它所用到的所有原始文件都列出来,并用空格隔开。例如:若目标体“hello”需要“hello.c”、“sunq.c”、“hello.h”三个依赖文件,则定义hello_SOURCES=hello.c sunq.c hello.h。要注意的是,如果要定义多个执行文件,则对每个执行程序都要定义相应的file_SOURCES。
Makefile.am是一种比Makefile更高层次的规则。只需指定要生成什么目标,它由什么源文件生成,要安装到什么目录等构成。
表一列出了可执行文件、静态库、头文件和数据文件,四种书写Makefile.am文件个一般格式。
表 1Makefile.am一般格式
对于可执行文件和静态库类型,如果只想编译,不想安装到系统中,可以用noinst_PROGRAMS代替bin_PROGRAMS,noinst_LIBRARIES代替lib_LIBRARIES。
Makefile.am还提供了一些全局变量供所有的目标体使用:
表 2 Makefile.am中可用的全局变量
在Makefile.am中尽量使用相对路径,系统预定义了两个基本路径:
表 3Makefile.am中可用的路径变量
在上文中我们提到过安装路径,automake设置了默认的安装路径:
1) 标准安装路径
默认安装路径为:$(prefix) = /usr/local,可以通过./configure --prefix=<new_path>的方法来覆盖。
其它的预定义目录还包括:bindir = $(prefix)/bin, libdir = $(prefix)/lib, datadir = $(prefix)/share, sysconfdir = $(prefix)/etc等等。
2) 定义一个新的安装路径
比如test, 可定义testdir = $(prefix)/test, 然后test_DATA =test1 test2,则test1,test2会作为数据文件安装到$(prefix)/ /test目录下
(9)运行:automake --adding-missing
[root@localhost auto]# automake --add-missing
configure.in: installing './install-sh'
configure.in: installing './missing'
Makefile.am: installing 'depcomp'
[root@localhost auto]# ls
aclocal.m4 autoscan.log configure.in hello.c hello.h Makefile.am missing
autom4te.cache configure depcomp install-sh Makefile.in config.h.in
automake工具将Makefile.am转换成Makefile.in。
在这里使用选项“—adding-missing”可以让automake自动添加一些必需的脚本文件。
automake是一个从文件Makefile.am自动生成Makefile.in的工具。每个Makefile.am基本上是一系列make的宏定义(make规则也会偶尔出现)。生成的Makefile.in也服从GNU Makefile标准。
典型的automake输入文件是一系列简单的宏定义。处理所有相关的文件并创建Makefile.in文件。在一个项目的每个目录中通常仅包含一个Makefile.am。
目前automake支持三种目录层次:平坦模式(flat)、混合模式(shallow)和深层模式(deep)。
(1)平坦模式指的是所有文件都位于同一个目录中。就是所有源文件、头文件及其他库文件都位于当前目录中,且没有子目录。如Termutils
(2)混合模式指的是主要的源代码都存储在顶层目录,其他各个部分则存储在子目录中。也就是主要源文件在当前目录中,而其他一些实现各部分功能的源文件位于各自不同的目录。如automake
(3)深层模式指的是所有源代码都被存储在子目录中;顶层目录主要包含配置信息。也就是所有源文件及程序员自己写的头文件都位于当前目录的一个子目录中,而当前目录里没有任何源文件。如GNU cpio和GNU tar
在这三种支持的目录层次中,平坦模式类型是最简单的,深层模式类型是最复杂的。但是这些模式使用autoconf和automake所遵循的基本原则和流程是一样的。
(10)运行:./configure
[root@localhost auto]# ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build enVironment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for Gcc... Gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether Gcc accepts -g... yes
checking for Gcc option to accept ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of Gcc... Gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
可以看到,在运行configure时收集了系统的信息,用户可以在configure命令中对其进行方便地配置。
在./configure的自定义参数有两种,一种是开关式(--enable-XXX或--disable-XXX),另一种是开放式,即后面要填入一串字符(--with-XXX=yyyy)参数。通过运行自动配置设置文件configure,把Makefile.in变成了最终的Makefile。
(11)运行:make
键入make默认执行”make all”命令,即目标体为all。
[root@localhost auto]# make
if Gcc -D PACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.Tpo" -c -o hello.o hello.c; \
then mv -f ".deps/hello.Tpo" ".deps/hello.Po"; else rm -f ".deps/hello.Tpo"; exit 1; fi
Gcc -g -O2 -o hello hello.o
此时在本目录下就生成了可执行文件"hello"。
(12)运行:./hello
[root@localhost auto]# ./hello
Hello automake!
(13)运行:make install
会把该程序安装到系统目录中去,如下所示:
[root@localhost auto]# make install
if Gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.Tpo" -c -o hello.o hello.c; \
then mv -f ".deps/hello.Tpo" ".deps/hello.Po"; else rm -f ".deps/hello.Tpo"; exit 1; fi
Gcc -g -O2 -o hello hello.o
make[1]: Entering directory '/root/workplace/auto'
test -z "/usr/local/bin" || mkdir -p -- "/usr/local/bin"
/usr/bin/install -c 'hello' '/usr/local/bin/hello'
make[1]: Nothing to be done for 'install-data-am'.
make[1]: LeaVing directory '/root/workplace/auto'
(14)运行:./hello
[root@localhost auto]# ./hello
Hello automake!
(15)运行:make clean
make会清除之前所编译的可执行文件及目标文件(object file, *.o),如下所示:
[root@localhost auto]# make clean
test -z "hello" || rm -f hello
rm -f *.o
(16)运行:make dist
make将程序和相关的文档打包为一个压缩文档以供发布,如下所示:
[root@localhost auto]# make dist
[root@localhost auto]# ls hello-1.0.tar.gz
hello-1.0.tar.gz
该命令生成了一个hello-1.0.tar.gz的压缩文件。
(17)在当前目录下解压hello-1.0.tar.gz:tar zxvf hello-1.0.tar.gz
(18)进入解压目录:cd./hello-1.0
(19)下面开始Linux下常见的安装软件步骤:./configure
(20)运行:make
(21)运行:./hello(在正常安装时这一步可省略)
(22)运行:make install
(23)运行:hello,查看结果是否正确。