1,关于GUN的编译过程
编译过程是分为四个阶段进行的,即预处理(Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)
CC -E hello.c -o hello.i
这是预编译过程,hello.i存放着hello.c预编译过后的代码
CC -S hello.i -o hello.s
这是编译过程,hello.s存放着编译生成的汇编文件
CC -C hello.s -o hello.o
这是汇编过程,将汇编文件hello.s 汇编成目标文件hello.o
CC hello.o -o jin
这是连接过程,将目标文件和C的标准输入输出库进行连接,最终可以生成可执行文件jin
图中的 “.PHONY” 表示隐晦规则,Makefile有自动推导的功能。
这要叫伪目标。伪目标有一个特性,就是会不管依赖规则,必须会执行。
如果你不想在命令执行的过程中给出警告信息。你可以在指令前加一个“-”。这样的话,编译器会自动忽略一些警告
2,静态模式
静态模式可以轻松的定义多个目标规则。可以大大地提高效率
- 第一行:设定你编译目标,可以是多个,中间使用空格来隔离
- 第二行:定义一个标签 all
- 第三行:指明了我们的目标从$objects中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“hello.o word.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“hello word”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“hello.c word.c”。而命令中的“<”和“@”则是自动化变量,“<”表示所有的依赖目标集(也就是“hello.cword.c”),“@”表示目标集(也就是“hello.o word.o”)。于是,上面的规则展开后等价于下面的规则:
hello.o : word.c
$(CC) -c $(CFLAGS) hello.c -o hello.o
bar.o : bar.c
$(CC) -c $(CFLAGS) word.c -o word.o
3,显示命令
在Makefile执行的过程中,会将执行的命令打印出来
如果在命令的前面加入“@”则这条命令将不会显示出来
再执行一遍
4,嵌套执行Makefile
在一些大型的项目中,我们会将不同的源文件放置在不同的文件夹下,如果我将所有的编译规则都书写在一个Makefile下,那么可维护性就会很差。比较好的做法就是在不同的下,写一个Makefile。使用嵌套的方式来书写Makefile
如果说在Modules这个目录下有一个makefile文件,来指明这个目录下的编译规则。
modules:
cd modules && $(MAKE)
等价于
modules:
$(MAKE) -C modules
他们的作用都为:先进入 modules目录,之后在此目录下执行 Make指令
嵌套执行Makefile 就涉及了变量的传输,这个函数传递参数的思想一致。
使用export 这个关键字来实现这个功能
export variable = value
如果你想传递所有的函数值,那么只使用 export 就可以了,后面无需跟其他的参数
需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。
但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:
modules:
cd modules && $(MAKE) MAKEFLAGS=
6,自定义命令包
如图编写一个规则
语法规则为:
define 命令包名
执行的指令。。。。
endef
使用时:
jin:
$(定义的命令包名)
7,变量的基础
在一些情况下,我们需要用到变量。有一些变量是很奇怪字串,如“<”、“@”等,这些是自动化变量。
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
变量和C /C++里的宏一样,可以用来替换文本
变量的另一种操作符:
X := foo
局部变量,局部变量有其作用范围,不会影响其他的变量
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”
使用条件判断
其关键字为: ifeq , else endif
实例如下
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
可见,在上面示例的这个规则中,目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。
使用函数
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$(<function> <arguments> )
或是
${<function> <arguments>}
这里,就是函数名,make支持的函数不多。是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,{x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以(bar)的值是“a,b,c”。
(2015/5/4更新)
call 函数和 eval 函数
eval函数 : 在自己的参数集里依次取出命令并执行它
$(eval jin jia bin)
执行的结果为:
make jin
make jia
make bin
call 函数:表示将参数依次填入对应的已定义好了的指令包内
#define haha
$(1)_install
#endef
$(call haha,jin)
执行这条语句后
结果为: jin_install
2015/5/10 更新
关于命令使用前面需要加 Tab 的问题
在makefile语法中,要执行一条指令是需要在前面加Tab 否则那么就回将这条指令视为空指令,并不执行。但是,当使用makefile的函数时,有且仅有这个函数没有返回值时,那么这条语句就可以执行了。如果这个函数有返回值,只需要申明一个变量来接收它的返回值就可以了。
但是可以执行。
在使用 include 包含其他的makefile函数时就不能直接使用 make 这条指令了
用为如果使用了这条指令,由于include是直接将所包含的makefile文件直接复制在原来的位置的,如果include里的makefile里有目标,那么使用make 运行的会是include里的makefile里的第一个目录
解决的方法可以是一下的的方法:
# 如果没有指定运行的目标,那么就会执行这个
.DEFAULT_GOAL := help
.PHONY: help
help:
@echo this is help
关于使用变量的情况
在Makefile中变量可以在任何地方定义。Makefile和C语言一样有一个预编译的过程。这个过程可以将变量压入栈。函数调用,或者是其他使用的情况就可以加载了。
像图中的
toprel = $(subst $(realpath $(ROOT_DIR))/,,$(abspath $(1)))
未完待续。。