Linux里Makefile是什么?它是如何工作的?
用这个方便的工具来更有效的运行和编译你的程序
makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接-----但是不是所有的文件都需要重新编译,makefile能够纪录文件的信息,决定在链接的时候需要重新编译哪些文件!
当你需要在一些源文件改变后运行或更新一个任务时,通常会用到make工具。make工具需要读取一个Makefile(或makefile)文件,在该文件中定义了一系列需要执行的任务。你可以使用make来将源代码编译为可执行程序。大部分开源项目会使用make来实现最终的二进制文件的编译,然后使用makeinstall命令来执行安装。
本文将通过一些基础和进阶的示例来展示make和Makefile的使用方法。在开始前,请确保你的系统中安装了make。
基础示例
依然从打印“HelloWorld”开始。首先创建一个名字为myproject的目录,目录下新建Makefile文件,文件内容为:
say_hello: echo"HelloWorld"
在myproject目录下执行make,会有如下输出:
$make echo"HelloWorld" HelloWorld
在上面的例子中,“say_hello”类似于其他编程语言中的函数名。这被称之为目标(target)。在该目标之后的是预置条件或依赖。为了简单起见,我们在这个示例中没有定义预置条件。echo‘HelloWorld'命令被称为步骤(recipe)。这些步骤基于预置条件来实现目标。目标、预置条件和步骤共同构成一个规则。
总结一下,一个典型的规则的语法为:
目标:预置条件
作为示例,目标可以是一个基于预置条件(源代码)的二进制文件。另一方面,预置条件也可以是依赖其他预置条件的目标。
final_target:sub_targetfinal_target.c Recipe_to_create_final_target sub_target:sub_target.c Recipe_to_create_sub_target
目标并不要求是一个文件,也可以只是步骤的名字,就如我们的例子中一样。我们称之为“伪目标”
再回到上面的示例中,当make被执行时,整条指令echo"HelloWorld"都被显示出来,之后才是真正的执行结果。如果不希望指令本身被打印处理,需要在echo前添加@
say_hello: @echo"HelloWorld"
重新运行make,将会只有如下输出:
$make HelloWorld
接下来在Makefile中添加如下伪目标:generate和clean:
say_hello: @echo"HelloWorld" generate: @echo"Creatingemptytextfiles..." touchfile-{1..10}.txt clean: @echo"Cleaningup..." rm*.txt
随后当我们运行make时,只有say_hello这个目标被执行。这是因为Makefile中的第一个目标为默认目标。通常情况下会调用默认目标,这就是你在大多数项目中看到all作为第一个目标而出现。all负责来调用它他的目标。我们可以通过.DEFAULT_GOAL这个特殊的伪目标来覆盖掉默认的行为。
在Makefile文件开头增加.DEFAULT_GOAL:
.DEFAULT_GOAL:=generate
make会将generate作为默认目标:
$make Creatingemptytextfiles... touchfile-{1..10}.txt
顾名思义,.DEFAULT_GOAL伪目标仅能定义一个目标。这就是为什么很多Makefile会包括all这个目标,这样可以调用多个目标。
下面删除掉.DEFAULT_GOAL,增加all目标:
all:say_hellogenerate say_hello: @echo"HelloWorld" generate: @echo"Creatingemptytextfiles..." touchfile-{1..10}.txt clean: @echo"Cleaningup..." rm*.txt
运行之前,我们再增加一些特殊的伪目标。.PHONY用来定义这些不是文件的目标。make会默认调用这些伪目标下的步骤,而不去检查文件名是否存在或最后修改日期。完整的Makefile如下:
.PHONY:allsay_hellogenerateclean all:say_hellogenerate say_hello: @echo"HelloWorld" generate: @echo"Creatingemptytextfiles..." touchfile-{1..10}.txt clean: @echo"Cleaningup..." rm*.txt
make命令会调用say_hello和generate:
$make HelloWorld Creatingemptytextfiles... touchfile-{1..10}.txt
clean不应该被放入all中,或者被放入第一个目标中。clean应当在需要清理时手动调用,调用方法为makeclean
$makeclean Cleaningup... rm*.txt
现在你应该已经对Makefile有了基础的了解,接下来我们看一些进阶的示例。
进阶示例
变量
在之前的实例中,大部分目标和预置条件是已经固定了的,但在实际项目中,它们通常用变量和模式来代替。
定义变量最简单的方式是使用=操作符。例如,将命令gcc赋值给变量CC:
CC=gcc
这被称为递归扩展变量,用于如下所示的规则中:
hello:hello.c ${CC}hello.c-ohello
你可能已经想到了,这些步骤将会在传递给终端时展开为:
gcchello.c-ohello
${CC}和$(CC)都能对gcc进行引用。但如果一个变量尝试将它本身赋值给自己,将会造成死循环。让我们验证一下:
CC=gcc CC=${CC} all: @echo${CC}
此时运行make会导致:
$make Makefile:8:***Recursivevariable'CC'referencesitself(eventually).Stop.
为了避免这种情况发生,可以使用:=操作符(这被称为简单扩展变量)。以下代码不会造成上述问题:
CC:=gcc CC:=${CC} all: @echo${CC}
模式和函数
下面的Makefile使用了变量、模式和函数来实现所有C代码的编译。我们来逐行分析下:
#Usage: #make#compileallbinary #makeclean#removeALLbinariesandobjects .PHONY=allclean CC=gcc#compilertouse LINKERFLAG=-lm SRCS:=$(wildcard*.c) BINS:=$(SRCS:%.c=%) all:${BINS} %:%.o @echo"Checking.." ${CC}${LINKERFLAG}$<-o$@ %.o:%.c @echo"Creatingobject.." ${CC}-c$< clean: @echo"Cleaningup..." rm-rvf*.o${BINS}
以#开头的行是评论
.PHONY=allclean行定义了all和clean两个伪目标。
变量LINKERFLAG定义了在步骤中gcc命令需要用到的参数。
SRCS:=$(wildcard*.c):$(wildcardpattern)是与文件名相关的一个函数。在本示例中,所有“.c”后缀的文件会被存入SRCS变量。
BINS:=$(SRCS:%.c=%):这被称为替代引用。本例中,如果SRCS的值为'foo.cbar.c',则BINS的值为'foobar'。
all:${BINS}行:伪目标all调用${BINS}变量中的所有值作为子目标。
规则:
%:%.o @echo"Checking.." ${CC}${LINKERFLAG}$<-o$@
下面通过一个示例来理解这条规则。假定foo是变量${BINS}中的一个值。%会匹配到foo(%匹配任意一个目标)。下面是规则展开后的内容:
foo:foo.o @echo"Checking.." gcc-lmfoo.o-ofoo
如上所示,%被foo替换掉了。$<被foo.o替换掉。$<用于匹配预置条件,$@匹配目标。对${BINS}中的每个值,这条规则都会被调用一遍。
规则:
%.o:%.c @echo"Creatingobject.." ${CC}-c$<
之前规则中的每个预置条件在这条规则中都会都被作为一个目标。下面是展开后的内容:
foo.o:foo.c @echo"Creatingobject.." gcc-cfoo.c
最后,在clean目标中,所有的二进制文件和编译文件将被删除。
下面是重写后的Makefile,该文件应该被放置在一个有foo.c文件的目录下:
#Usage: #make#compileallbinary #makeclean#removeALLbinariesandobjects .PHONY=allclean CC=gcc#compilertouse LINKERFLAG=-lm SRCS:=foo.c BINS:=foo all:foo foo:foo.o @echo"Checking.." gcc-lmfoo.o-ofoo foo.o:foo.c @echo"Creatingobject.." gcc-cfoo.c clean: @echo"Cleaningup..." rm-rvffoo.ofoo
这些和到一起,就是makefile,当然这些功能还太少,可以加上很多别的项目。但宗旨就是:让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动的发现最终的生成文件已经过时,而重新编译相应的模块。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接