网站建设的电话销售好做吗,商务网站价格,在手机上建设网站教程,简历制作专业机构一、基本概念
通常我们编写好代码后#xff0c;都需要编译#xff0c;只是这些操作是由IDE来完成#xff0c;我们只需要点击一个编译按钮。当项目工程越来越庞大#xff0c;存在几十个甚至更多的文件的时候#xff0c;你使用的不是IDE工具#xff0c;而是命令行#xf…一、基本概念
通常我们编写好代码后都需要编译只是这些操作是由IDE来完成我们只需要点击一个编译按钮。当项目工程越来越庞大存在几十个甚至更多的文件的时候你使用的不是IDE工具而是命令行那么不同的人在编译你的项目的时候都需要一个一个文件的 gcc -o asample.c bsample.c ...... xxx.out 这样慢慢的一个文件一个文件的去找到以后再编译吗
答案肯定是否定的当你工程的文件多了以后时间一长可能你自己都不能记住所有的文件。所以这个时候我们就可以使用 make 来根据 Makefile 对整个项目进行管理和构建。
Makefile 文件描述了整个工程的编译、连接等规则。其中包括工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。尽管看起来可能是很复杂的事情但是为工程编写Makefile 的好处是能够使用一行命令来完成“自动化编译”。当我们需要编译工程时只需要在命令行输入 make整个工程完全自动编译极大提高了效率。
make是一个命令工具它能解释Makefile 中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile 中可以使用系统shell所提供的任何命令来完成想要的工作。
二、Makefile原理
想要掌握makefile首先需要了解两个概念⼀个是⽬标target另⼀个就是依赖dependency。⽬标就是指要⼲什么或说运⾏ make 后⽣成什么⽽依赖是告诉 make 如何去做以实现⽬标。在 Makefile 中⽬标和依赖是通过规则rule来表达的。
1. 目标
首先我们重建一个Makefile文件其内容如下
all:echo Hello world
上面Makefile 中的 all 就是我们 的⽬标⽬标放在‘’的前⾯其名字可以是由字⺟和下划线‘_’组成 。echo “Hello World”就是⽣成⽬标的命令这些命令可以是任何你可以在你的环境中运⾏的命令以及 make 所定义的函数等等。all ⽬标的定义其实是定义了如何⽣成 all ⽬标这我们也称之为规则.
可以在Makefile文件中定义多个目标
all:echo Hello world
test:echo My test
运行结果如下 由运行结果可知一个Makefile文件中可以定义多个目标。执行make命令时我们需要指定目标当没有指定具体目标时那么 make 以 Makefile ⽂件中定义的第⼀个⽬标作为这次运⾏的⽬标。这“第⼀个”⽬标也称之为默认⽬标和是不是all没有关系。当 make 得到⽬标后会找到定义⽬标的规则然后运⾏规则中的命令来达到构建⽬标的⽬的。
上述输出结果中每次都输出了“echo ......”的内容如果不想输出该内容可以在命令的前面加上其作用就是 在运行的时候使这一行命令不显示出来。
all:echo Hello world
test:echo My test
运行结果 接着我们再改动一下Makefile文件
all: testecho Hello world
test:echo My test
运行结果 可以发现此时 test 也被构建了。
2. 依赖
如上面的Makefileall ⽬标后⾯的 test 是告诉 makeall ⽬标依赖 test ⽬标这⼀依赖⽬标在 Makefile 中⼜被称之为先决条件。出现这种⽬标依赖关系时make⼯具会按 从左到右的先后顺序先构建规则中所依赖的每⼀个⽬标。如果希望构建 all ⽬标那么make 会在构建它之 前得先构建 test ⽬标。
3. 规则
⼀个规则是由⽬标targets、先决条件prerequisites以及命令commands所组成的。
规则的语法
targets : prerequisitescommand
... 需要指出的是⽬标和先决条件之间表达的就是依赖关系dependency这种依赖关系指明在构建⽬标之前必须保证先决条件先满⾜或构建。⽽先决条件可以是其它的⽬标当先决条件是⽬标时其必须先被构建出来。还有就是⼀个规则中⽬标可以有多个当存在多个⽬标且这⼀规则是 Makefile 中的第⼀个规则时如果我们运⾏ make 命令不带任何⽬标那么规则中的第⼀个⽬标将被视为是缺省⽬标。
规则的功能就是指明 make 什么时候以及如何来为我们重新创建⽬标在 Hello World 例⼦中不论我们 在什么时候运⾏ make 命令带⽬标或是不带⽬标其都会在终端上打印出信息来和我们采⽤ make 进⾏代码编译时的表现好象有些不同。当采⽤ Makefile 来编译程序时如果两次编译之间没有任何代码 的改动理论上说来我们是不希望看到 make 会有什么动作的只需说“⽬标是最新的”⽽我们的最终 ⽬标也是希望构建出⼀个“聪明的” Makefile 的。与 Hello World 相⽐不同的是采⽤ Makefile 来进⾏ 代码编译时Makefile 中所存在的先决条件都是具体的程序⽂件后⾯我们会看到。
三、Makefile由浅入深
新建两个c文件
test.c
#include stdio.hvoid run()
{printf(this is run()!\n);
}
main.c
extern void run();int main()
{run();return 0;
}
Makefile
all: main.o test.ogcc -o my_test main.o test.o
main.o: main.cgcc -o main.o -c main.c
test.o: test.cgcc -o test.o -c test.c
clean:rm my_test main.o test.o
编译的结果如下 第⼆次编译的时候并没有构建⽬标⽂件的动作但为什么有构建 my_test 可执⾏程序的动作呢
为了明⽩为什么我们需要了解 make 是如何决定哪些⽬标这⾥是⽂件是需要重新编译的。为什么 make会知道我们并没有改变 main.c 和 test.c 呢答案很简单通过⽂件的时间戳当 make 在运⾏⼀个规则时我们前⾯已经提到 了⽬标和先决条件之间的依赖关系make 在检查⼀个规则时采⽤的⽅法是如果先决条件中相关的⽂件的时间戳⼤于⽬标的时间戳即先决条件中的⽂件⽐⽬标更新则知道有变化那么需要运⾏规则当中 的命令重新构建⽬标。这条规则会运⽤到所有与我们在 make时指定的⽬标的依赖树中的每⼀个规则。⽐如对于 my_test 项⽬其依赖树中包括三个规则make 会检查所有三个规则当中的⽬标⽂件与先决条件⽂件之间的时间先后关系从⽽来决定是否要重新创建规则中的⽬标。
第二次编译的时候为什么 my_test 会被重新构建
因为我们构建的目标是all而all在我们编译的过成中并不生成所以第二次make的时候找不到所以又重新编译了一遍。
修改Makefile文件
my_test: main.o test.ogcc -o my_test main.o test.o
main.o: main.cgcc -o main.o -c main.c
test.o: test.cgcc -o test.o -c test.c
clean:rm my_test main.o test.o
之后运行 可以发现第二次make的时候不会重新编译了。一个文件是否改变不是看这个文件的大小是否改变而是看这个文件的时间戳是否发生了变化。 可以使用 touch 命令改变文件的时间戳这样就能再次编译了。
假目标
我们在当前目录使用 touch clean 命令新建一个clean文件然后执行 make clean可以发现并没有像之前一样清理文件。 因为这个时候 make 认为 clean 是一个文件并且在当前目录下找到了这个文件再加上 clean 目标没有任何先决条件因此make认为当前的clean是最新的。
如何解决上面这个问题使用假目标假目标最常用的情景就是避免所定义的目标和已经存在的文件重名的情况假⽬标可以采⽤.PHONY 关键字来定义需要注意的是其必须是⼤写字⺟。下面使用假目标修改Makefile
.PHONY: clean
my_test: main.o test.ogcc -o my_test main.o test.o
main.o: main.cgcc -o main.o -c main.c
test.o: test.cgcc -o test.o -c test.c
clean:rm my_test main.o test.o
采⽤.PHONY 关键字声明⼀个⽬标后make 并不会将其当作⼀个⽂件来处理⽽只是当作⼀个概念上的⽬标。对于假⽬标我们可以想像的是由于并不与⽂件关联所以每⼀次 make 这个假⽬标时其所在的规则中的命令都会被执⾏。
变量
先看代码
.PHONY: clean
CC gcc
RM rm
EXE my_test
OBJS main.o test.o
$(EXE): $(OBJS)$(CC) -o my_test main.o test.o
main.o: main.c$(CC) -o main.o -c main.c
test.o: test.c$(CC) -o test.o -c test.c
clean:$(RM) $(EXE) $(OBJS)
运行结果 变量的使用可以提高makefile的可维护性。⼀个变量的定义很简单就是⼀个名字变量名后⾯跟上⼀个等号然后在等号的后⾯放这个变量所期望的值。对于变量的引⽤则需要采⽤$(变量名)或者${变量名}这种模式。在这个 Makefile 中我们引⼊了 CC 和 RM 两个变量⼀个⽤于保存编译器名⽽另⼀个⽤于指示删除⽂件的命令是什么。还有就是引⼊了 EXE 和 OBJS 两个变量⼀个⽤于存放可执⾏⽂件名可另⼀个则⽤于放置所有的⽬标⽂件名。采⽤变量的话当我们需要更改编译器时只需更改变量赋值的地⽅⾮常⽅便。
自动变量
对于每⼀个规则⽬标和先决条件的名字会在规则的命令中多次出现每⼀次出现都是⼀种麻烦更为麻烦的是如果改变了⽬标或是依赖的名那得在命令中全部跟着改。有没有简化这种更改的⽅法呢这我们需要⽤到 Makefile 中的⾃动变量最常用包括
$⽤于表示⼀个规则中的⽬标。当我们的⼀个规则中有多个⽬标时$所指的是其中任何造成命令被运⾏的⽬标。$^则表示的是规则中的所有先择条件。$表示的是规则中的第⼀个先决条件。
.PHONY:all
all:first second thirdecho \$$ $echo \$$^ $^echo \$$ $first second third:运行结果 需要注意的是在 Makefile 中‘$’具有特殊的意思因此如果想采⽤ echo 输出‘$’则必需⽤两个连着的‘$’。还有就是$对于 Shell 也有特殊的意思我们需要在“$$”之前再加⼀个脱字符‘\’。
刚刚的 Makefile 文件可以修改如下
.PHONY: clean
CC gcc
RM rm
EXE my_test
OBJS main.o test.o
$(EXE): $(OBJS)$(CC) -o $ $^
main.o: main.c$(CC) -o $ -c $^
foo.o: foo.c$(CC) -o $ -c $^
clean:$(RM) $(EXE) $(OBJS) 特殊变量
1.MAKE变量
它表示的是make 命令名是什么。当我们需要在 Makefile 中调⽤另⼀个 Makefile 时需要⽤到这个变量采⽤这种⽅式有利于写⼀个容易移植的 Makefile。
.PHONY: clean
all:echo MAKE $(MAKE)2.MAKECMDGOALS变量
.PHONY: all clean
all clean:echo \$$ $echo MAKECMDGOALS $(MAKECMDGOALS)从测试结果看来MAKECMDGOALS 指的是⽤户输⼊的⽬标当我们只运⾏ make 命令时虽然根据 Makefile 的语法第⼀个⽬标将成为缺省⽬标即 all ⽬标但 MAKECMDGOALS 仍然是空⽽不是 all这⼀点我们需要注意。
递归扩展变量
之前我们示例了使⽤等号进⾏变量定义和赋值对于这种只⽤⼀个“”符号定义的变量我们称之为递归扩展变量recursively expanded variable。
除了递归扩展变量还有⼀种变量称之为简单扩展变量simply expanded variables是⽤“:”操作符来定义的。对于这种变量make 只对其进⾏⼀次扫描和替换。
.PHONY: all
x foo
y $(x) b
x later
xx : foo
yy : $(xx) b
xx : later
all:echo x $(y), xx $(yy)另外还有一种条件赋值符“”条件赋值的意思是当变量以前没有定义时就定义它并且将左边的值赋值给它如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能。
.PHONY: all
foo x
foo ? y
bar ? y
all:echo foo $(foo), bar $(bar)此外还有操作符对变量进⾏赋值的⽅法
.PHONY: all
objects main.o foo.o bar.o utils.o
objects another.o
all:echo $(objects)
override指令
在设计 Makefile 时我们并不希望⽤户将我们在 Makefile 中定义的某个变量覆盖掉那就得⽤ override 指令了。
.PHONY: all
override foo x
all:echo foo $(foo)模式
如果对于每⼀个⽬标⽂件都得写⼀个不同的规则来描述那会是⼀种“体⼒活”太繁了对于⼀个⼤型项⽬就更不⽤说了。Makefile 中的模式就是⽤来解决我们的这种烦恼的。
.PHONY: clean
CC gcc
RM rm
EXE my_test
OBJS main.o test.o
$(EXE): $(OBJS)$(CC) -o $ $^
%.o: %.c$(CC) -o $ -c $^
clean:$(RM) $(EXE) $(OBJS)与 my_test 前⼀版本的 Makefile 相⽐最为直观的改变就是从⼆条构建⽬标⽂件的规则变成了⼀条。模式类似于我们在 Windows 操作系统中所使⽤的通配符当然是⽤“%”⽽不是“*”。采⽤了模式以后不论有多少个源⽂件要编译我们都是应⽤同⼀个模式规则的很显然这⼤⼤的简化了我们的⼯作。使⽤了模式规则以后你同样可以⽤这个 Makefile 来编译或是清除 my_test 项⽬这与前⼀版本在功能上是完全⼀样的。
本篇只简单介绍Makefile便于进一步学习CMake。目前接触到的项目中多数采用CMake生成 Makefile的方式来进行编译。除上述所介绍的内容外 Makefile 还有函数等其他特性感兴趣的小伙伴可以自行研究一下。