Makefile的使用

        在Linux中使用 make 命令来编译程序,特别是大程序;而 make 命令所执行的动作 依赖于 Makefile 文件。以下是最简单的Makefile文件:

 首先,包含如下文件:

Makefile文件内容如下所示:

然后,直接执行 make 命令编译程序:

执行 make clean 命令 即可清除编译出来的结果:

        make命令 根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免已经编译过的、没有变化的程序,可以大大提高编译效率。

一、Makefile规则与示例讲解

1、规则

规则如下所示:

目标(target): 依赖(prerequiries)
<tab>命令(command)

需要特别注意,必须以Tab键进行缩进,不能以空格键缩进。

        如果 “依赖文件” 比 “目标文件” 更加新,那么执行 “命令” 来重新生成 “目标文件”

        命令执行的两个条件:依赖文件比目标文件新,或者是 目标文件没有生成

这里介绍两个函数:

(1)$(foreach var, list, text)

        简单地说,就是 for each var in list, change it to text

        其实就是 对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。

如:

objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d)    //最终 dep_files := .a.o.d .b.o.d

(2)$(wildcard pattern)

        pattern 所列出的文件是否存在,把存在的文件都列出来。

如:

src_files := $( wildcard *.c)    //最终 src_files 中列出了当前目录下的所有.c文件

2、示例讲解

(1)简单粗暴,效率低:

test : main.c sub.c sub.h
	gcc -o test main.c sub.c

(2)效率高,相似规则太多太啰嗦,不支持检测头文件:

test : main.o sub.o
	gcc -o test main.o sub.o

main.o : main.c
	gcc -c -o main.o main.c

sub.o : sub.c
	gcc -c -o sub.o sub.c

clean:
	rm *.o test -f

(3)效率高,精炼,不支持头文件检测:

test : main.o sub.o
	gcc -o test main.o sub.o

%.o : %.c
	gcc -c -o $@ $<

clean:
	rm *.o test -f

(4)效率高,精炼,支持头文件检测(但是需要手动添加头文件规则):

test : main.o sub.o
	gcc -o test main.o sub.o

%.o : %.c
	gcc -c -o $@ $<

sub.o : sub.h

clean:
	rm *.o test -f

(5)效率高,精炼,支持自动检测头文件

objs := main.o sub.o
test : $(objs)
	gcc -o test $^

# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))

# 把依赖文件包含进来
ifneq ($(dep_files),)
 include $(dep_files)
endif

%.o : %.c
	gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
	rm *.o test -f
distclean:
	rm $(dep_files) *.o test -f

二、通用Makefile的使用

1、解析

(1)make 命令的使用

        执行 make 命令时,它会去 当前目录下 查找名为 “Makefile” 的文件,并根据它的指示去执行操作,生成 第一个 目标。

        我们可以使用 “ -f ” 选项指定文件,不再使用名为 “Makefile” 的文件,比如:

make -f Makefile.buid

        我们也可以使用 “ -C ”选项指定目录,切换到其他目录里去,比如:

make -C a/ -f Makefile.buid

        我们可以指定目标,不再 默认生成 第一个 目标:

make -C a/ -f Makefile.buid other_target

(2)即时变量、延时变量、自动变量

  • 延迟赋值,在Makefile运行时才会被赋值
  • := 立即赋值,立即赋值是在真正运行前就会被赋值
  • ?= 空赋值,如果变量没有设置过才会被赋值
  • += 追加赋值,可以理解为字符串的加操作

        变量的定义语法如下所示:

A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量;
// 如果 D 在前面是立即变量,那么现在它还是立即变量

        上面语句中,变量A是延时变量,它的值在使用时才展开、才确定。比如:

A = $@
test:
	@echo $A

        上述Makefile中,变量A的值在执行时才确定,它等于test,是延时变量。

        如果使用 “ A := $@ ”,这是立即变量,这时" $@ "为空,所以A的值就为 空。

自动变量:

        Makefile有很多自动变量,这里只介绍几个常用的,分别是 $<、$^、$@,其它的可以去参考Makefile文档。

$< 表示第一个依赖的文件,例如:

test: test.o test2.o  
    echo $<
test.o:
test2.o:

        最终的结果是打印了 test.o,也就是test第一个依赖。

$^ 表示所有依赖,例如:

test: test.o test2.o  
    echo $^
test.o:
test2.o:

        最终结果是打印了 test.o test2.o,是test全部的依赖。

$@ 表示目标,例如:

test: test.o test2.o  
    echo $@
test.o:
test2.o:

        最终结果打印的是 test,也就是Makefile中的目标。

(3)变量的导出(export)

        在编译时,我们会不断地使用 " make -C dir " 切换到其它目录,执行其他目录里的 Makefile。如果 想让某个变量的值在所有目录中都可见,要把它 export 出来。比如:

CC = $(CROSS_COMPILE)gcc

        上面的CC变量表示编译器,在整个过程中都是一样的。定义它之后,要使用 “ export CC ” 把它导出来。

(4)Makefile中可以使用shell命令

比如:

TOPDIR := $(shell pwd)

        这是一个即时变量,TOPDIR 等于 shell 命令pwd 的结果。

(5)在Makefile中怎么放置 第一个 目标

        执行 make 命令时,如果不指定目标,那么它默认是去生成 第一个 目标。

        所以 “ 第一个目标 ” ,位置很重要。有时候不太方便把 第一个目标 完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:

First_target: // 这句话放在前面
.... 		// 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
	command

(6)假想目标

        比如,我们的 Makefile 文件中有这样的目标:

clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

        如果当前目录下,恰好有名为 “ clean ” 的文件,那么执行 “ make clean ” 时,它就不会执行上面那些删除命令。

        这时,我们需要把 “ clean ” 这个目标,设置为 “ 假想目标 ”,这样可以确保执行 “ make clean ” 时,那些删除命令肯定可以得到执行。

        使用下面的语句 把 “ clean ” 设置为假想目标:

.PHONY : clean

2、通用Makefile的设计思想

(1)在Makefile文件中确定要编译的文件、目录

比如:

obj-y += main.o
obj-y += a/

        “ Makefile ” 文件总是被 “ Makefile.build ” 包含的。

(2)在 Makefile.build 中设置编译规则,有 3条规则

        怎么编译子目录?进入子目录编译

$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

        怎么编译当前目录中的文件?

%.o : %.c
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

        当前目录下的 .o 和 子目录下的 build-in.o 要打包起来

built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

(3)顶层 Makefile 中把顶层目录的 build-in.o 链接成 APP

$(TARGET) : built-in.o
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

(4)情景演绎

使用情景:

./Makefile:

CROSS_COMPILE = 
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include

LDFLAGS := 

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := test


obj-y += main.o
obj-y += sub.o
obj-y += a/


all : start_recursive_build $(TARGET)
	@echo $(TARGET) has been built!

start_recursive_build:
	make -C ./ -f $(TOPDIR)/Makefile.build

$(TARGET) : built-in.o
	$(CC) -o $(TARGET) built-in.o $(LDFLAGS)

clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)
	

./a/Makefile:


EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3

obj-y += sub2.o 
obj-y += sub3.o 

Makefile.build:

PHONY := __build

__build:

obj-y :=
subdir-y :=
EXTRA_CFLAGS :=

include Makefile

__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))

ifneq ($(dep_files),)
  include $(dep_files)
endif

PHONY += $(subdir-y)

__build : $(subdir-y) built-in.o debug

debug:
	@echo cur_objs = $(cur_objs)
	@echo obj-y = $(obj-y)
	@echo subdir_objs = $(subdir_objs)
	@echo subdir-y = $(subdir-y)

$(subdir-y):
	@echo subdir-y = $(subdir-y)
	make -C $@ -f $(TOPDIR)/Makefile.build

built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

dep_file = .$@.d

%.o : %.c
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY)

对上述情景的理解如下:

 整理,简化,大致是这样的:

        首先,在顶层执行 make,执行顶层Makefile,第一个目标为 all,而其第一个依赖为 start_recursive_build ,接着根据这个依赖,会去调用 make -C ./ -f $(TOPDIR)/Makefile.build ,可以替换为 make -C ./ -f $(shell pwd)/Makefile.build ,即在当前目录下,去执行  Makefile.build。

        因为 Makefile.build 脚本中包含了 include Makefile,所以这时候,会根据顶层Makefile,给 Makefile.build 中的 obj- 和 subdir-y 进行赋值,那么就有:

        obj-y = main.o sub.o a/
        cur_objs = main.o sub.o

        subdir-y = a
        subdir_objs = a/built-in.o

        第一个目标为 __build ,且其第一个依赖为 $(subdir-y),也就是a目录,先运行第一个依赖的命令,然后调用命令为 make -C $@ -f $(TOPDIR)/Makefile.build,替换后为 make -C a -f $(shell pwd)/Makefile.build。意思为:去子目录a下,运行Makefile.build。

        接着进入子目录运行 Makefile.build,同样根据子目录下的 Makefile 去给  obj- 和 subdir-y 进行赋值,由于没有子目录了,所以 subdir-y 为空,如下所示:

        obj-y = sub2.o sub3.o
        cur_objs = sub2.o sub3.o
        subdir_objs =
        subdir-y =

        第一个目标还是 __build,其依赖项两个:$(subdir-y)built-in.o。前者为空,因此不需要再次递归调用了。第二个目标是 built-in.o,找到其对应的依赖为$(cur_objs) $(subdir_objs),而subdir_objs又为空,因此不需要执行。

        由于已经子目录下已经执行完了,退回顶层目录文件夹下 的 Makefile.build 脚本中继续执行,这时,由于 __build 目标的第一个依赖 $(subdir-y) 已经执行结束,则开始执行第二个依赖 built-in.o ,而此时对应的两个依赖,$(cur_objs) $(subdir_objs),第一个生成了 %.o ,第二个为 a/built-in.o ,已经在上一步生成了。因此链接生成顶层的 built-in.o ,此时,目标生成,退出 Makefile.build 脚本,回到顶层的 Makefile 中。

        回到顶层的 Makefile ,继续执行第一个目标 all 的 第二个依赖 $(TARGET) ,而 $(TARGET) 的依赖 built-in.o 也生成了,执行最后的命令 $(CC) $(LDFLAGS) -o $(TARGET) built-in.o 就结束 顶层Makefile 脚本了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值