目录
前言:我们从几个方面学习Makefile(智能化的自动编译工具)
1.Makefile中运行自定义函数并且调用函数(类似与shell中函数) 2.Makefile中调用shell命令
前言:我们从几个方面学习Makefile(智能化的自动编译工具)
1.Makefile是什么?
2.Makefile能做什么?
3.了解Makefile中的一些语法,能够编写/修改简单的Makefile(一般的Makefile都是自动生成的)
1.Makefile是什么
Makefile是make工具的配置文件,指导make如何去编译工程的文件
什么是make呢?
sudo apt install make
make是一个智能化的自动编译工程的管理工具(命令),它可以决定在一个工程中哪些文件需要被编译,哪些文件先编译,哪些文件后编译,以及如何去链接编译之后的文件
make的功能之所以强大,是因为Makefile文件告诉了make如何去工作,以及如何产生目标文件
把make的规则写入到Makefile文件中,make每一次编译都会更根据Makefile文件进行编译
所以make的正常工作,是离不开makefile文件的
2.了解Makefile中的一些语法(文件格式)
一个最简单的Makefile是由一些"目标和规则"组成的
大概形式如下:
TARGET:PREREQUISITES
<Tab键>RECIPE
<Tab键>RECIPE
<Tab键>RECIPE
.....
TARGET2:PREREQUISITES
<Tab键>RECIPE
<Tab键>RECIPE
<Tab键>RECIPE
.....
TARGET:是这一次make的目标名称,你这个Makefile的目的是什么
一个Makefile中可以有很多个目标
默认完成的是第一个目标,其他目标也叫做"伪目标"
PREREQUISITES:依赖,完成前面的目标,需要依赖哪些文件或者库...(也可以为空)
在Make的时候,第一步会检查当前目标依赖的文件是否存在
如果存在:
则按照目标下面的RECIPE命令列表生成对应的目标
如果不存在:
就搜索整个Makefile中,查看是否有以"依赖文件"为目标名的目标
如果没有就报错,如果有,就先生成以"依赖文件"为目标名的目标,以此递归
RECIPE:生成上面的目标需要执行的命令列表(前面必须有一个tab键)
例子:
现实生活中的Makefile,目标是西e
TomatoFireEgg:Tomato Eggs
echo "油,搞里头"
echo "鸡蛋,搞里头"
echo "西红柿,搞里头"
echo "搞里头"
echo "翻炒一下"
Tomato:
echo "生成一个Tomato"
Eggs:KUN
echo "Kun 生了一个egg"
KUN:
echo "唱跳两年半!"
make的时候,默认是完成第一个目标!
make的用法:
make 会自动的取当前目录下面的Makefile中完成"目标"
make + 目标名
如果没有加目标,默认生成Makefile中的第一个目标
make -C 目录
到指定的目录中去查找Makefile文件并且生成目标
make在执行的时候,会自动的把执行过的命令打印到终端上面
make是非常智能的,它能够根据文件的修改时间,去判断哪些文件需要先编译,哪些文件不需要被重新编译
如:
CC:=gcc
Graph:graph.o LKqueue.o main.o
$(CC) graph.o LKqueue.o main.o -o Graph
graph.o:
$(CC) -c graph.c -o graph.o
LKqueue.o:
$(CC) -c LKqueue.c -o LKqueue.o
main.o:
$(CC) -c main.c -o main.o
clean:
rm *.o
rm Graph
我们的工程中,有很多C文件的编译格式是类似的,如果工程中增加一个源文件,就需要去修改Makefile文件,增加编译规则,这其实很麻烦
我们能不能使用一些变量去表示Makefile中的文件列表呢?
3.Makefile中的变量
Makefile中可以自己定义变量,也可以使用Makefile中自带的变量(环境变量)
自定义变量:没有类型,都是字符串,而且变量名可以随意指定
3.1 makefile中变量的赋值和使用
变量可以不需要预先定义,和shell一样
如果变量名没有预先定义并且赋值,则变量的值为空
变量的定义方法:
变量名
如何使用变量:
$(变量名)
变量的定义不能放到指令列表中(一般放到Makefile的最前面)
在Makefile中给变量赋值有几种情况:
1.简单赋值 :=(不会向上展开)
B:=$(A)
A:=12345
B为空
A为12345
---------------
A:=12345
B:=$(A)
C:=A
$(A) 12345
$(B) 12345
$(C) A
2.递归赋值 =(会向上展开)
B=$(A)
A=12345
$(A) 12345
$(B) 12345
注意不要写成递归调用自己
B=$(A)
A=$(B)
3.追加赋值 +=
A:=123
A+=456
$(A) 123456
4.条件赋值 ?=
当变量以前没有定义或者没有值的时候,我们才会给他赋值
A?=hello
因为A以前没有定义并且没有值,所有A为hello
B:=123
B?=ABC
$(B) 123
3.2 自动变量
系统已经帮我们定义好了的变量,我们只需要使用即可
自动变量:make中的内置变量,make赋予这些变量一些特殊的含义
$@ 目标的完整名字
写在哪一个目标下面,就表示哪一个目标的名字
$+ 所有依赖文件的名字,以空格隔开,可能包含重复的依赖名
写在哪一个目标下面,就表示哪一个目标的依赖
$^ 所有依赖文件的名字,以空格隔开,不会包含重复的依赖名
写在哪一个目标下面,就表示哪一个目标的依赖
$< 第一个依赖文件的名字
main:a.c b.c
echo $< -----> a.c
$? 所有时间戳比目标文件新的依赖文件名称(在make的时候,需要重新编译的文件)
在生成目标文件之后,你又修改了的依赖文件
3.3 Makefile中的注释信息
以#开头的行是注释
如:
#目标名称
TARGET:=Graph
#头文件路径
INC:=../inc
#库文件的路径
LIB:= -lfind_max
LIB_PATH:= -L../lib
#编译器名称
CC:=gcc
$(TARGET):graph.o LKqueue.o main.o
$(CC) $^ -o $@ -I$(INC) $(LIB) $(LIB_PATH)
graph.o:graph.c
$(CC) -c $^ -o $@
LKqueue.o:LKqueue.c
$(CC) -c $^ -o $@
main.o:main.c
$(CC) -c $^ -o $@
clean:
rm *.o
rm Graph
如果在上面的工程中,增加了一个C的源文件,可能需要修改Makefile
修改的时候,也是增加一条关于.c ----->.o的规则
所有的.c----->.o的规则都是一样的,这本身也是一种规则,我们可以创建一个默认的规则,把上面的.c----->.o写成一个通用的形式(在增加C源文件之后,可以不需要修改Makefile)
Makefile中的通配符
% 表示任意一个文件名(不包括扩展名)
%.o 表示任意一个以.o结尾的文件
%.c 表示任意一个以.c结尾的文件
同名的.o一般会依赖于同名的.c
=========>
#目标名称
TARGET:=Graph
CC:=gcc
$(TARGET):graph.o Lkqueue.o main.o
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $< -o $@
clean:
rm *.o
rm $(TARGET)
我们也可以自动的在指定的工程目录中去查找*.c/*.cpp文件
使用内置的函数即可
4.Makefile中的函数和命令
1.Makefile中运行自定义函数并且调用函数(类似与shell中函数)
2.Makefile中调用shell命令
格式:
$(shell 命令名称 参数列表)
如果你需要使用shell命令或者函数的结果
=====>
变量名=$(shell 命令名称 参数列表) / $(函数名 函数的参数列表)
如:
Date=$(shell date)
PWD=$(shell pwd)
3.makefile中调用内置的函数
格式:
$(函数名 函数的参数列表)
我们简单的了解几个常用的函数和使用方式即可
wildcard:文件名展开函数
展开指定的目录下面符合wildcard参数描述的文件名称,获取的文件名称之间使用空格隔开
例子:
CSRCS 表示C源文件列表
CSRCS:=$(wildcard *.c)
在当前的目录下面查找所有的以.c结尾的文件名,然后赋值给 CSRCS
通过这种方式,我们就可以获取到所有的源文件名称
patsubst:模式字符串替换函数
把指定的字符串中符合某一个模式的字符串,替换为指定的字符串(可以使用通配符)
例子:
CSRCS:=$(wildcard *.c)
OBJS:=$(patsubst %.c,%.o,$(CSRCS))
把$(CSRCS)中以.c结尾的字符串的文件名称,替换为同名的以.o结尾的文件名
我们一般可以认识和使用一个简单通用的makefile即可