Makefile

概念

假如有三个C文件foo.c, bar.c, main.c,要编译成一个可执行程序test,会执行这样的命令:
gcc -Wall -c foo.c -o foo.o
gcc -Wall -c bar.c -o bar.o
gcc -Wall -c main.c -o main.o
gcc main.o foo.o bar.o -lpthread -o test
先将三个C文件都编译出来,然后再链接成一个可执行文件。但是当文件特别多的情况下,一个一个去编译,然后再去链接起来吗?可能会想创建一个shell文件,将上面的步骤放进去,写成一个build.sh,每次编译只需执行这个脚本。

存在的问题是:假设修改了foo.c,但没有修改bar.c和main.c,那么执行这个脚本是很浪费的,因为它会无条件也重新编译bar.c和main.c。
所以,这个脚本更合理的写法应该这样:
[ foo.o -ot foo.c ] && gcc -Wall -c foo.c -o foo.o
[ bar.o -ot bar.c ] && gcc -Wall -c bar.c -o bar.o
[ main.o -ot main.o] && gcc -Wall -c main.c -o main.o
[ test -ot main.o ] && [ test -ot foo.o ] && [ test -ot bar.o ] && gcc main.o foo.o bar.o -lpthread -o test

用Makefile实现

#sample1
foo.o: foo.c
  gcc -Wall -c foo.c -o foo.o
bar.o: bar.c
  gcc -Wall -c bar.c -o woo.o
main.o: main.c
  gcc -Wall -c main.c -o main.o
test: foo.o bar.o main.o
  gcc main.o foo.o bar.o -lpthread -o test

Makefile文件中,foo.o: foo.c定义了一个“依赖”,说明foo.o是依赖foo.c编译成的,它后面缩进的那些命令就是简单的shell脚本,称为规则(rule)。而Makefile的作用是定义一组依赖,当被依赖的文件比依赖的文件新,就执行规则。这样前面的问题就解决了。
Makefile中的依赖定义构成了一个依赖链(树),比如上面这个Makefile中,test依赖于main.o,main.o又依赖于main.c,所以,当你去满足test的依赖时,首先去检查main.o的依赖,直到找到依赖树的叶子节点(main.c),然后进行时间比较。Makefile的执行过程不是基于语句顺序的,而是基于依赖链的顺序的。

两个概念,⼀个是⽬标(target),另⼀个就是依赖(dependency)。⽬标就是指要⼲什么,或说运⾏ make 后⽣成什么,⽽依赖是告诉 make 如何去做以实现⽬标。在 Makefile 中,⽬标和依赖是通过规则(rule)来表达的,另外一个概念就是命令,通过命令将依赖生成目标。

目标

定义一个目标

all:
	echo "Hello world"

也可以定义多个目标

all:
	echo "Hello world"
test:
	echo "test"

make (等同于make all,默认⽬标是第⼀个⽬标)
make all
make test

依赖

all: test
	@echo "Hello world"
test:
	@echo "test game"

all ⽬标后⾯的 test 是告诉 make,all ⽬标依赖 test ⽬标,这⼀依赖⽬标在 Makefile 中⼜被称之为先决条件。出现这种⽬标依赖关系时,make⼯具会按从左到右的先后顺序先构建规则中所依赖的每⼀个⽬标。如果希望构建 all ⽬标,那么make 会在构建它之前得先构建 test ⽬标,这就是为什么我们称之为先决条件的原因。
在这里插入图片描述

规则

⼀个规则是由⽬标(targets)、先决条件(prerequisites)以及命令(commands)所组成的。

target ... : prerequisites ...
    command
    ...

target - 规则的目标,可以是Object 文件 也可以是可执行文件。有些目标可以没有依赖而只有动作(命令行),比如"clean";有些目标可以没有动作而只有依赖,比如"all",通常仅仅用作"终极目标";
prerequisites - 规则的依赖,通常一个目标依赖于一个或者多个文件;
command - 规则的的命令(任意的shell命令),Makefile中的命令必须以 [tab] 开头。

工作原理

在这里插入图片描述

// foo.c
#include <stdio.h>
 
void foo()
{
    printf("this is foo() !\n");
}
// main.c
extern void foo();
 
int main()
{
    foo();
    return 0;
}
// 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

在这里插入图片描述
make
gcc -o main.o -c main.c
gcc -o foo.o -c foo.c
gcc -o simple main.o foo.o
./simple
this is foo() !
make
gcc -o simple main.o foo.o

第⼆次编译并没有构建⽬标⽂件的动作,但为什么有构建simple可执⾏程序的动作呢?构建的目标是all,而all在我们编译的过成中并不生成,所以第二次make的时候找不到,又重新编译了一遍。

// makefile
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

make
gcc -o main.o -c main.c
gcc -o foo.o -c foo.c
gcc -o simple main.o foo.o
./simple
this is foo() !
make
make: ‘simple’ is up to date.

Makefile的五个组成部分:显示规则,隐式规则,变量定义,文件指示,注释。
显式规则:说明如何生成一个或多个目标文件(包括生成的文件,文件的依赖文件,生成的命令)
隐式规则:make的自动推导的功能所执行的规则
变量定义:在Makefile中可以定义一系列的变量(一般是字符串,类似C语言的宏),执行Makefile时变量会被扩展到相应的引用位置
文件指示:其包括三个部分
1、在一个Makefile中引用另一个Makefile(类似C语言中的include)
2、根据某些情况指定Makefile中的有效部分(类似C语言中的预编译#if一样)
3、定义一个多行的命令
注释:Makefile中只有行注释(用#字符),使用“#”字符可以用反斜框进行转义(“/#”)

GNU make 的工作方式:
1、读入主Makefile(主Makefile中可以引用其他Makefile)
2、读入被include的其他Makefile
3、初始化文件中的变量
4、推导隐晦规则,并分析所有规则
5、为所有的目标文件创建依赖关系链
6、根据依赖关系,决定哪些目标要重新生成
7、执行生成命令

规则中的通配符

*     :: 表示任意一个或多个字符
?     :: 表示任意一个字符
[...] :: ex. [abcd] 表示a,b,c,d中任意一个字符, [^abcd]表示除a,b,c,d以外的字符,[0-9]表示 0~9中任意一个数字
~     :: 表示用户的home目录
不用前缀 :: 输出执行的命令以及命令执行的结果,出错的话停止执行
前缀 @   :: 只输出命令执行的结果,出错的话停止执行
前缀 -   :: 命令执行有错的话,忽略错误,继续执行
@echo "SRCS: " :: 执行make时终端只打印结果:"SRCS: ",不会显示命令echo "SRCS: "

GNU提供一个机制可以查看C代码文件依赖那些文件,这样在写 Makefile 目标的时候就不用打开C源码来看其依赖那些文件了。
比如,下面命令显示内核源码中 virt/kvm/kvm_main.c 中的依赖关系
$ cd virt/kvm/
$ gcc -MM kvm_main.c
kvm_main.o: kvm_main.c iodev.h coalesced_mmio.h async_pf.h   <-- 这句就可以加到 Makefile 中作为编译 kvm_main.o 的依赖关系

模式规则
模式规则其实也是普通规则,但它使用了如%这样的通配符。如下面的例子:
%.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -O $@
此规则描述了一个.o文件如何由对应的.c文件创建。规则的命令行中使用了自动化变量“$<”和“$@”,其中自动化变量“$<”代表规则的依赖,“$@”代表规则的目标。此规则在执行时,命令行中的自动化变量将根据实际的目标和依赖文件取对应值。
其含义是,字指出了从所有的.c文件生成相应的.o文件的规则。如果要生成的目标是”a.o b.o”,那么%.c就是”a.c b.c”。
自动变量

在makefile的规则命令行中
$var是在命令中引用makefile变量,读取变量然后扩展开将其值作为参数传给shell命令;
$$var是在访问一个shell命令内定义的变量,而非makefile的变量

变量覆盖 override
作用是使 Makefile中定义的变量能够覆盖 make 命令参数中指定的变量
1.如果没有使用override定义变量,执行make时,通过命令行定义了一个变量,它将替代在Makefile中出现的同名变量的定义。
2.如果不希望命令行指定的变量值替代在Makefile中的变量定义,那么使用指示符override来对这个变量进行声明
3.在定义时使用了override,则后续对它值进行追加时,也需要使用带有override指示符的追加方式。否则对此变量值的追加不会生效

wildcard 扩展通配符
在Makefile规则中通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。如果需要通配符有效使用函数wildcard
notdir 去除路径
patsubst 替换通配符
src=$(wildcard *.c ./sub/*.c)  //a.c b.c ./sub/sa.c ./sub/sb.c
dir=$(notdir $(src)) //a.c b.c sa.c sb.c
obj=$(patsubst %.c,%.o,$(dir) ) =>等价于obj=$(dir:%.c=%.o) //a.o b.o sa.o sb.o
subst 替换函数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是操作的字符串
$(subst from,to,text) --->>字符串text中的from字符串替换成to
$(subst a,the,There is a big tree)返回结果:There is the big tree

关键字

clean 防止存在文件名为clean的文件时,make clean 失效;如果目录中出现了"clean"文件,则规则失效了:没有依赖文件,文件"clean"始终是新的,命令永远不会执行;为避免这个问题,可使用".PHONY"指明该目标。这样执行make clean会无视clean文件存在与否;.PHONY : clean。采⽤.PHONY 关键字声明⼀个⽬标后,make 并不会将其当作⼀个⽂件来处理,⽽只是当作⼀个概念上的⽬标,每次 make 这个假⽬标时,其所在的规则中的命令都会被执⾏。

关键字 vpath

vpath <directories>            :: 当前目录中找不到文件时, 就从<directories>中搜索
vpath <pattern> <directories>  :: 符合<pattern>格式的文件, 就从<directories>中搜索
vpath <pattern>                :: 清除符合<pattern>格式的文件搜索路径
vpath                          :: 清除所有已经设置好的文件路径

变量

变量的定义很简单,就是⼀个名字(变量名)后⾯跟上⼀个等号,然后在等号的后⾯放这个变量所期望的值。对于变量的引⽤,则需要采⽤$(变量名)。

定义变量
OBJS = programA.o programB.o
OBJS-ADD = $(OBJS) programC.o

OBJS := programA.o programB.o
OBJS-ADD := $(OBJS) programC.o
其中 =:= 的区别在于:
:= 只能使用前面定义好的变量(使用前必须定义,不然为空)
= 可以使用后面定义的变量

.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)
$(CURDIR)  当前目录,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)

特殊变量,VPATH 路径搜索。
当 Makefile 中涉及到大量源文件时(源文件和Makefile有可能不在同一个目录),这时最好将源文件的路径明确在Makefile中,便于编译时查找。Makefile中有个特殊的变量 VPATH 用来完成这个功能。
指定了 VPATH 后,如果当前目录中没有找到相应文件或依赖的文件,Makefile 回到 VPATH 指定的路径中再去查找。
VPATH = src:…/headers ;指定两个目录(src和…/headers),目录由冒号":"分隔,make 按照这个顺序进行搜索。

-rm:忽略命令rm的执行错误
all:
伪目标,是所有目标的目标,其功能是编译所有的目标。
可以用来构造两个目标:all:main1 main2
@gcc a.c   不在终端上显示,直接执行命令
make dir -p ./yangjinjing  no error if existing
= 基本的赋值
:= 覆盖之前的值
?= 如果没有被赋值过就赋予等号后面的值
+= 添加等号后面的值
%.o : %.c
   $(CC) -c $(CFLAGS) $^ -o $@ 把所有的.c文件都编译成.o文件

函数

wildcard: src=$(wildcard *.c ./sub/*.c)  //a.c b.c ./sub/sa.c ./sub/sb.c
notdir: dir=$(notdir $(src)) //a.c b.c sa.c sb.c
patsubst: obj=$(patsubst %.c,%.o,$(dir))  // a.o b.o sa.o sb.o
subst: $(subst a,the,There is a big tree) // 返回结果:There is the big tree
override:

call 函数调用

$(call RM,*.o)  # RM:函数名,*.o:参数

-DDEBUG
编译标记,定义DEBUG这个符号。
$(filter %.o, $(files)):filter函数过滤掉不符合%.o的文件

使用Make进行源码安装
(1)正常的编译安装/卸载:./bootstrap; make; make install
源码的安装一般由3个步骤组成:配置(configure)、编译(make)、安装(make install)
configure文件是一个可执行的脚本文件,它有很多选项,在待安装的源码目录下使用命令./configure –help可以输出详细的选项列表。
其中 --prefix选项 是配置安装目录,如果不配置该选项,安装后可执行文件默认放在/usr/local/bin,库文件默认放在/usr/local/lib,配置文件默认放在/usr/local/etc,其它的资源文件放在/usr/local/share,比较凌乱。
如果配置了–prefix,如:$ ./configure --prefix=/usr/local/test安装后的所有资源文件都会被放在/usr/local/test目录中,不会分散到其他目录。
使用–prefix选项的另一个好处是方便卸载软件或移植软件;当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;而移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统下)。
当然要卸载程序,也可以在原来的make目录下用一次make uninstall,但前提是Makefile文件有uninstall命令。

实例

#指定编译器
CC:=gcc
OD:=
#存放目标文件的相对路径(当前目录为Makefile文件存放的目录),最后不要打斜杠
BUILD:=build
#生成的可执行文件
TARGET:=emu
#目标文件列表,源文件后添加.o,包含源文件后缀名
OBJS:=npp.c.o register.c.o cau_main.c.o command.c.o common.c.o \
    opc_send_cmd_addr.c.o \
    opc_nand_data_rd.c.o \
    opc_nand_data_wr.c.o \
    opc_load_ctx.c.o \
    opc_reg_op.c.o \
    opc_cri.c.o \
    opc_ts.c.o \
    opc_wr_reg.c.o \
    zf_log.c.o
#源文件搜索路径,以冒号隔开
SRCDIR:=./src
#头文件搜索路径,以冒号隔开
INCDIR:=./inc
#编译器编译选项
CFLAGS:=-O0 -g -c -Wall -Werror -DLITTLE_ENDIAN
#链接选项
LDFLAGS:=
#静态库
STATIC_LIBS:=
#------------------------------------------------------------------------------
#源文件自动搜索路径
VPATH:=$(subst :, ,$(SRCDIR))
#指定编译器头文件路径
#用空格替换INCDIR中的冒号,patsubst在subst返回的每个字符串前面加上-I, override 
#在FLAGS后面追加patsubst返回的字符串
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(INCDIR)))
#生成带路径的目标文件列表
BUILD_OBJS:=$(patsubst %.o,$(BUILD)/%.o,$(OBJS))
$(TARGET): $(OBJS)
    @echo $(CC) builds target $(BUILD)/$@
    @$(CC) -o $(BUILD)/$@ $(BUILD_OBJS) $(STATIC_LIBS) $(LDFLAGS)
%.c.o:%.c
    @mkdir -p $(BUILD)
    $(CC) -c $(CFLAGS) $^ -o $(BUILD)/$@
%.S.o:%.S
    @mkdir -p $(BUILD)
    $(CC) -c $(CFLAGS) $^ -o $(BUILD)/$@
.PHONY:clean
clean:
    @rm -rf $(BUILD)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春夏与冬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值