通用makefile是如何炼成的(VII)

上一次我们试图引入多模块,结果失败了,今天我们来帮助失败实现做母亲的愿望。在正式开始之前,先做一个孕前检查

我们上一次的失败原因有两个

1.  编译目标重定义。

2.  一些变量定义被覆盖。

这些实际上是递归形式的多模块makefile必然要遇到的两个问题。

对症下药的药方就是makefile中的“多规则目标”(具体参看makefile必知必会,简单而言就是三句话:

makefile允许目标的依赖关系通过多个规则定义,但是实现该目标的命令只能在一个规则中定义,实现时会自动合并该目标所有的依赖关系。

下面来解决第一个问题,clean目标重定义。既然clean目标的实现命令不能在多个规则中定义,那么我们就为clean目标建立一个依赖关系clean:$(TARGET).clean, 在依赖目标$(TARGET).clean中定义命令。

由于$(TARGET).clean,各个模块必然不同,自然而然地就解决了目标重定义的问题。

# executable.mk

## 编译生成obj文件的通用规则
include build/targets/object.mk
	
## 利用多规则定义,将clean目标转发给$(TARGET).clean,从而避免出现clean目标重定义的问题
## 对all,不需要额外处理,因为$(TARGET)本身就要求不同模块目标不同
all:	$(TARGET)
clean: $(TARGET).clean

## 定义可执行文件hello.exe的编译规则	
$(TARGET) :	$(OBJS) $(LIBS)
	$(LD) $(LDFLAGS)  -o $@ $^
	
## 定义 目标clean,一般就是删除所有obj文件,以及可执行文件。
.PHONY: $(TARGET).clean
$(TARGET).clean:
	$(RM) $(TARGET) $(OBJS) $(DEPS)


下面来解决第二个问题,  首先稍微解释一下这条规则$(ALL_TARGETS)  :          OBJS  := $(OBJS)   它的与众不同在于依赖目标是一个赋值表达式。

这条规则的妙处在于能将变量的值与目标$(ALL_TARGETS)绑定起来。简单说来,不管之前OBJS的值是什么,当需要生成目标$(ALL_TARGETS)时,由于依赖关系,会先去计算“OBJS  := $(OBJS)“这条语句。那么这里面 $(OBJS)的值是怎么算呢?它的值来源于makefile的第一次解析,因为目标和依赖目标中的变量是立即展开的。于是乎

当makefile解析到模块A时,假设ALL_TARGETS=a, OBJS=a.o, 这时上面的规则就变成 a : OBJS := a.o;

当解析到模块B时, 假设ALL_TARGETS=b,  OBJS=b.o.  这时上面的规则变成b : OBJS :=b.o; 

然后makefile开始第二次扫描,  注意,在此之前OBJS的值已经是b.o了。当makefile去生成目标a时,会自动计算依赖目标,也就是表达式"OBJS := a.o", 于是OBJS又恰到好处的还原为需要的值。

这个技术很像是变量存储,因此我们新增了一个vars-stash.mk, 利用这个技术做变量存储,防止被覆盖。

# vars-stash.mk

$(info ALL_TARGETS=$(ALL_TARGETS))
$(ALL_TARGETS) : AR       := $(AR)
$(ALL_TARGETS) : ARFLAGS  := $(ARFLAGS)
$(ALL_TARGETS) : ARLIBS   := $(ARFLAGS)

$(ALL_TARGETS) : CC       := $(CC)
$(ALL_TARGETS) : CPP      := $(CPP)
$(ALL_TARGETS) : CXX      := $(CXX)
$(ALL_TARGETS) : CFLAGS   := $(CFLAGS)
$(ALL_TARGETS) : CPPFLAGS := $(CPPFLAGS)
$(ALL_TARGETS) : CXXFLAGS := $(CXXFLAGS)

$(ALL_TARGETS) : LD       := $(LD)
$(ALL_TARGETS) : LDLIBS   := $(LDLIBS)
$(ALL_TARGETS) : LDFLAGS  := $(LDFLAGS)

$(ALL_TARGETS) : OBJS  := $(OBJS)
$(ALL_TARGETS) : DEPS  := $(DEPS)
$(ALL_TARGETS) : TARGET  := $(TARGET)

在executable.mk中,直接包含vars-stash.mk

## 定义 目标clean,一般就是删除所有obj文件,以及可执行文件。
.PHONY: $(TARGET).clean
$(TARGET).clean:
	$(RM) $(TARGET) $(OBJS) $(DEPS)

ALL_TARGETS := $(OBJS) $(TARGET) $(TARGET).clean
include build/targets/vars-stash.mk


至此,失败终为成功之母,简单调整,多模块的makefile就在襁褓之中。

为hello文件新建一个module.mk, 配置好源文件列表,编译目标,以及引用static-library.mk, 就可以正常编译得到hello.a。同时修改主文件夹下的module.mk, 在依赖目标中增加hello.a

## main.mk

## 只要给定源文件目录以及目标hello.exe
## 调用executable.mk,就可以自动编译得到想要的可执行文件
SRC_FILES:= src/main.cpp
LDLIBS += src/hello/hello.a
TARGET:=hello.exe

## 定义了如何生成可执行文件的通用规则
include build/targets/executable.mk




今天的工作到此为止。这一次的调整还是比较大的,中途也不太方便做测试。必须在调整完target之后,才能做多模块化编译。其实我中间也费了不少时间。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值