码迷,mamicode.com
首页 > 其他好文 > 详细

Makefile语法基础介绍

时间:2015-04-21 16:09:11      阅读:190      评论:0      收藏:0      [点我收藏+]

标签:

在Linux下,make是一个命令工具,是一个解释Makefile中指令的命令工具。make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么样去编译和链接程序。

make如何工作:在默认的方式下,只输入make命令,那么:

(1)、make会在当前目录下找名字叫”Makefile”或”makefile”的文件;

(2)、如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件;

(3)、如果目标文件不存在,或是目标文件所依赖的后面的.o文件的修改时间要比这个目标文件新,那么,它就会执行后面所定义的命令来生成目标文件;

(4)、如果目标文件所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件;

(5)、当然,对应的.c和.h文件是存在的,于是make会生成.o文件,然后再用.o文件生成make的终极任务,也就是执行文件(目标文件)了。

Makefile里主要包含了5部分:显示规则、隐晦规则、变量定义、文件指示和注释

(1)、显示规则:显示规则说明了,如何生成一个或多个的目标文件。这是由Makefile的书写者明显指出要生成的文件,文件的依赖文件,生成的命令。

(2)、隐晦规则:由于make有自动推导的功能,所以隐晦规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

(3)、变量定义:在Makefile中要定义一系列的变量,变量一般都是字符串。当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

(4)、文件指示:其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样,另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样,还有就是定义一个多行的命令。

(5)、注释:Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用”#”字符,这个就像C/C++中的”//”一样。如果要在你的Makefile中使用”#”字符,可以用反斜框进行转义,如”\#”。

在Makefile中的命令,必须要以[Tab]键开始

Makefile的文件名:默认的情况下,make命令会在当前目录下按顺序找寻文件名为”GNUmakefile”、”makefile”、”Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用”Makefile”这个文件名。最好不要用”GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的”makefile”文件名敏感,但是基本上来说,大多数的make都支持”makefile”和”Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如”Make.Linux”等,如果要指定特定的Makefile,你可以使用make的”-f”和”--file”参数,如make  -f Make.Linux或make  --file  Make.Linux.

引用其它的Makefile:在Makefile使用include关键字可以把别的Makefile包含进来,被包含的文件会原模原样的放在当前文件的包含位置。

include的语法是:include filename

filename可以是当前操作系统Shell的文件模式(可以包含路径和通配符)。

在include前面可以有一些空字符,但是决不能是[Tab]键开始。include和filename可以用一个或多个空格隔开。

make命令开始时,会找寻include所指出的其它Makefile,并把其内容安置在当前的位置。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会再下面的几个目录下找:(1)、如果make执行时,有”-I”或”--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找;(2)、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号”-”,如-include filename,其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。

环境变量MAKEFILES:如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变量中引入的Makefile的”目标”不会起作用,如果环境变量中定义的文件发现错误,make也不会理。建议,不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响。

GNU的make工作时的执行步骤

(1)、读入所有的Makefile;

(2)、读入被include的其它Makefile;

(3)、初始化文件中的变量;

(4)、推导隐晦规则,并分析所有规则;

(5)、为所有的目标文件创建依赖关系链;

(6)、根据依赖关系,决定哪些目标要重新生成;

(7)、执行生成命令。

Makefile文件书写规则

targets:  prerequisites
command
... ...
或者:

targets: prerequisites ; command
command
... ...

targets是文件名,以空格分开,可以使用通配符。一般来说,目标基本上是一个文件,但也有可能是多个文件。

command是命令行,如果其不与”targets: prerequisites”在一行,那么,必须以[Tab]键开头,如果和prerequisites在一行,那么可以用分号作为分隔。

prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是”过时的”,被认为是需要重新生成的。

如果命令太长,可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。

在规则中使用通配符:make支持三种通配符:”*”、”?”、”[…]”。这和Unix的B-Shell是相同的。

波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是”~/test”,这就表示当前用户的$HOME目录下的test目录。而”~spring/test”则表示用户spring的宿主目录下的test目录。

文件搜寻:Makefile文件中的特殊变量”VPATH”可以实现文件搜寻,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件。

另一个设置文件搜索路径的方法是使用make的”vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,它和VPATH变量很类似。它可以指定不同的文件在不同的搜索目录中。

伪目标:”伪目标”并不是一个文件,只是一个标签,由于”伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个”目标”才能让其生效。当然,”伪目标”的取名不能和文件名重名,不然其就失去了”伪目标”的意义了。为了避免和文件重名的这种情况,我们可以使用一个特殊的标记”.PHONY”来显示地指明一个目标是”伪目标”,向make说明,不管是否有这个文件,这个目标就是”伪目标”。

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为”默认目标”,只要将其放在第一个。

多目标:Makefile的规则中目标可以不止一个,其支持多目标

静态模式:可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。

自动生成依赖性:大多数的C/C++编译器都支持一个”-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。如果你使用GNU的C/C++编译器,你得用”-MM”参数,不然,”-M”参数会把一些标准库的头文件也包含进来。

书写命令:每条规则中的命令和操作系统Shell的命令行是一致的。make会按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间的空格或是空行会被忽略,但是如果该空格或空行是以[Tab]键开头的,那么make会认为其是一个空命令。

make的命令默认是被”/bin/sh”----UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。

显示命令:通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用”@”字符在命令行前,那么这个命令将不被make显示出来。如果make执行时,带入make参数”-n”或”--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。

命令执行:当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令

#如果要让上一条命令的结果应用于下一条命令时,
#应该使用分号分隔这两条命令
#注意以下两者的区别

exec:
	@cd /home/spring; pwd #希望得到的结果
	@cd /home/spring
	pwd

make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell----/bin/sh来执行命令。

忽略命令的出错,我们可以在Makefile的命令行前加一个减号”-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。还有一个全局的办法是,给make加上”-i”或是”--ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。

嵌套执行make:可以通过export命令传递变量到下级Makefile中。需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFLAGS变量,其中包含了make的参数信息。但是make命令中有几个参数并不往下传递,它们是”-C”、”-f”、”-h”、”-o”和”-W”。”-w”或是”--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。

定义命令包:如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种变量序列的语法以”define”开始,以”endef”结束。

使用变量:在Makefile中定义的变量,就像是C/C++语言中的宏一样,它代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在”目标”、”依赖目标”、”命令”或是Makefile的其它部分中。

变量的名字可以包含字符、数字、下划线(可以是数字开头),但不应该含有”:”、”#”、”=”或是空字符(空格、回车等)。变量是大小写敏感的。传统的Makefile的变量名是全大写的命名方式,但推荐使用大小写搭配的变量名,这样可以避免和系统的变量冲突,而发生意外的事情。

变量的基础:变量在声明时需要给予初值,而在使用时,需要给在变量名前加上”$”,但最好用小括号”()”或是大括号”{}”把变量给包起来。如果你要使用真实的”$”字符,那么你需要用”$$”来表示

变量中的变量:在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来用变量定义变量的值:(1)、简单的使用”=”号,在”=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值;(2)、使用”:=”操作符,这种方法,前面的变量不能使用后面的变量,只能使用前面已经定义好了的变量。

#变量中的变量

#使用“=”,右侧的变量不一定非要是已定义好的值,其也可以使用后面定义的值
foo = $(bar) hello 
bar = $(ugn)
ugn = spring

#使用":="操作符,前面的变量不能使用后面的变量,
#只能使用前面已经定义好了变量,此时y的值为fun
y := $(x)fun
x := test
 
exec:
	echo $(foo) #spring hello
	echo $(y) #fun

#定义一个变量其值是空格

#但是这个例子好像并没有起到作用????   
nullstring :=
space := $(nullstring)#end of the line
out := spring$(space)spring

#以下这个例子可以获得预期的结果
dir := /bin/sh     #directory
path := $(dir)/file

exec:
	echo $(out) #springspring
	echo $(path) #/bin/sh /file

#变量值的替换

#将变量foo中全部以.o字串结尾全部替换成.c
foo := a.o b.o c.o
bar := $(foo:.o=.c)

#静态模式实现变量值的替换:依赖被替换字串中有相同模式
bar1 := $(foo:%.o=%.c)

#把变量的值再当成变量
x = $(y)
y = z
z = Hello
a := $($(x))

exec:
	echo $(bar) #a.c b.c c.c
	echo $(bar1) #a.c b.c c.c
	echo $(a) #Hello

还有一个比较有用的操作符”?=”,如foo  ?=  bar,其含义是,如果foo没有被定义过,那么变量foo的值就是”bar”,如果foo先前被定义过,那么这条语句将什么也不做。

变量高级用法:(1)、变量值的替换:可以替换变量中共有的部分,其格式是”$(var:a=b)”或是”${var:a=b}”,其意思是,把变量”var”中所有以”a”字串”结尾”的”a”替换成”b”字符串。这里的”结尾”意思是”空格”或是”结束符”。另外一种变量替换的技术是以”静态模式”定义的,其格式是”$(var:%.a=%.b)”,这依赖于被替换字串中有相同的模式。(2)、把变量的值再当成变量。

追加变量值:可以使用”+=”操作符给变量追加值。如果变量之前没有定义过,那么,”+=”会自动变成”=”,如果前面有变量定义,那么”+=”会继承于前次操作的赋值符。如果前一次的是”:=”,那么”+=”会以”:=”作为起赋值符。

#追加变量值

objects = main.o foo.o bar.o

#下面两行语句等价
#objects := $(objects) another.o
objects += another.o

variable := value
#下面两行语句等价
#variable := $(variable) more
variable += more

exec:
	echo $(objects) #main.o foo.o bar.o another.o
	echo $(variable) #value more

override指示符:有些变量通常是make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果想在Makefile中设置这类参数的值,那么,可以使用”override”指示符。

多行变量:使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令。define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和”=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。

环境变量:make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖(如果make指定了”-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)。

使用条件判断:可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值:ifeq、ifneq、ifdef、ifndef。注意:make在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,最好不要把自动化变量(如”$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。而且,make不允许把整个条件语句分成两部分放在不同的文件中。

#使用条件判断

#ifdef
bar = 
foo = $(bar)
ifdef foo
    frobozz = yes
else
    frobozz = no
endif

#ifeq
ifeq ($(CC), gcc)
    b = gcc
else
    b = other
endif

exec:
	echo $(frobozz) #yes
	echo $(b) 

使用函数:在Makefile中可以使用函数来处理变量。函数调用后,函数的返回值可以当作变量来使用。函数参数间以逗号”,”分隔,而函数名和参数之间以”空格”分隔。函数调用以”$”开头,以圆括号或花括号把函数名和参数括起

#使用函数

#subst
comma := ,
empty :=  
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))

#sort
str := foo bar lose
str := $(sort $(str))

#foreach
names := a b c d
files := $(foreach n,$(names),$(n).o)

#shell
directory := $(shell pwd)

exec:
	echo $(bar) #a,b,c
	echo $(str) #bar foo lose
	echo $(files) #a.o b.o c.o d.o
	echo $(directory)

常用的字符串处理函数有:subst、patsubst、strip、findstring、filter、filter-out、sort、word、wordlist、words、firstword。

文件名操作函数:每个函数的参数字符串都会被当作一个或是一系列的文件名来对待。常用的函数有:dir、notdir、suffix、basename、 addsuffix、addprefix、join。

foreach函数:这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。

if函数:if函数很像GNU的make所支持的条件语句ifeq。if函数可以包含”else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。

call函数:是唯一一个可以用来创建新的参数化的函数。

origin函数:它并不操作变量的值,它只是告诉你这个变量是哪里来的。

shell函数:它的参数应该就是操作系统Shell的命令,它和反引号”`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,可以用操作系统命令以及字符串处理命令awk、sed等命令来生成一个变量。

控制make的函数:error、warning。

make的运行:一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的Makefile来执行,一切都是自动的。

make的退出码:0,表示成功执行;1,如果make运行时出现任何错误,其返回1;2,如果你使用了make的”-q”选项,并且make使得一些目标不需要更新,那么返回2.

指定Makefile:GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件----“GNUmakefile”、”makefile”和”Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。

我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,要使用make的”-f”或是”--file”参数(“--makefile”参数也行)。

指定目标:一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。若要指示make,让其完成你所指定的目标,需要在make命令后直接跟目标的名字

任何在makefile中的目标都可以被指定成终极目标,但是除了以”-”打头,或是包含了”=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量

隐含规则:也就是一种惯例,make会按照这种”惯例”来运行,哪怕我们的Makefile中没有书写这样的规则。

隐含规则使用的变量:可以把隐含规则中使用的变量分成两种,一种是命令相关的,如”CC”,一种是参数相关的,如”CFLAGS”。

命令相关的变量:

(1)、AR:函数库打包程序,默认命令是”ar”;

(2)、AS:汇编语言编译程序,默认命令是”as”;

(3)、CC:C语言编译程序,默认命令是”cc”;

(4)、CXX:C++语言编译程序,默认命令是”g++”;

(5)、CO:从RCS文件中扩展文件程序,默认命令是”co”;

(6)、CPP:C程序的预处理器(输出是标准输出设备),默认命令是”$(CC)-E”;

(7)、FC:Fortran和Ratfor的编译器和预处理程序,默认命令是”f77”;

(8)、GET:从SCCS文件中扩展文件的程序,默认命令是”get”;

(9)、LEX:Lex方法分析器程序(针对于C或Ratfor),默认命令是”lex”;

(10)、PC:Pascal语言编译程序,默认命令是”pc”;

(11)、YACC:Yacc文法分析器(针对于C程序),默认命令是”yacc”;

(12)、YACCR:Yacc文法分析器(针对于Ratfor程序),默认命令是”yacc-r”;

(13)、MAKEINFO:转换Texinfo源文件(.texi)到Info文件程序,默认命令是”makeinfo”;

(14)、TEX:从TeX源文件创建TeX DVI文件的程序,默认命令是”tex”;

(15)、TEXI2DVI:从Texinfo源文件创建TeX DVI文件的程序,默认命令是”texi2dvi”;

(16)、WEAVE:转换Web到TeX的程序,默认命令是”weave”;

(17)、CWEAVE:转换C Web到TeX的程序,默认命令是”cweave”;

(18)、TANGLE:转换Web到Pascal语言的程序,默认命令是”tangle”;

(19)、CTANGLE:转换C Web到C,默认命令是”ctangle”;

(20)、RM:删除文件命令,默认命令是”rm-f”。

参数相关的变量:如果没有指明其默认值,那么其默认值都是空。

(1)、ARFLAGS:函数库打包程序AR命令的参数,默认是”rv”;

(2)、ASFLAGS:汇编语言编译器参数(当明显地调用”.s”或”.S”文件时);

(3)、CFLAGS:C语言编译器参数;

(4)、CXXFLAGS:C++语言编译器参数;

(5)、COFLAGS:RCS命令参数;

(6)、CPPFLAGS:C预处理器参数(C和Fortran编译器也会用到);

(7)、FFLAGS:Fortran语言编译器参数;

(8)、GFLAGS:SCCS “get”程序参数;

(9)、LDFLAGS:链接器参数,如”ld”;

(10)、LFLAGS:Lex文法分析器参数;

(11)、PFLAGS:Pascal语言编译器参数;

(12)、RFLAGS:Ratfor程序的Fortran编译器参数;

(13)、YFLAGS:Yacc文法分析器参数。

定义模式规则:可以使用模式规则来定义一个隐含规则,一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有”%”字符。”%”的意思是表示一个或多个任意字符。在依赖目标中同样可以使用”%”,只是依赖目标中的”%”的取值,取决于其目标。注意:”%”的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入Makefile时,而模式规则中的”%”则发生在运行时

自动化变量:就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中

以下是自动化变量的说明:

(1)、$@:表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,”$@”就是匹配于目标中模式定义的集合;

(2)、$%:仅当目标是函数库文件时,表示规则中的目标成员名。如果目标不是函数库文件,那么,其值为空;

(3)、$<:依赖目标中的第一个目标名字。如果依赖目标是以模式(即”%”)定义的,那么”$<”将是符合模式的一系列的文件集,注意,其是一个一个取出来的;

(4)、$?:所有比目标新的依赖目标的集合,以空格分隔;

(5)、$^:所有依赖目标的集合,以空格分隔,如果在依赖目标中有多个重复的,那么这个变量会出去重复的依赖目标,只保留一份;

(6)、$+:这个变量很像”$^”,也是所有依赖目标的集合,只是它不去除重复的依赖目标;

(7)、$*:这个变量表示目标模式中”%”及其之前的部分。如果目标是”dir/a.foo.b”,并且目标的模式是”a.%.b”,那么,”$*”的值就是”dir /a.foo”。如果目标中没有模式的定义,那么”$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么”$*”就是除了后缀的那一部分。例如,如果目标是”foo.c”,因为”.c”是make所能识别的后缀名,所以,”$*”的值就是”foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,应该尽量避免使用”$*”。

使用make更新函数库文件:函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是由命令”ar”来完成打包工作。

以上内容整理自陈皓的CSDN博客:http://blog.csdn.net/haoel/article/category/9198


Makefile语法基础介绍

标签:

原文地址:http://blog.csdn.net/fengbingchun/article/details/45170853

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!