Linux下GNU的makefile

关于程序编译的一些规范和方法,一般来说,首先都是对源文件编译成中间代码文件,然后再通过链接将生产的中间代码文件合成可执行的文件

Windons下 :.cpp .h源文件编译 .obj中间文件 链接 .exe 等可执行文件
Linux下:.c .h 源文件编译 .o中间文件 链接 可执行文件

       但是当源文件太多,编译生产的中间目标文件太多,而链接时要明确指出生成可执行文件必要的中间文件,显然太费事,所以可以对中间文件打个包,windows 下的生产的 .lib(库文件).dll而liunx 下生产的就是 .a .so 文件

windows 下一些编程软件内部都帮你做了,所以你可能没啥感觉,但是liunx 下你大部分需要自己写,其中makefile就定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,所以了解makefile,你就知道如何将 .c .h 文件编译链接生产最终的可执行文件

接下来进入主题,通过一个makefile 例子 初步了解makefile

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

edit : $(objects)
    cc -o edit $(objects)
 
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
 
.PHONY : clean
clean :
    rm edit $(objects)


解释下上面的makefile 的例子:

(1) 
edit : $(object)
	cc- o edit $(object)

这句话遵循makefile 的基本规则,target... : prerequisites ...  command

target 目标文件,也可以是可执行文件
prerequisites 生产目标文件所依赖的文件
command 也就是make 执行的命令

指的是要生成的目标文件edit 所要依赖 object 里的文件,而object 是个变量,$(object) 指的就是main.o kbd.o command.o display.o insert.o search.o files.o utils.o 最后是一条命令指令,功能是根据object文件生产可执行文件edit
linux下的cc是gcc的符号链接,能通过$ls -l /usr/bin/cc来简单察看:/usr/bin/cc->gcc

gcc  -o 指定可执行文件名
-c 只输出编译结果 不链接,不生成可执行文件
-g 表示在编译时提供以后进行调试的信息

需要特别注意的是command命令,一定要以一个tab键开头


(2) main.o:defs.h,其实这句话也符合上面语法的基本规则,它等价于

main.o:defs.h main.c 
       cc -c main.c 

怎么会等价呢?原因很简单,GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c就会是whatever.o的依赖文件。并且 cc -c whatever.c也会被推导出来,所以main.o:defs.h 一条语句就能达到两条语句的效果,使得代码更加简洁

(3)

.PHONY : clean
clean :
          rm edit $(objects)
最后几句话其实就是个伪目标,提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)

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

有点像C语言中的lable一样,其冒号后什么也没有,那么make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份

需要注意的事,像clean这种伪目标基本都是放在makefile的最后的,这主要跟make的工作方式有关,它会找文件的第一个目标文件,找到后并把它作为最终的目标文件,而把clean作为最终的目标文件显然是不合适的


make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

  1.   make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2.   如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
  3.   如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
  4.   如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
  5.   当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件edit了。
通过上面例子,相信对makefile多多少少有些了解,而下面主要记下一些在makefile里比较常用的语法和它代表的意思

(1)*.o通配符

objects = *.o 表示了通配符同样可以用在变量中。特别注意的是objects的值就是“*.o”,而不是指objects 会是所有.o文件的展开,如果你要让通配符在变量中展开:

objects的值是所有[.o]的文件名的集合 :objects := $(wildcard *.o) 

其中wildcard是关键字,表示的将通配符自动展开,其中*.o就是通配符

objects := $(patsubst %.c,%.o,$(wildcard *.c))

下划线这句话意思指的就是,使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。最后将.o文件列表赋值给objects这个变量,其中patsubst是个函数,替换通配符的作用

(2)静态模式:

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

<targets...>: <target-pattern>: <prereq-patterns ...>
   <commands>

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

看一个例子:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
        $(CC) -c $(CFLAGS) $< -o $@
指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标(即foo.o  bar.o),而“%.c"计算方式指的就是模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀

于是我们的依赖目标就是“foo.cbar.c”

命令中的“$<”和“$@”则是自动化变量 “$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也褪恰癴oo.o bar.o”)

所以上面的等价于:
 foo.o : foo.c
           $(CC) -c $(CFLAGS) foo.c -o foo.o
   bar.o : bar.c
           $(CC) -c $(CFLAGS) bar.c -o bar.o

(3)自动产生依赖性

如果你的一个main.c文件包含头文件defs.h

gcc-MM main.c的输出则是:main.o: main.c defs.h(即通过gcc -MM file可获得file文件的依赖性)

(4) 目标变量

可以为某个目标设置局部变量,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值

这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
        $(CC) $(CFLAGS) prog.o foo.o bar.o


prog.o : prog.c
         $(CC) $(CFLAGS) prog.c


foo.o : foo.c
        $(CC) $(CFLAGS) foo.c


bar.o : bar.c
        $(CC) $(CFLAGS) bar.c
不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”
(5)条件语法

条件表达式的语法为:
<conditional-directive>
<text-if-true>
endif
以及:
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
其中<conditional-directive>表示条件关键字,如“ifeq”。这个关键字有四个。
(1)ifeq (<arg1>, <arg2> ) 比较参数“arg1”和“arg2”的值是否相同,相同为真

(2)ifneq (<arg1>, <arg2> )于ifeq相反,不相同为真

(3)ifdef <variable-name> 如果变量<variable-name>的值非空,那到表达式为真

(4)ifndef <variable-name> 于ifdef相反,值空则是true


(6)函数的使用:

其语法如下:
$(<function> <arguments> )
<function>就是函数名,<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头

看一个示例:

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

subst(varg1,varg2,varg3)函数是将varg3字符串中的varg1字符替换成varg2,而该示例就将”a b c"替换成“a,b,c"

(7)模式规则:

至少在规则的目标定义中要包含"%",目标中的"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。

模式规则如下:
%.o : %.c ; <command ......>

其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果要生成的目标是"a.o b.o",那么"%c"就是"a.c b.c"。

示例:

%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

表示了把所有的[.c]文件都编译成[.o]文件.


(8)一些常用的字符串处理函数

$(subst <from>,<to>,<text> )
名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。


$(patsubst <pattern>,<replacement>,<text> )
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)返回:函数返回被替换过后的字符串。

$(findstring <find>,<in> )
名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串

$(strip <string> )
名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。


$(filter-out <pattern...>,<text> )

名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可
以有多个模式。


$(sort <list> )
名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。


$(call <expression>,<parm1>,<parm2>,<parm3>)

<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是 call函数的返回值。例如:

reverse = $(1) $(2)
foo = $(call reverse,a,b)

foo的值就是 a b


shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数

contents := $(shell cat foo)

(9)一些常用的命令变量和参数变量

AR 函数库打包程序。默认命令是“ar”。
AS 汇编语言编译程序。默认命令是“as”。
CC C语言编译程序。默认命令是“cc”。
CXX C++语言编译程序。默认命令是“g++”。
CO 从 RCS文件中扩展文件程序。默认命令是“co”。
CPP C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
RM 删除文件命令。默认命令是“rm –f”。
ARFLAGS 函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS 汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS C语言编译器参数。
CXXFLAGS C++语言编译器参数。
CPPFLAGS C预处理器参数。( C 和 Fortran 编译器也会用到)

(10)自动化变量:

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了

$@     有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

$%    仅当目标是函数库文件中(liunx 是.a,windows下是.lib文件),表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。

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

$?     所有比目标新的依赖目标的集合。以空格分隔。

$^     所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$*     这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir /a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出

例如,假设有一个函数库文件叫"lib",其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是:
lib : foo.o bar.o lose.o win.o
ar r lib $?


上面列出的6个自动化变量,四个变量($@、$<、$%、$*)在扩展时只会有一个文件,而($^,S?)的值是一个列表集合


出个例子:

LIBS=foo.k bar.k

.PHONY : all

all: $(LIBS)
    @echo final

$(LIBS): %.k : text.g
    @echo $*

text.g:
    echo text.g
上面这个例子用到了静态模式和$*如果你对makefile 有不错的理解不难发现最后的输出结果是:

text.g

foo

bar

final


终于收尾了,写博客和看博客还真是两码事,以上都是通过看了网上的一些博客,然后自己做的总结。大部分例子借助了招封的博客,要想了解更多可以去看看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值