makefile学习笔记1

目录

概要

Makefile的基本语法

1.首次编写makefile

规则:

规则的功能:

makefile的实例:

​编辑

2.关键知识点

1.假目标:

2.变量:

1.自动变量(☆☆☆☆☆):

2.特殊变量:

2.1.1 MAKE变量:

2.2.2MAKECMDGOALS变量:

3.扩展变量(expanded variable)

3.1递归扩展变量"="(recursively expanded variable)

3.2简单拓展变量":="(simply expanded variables)

3.3条件赋值符"?="

3.4累加操作符"+="

4.override指令

5.模式:

6.函数:

6.1 addprefix函数

6.2 filter函数

6.3 filter-out函数

6.4 patsubst函数(☆☆☆)

6.5 strip函数

6.6 wildcard函数(☆☆☆)

3.makefile拔高

3.1创建目录:

3.2创建并删除目录:

3.3增加头文件

3.4将文件放进目录

小结


概要

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》后续笔者会继续补充,该篇属于刚接触基础语法。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值