目录
3.1递归扩展变量"="(recursively expanded variable)
3.2简单拓展变量":="(simply expanded variables)
概要
makefile语言的本质:
make是一个命令工具,是一个解释Makefile中指令的命令工具。大多数的IDE都有这个命令。
优势:使用makefile语言的目的本质是实现自动化编译,极大的提高了软件的开发效率
Makefile的基本语法
模板:
//目标:依赖
//命令
target...:prerequisites...
command
//(西红柿炒蛋)解释:
target:西红柿炒蛋(最终要生成的东西)
prerequisites:西红柿,鸡蛋,油,盐...(需要的原材料)
command:炒(动作)
注意:command命令前面的空位是一个tab键,而不是空格。
1.首次编写makefile
//其中echo代表输出,@表示去除多余命令行
all:
@echo "Hello world"
test:
@echo "test game"
⼀个 Makefile 中可以定义多个⽬标。调⽤ make 命令时,我们得告诉它我们的⽬标是什么,即要它⼲什么。当没有指明具体的⽬标是什么 时,那么 make 以 Makefile ⽂件中定义的第⼀个⽬标作为这次运⾏的⽬标。这“第⼀个”⽬标也称之 为默认⽬标(和是不是all没有关系)。当 make 得到⽬标后,先找到定义⽬标的规则,然后运⾏规则中的命令来达到构建⽬标的⽬的。
执行结果:
做如下改动
//在all的后面加上test
all: test
@echo "Hello world"
test:
@echo "test game"
解析:其中all: test就是告诉make,all 目标依赖test目标,这一依赖目标称为 先决条件 出现这种⽬标依赖关系时,make⼯具会按 从左到右的先后顺序先构建规则中所依赖的每⼀个⽬标,如果希望构建 all ⽬标,那么make 会在构建它之 前得先构建 test ⽬标,这就是为什么我们称之为先决条件的原因。
运行结果:
规则:
⼀个规则是由⽬标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。
1.构造目标之前必须保证先决条件先满足(或构建),两者属于依赖关系。
2.一个规则可以有多个目标,每个目标的先决条件可以是其他目标。
3.当先决条件是目标时候,其必须被先构造出来。
4.当存在多个⽬标,且这⼀规则是 Makefile 中的第⼀个规则时,如果我们运⾏ make 命令不带任何⽬标,那么规则中的第⼀个⽬标将被视为是缺省⽬标。
规则的功能:
功能:指明 make 什么时候以及如何来为我们重新创建⽬标
//all 是⽬标,test 是 all ⽬标的依赖⽬标,⽽@echo “Hello World”则是⽤于⽣成 all ⽬标的命令。
targets : prerequisites
command
...
makefile的实例:
需求:foo.c,main.c,makefile文件
1.foo.c
#include <stdio.h>
void foo()
{
printf("this is foo() !\n");
}
2.main.c
extern void foo();
int main()
{
foo();
return 0;
}
3.makefile
all: main.o foo.o
gcc -o simple main.o foo.o
main.o: main.c
gcc -o main.o -c main.c
foo.o: foo.c
gcc -o foo.o -c foo.c
clean:
rm simple main.o foo.o
形成的依赖树:
执行:
执行思路:
1.第一次编译:make执行首先执行先决条件里面的内容main.o和foo.o中的内容再执行目标里面的内容。
2.第二次编译:make通过文件的时间戳判断我们没有改变main.o和foo.o中的内容因此没有再次编译。
原理:当 make 在运⾏⼀个规则时,make 在检查⼀个规则时,采⽤的⽅法是:如果先决条件中相关的⽂件的时间戳⼤于⽬标的时间戳,则确定变化需要运⾏规则当中 的命令重新构建⽬标。
这条规则会运⽤到所有与我们在 make时指定的⽬标的依赖树中的每⼀个规则。
2.关键知识点
1.假目标:
存在的意义:避免所定义的目标和的已经存在文件是从重名的情况,假⽬标可以采⽤.PHONY 关键字来定义,需要注意的是其必须是⼤写字⺟。
例子:
.PHONY: clean
simple: main.o foo.o
gcc -o simple main.o foo.o
main.o: main.c
gcc -o main.o -c main.c
foo.o: foo.c
gcc -o foo.o -c foo.c
clean:
rm simple main.o foo.o
运行结果:
2.变量:
意义:变量的使用可以提高makefile的可维护性。⼀个变量的定义很简单,就是⼀个名字(变量名)后⾯跟上⼀个等号,然后在等号的后⾯放这个变量所期望的值。对于变量的引⽤,则需要采⽤$(变量名)或者${变量名}这种模式。
代码变量化:
.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
$(EXE): $(OBJS)
$(CC) -o $(EXE) $(OBJS)
main.o: main.c
$(CC) -o main.o -c main.c
foo.o: foo.c
$(CC) -o foo.o -c foo.c
clean:
$(RM) $(EXE) $(OBJS)
运行结果:
1.自动变量(☆☆☆☆☆):
存在意义:每⼀个规则,⽬标和先决条件的名字会在规则的命令中多次出现,每⼀次出现都是⼀种麻烦,更为麻烦的是,如果改变了⽬标或是依赖的名,那得在命令中全部跟着改。有没有简化这种更改的⽅法我们需要⽤到 Makefile 中的⾃动变量
- $@⽤于表示⼀个规则中的⽬标。当我们的⼀个规则中有多个⽬标时,$@所指的是其中任何造成命令被运⾏的⽬标。
- $^则表示的是规则中的所有先择条件。
- $<表示的是规则中的第⼀个先决条件。
案例:
.PHONY:all
all:first second third
@echo "\$$@ = $@"
@echo "\$$^ = $^"
@echo "\$$< = $<"
first second third:
运行结果:
注意:在 Makefile 中‘$’具有特殊的意思,因此,如果想采⽤ echo 输出‘$’,则必需⽤两个连着的‘$’。还有就是,$@对于 Shell 也有特殊的意思,我们需要在“$$@”之前再加⼀个脱字符‘\’。
修改simple的makefile为自动变量形式
.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.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.1.1 MAKE变量:
意义:表示的是make 命令名是什么。当我们需要在 Makefile 中调⽤另⼀个 Makefile 时需要⽤到这个变量,采⽤这种⽅式,有利于写⼀个容易移植的 Makefile。
.PHONY: clean
all:
@echo "MAKE = $(MAKE)"
运行结果:
2.2.2MAKECMDGOALS变量:
意义:它表示的是当前⽤户所输⼊的 make ⽬标是什么。
案例:
.PHONY: all clean
all clean:
@echo "\$$@ = $@"
@echo "MAKECMDGOALS = $(MAKECMDGOALS)"
运行结果:
注意:MAKECMDGOALS 指的是⽤户输⼊的⽬标,当我们只运⾏ make 命令时,虽然根据
Makefile 的语法,第⼀个⽬标将成为缺省⽬标,即 all ⽬标,但 MAKECMDGOALS 仍然是空,⽽不是all,这⼀点我们需要注意。
3.扩展变量(expanded variable)
3.1递归扩展变量"="(recursively expanded variable)
定义:使⽤等号进⾏变量定义和赋值,对于这种只⽤⼀个“=”符号定义的变量,我们称之为递归扩展变量。
.PHONY: all
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
@echo $(foo)
结果:
3.2简单拓展变量":="(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)"
运行结果说明:"="可进行多次赋值,x被最终赋值为later。":="只能单次赋值赋值为foo后二次赋值无效。
3.3条件赋值符"?="
本质:条件赋值的意思是当变量以前没有定义时,就定义它并且将右边边的值赋值给它,如果已经定义了那么就不再改变其值。条件赋值类似于提供了给变量赋缺省值的功能。
.PHONY: all
foo = x
foo ?= y
bar ?= y
all:
@echo "foo = $(foo), bar = $(bar)"
结果:
3.4累加操作符"+="
本质:类似于累加操作符
.PHONY: all
objects = main.o foo.o bar.o utils.o
objects += another.o
all:
@echo $(objects)
4.override指令
意义:我们并不希望⽤户将我们在 Makefile 中定义的某个变量覆盖掉,那就得⽤ override 指令了。
.PHONY: all
override foo = x
all:
@echo "foo = $(foo)"
结果:foo 改变无效
5.模式:
意义:每⼀个⽬标⽂件都得写⼀个不同的规则来描述,那会是⼀种“体⼒活”,太繁了!对于⼀个⼤型项⽬更是如此,Makefile 中的模式就是⽤来解决我们的这种烦恼的。
对比上面版本1的样式模式版更为简单:
//版本1:
.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.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:
.PHONY: clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
$(EXE): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)
注意:模式的通配符 “%”等价于“*”。采⽤了模式以后,不论有多少个源⽂件要编译,我们都是应⽤同⼀个模式规则的,很显然,这⼤⼤的简化了我们的⼯作。
6.函数:
背景:我们在这个Makefile中指明每⼀个需要被编译的源程序。对于⼀个源程序⽂件⽐较多的项⽬,如果每增加或是删除⼀个⽂件都得更新 Makefile,其⼯作量也不可⼩视。
采⽤ wildcard 和 patsubst 两个函数后 simple 项⽬的 Makefile。
.PHONY: clean
CC = gcc
RM = rm
EXE = simple
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
$(EXE): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(EXE) $(OBJS)
说明:我们来模拟增加⼀个源⽂件的情形,看⼀看如果我们增加⼀个⽂件,在 Makefile 不做任
何更改的情况下其是否仍能正常的⼯作。增加⽂件的⽅式仍然是采⽤ touch 命令,通过 touch 命令
⽣成⼀个内容是空的 bar.c 源⽂件,然后再运⾏ make 和 make clean。
6.1 addprefix函数
功能:给字符串中的每个⼦串前加上⼀个前缀。
//其形式是:$(addprefix prefix, names...)
.PHONY:all
without_dir= foo.c bar.c main.c
with_dir:=$(addprefix objs/, $(without_dir))
all:
@echo $(with_dir)
输出结果:每个子串上都添加前缀
6.2 filter函数
功能:函数⽤于从⼀个字符串中,根据模式得到满⾜模式的字符串。
//其形式是:$(filter pattern..., text)
.PHONY: all
sources = foo.c bar.c baz.s ugh.h
sources := $(filter %.c %.s, $(sources))
all:
@echo $(sources)
结果:
6.3 filter-out函数
功能:filter-out 函数⽤于从⼀个字符串中根据模式滤除⼀部分字符串。
//其形式是: $(filter-out pattern..., text)
.PHONY: all
objects = main1.o foo.o main2.o bar.o
result := $(filter-out main%.o, $(objects))
all:
@echo $(result)
6.4 patsubst函数(☆☆☆)
功能:patsubst 函数是⽤来进⾏字符串替换的。
//其形式是:$(patsubst pattern, replacement, text)
.PHONY:all
mixed=foo.c bar.c main.o
objects:=$(patsubst %.c, %.o, $(mixed))
all:
@echo $(objects)
运行结果:
6.5 strip函数
功能:⽤于去除变量中的多余的空格。
//其形式是:$(strip string)
.PHONY:all
original=foo.c bar.c
stripped:=$(strip $(original))
all:
@echo "original = $(original)"
@echo "stripped = $(stripped)"
运行结果:
6.6 wildcard函数(☆☆☆)
功能:wildcard 是通配符函数,通过它可以得到我们所需的⽂件,这个函数类似我们在 Windows 或Linux 命令⾏中的“*”。
//其形式是:$(wildcard pattern)
.PHONY:all
SRC=$(wildcard *.c)
all:
@echo "SRC = $(SRC)"
运行结果:
3.makefile拔高
3.1创建目录:
目的:编译项⽬之前希望⽤于存放⽂件的⽬录先准备好,当然,我们可以在编译之前通过⼿动
来创建所需的⽬录,但这⾥我们希望采⽤⾃动的⽅式。
1.首先写一个makefile用于创建相关的目录。
//创建目录的makefile文件
.PHONY:all
MKDIR=mkdir
DIRS=objs exes
all:$(DIRS)
$(DIRS):
$(MKDIR) $@
代码依赖树关系:
运行思路:
1.假命名。
2.设置两个变量并进行赋值 MKDIR=mkdir 和 DIRS=objs exes。
3.对于all:$(DIRS) 中先执行先决条件即$(DIRS) == objs exes原式的真实含义为all:objs exes。
4.执行下一步 命令环节 进行创建目录,最终达成目标。
结果:
3.2创建并删除目录:
//原有基础上创建一个clean目标
.PHONY:all
MKDIR=mkdir
DIRS=objs exes
RM=rm
RMFLAGS=-fr
all:$(DIRS)
$(DIRS):
$(MKDIR) $@
clean:
$(RM) $(RMFLAGS) $(DIRS)
结果:
3.3增加头文件
.PHONY:all clean
MKDIR=mkdir
RM=rm
RMFLAGS=-fr
CC=gcc
EXE=test
DIRS=objs exes
SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
all:$(DIRS) $(EXE)
//创建文件夹objs 和 exes
$(DIRS):
$(MKDIR) $@
//首先先执行 先决条件 $(OBJS)即(调用SRCS函数获取所有.c结尾的文件,将其转为.o),再执行下面的%.o:%.c命令
$(EXE):$(OBJS)
$(CC) -o $@ $^
//本质是foo.o:foo.c和main.o:main.c
%.o:%.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)
结果:
3.4将文件放进目录
目的:将⽬标⽂件或是可执⾏程序分别放⼊所创建的 objs 和 exes ⽬录中,我们需要⽤到 Makefile中的⼀个函数 —— addprefix。对上面的makefile进行修改。
.PHONY:all clean
MKDIR=mkdir
RM=rm
RMFLAGS=-fr
CC=gcc
DIR_OBJS=objs
DIR_EXE=exes
DIRS=$(DIR_OBJS) $(DIR_EXE)
EXE=test
EXE:=$(addprefix $(DIR_EXE)/, $(EXE))
SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
OBJS:=$(addprefix $(DIR_OBJS)/, $(OBJS))
all:$(DIRS) $(EXE)
$(DIRS):
$(MKDIR) $@
$(EXE):$(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o:%.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS)
运行结果:
解析:最⼤的变化除了增加了对于 addprefix 函数的运⽤为每⼀个⽬标⽂件加上“objs/”前缀外,还有⼀个很⼤的变化是,我们需要在构建⽬标⽂件的模式规则中的⽬标前也加上“objs/”前缀,即增加“$(DIR_OBJS)/”前缀。之所以要加上,是因为规则的命令中的-o 选项需要以它作为⽬标⽂件的最终⽣成位置,还有就是因为 OBJS 也加上了前缀,⽽要使得 Makefile 中的⽬标创建规则被运⽤,也需要采⽤相类似的格式,即前⾯有“objs/”。此外,由于改动后的 Makefile 会将所有的⽬标⽂件放⼊ objs ⽬录当中,⽽我们的 clean 规则中的命令包含将 objs ⽬录删除的操作,所以我们可以去除命令中对 OBJS 中⽂件的删除。这导致的改动就是 Makefile 中的最后⼀⾏中删除了$(OBJS)。同样的方法将 test 放入到 exes 文件夹中。
小结
本篇是笔者初次接触makefile语言所编译脚本时做的第一篇笔记《makefile学习笔记1》后续笔者会继续补充,该篇属于刚接触基础语法。