软件开发工具——Make

掌握Makefile的使用方法和工做流程;linux

掌握make工具变量的相关知识,包括其引用、定义及分类等;c++

掌握Makefile常见的函数含义;shell

掌握Makefile与shell命令行的通讯方法;编程

掌握Makefile的常见语法规则,包括显式规则、隐式规则及静态模式规则;编程语言

了解autotools的用法,了解autotools中经常使用的工具链以及如何使用工具链自动建立Makefile文件。编辑器

一、Make工具概述函数

  Makefile带来的好处就是“自动化编译”,一旦写好,只须要在shell命令行中输入一个make命令,整个工程彻底自动编译,能够极大提升软件开发的效率。工具

  make是一个命令工具,它解释Makefile中的语法规则。Makefile有本身的书写格式、关键字和函数,这个文件告诉make以何种方式编译源代码和连接程序。典型地,可执行文件可由一些.o文件按照必定的顺序编译,若是在工程中已经存在Makefile,当对工程中的若干源文件修改之后,可自动根据修改状况完成源文件对应.o文件的更新、库文件的更新、最终的可执行程序的更新。this

  那么若是来判断一个大型工程中哪些文件发生了改变,哪些没发生变化呢?spa

  Linun系统判断工程中文件是否发生改变的方法,是判断文件的创建和修改时间,即“文件时间戳”,Makefile文件就是基于这种“时间戳”来运行的。若是系统判断“文件时间戳”晚于上次编译的“时间戳”,就代表此文件在上次编译以后从新被修改过,须要从新编译;若是时间戳同上次同样没有变化,就代表没有修改,不须要从新编译。

二、Makefile起步

  咱们的规则是:

  (1)若是工程没有编译过,那么全部C文件都要编译并被连接;

  (2)若是工程的某几个C文件被修改,那么只编译被修改的C文件,并连接目标工程;

  (3)若是工程的头文件被改变了,那么须要编译引用了这几个头文件的C文件,并连接目标程序。

  Makefile的大体结构:

  TRAGET: Dependency file

  <TAB> COMMAND

  TRAGET: Dependency file

  <TAB> COMMAND

  ......

  TRAGET: Dependency file

  <TAB> COMMAND

  在Makefile结构中,TARGET、Dependency file、COMMAND是相互关联、密切联系的3部分。

  TARGET:表示make工具建立的目标体,一般是最后须要生成的文件名或者为了实现这个目的而必需的中间过程文件名。能够是.o文件,也能够是最后的可执行程序的文件名等。

  Dependency file:表示要建立目标体须要的依赖文件,一般一个目标依赖于一个或者多个文件。若是其中一个文件比目标文件的“时间戳”新,这个目标就认为是“过期的”,须要从新编译生成。

  COMMAND:表示建立目标体时须要运行的命令,它限定了make执行这条规则时须要的动做,一般可由一行或者多行命令组成。若是COMMAND不与“TARGET: Dependency ifle”在一行,那么它必须以[TAB]字符做为本行的开头,[TAB]字符告诉make此行是一个命令行;若是与"TARGET: Dependency file“在一行,那么可使用分号做为分隔符,若是COMMAND命令太长,可以使用反斜杠(\)做为换行符。

  注:Makefile中“#”字符后的内容被看成是注释内容。若是此行的第一个非空字符为“#”,表示此行为注释行。当在Makefile中使用真实的字符“#”时,可使用反斜杠加“#”(\#)来实现,它表示将“#”做为一个普通字符而不是注释的开始标志。

  Makefile的通常工做过程描述:

  (1)读取Makefile。根据make的执行选项,查找当前的目录或者其余目录要执行的Makefile。

  (2)初始化Makefile。将制定的Makefile中的变量进行替换,若是该Makefile中包含其余的文件,则将其加载。

  (3)解释规则。将Makefile中的执行规则进行解析,同时推导文件中的隐藏规则,其次,查找文件中目标、依赖、命令之间的关系,为建立目标创建关系链。

  (4)分析变动。根据依赖关系和“时间戳”,判断是否有依赖文件发生变化,若是有变化,则进行从新编译;若是没有变化,当前的目标不须要从新编译。

  (5)执行。执行Makefile中的命令。

  Makefile编写完毕,就能够执行make命令进行编译操做。make的执行同其余命令同样,也有丰富的选项供用户选择,能够完成不一样的功能。make工具的经常使用选项:

  -f file  将指定当前目录下的file做为Makefile

  -I dir  将dir做为被包含的Makefile所在目录

  -C dir  将指定目录下的file做为Makefile

  -i    忽略全部命令执行错误

  -j    输出规则中命令的详细信息

  -n    只打印要执行的命令,但不执行这些命令

  -s    在执行命令时不显示命令

  -d    除打印正常的操做信息外,还打印调试信息

  一个目标能够没有依赖文件,只有命令,好比Makefile中的伪命令“clean“表示删除make过程当中的中间文件,它就没有依赖,只有命令。伪命令是为其余命令服务的,不是强制性的。伪命令通常包括clean(删除中间文件)、install(安装编译好的程序)、uninstall(卸载已安装的程序)以及print(输出发生改变的源文件)等。

 三、Makefile变量

  在Makefile中,变量是一个名字,它不只能够表明一个文本字符串,并且能够用来表明文件名、编译选项、程序运行的选项参数、搜索源文件的目录,以及编译输出的目录。在Makefile的目标、依赖、命令中任意引用变量的地方,在执行make命令后,都会被变量定义的值所取代。

  在Makefile中变量有如下几个特征:

  (1)Makefile中变量和函数的展开(除规则命令行中的变量和函数之外),是在make读取Makefile文件时进行的,这里的变量不只包括使用“=”定义的变量,并且包含使用指示符“define”定义的。

  (2)变量的命名能够包含字符、数字、下划线,但绝对不可使用含有“:”、“#”、“=”或是空字符(空格、回车等)的字符,同时变量中字母、数字以及下划线之外的字符,用户应尽可能避免使用,由于它们可能赋予其余特别的含义。

  (3)变量中的大小写也是很是敏感的。推荐的方法是对于内部定义的通常 变量使用小写方式,而对于一些参数列表(例如:编译选项CFLAGS)采用大写方式。

  (4)有一些变量名只包含一个或者不多的几个特殊字符,称它们为自动化变量。像“$”、“$@”、“$?”、“$*”等,这些变量用户在定义中也不可使用。

  当定义了一个变量后,就能够在Makefile的不少地方使用这个变量。变量的引用方式是:

  $(VARIABLE_ANME) 或者 ${VARIABLE_NAME}

  美圆符号“$”在Makefile中有特殊的含义,全部在命令或者文件中使用“$”时须要用两个美圆符号“$$”来表示。

  在Makefile中对一些简单变量的引用,也能够不使用“()”和“{}”来标记变量,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的状况。

  Makefile文件在进行变量定义时一般能够采用两种方式,第一种是递归展开定义法,另外一种是直接展开定义法。二者虽然均可以对须要的变量进行定义,可是也存在一些差别,主要的不一样在于定义的方式和展开的时机不一样,递归展开定义法可使用以前没有定义过的变量,而直接展开定义法不容许引用变量以后定义过的变量。

  (1)递归展开定义

  这种变量定义法是经过“=”或者指示符“define”来定义变量。其格式以下:

  Var=variable

  其中,Var是变量名,variable是赋予变量Var的值。

  对使用递归展开定义的变量,其引用的地方是严格的文本替换过程。变量将会原样地被字符串替代。若是此变量定义中存在对其余变量的引用,那么被引用的变量会在此变量被展开的同时被展开。

  (2)直接展开定义

  为避免“递归展开法”定义变量出现的死循环和效率低的问题,Makefile中可以使用另一种变量定义的方式,称为直接展开定义。这种方式使用“:=”定义变量。其格式以下:

  Var := variable

  同递归定义法不一样,直接展开定义法在调用变量时,变量值对另外变量的引用在定义时被展开。因此在变量被定义后就是一个实际所须要定义的文本串,再也不包含任何其余变量的引用。其次,须要注意的是,使用直接定义法定义变量时,不能对其后定义的变量进行引用。

  通常而言,在复杂的Makefile中,推荐使用直接展开式变量,由于这种变量的使用方式和大多数编程语言中的变量使用方式基本相同。它可使一个比较复杂的Makefile在必定程度上具备可预测性,并且这种变量容许用户利用以前定义的值来从新定义。所以,在Makefile变量定义时,应尽可能避免和减小递归方式的使用。

  (3)变量嵌套定义

  在Makefile中还有一种变量高级使用方法,称为“变量嵌套定义”。它表示在一个变量中能够包含对其余变量的引用。

  例如:

  variable1 = variable2

  varialbe2 = variable3

  w := $($(variable1))

  (4)替换引用定义

  对于一个已经定义的变量,可使用“替换引用”将其值中的后缀字符(串)使用指定的字符(串)替换。格式是:

  $(VAR:A=B) 或者 ${VAR:A=B}

  意思是,替换变量VAR中全部A字符结尾的字为B结尾的字。“结尾”的含义是空格以前(变量值多个字之间使用空格分开)。而对于变量其余部分的“A"字符不进行替换。例如:

  foo := a.o b.o c.o

  bar := $(foo:.o=.c)

  变量分类:

  除用户本身定义的变量(称为自定义变量)外,Makefile文件中还存在3中重要的变量,它们分别是:预约义变量、自动变量和环境变量。这3中变量是系统级变量,都有其默认值,用户通常不须要在Makefile中从新定义,可在Makefile中直接引用(也可根据须要替换其默认值)。

  (1)预约义变量

  预约义变量是进行程序预编译时常用的变量。有时候使用GCC进行程序预编译时,一般对某些编译的选项屡次使用,预编译变量使复杂的编译选项可以 更条理、更直观。

  AR  库文件维护程序的名称,默认值为ar

  AS  汇编程序的名称,默认值为as  

  CC  c编译器的名称

  CPP  c预编译器的名称,默认值为$(CC) -E

  CXX  c++编译器的名称,默认值为g++

  PC  Pascal编译器的名称

  FC  Fortran编译器的名称,默认值为f77

  RM  文件删除程序的名称,默认值为rm -f

  ARFLAGS  库文件维护程序的选项,无默认值

  ASFLAGS  汇编程序的选项,无默认值

  CFLAGS  c编译器的选项,无默认值

  CPPFLAGS  c预编译的选项,无默认值

  CXXFLAGS  c++编译器的选项,无默认值

  FFLAGS  Fortran编译器的选项,无默认值

  其中,最重要也是最常用的变量是CC和CFLAGS。因为CC没有默认值,所以常常要把“CC=gcc”、“CC=arm-linux-gcc”等放到变量定义中。

  (2)自动变量

  为了简化Makefile的编写,Makefile引入自动变量,自动变量能够表明编译语句中出现的目标文件和依赖文件等。使用自动变量能够为Makefile的编写提供方便。

  $@  表示当前规则中的完整目标文件名

  $?  新修改过的依赖文件列表,即全部时间戳比目标文件晚的依赖文件,并以空格分开

  $*  不包含扩展名的目标文件名

  $<  当前规则中的第一个依赖文件名

  $%  当目标文件为库文件时,该变量为库文件名

  $^  全部依赖文件,以空格分开,不包含重复的依赖文件

  $+  全部依赖文件,以空格分开,并以出现的前后为序,可能包含重复的依赖文件

  (3)环境变量

  make命令在运行时,系统中全部的环境变量对它都是可见的。在Makefile中,能够引用任何已定义的系统环境变量。(这里咱们区分系统环境变量和make环境变量,系统环境变量是这个系统全部用户所拥有的,而make的环境变量只是对于make的一次执行过程有效。)

  Makefile中最多见的环境变量是VPATH。一般在一些大的工程中,有大量的源文件,并放在不一样的目录中。因此,当make须要寻找文件依赖关系时,就要在文件前加上路径。Makefile文件中的环境变量“VPATH”就是完成添加路径的功能,若是没有指明这个变量,make只会在当前目录下寻找依赖关系和目标文件。若是定义了“VPATH”变量make就会在在当前目录下找不到所需文件的状况下,到指定的目录中寻找文件。这有点相似gcc的目录选项中的“-I”和“-L“选项。若是要添加多个目录,通常使用“:”做为分隔。 

四、Makefile经常使用函数

  $(subst A, B, text)  将文本text中的每一个A字符用B字符替换

  $(patsubst A, B, text)  将文本text中符合格式为A的字符,用格式B替换。

  $(strip text)  将text中多余的空格进行压缩(包括前导或者结尾的空格字符),并将多个空格变为单个空格

  $(findstring A, text)  在text中查找字符串A,若是找到返回值为A,不然为空

  $(sort text)  将text中的字按字母顺序排序,并去掉其中重复的单词。其输出为单个空格隔开的单词列表。若是第一个字母相同则比较第二个,依此类推

  $(word N, text)  将text中第N个单词取出,并返回这个单词。若是不存在第N个单词,则返回值为空

  $(wordlist N1, N2, text)  取出text中第N1到第N2个单词,其中N1,N2表示单词在text的位置数字

  $(words text)  此函数表示统计text中的单词数目

  $(filter A..., text)  在text中寻找由空格隔开而且匹配格式为A的字,去除不符合格式A的字符

  $(filter-out A..., text)  返回由空格隔开的 而且不匹配格式为A的字符,除去格式为A的字符

  $(dir text)  取出text中每一个文件的路径部分

  $(notdir text)  取出text中的每一个文件名

  $(addsuffix A, text)  将text中每一个文件名后添加后缀“A”

  $(addprefix A, text)  将text中每一个文件名后添加前缀“A”

  $(if A, B, C)  判断A,对变量A展开后,若是A的结果非空,则条件为真,同时将B做为函数的表达式;若是条件为假,则将C做为函数的表达式 

五、Makefile与shell 

  Makefile文件经过shell函数与外部进行通讯。它实现的功能同shell中的引用(``)相似,其返回结果是该命令在shell中执行的结果。Make命令仅对它的返回值看成字符串对待,若是函数返回结果中存在换行符,那么将其替换为空格,并去掉末尾的回车符号。在大多数状况下,make命令时在读取解析Makefile时完成对函数shell的展开。

  shell函数的格式以下:

  $(shell shell-command)

  此函数的做用是在Makefile中执行shell-command命令,并将它的执行结果返回Makefile。  

六、Makefile规则语法

  Makefile中常见的规则包括显示规则、隐式规则以及静态模式规则3类。

  (1)显示规则

  显示规则描述了如何将“依赖文件”转变为“目标文件”,书写这种规则的Makefile须要用户明确地给出目标文件、目标依赖文件的列表,以及更新目标文件所须要的命令。

  (2)隐式规则

  隐式规则就像其名,它会把一部分规则“隐藏”,不要求用户将全部的规则列出,也不须要详细指定编译的具体细节,甚至有时候不须要任何规则,系统会根据要产生的可执行文件及其依赖文件(典型的是根据文件名的后缀),自动推导出其依赖文件时如何使用默认命令编写规则的。例如:典型地,make命令对c文件的编译过程是由.c源文件编译成.o目标文件。

  另外,在make命令执行时,根据须要也可能用多个隐含规则。好比:make命令将从一个.y文件生成对应的.c文件,再生成最终的.o文件。也就是说,只要目标文件名中除后缀之外的其余部分相同,make命令就可以使用若干个隐含规则来最终产生这个目标文件(固然,最原始的那个文件必须存在)。

  在Makefile中常见的隐式规则:

  c编译:将file.c变为file.o  $(CC)$(CPPFLAGS)$(CFLAGS) -c file.c -o file.o

  c++编译:将file.cc变为file.o  $(CXX)$(CPPFLAGS) -c file.cc -o file.o

  Pascal编译:将file.p变为file.o  $(CP)$(PFLAGS) -c file.p -o file.o

  Fortran编译:将file.r变为file.o  $(CP)$(FFLAGS) -c file.r -o file.o

  注:以上file均表示任意文件名

  (3)静态模式规则

  模式规则是用来定义具备相同处理规则的多个文件。

  模式规则相似于普通规则,只是在模式规则中,目标名须要包含模式字符“%”,该模式字符“%”被用来匹配一个文件名,能够匹配任何非空字符串在依赖文件中一样可使用“%”,依赖文件中的模式字符“%”的取值由目标中的“%”来决定。例如:对于模式规则“%.o:%.c”,它表示的含义是:全部的.o文件依赖于对应的.c文件,“%.c"能够匹配到全部以.c结尾的文件,“s%.c"能够匹配到全部第一个字母为“s”,并且必须以.c结尾的文件。

  要注意的是:模式字符“%”的匹配和替换发生在规则中全部变量和函数引用展开以后,变量和函数的展开通常发生在make读取Makefile时,而模式规则中“%”的匹配和替换则发生在make执行时。

  c语言的模式规则通常可描述为:

  %.o : %.c

    COMMAND

  这个模式规则指定了全部的文件“.c"都用来建立文件“.o",文件“.c"应该是已存在的或者可被建立的。

  模式规则中的依赖文件也能够不包含模式字符“%”。当依赖文件名中不包含模式字符“%”时,其含义是全部符合目标模式的目标文件都依赖于一个指定的文件(例如:“%.o : debug.h ”表示全部的".o"文件都依赖于头文件"debug.h")。这样的模式规则在不少场合是很是有用的。

  注:

  (1)在使用模式规则时,指定的目标必须和目标模式相匹配,不然执行make时将会获得一个错误提示。

  (2)相比隐式规则,模式规则更能体现其优势。对于没法肯定工做目录内容或者不能肯定是否存在无关文件,使用隐含规则可能会致使make命令失败。其次,当存在多个适合此文件的隐含规则时,系统将不能正确判断使用何种规则也可能致使make失败。在这两种状况下使用模式规则,就能够避免这些不肯定因素,由于静态模式中,指定的目标文件有明确的规则描述其依赖关系。

  除了以上3中规则外,Makefile中还存在一些基础规则,包括:

  (1)在Makefile文件中,除“终极目标”所在的规则外,其他规则的顺序在Makefile中没有意义(终极目标是指Makefile文件第一个规则的目标,通常是执行make后生成的可执行文件)。若是在Makefile中第一个规则有多个目标的话,那么多个目标中的第一个将会被做为make的“终极目标”

  (2)规则的命令部分有两种书写方式,一种是把命令和依赖文件放在一行,中间使用分号(:)隔开,另外一种是命令在依赖文件的下一行。看成独立的命令行时,此行必须以Tab开始。

  (3)对于Makefile中一个较长的行,咱们可使用反斜杠“\”将其书写到几个独立的行上。 

七、Makefile自动编写工具

  autotool工具是当今Linux世界中比较经常使用的Makefile自动生成工具。autotool是一系列工具,主要包括autoscan、autoconf、aclocal、autoheader、automake等。

  下面用一个简单的程序exautomake.c来说述autotool编写Makefile文件的方法。该文件的代码以下:

  #include <stdio.h>

  int main()

  {

    printf( "this is the first automake example!" );
  }

  第一步:autoscan

  autoscan工具用来扫描当前目录下是否有生成Makefile的源文件(configure.scan、autoscan.log),若是没有这两个文件,系统会自动生成configure.scan、autoscan.log这两个文件。

  所以,在命令行下输入:

  #autoscan

  autom4te: configure.ac: no such file or directory
  autoscan: /usr/bin/autom4te failed with exit status: 1

  # ls

  autoscan.log  configure.scan  exautomake.c  exautomake.c~

  其中,configure.scan是configure.in的原型,(configure.in是autoconf的脚步配置文件),因此下一步工做就是要对configure.scan进行修改,将其转化为configure.in。

  第二步:aclocal、autoconf及autoheader

  使用编辑器打开configure.scan文件,其内容以下:

 

  #                                               -*- Autoconf -*-
  # Process this file with autoconf to produce a configure script.

 

  AC_PREREQ(2.59)                              #命令行1
  AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)   #命令行2
  AC_CONFIG_SRCDIR([exautomake.c])                  #命令行3
  AC_CONFIG_HEADER([config.h])                    #命令行4

 

  # 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_OUTPUT                              #命令行5

  命令行1中,AC_PREREQ用来声明本文件要求的autoconf版本

  命令行2,用来定义软件的名称和版本等信息

  命令行3,AC_CONFIG_SRCDIR用来检查所指定的源码文件是否存在,来肯定源码目录的有效性。这个参数通常不须要修改

  命令行4,AC_CONFIG_HEADER用于生成config.h文件,以便autoheader使用

  将configure.scan文件修改成configure.in,修改的地方共有4处:

  第一,将命令行2注释掉,并在命令行2后添加一新行,并键入"AC_INIT(exautomake,1.0)",表示编写该程序的名字及版本信息。

  第二,在命令行3前添加新行,并键入"AM_INIT_AUTOMAKE(exautomake,1.0)",这是automake必备的一行,同前面同样,"exautomake"是所要产生的软件的名字,"1.0"表示版本号,通常而言,第一次编写版本号均可定为"1.0".

  第三,将命令行4修改成"AM_CONFIG_HEADER([config.h])".(实践证实:改此处或者不改均可以)

  修改完毕后,将configure.scan在当前目录下另存为configure.in。接下来要运行aclocal,系统会自动生成一个aclocal.m4文件,该文件的主要做用是处理当前的定义行;随后运行autoconf命令,该命令一样会在当前目录下生成一个名为configure的文件;最后执行autoheader,它负责生成config.h.in文件。

  第四,将命令行5修改成"AC_OUTPUT([Makefile])".

  #aclocal

  #autoconf

  #autoheader

  第三步:automake

  automake的执行相当重要。automake须要使用脚本配置文件Makefile.am,对于这个文件用户须要本身建立。

  使用编辑器编写程序Makefile.am以下:

  AUTOMAKE_OPTIONS=foreign          #命令行1

  bin_PROGRAMS=exautomake          #命令行2

  exautomake_SOURCES=exautomake.c     #命令行3

  其中,命令行1中,AUTOMAKE_OPTIONS表示为automake进行设置的选项。automake提供了3种软件等级:foreign、gnu、gnits,让用户选择使用,默认等级是gnu,在本例中使用foreign等级,只检查必须的文件。

  命令行2中,bin_PROGRAMS表示要产生的可执行文件名。若是要产生多个可执行文件,每一个文件名用空格隔开。

  命令行3中的exautomake_SOURCES是用来定义exautomake这个执行程序的依赖文件。若是文件须要多个源文件,就要在后面添加生成它的源文件。例如:若生成的exautomake除须要exautomake.c外,还须要wth.c、wang.c,就要使用多个源文件定义方法"exautomake_SOURCES=exautomake.c wth.c wang.c".

  接下来就是使用automake生成configure.in文件,通常而言,在执行automake时,要在后面添加选项"--add-missing",表示让automake自动添加一些必要的脚步文件。

  #automake --add-missing

  第四步:运行configure

  经过运行configure,就能够把Makefile变成最终的Makefile

  #./configure

  到此为止,makefile就能够自动生成了。

  第五步:执行make

  执行make命令将默认执行make all,即编译全部目标体,执行完毕,会在当前目录下生成exautomake的可执行文件。

  使用autotools工具除生成exautomake目标以外,还默认生成install(安装该程序到系统中),clean(清楚以前编译的全部可执行文件及目标文件)等目标文件。

相关文章
相关标签/搜索