思考:什么是make,为什么需要make?
make是一个应用程序:
解析源程序之间的依赖关系,根据依赖关系自动维护编译工作,执行宿主操作系统中的各种命令。
makefile是一个描述文件:
定义了一系列的规则来指定源文件编译的先后顺序。
拥有特定的语法规则,支持函数定义和函数调用。
能够直接集成操作系统中的各种命令。
make和makefile之间的关系:
makefile中的描述用于指导make程序如何完成工作,make根据makefile中的规则执行命令,最后完成编译输出。
最简单的makefile示例:
hello:(目标)
echo "hello makefile"(实现目标所需执行的命令)
注意:目标后的命令需要用Tab(‘\t’)隔开!
make 程序的使用示例 make -f mf.txt hello //-f指定文件,表示后面的文件找到执行的动作mf.txt表示指导工作的文件
功能说明:以hello关键字作为目标查找mf.txt文件,并执行hello处的命令。
make程序的简写实例: make hello
功能说明:以hello关键字作为目标查找makefile或、Makefile文件,并执行hello处的命令。
make
功能说明:查找makefile或Makefile文件中最顶层目标,并执行最顶层目标的命令。
小结:
make只是一个特殊功能的应用程序。make用于根据指定的目标执行相应的命令。makefile用于定义目标和实现目标所需的命令。makefile有特定的语法规则,支持函数定义和调用。
2、初识makefile的结构
makefile的意义:makefile用于定义源文件间的依赖关系,makefile说明如何编译各个源文件并生成可执行文件。
依赖的定义:
target: prerequisites; command1 目标:依赖条件; 命令
'\t' command2 //命令写到下一行不要; 但要加tab键
依赖条件可以省略,只要执行后边的目标,目标就可以完成。
makefile中的元素含义:
targets:通常是需要生成的目标文件名。make所需执行的命令名称。
prerequisite:当前目标所以依赖的其他目标或文件。
command:完成目标所需要执行的命令。gcc
规则中的注意事项:targets可以包含多个目标:使用空格对多个目标名进行分隔。
prerequisites可以包含多个依赖:使用空格对多个依赖进行分隔。
规则中的注意事项:
[Tab]键: ‘\t’
每一个命令行必须以[Tab]字符开始。
[Tab]字符告诉make此行是一个命令行。
续行符:\
可以将内容分开写到下一行,提高可读性。
一个makefile的依赖示例:
all:test
echo "make all" //test成立,执行此行
test:
echo "make test" //test子目标没有依赖,只要执行此行命令,目标完成
all依赖于test,test是all的依赖条件,all完成,需要两个条件,一是依赖条件,二是命令
依赖规则:
当目标对应的文件不存在,执行对应命令。
当依赖在时间上比目标更新,执行对应命令。
当依赖关系连续发生时,对比依赖链上的每一个目标。确保都存在
小技巧:makefile中可以在命令前加上@符,作用为命令无回显。只打印结果(无回显字符)
all:test
@echo "make all"
test:
@echo "make test" //执行,test完成
打印:make test make all
第一个make的编译案例:
hello.out : main.o func.o
gcc -o hello.out main.o func.o
main.o : main.c
gcc -o main.o -c main.c
func.o : func.c //func.c时间比func.o更晚的话,重新生成func.o
gcc -o func.o -c func.c
小技巧:工程中可以将最终可执行文件名和all同时作为makefile中第一条规则的目标。
hello.out all: main.o func.o //
gcc -o hello.out main.o func.o
hello.out all: func.o main.o //如果没有hello.out,依赖没有更新,但是每次都会执行下边的命令gcc -o hello.out main.o func.o,不用执行的,hello最新,不执行下边命令,如果强制编译的话,可以 make all,必然编译。只打make的话,make到makefile中找第一条依赖规则,然后找第一条依赖规则中的第一个目标,名字是hello.out,看看是不是最新的,如果是,没必要编译。
另一个应用:
小结:makefile用于定义源文件间的依赖关系。makefile说明如何编译各个源文件并生成可执行文件。makefile中的目标之间存在连续依赖关系。依赖存在并且命令执行成功是目标完成的充要条件。
3、伪目标的引入
mekefile中的目标究竟是什么?
默认情况下:make认为目标对应着一个文件。make比较目标文件和依赖文件的新旧关系,决定是否执行命令。make以文件处理作为第一优先级。
下面的代码有什么意义?
clean:
rm *.o hello.out //删除 .o和.out文件
make clean
hello.out all: func.o main.o
gcc -o hello.out func.o main.o
func.o: func.c
gcc -o func.o -c func.c
main.o:main.c
gcc -o main.o -c main.c
clean :
rm *.o hello.out
问题:如果文件夹中有clean文件,clean是最新的,不执行下边的命令。
解决方法:makefile中的伪目标:
通过.PHONY关键字声明一个伪目标,伪目标不对应任何实际的文件。不管伪目标的依赖是否更新,命令总是执行。
伪目标的语法:先声明,后使用。
本质:伪目标是make中特殊目标 .PHONY的依赖。
.PHONY:clean //clean是依赖条件
##注释##
clean:
rm *.o hello.out
.PHONY:clean //验证
clean :
rm *.o hello.out
伪目标的妙用:规则调用(函数调用)
.PHONY: clean rebuild all //声明三个伪目标
## other rules ##
rebuild:clean all
clean:
rm *.o hello.out
原理:当一个目标的依赖包含伪目标时,伪目标所定义的命令总是会被执行。
hello.out : func.o main.o
gcc -o hello.out func.o main.o
func.o: func.c
gcc -o func.o -c func.c
main.o:main.c
gcc -o main.o -c main.c
.PHONY:rebuild clean all
rebuild:clean all
all: hello.out
clean :
rm *.o hello.out
技巧二:绕开.PHONY关键字定义伪目标
原理:如果一个规则没有命令或者依赖,并且它的目标不是一个存在的文件名,在执行此规则时,目标总会被认为是最新的。
clean:FORCE
rm *.o hello.out
FORCE: //FORCE总认为最新的,clean就不是最新的,则clean之下的命令会执行
hello.out : func.o main.o
gcc -o hello.out func.o main.o
func.o: func.c
gcc -o func.o -c func.c
main.o:main.c
gcc -o main.o -c main.c
clean :FORCE
rm *.o hello.out
FORCE:
小结:默认情况下,make认为目标对应着一个文件。.PHONY用于声明一个伪目标,伪目标不对应实际的文件。伪目标的本质是make中特殊目标.PHONY的依赖。使用伪目标可以模拟“函数调用”。
4、变量和不同的赋值方式
makefile中支持程序设计语言中变量的概念。
makefile中变量只代表文本数据(字符串)。不需要什么类型
makefile中的变量名规则:
变量名可以包含字符,数字,下划线。不能包含“:”,“#”,“=”,或‘’
变量名大写敏感。
变量的定义和使用:
CC :=gcc
TARGET :=hello.out -->变量定义
变量使用
$(TARGET): func.o main.o //hello.out : func.o main.o
$(CC) -o $(TARGET) func.o main.o // gcc -o hello.out func.o main.o
CC:=g++
TARGET:=hello-world.out
$(TARGET) : func.o main.o
$(CC) -o $(TARGET) func.o main.o
func.o: func.c
$(CC) -o func.o -c func.c
main.o:main.c
$(CC) -o main.o -c main.c
.PHONY:rebuild clean all
rebuild:clean all
all: $(TARGET)
clean :
rm *.o $(TARGET)
makefile中变量的赋值方式:
简单赋值(:=)
递归赋值(=)
条件赋值(?=) 第一次会赋值,之后不会
追加赋值(+=) 当前变量追加一个变量,字符串拼接
不同的赋值方式意义不同。
简单赋值:
程序设计语言中的通用的赋值方式,只针对当前语句的变量有效。
x :=foo
y :=$(x)b
x :=new==>x=>new y=>foob
.PHONY:test
test:
@echo "x=>$(x)"
@echo "y=>$(y)"
递归赋值(=)
赋值操作可能影响多个其他变量。所有与目标变量相关的其它变量都将受到影响。
x :=foo
y :=$(x)b
x =new ==>x=>new y=>newb //与目标变量相关的其他变量都要受到影响。
.PHONY:test
test:
@echo "x=>$(x)"
@echo "y=>$(y)"
条件赋值(?=)
如果变量未定义,使用赋值符号中的值定义变量。如果变量已经定义,赋值无效。
x :=foo
y :=$(x)b
x ?=new ==>x=>foo y=>foob
.PHONY:test
test:
@echo "x=>$(x)"
@echo "y=>$(y)"
第一次定义变量的时候,可以用条件赋值。
追加赋值(+=)
原变量值之后加上一个新值。原变量值与新值之间由空格隔开。
x :=foo
y :=$(x)b
x +=$(y) ==>x=>foo foob y=>foob
.PHONY:test
test:
@echo "x=>$(x)"
@echo "y=>$(y)"
#ex1#x:= foo
#y:=$(x)b
#x:=new
#ex2
#x=foo
#y=$(x)b
#x=new
#a=$(b)
#b=$(c)
#c=hello-makefile
#ex3
#x:= foo
#y:=$(x)b
#x?=new
#ex4
x:= foo
y:=$(x)b
x+=new
.PHONY:test
test:
@echo "x=>$(x)"
@echo "y=>$(y)"
@echo "a=>$(a)"
@echo "a=>$(b)"
@echo "a=>$(c)"
小结:
makefile中支持变量的定义和使用,makefile中存在四种变量的赋值方式。
简单赋值(:=)。递归赋值(=)。条件赋值(?=)。追加赋值(+=)。
5、预定义变量的使用
在makefile中存在一些预定义的变量
自动变量:$@ ,$^ ,$<
特殊变量:$(MAKE),$(MAKECMDGOALS),$(Makefile_LIST)
$(MAKE_VERSION),$(CURDIR),$(.VARIABLES)....
自动变量的意义:$@: 当前规则中触发命令被执行的目标。当前目标
$^ 当前规则中的所有依赖。
$< 当前规则中的第一个依赖。
自动变量的使用示例:
all:first second third
@echo "\$$@=>$@" //第一个$是转义,打印@值
@echo "$$^=>$^"
@echo "$$< =>$<"
make all
注意:1. “$”对于makefile有特殊含义:输出时需要加上一个“$”进行转义。
2.“$@”对于Vash Shell有特殊含义:输出时需要加上“\”进行转义。
.PHONY:all first second third
all:first second third
@echo "\$$@=>$@"
@echo "$$^=>$^"
@echo "$$< =>$<"
first:
second:
third:
打印:
delphi@delphi-vm:~$ make
$@=>all
$^=>first second third
$< =>first
delphi@delphi-vm:~$ ^C
delphi@delphi-vm:~$
改写:
CC:=g++
TARGET:=hello-world.out$(TARGET) : func.o main.o
$(CC) -o $@ $^ #$(TARGET) func.o main.o
func.o: func.c
$(CC) -o $@ -c $^ #func.o func.c
main.o:main.c
$(CC) -o $@ -c $^
.PHONY:rebuild clean all
rebuild:clean all
all: $(TARGET)
clean :
rm *.o $(TARGET)
一些特殊变量的含义:
$(MAKE):当前make解释器的文件名
$(MAKECMDGOALS):命令行中指定的目标名(make的命令行参数)
$(MAKEFILE_LIST):
make所需要处理的makefile文件列表。当前makefile的文件名总是位于列表的最后。文件名之间以空格进行分隔。
.PHONY: all out first second third
all out:
@echo "$(MAKE)"
@echo "$(MAKECMDGOALS)"
@echo "$(MAKEFILE_LIST)"
delphi@delphi-vm:~$ make
make
makefile
delphi@delphi-vm:~$ make all
make
all
makefile
delphi@delphi-vm:~$ make all out
make
all out
makefile
make
all out
makefile
delphi@delphi-vm:~$
然后:
first :
@echo "first"
second :
@echo "second"
third :
@echo "third"
test:
$(MAKE) first
$(MAKE) second
$(MAKE) third
delphi@delphi-vm:~$ make test
make first
make[1]: 正在进入目录 `/home/delphi'
first
make[1]:正在离开目录 `/home/delphi'
make second
make[1]: 正在进入目录 `/home/delphi'
second
make[1]:正在离开目录 `/home/delphi'
make third
make[1]: 正在进入目录 `/home/delphi'
third
make[1]:正在离开目录 `/home/delphi'
一些特殊变量的含义:
$(MAKE_VERSION):当前make解释器的版本
$(CURDIR):当前make解释器的工作目录
$(.VARIABLES):所有已经定义的变量名列表(预定义变量和自定义变量)
.PHONY:test1 test2
TDelphi:=Delphi Tang
D.t.sofware:=D.T.
test1:
@echo "$(MAKE_VERSION)"
@echo "$(CURDIR)"
@echo "$(.VARIABLES)"
test2:
@echo "$(RM)"打印出 #rm -f ->RM表示强制删除的意思
小结:makefile提供了预定义变量供开发者使用。预定于变量的使用能够使得makefile的开发更高效。自动变量是makefile中最常见的元素。使用 $(.VARIABLES) 能够获取所有的特殊变量。
使用:先打印所有预定义变量,看看有没有自己需要的,复制出来,去gnu make解释器说明文档查找功能。