安阳网站建设哪家专业,go做的网站,php网站建设制作设计,莱芜网站建设排行Makefile基础使用和实战详解一、基础1.1、简单的Makefile1.2、多文件编译1.3、伪对象.PHONY二、变量2.1、自动变量2.2、特殊变量2.3、变量的类别2.4、变量及其值的来源2.5、变量引用的高级功能2.6、override 指令三、模式四、函数4.1、addprefix 函数4.2、filter函数4.3、filte…
Makefile基础使用和实战详解一、基础1.1、简单的Makefile1.2、多文件编译1.3、伪对象.PHONY二、变量2.1、自动变量2.2、特殊变量2.3、变量的类别2.4、变量及其值的来源2.5、变量引用的高级功能2.6、override 指令三、模式四、函数4.1、addprefix 函数4.2、filter函数4.3、filter-out函数4.4、patsubst 函数4.5、strip函数4.6、wildcard 函数总结一、基础
Makefile 其实只是一个指示 make 程序如何为我们工作的命令文件我们说 Makefile 其实是在说 make。而对于项目来说Makefile 是指软件项目的编译环境。
Makefile 的好坏对于项目开发有些什么影响呢设计得好的 Makefile当我们重新编译时只需编译那些上次编译成功后修改过的文件也就是说编译的是一个 delta而不是整个项目。反之如果一个不好的 Makefile 环境可能对于每一次的编译先要 clean然后再重新编译整个项目。两种情况的差异是显然的后者将耗费开发人员大量的时间用于编译也就意味着低效率。
最为重要的是掌握二个概念一个是目标target另一个就是依赖dependency。目标就是指要干什么或说运行 make 后生成什么而依赖是告诉 make 如何去做以实现目标。在 Makefile 中目标和依赖是通过规则rule来表达的。我们最为熟悉的是采用make 来进行软件产品的代码编译但它可以被用来做很多很多的事情。驾驭 Makefile最为重要的是要学会采用目标和依赖关系来思考所需解决的问题。
Makefile三要素
Makefile工作原理
1.1、简单的Makefile
1示例一
all:echo hello all
test:echo hello test执行结果
$ make
hello all
$ make test
hello test
$ make all
hello all需要注意的是echo 前面必须只有 TAB即键盘TAB键且至少有一个 TAB而不能用空格代替。对于很多初学者最为容易犯的就是这种“低级”错误。 这种错误往往在对 Makefile 进行调试时还不大容易发现因为从文本编辑器中看来TAB 与空格有时没有太明显的区别。 Makefile 中的 all 就是目标目标放在‘’的前面其名字可以是由字母和下划线‘_’组成 。echo “hello all”就是生成目标的命令这些命令可以是任何你可以在你的环境中运行的命令以及 make 所定义的函数等等。all 目标在这里就是代表希望在终端上打印出“hello all”有时目标会是一个比较抽象的概念。all 目标的定义其实是定义了如何生成 all 目标这我们也称之为规则。
2示例二在示例一的基础上调换目标位置。
test:echo hello test
all:echo hello all执行结果
$ make
hello test
$ make test
hello test
$ make all
hello all可知 一个 Makefile 中可以定义多个目标。 调用 make 命令时我们得告诉它我们的目标是什么即要它干什么。当没有指明具体的目标是什么时那么 make 以 Makefile 文件中定义的第一个目标作为这次运行的目标。这“第一个”目标也称之为默认目标和是不是all没有关系。
当 make 得到目标后先找到定义目标的规则然后运行规则中的命令来达到构建目标的目的。现在所示例的 Makefile 中每一个规则中都只有一条命令而实际的 Makefile每一个规则可以包含很多条命令。 注意命令前加了一个‘’ 这一符号告诉 make在运行时不要将这一行命令显示出来。
3示例三
all:testecho hello all
test:echo hello test执行结果
$ make
hello test
hello all
$ make test
hello test
$ make all
hello test
hello all会发现当运行 make 时test 目标也被构建了。这里需要引入 Makefile 中依赖关系的概念all 目标后面的 test 是告诉 makeall 目标依赖 test 目标这一依赖目标在 Makefile 中又被称之为先决条件。出现这种目标依赖关系时make工具会按从左到右的先后顺序先构建规则中所依赖的每一个目标。如果希望构建 all 目标那么make 会在构建它之前得先构建 test 目标这就是为什么我们称之为先决条件的原因。
1.2、多文件编译
目前有两个源文件需要编译成一个应该程序 foo.c
#include stdio.h
void foo()
{printf(This is foo() \n);
}main.c
extern void foo();
int main()
{foo();return 0;
}它们的依赖关系可总结为如下
我们就可以根据依赖关系写Makefile了
all:main.o foo.ogcc -o simple main.o foo.o
main.o:gcc -o main.o -c main.c
foo.o:gcc -o foo.o -c foo.c
clean:rm simple main.o foo.o增加了一个 clean 目标用于删除所生成的文件包括目标文件和 simple 可执行程序这在现实的项目中很是常见。 值得注意的是如果执行两次make会怎么样执行效果如下
$make
gcc -c main.c -o main.o
gcc -c foo.c -o foo.o
gcc -o simple main.o foo.o$make
gcc -o simple main.o foo.o注意到第二次编译并没有构建目标文件的动作但有构建simple可执行程序的动作我们需要了解 make 是如何决定哪些目标这里是文件是需要重新编译的。为什么 make会知道我们并没有改变 main.c 和 foo.c 呢通过文件的时间戳。当 make 在运行一个规则时我们前面已经提到了目标和先决条件之间的依赖关系make 在检查一个规则时采用的方法是如果先决条件中相关的文件的时间戳大于目标的时间戳即先决条件中的文件比目标更新则知道有变化那么需要运行规则当中的命令重新构建目标。这条规则会运用到所有与我们在 make时指定的目标的依赖树中的每一个规则。比如对于 simple 项目其依赖树中包括三个规则make 会检查所有三个规则当中的目标文件与先决条件文件之间的时间先后关系从而来决定是否要重新创建规则中的目标。
那为什么会执行一次gcc -o simple main.o foo.o呢因为all文件在我们的编译过程中并不生成即 make 在第二次编译时找不到所以又重新编译了一遍。如果把all改为simple那就是我们所期望的结果
simple:main.o foo.ogcc -o simple main.o foo.o
main.o:gcc -o main.o -c main.c
foo.o:gcc -o foo.o -c foo.c
clean:rm simple main.o foo.o执行结果
$make
gcc -c main.c -o main.o
gcc -c foo.c -o foo.o
gcc -o simple main.o foo.o$make
make: simple is up to date.另外对于make 工具一个文件是否改动不是看文件大小而是其时间戳。比如用 touch 命令来改变文件的时间戳就行了这相当于模拟了对文件进行了一次编辑而不需真正对其进行编辑。make 发现了 foo.c 需要重新被编译而这最终也导致了 simple 需要重新被编译。
$ls -l foo.c
-rw-rw-r-- 1 fly fly 65 1月 30 14:15 foo.c$touch foo.c
$ls -l foo.c
-rw-rw-r-- 1 fly fly 65 1月 30 15:42 foo.c$make
gcc -c foo.c -o foo.o
gcc -o simple main.o foo.o1.3、伪对象.PHONY
在前面的示例项目中现在假设在程序所在的目录下面有一个 clean 文件这个文件也可以通过 touch 命令来创建。创建以后运行 make clean 命令
$ls -l clean
ls: cannot access clean: No such file or directory$touch clean
$ls -l clean
-rw-rw-r-- 1 fly fly 65 1月 30 16:42 clean$make clean
make: clean is up to date.会发现 make 总是提示 clean 文件是最新的而不是按我们所期望的那样进行文件删除操作。这是因为 make 将 clean 当作文件且在当前目录找到了这个文件加上 clean 目标没有任何先决条件所以当我们要求 make 为我们构建 clean 目标时它就会认为 clean 是最新的。
对于这种情况在现实中也难免存在所定义的目标与所存在的文件是同名的采用 Makefile如何处理这种情况呢Makefile 中的假目标phony target可以解决这个问题。假目标可以采用.PHONY 关键字来定义需要注意的是其必须是大写字母。
.PHONY:clean
simple:main.o foo.ogcc -o simple main.o foo.o
main.o:gcc -o main.o -c main.c
foo.o:gcc -o foo.o -c foo.c
clean:rm simple main.o foo.o将 clean 变为假目标后的Makefile更改后运用 make clean 命令的结果
$make clean
rm simple main.o foo.o采用.PHONY 关键字声明一个目标后make 并不会将其当作一个文件来处理而只是当作一个概念上的目标。对于假目标我们可以想像的是由于并不与文件关联所以每一次 make 这个假目标时其所在的规则中的命令都会被执行。
二、变量
在 Makefile 中通过使用变量来使得它更简洁、更具可维护性。
.PHONY:clean
CCgcc
RMrm
EXEsimple
OBJSmain.o foo.o$(EXE):$(OBJS)$(CC) -o $(EXE) $(OBJS)
main.o:$(CC) -o main.o -c main.c
foo.o:$(CC) -o foo.o -c foo.c
clean:$(RM) $(EXE) $(OBJS)一个变量的定义很简单就是一个名字变量名后面跟上一个等号然后在等号的后面放这个变量所期望的值。对于变量的引用则需要采用(变量名)或者(变量名)或者(变量名)或者{变量名}这种模式。采用变量的话当我们需要更改编译器时只需更改变量赋值的地方非常方便如果不采用变量那我们得更改每一个使用编译器的地方很是麻烦。显然变量的引入增加了 Makefile 的可维护性。既然定义了一个 CC 变量当然也可以将-o 或是-c 命令参数也定义成为一个变量因为如果我们更改了一个编译器那么很有可能其使用参数也得跟着改变。
2.1、自动变量
有时候目标和先决条件的名字会在规则的命令中多次出如果改变了目标或是依赖的名那得在命令中全部跟着改。这就需要用到 Makefile 中的自动变量它们包括
$ 用于表示一个规则中的目标。当我们的一个规则中有多个目标时$所指的是其中任何造成命令被运行的目标。$^则表示的是规则中的所有先决条件。$表示的是规则中的第一个先决条件。
除了上面的两个自动变量在 Makefile 中还有其它的动变量。如下是测试上面三个自动变量的值的 Makefile
.PHONY:all
all:first second thridecho \$$$echo $$^$^echo $$$
first second thrid:运行结果
$ make$all
$^first second thrid
$first需要注意的是在 Makefile 中‘’具有特殊的意思因此如果想采用echo输出‘’具有特殊的意思因此如果想采用 echo 输出‘’具有特殊的意思因此如果想采用echo输出‘’则必需用两个连着的‘’。还有就是’。还有就是’。还有就是对于 Shell 也有特殊的意思需要在“$$”之前再加一个脱字符‘\’。
我们就可以将simple项目的Makefile改为如下
.PHONY:clean
CCgcc
RMrm
EXEsimple
OBJSmain.o foo.o$(EXE):$(OBJS)$(CC) -o $ $^
main.o:main.c$(CC) -o $ -c $^
foo.o:foo.c$(CC) -o $ -c $^
clean:$(RM) $(EXE) $(OBJS)自动变量在对它们还不熟悉时看起来可能有那么一点吃力但熟悉了你就会觉得其简捷洁那时也会觉得它们好用。
2.2、特殊变量
在 Makefile 中有几个特殊变量可能经常需要用到。 1第一个就是 MAKE 变量它表示的是make 命令名是什么。当我们需要在 Makefile 中调用另一个 Makefile 时需要用到这个变量采用这种方式有利于写一个容易移植的 Makefile。
.PHONY: all
all:
echo MAKE $(MAKE)执行
$make
MAKE make2第二个特殊变量则是 MAKECMDGOALS它表示的是当前用户所输入的 make 目标是什么。
.PHONY: all clean
all clean:
echo \$$ $
echo MAKECMDGOALS $(MAKECMDGOALS)执行
$make
$ all
MAKECMDGOALS $make all
$ all
MAKECMDGOALS all$make clean
$ clean
MAKECMDGOALS clean$make all clean
$ all
MAKECMDGOALS all clean
$ clean
MAKECMDGOALS all cleanMAKECMDGOALS 指的是用户输入的目标当只运行 make 命令时虽然根据 Makefile 的语法第一个目标将成为缺省目标即 all 目标但 MAKECMDGOALS 仍然是空而不是 all这一点需要注意。
2.3、变量的类别
1只用一个“”符号定义的变量称之为递归扩展变量recursively expanded variable。
.PHONY:all
foo$(foo2)
foo2$(foo3)
foo3 FLY.
all:echo $(foo)执行
$ make
FLY.递归扩展变量的引用是递归的。这种递归性有利也有弊。利的方面是最后foo将会被展开。但也存在弊那就是我们不能对foo变量再采用赋值操作。如下的方式会出现一个死循环
foo$(foo) -O2除了递归扩展变量还有一种变量称之为简单扩展变量simply expanded variables是用“:”操作符来定义的。对于这种变量make 只对其进行一次扫描和替换。
.PHONY:all
xfly
y$(x) FLY
xlater
xx:fly
yy:$(xx) FLY
xx:fly
all:echo x$(y),xx$(yy)执行
$ make
xlater FLY,xxfly FLY可以明显的看出 make 是如何处理递归扩展变量和简单扩展变量的。 3Makefile中还存在一种条件赋值符“?”。
.PHONY:all
foox
foo?y
bar?y
all:echo foo$(foo),bar$(bar)执行
$ make
foox,bary条件赋值的意思是当变量以前没有定义时就定义它并且将左边的值赋值给它如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能。 对于前面所说的变量类别是针对一个赋值操作而言的。
2.4、变量及其值的来源
在 Makefile 中我们可以对变量进行定义。此外还有其它的地方让 Makefile 获得变量及其值。比如 1对于前面所说到的自动变量其值是在每一个规则中根据规则的上下文自动获得变量值的。 2可以在运行 make 时在 make 命令行上定义一个或多个变量。在 make 命令行中定义的变量及其值同样在 Makefile 中是可见的。其实我们可以通过在 make 命令行中定义变量的方式从而覆盖 Makefile 中所定义的变量的值。
$ make barx3变量还可以来自于 Shell 环境例如采用 Shell 中的 export 命令定义了一个变量后再执行Makefile。
$ export barx
$ make
foo x, bar x4Makefile 还可以采用“”操作符对变量进行赋值的方法。
.PHONY: all
objects main.o foo.o bar.o utils.o
objects another.o
all:echo $(objects)等价于
.PHONY:all
objects main.o foo.o bar.o utils.o
objects: $(objects) another.o
all:echo $(objects)2.5、变量引用的高级功能
在赋值的同时完成后缀替换操作。
.PHONY:all
foo a.o b.o c.o
bar:$(foo:.o.c)
all:echo bar$(bar)执行结果
$ make
bara.c b.c c.cbar 变量中的文件名从.o 后缀都变成了.c。这种功能也可以采用 patsubst 函数来实现与函数相比这种功能更加的简洁。当然patsubst 功能更强而不只是用于替换后缀。
2.6、override 指令
前面了解到我们可以采用在 make 命令行上定义变量的方式使得 Makefile 中定义的变量覆盖掉从而不起作用。可能在设计 Makefile 时我们并不希望用户将我们在 Makefile 中定义的某个变量覆盖掉那就得用 override 指令了。
.PHONY:all
override foo a.o b.o c.o
bar:$(foo:.o.c)
all:echo bar$(bar)执行
$ make foobb.o cc.o
bara.c b.c c.c三、模式
对于前面的 Makefile其中存在多个规则用于构建目标文件。比如main.o 和 foo.o 都是采用不同的规则进行描述的。如果对于每一个目标文件都得写一个不同的规则来描述太繁了对于一个大型项目就更不用说了。Makefile 中的模式就是用来解决这种烦恼的。我们可以把之前的simple项目的Makefile改成这样
.PHONY:clean
CCgcc
RMrm
EXEsimple
OBJSmain.o foo.o$(EXE):$(OBJS)$(CC) -o $ $^
%.o:%.c$(CC) -o $ -c $^
clean:$(RM) $(EXE) $(OBJS)与 前一版本的 Makefile 相比最为直观的改变就是从二条构建目标文件的规则变成了一条。模式类似于 Windows 操作系统中所使用的通配符当然是用“ % ”而不是“ * ”。采用了模式以后不论有多少个源文件要编译我们都是应用同一个模式规则的很显然这大大的简化了我们的工作。使用了模式规则以后同样可以用这个 Makefile 来编译或是清除 simple 项目这与前一版本在功能上是完全一样的。
四、函数
函数是 Makefile 中的另一个利器现在看一看采用函数如何来简化 simple 项目的 Makefile。对于 simple 项目的 Makefile尽管使用了模式规则但还有一件比较恼人的事得在这个Makefile 中指明每一个需要被编译的源程序。对于一个源程序文件比较多的项目如果每增加或是删除一个文件都得更新 Makefile其工作量也不可小视
下面是采用了 wildcard 和 patsubst 两个函数后 simple 项目的 Makefile。需要注意的是函数的语法形式很特别不过只要记住其形式就行了。
.PHONY:clean
CCgcc
RMrm
EXEsimple
SRCS$(wildcard *.c)# 把.c替换为.o
OBJS$(patsubst %.c,%.o,$(SRCS))$(EXE):$(OBJS)$(CC) -o $ $^
%.o:%.c$(CC) -o $ -c $^
clean:$(RM) $(EXE) $(OBJS)现在来模拟增加一个源文件的情形看一看如果增加一个文件在 Makefile 不做任何更改的情况下其是否仍能正常的工作。增加文件的方式仍然是采用 touch 命令通过 touch 命令生成一个内容是空的 foo2.c 源文件然后再运行 make 和 make clean。
$ touch foo2.c
$ make
gcc -o foo.o -c foo.c
gcc -o main.o -c main.c
gcc -o foo2.o -c foo2.c
gcc -o simple foo.o main.o foo2.o$ make clean
rm simple foo.o main.o foo2.o从结果来看函数真的起作用了这功能很酷更多内容可以看一看《GUN make》以了解 Makefile 中到底有些什么函数这样的话当在碰到具体的问题时就会想到它们。
4.1、addprefix 函数
addprefix 函数是用来在给字符串中的每个子串前加上一个前缀其形式是
$(addprefix prefix, names...)示例
.PHONY:all
no_dirfoo.c foo2.c main.o
no_dir:$(addprefix objs/,$(no_dir))
all:echo $(no_dir)执行
$ make
objs/foo.c objs/foo2.c objs/main.o4.2、filter函数
filter 函数用于从一个字符串中根据模式得到满足模式的字符串其形式是
$(filter pattern..., text)示例
.PHONY:all
srcsfoo.c foo2.c main.s main.h
srcs:$(filter %.c %.s,$(srcs))
all:echo $(srcs)执行
$ make
foo.c foo2.c main.s从结果来看经过 filter 函数的调用以后source变量中只存在.c 文件和.s 文件了而.h 文件则被过滤掉了。
4.3、filter-out函数
filter-out 函数用于从一个字符串中根据模式滤除一部分字符串其形式是
$(filter-out pattern..., text)示例
.PHONY:all
srcsfoo.c foo2.c main1.c main2.c main.h
srcs:$(filter-out main%.c,$(srcs))
all:echo $(srcs)执行
$ make
foo.c foo2.c main.h从结果来看filter-out 函数将 main1.c 和 main2.c从 src变量中给滤除了。filter 与 filter-out 是互补的。
4.4、patsubst 函数
patsubst 函数是用来进行字符串替换的其形式是
$(patsubst pattern, replacement, text)示例
.PHONY:all
srcsfoo.c foo2.c main1.c main2.c
objs:$(patsubst %.c,%.o,$(srcs))
all:echo $(objs)执行
$ make
foo.o foo2.o main1.o main2.o可以看出采用patsubst 函数进行字符串替换时我们希望将所有的.c 文件都替换成.o 文件。当然由于patsubst 函数可以使用模式所以其也可以用于替换前缀等等功能更加的强。
4.5、strip函数
strip 函数用于去除变量中的多余的空格其形式是
$(strip string)示例
.PHONY:all
srcsfoo.c foo2.c main1.c main2.c
objs:$(strip $(srcs))
all:echo $(srcs)echo $(objs)执行
$ make
foo.c foo2.c main1.c main2.c
foo.c foo2.c main1.c main2.c从结果来看strip 函数将 foo.c 和 bar.c 之间的多余的空格给去除了。
4.6、wildcard 函数
wildcard 是通配符函数通过它可以得到我们所需的文件这个函数如果我们在 Windows 或是Linux 命令行中的“*”。其形式是
$(wildcard pattern)示例
.PHONY:all
srcs$(wildcard *.c)
all:echo $(srcs)执行
$ make
foo.c main.c foo2.c从当前 Makefile 所在的目录下通过 wildcard 函数得到所有的 C 程序源文件。
总结
Makefile的一个规则是由目标targets、先决条件prerequisites以及命令commands所组成的。需要指出的是目标和先决条件之间表达的就是依赖关系dependency这种依赖关系指明在构建目标之前必须保证先决条件先满足或构建。而先决条件可以是其它的目标当先决条件是目标时其必须先被构建出来。还有就是一个规则中目标可以有多个当存在多个目标且这一规则是Makefile 中的第一个规则时如果运行make 命令不带任何目标那么规则中的第一个目标将被视为是缺省目标。掌握如果在头脑中勾画出我们想让 make 做的事的“依赖树”是编写 Makefile 最为重要和关键的一步。编译时出现“undefined reference to … ”时有两个原因第一种是源码没有被编译进去第二种是对应库没有被引用。