当前位置: 首页 > news >正文

上层 Makefile 控制下层 Makefile ---- 第二部分(补充一些例子与细节)

1. 递归调用子目录 Makefile

通过 $(MAKE) -C 进入子目录并执行其 Makefile,这是最常见的分层构建方法。

示例:基本递归调用

目录结构:

project/
├── Makefile         # 顶层 Makefile
├── lib/
│   ├── Makefile     # 子目录 Makefile
│   └── src/
├── app/
│   ├── Makefile
│   └── src/
└── test/
    ├── Makefile
    └── src/

顶层 Makefile 内容:

SUBDIRS = lib app test

.PHONY: all clean $(SUBDIRS)

all: $(SUBDIRS)

$(SUBDIRS):
	$(MAKE) -C $@

clean:
	for dir in $(SUBDIRS); do \
		$(MAKE) -C $$dir clean; \
	done

说明:

  • SUBDIRS 定义了所有子目录。

  • all 目标依赖 $(SUBDIRS),按顺序调用每个子目录的 make

  • clean 目标递归调用所有子目录的 clean 目标。


2. 处理子目录依赖关系

确保子目录按依赖顺序构建(例如 app 依赖 lib)。

示例:显式声明依赖
# 顶层 Makefile
SUBDIRS = lib app test

# 定义依赖关系
app: lib
test: app

.PHONY: all $(SUBDIRS)

all: $(SUBDIRS)

$(SUBDIRS):
	$(MAKE) -C $@

说明:

  • app: lib 表示构建 app 前必须先完成 lib 的构建。

  • 此时运行 make all 会按顺序执行:lib → app → test


3. 变量传递到子 Makefile

通过 export 或命令行将变量传递给子目录的 Makefile。

方法 1:全局导出变量
# 顶层 Makefile
export CFLAGS = -O2 -Wall
SUBDIRS = lib app test

all: $(SUBDIRS)
	@echo "所有子目录构建完成"

$(SUBDIRS):
	$(MAKE) -C $@

子目录 Makefile(例如 lib/Makefile):

# 直接使用上层导出的 CFLAGS
lib.o: src/lib.c
	gcc $(CFLAGS) -c src/lib.c -o lib.o
方法 2:命令行显式传递变量
# 顶层 Makefile
SUBDIRS = lib app test

all: $(SUBDIRS)
	@echo "所有子目录构建完成"

$(SUBDIRS):
	$(MAKE) -C $@ CFLAGS="$(CFLAGS)"

说明:

  • 子目录 Makefile 中,直接使用 $(CFLAGS)


4. 并行构建优化

利用 make -jN 启用并行构建,需确保依赖关系正确。

示例:允许无依赖子目录并行构建
SUBDIRS = lib utils app test

# 定义依赖关系
app: lib utils
test: app

.PHONY: all $(SUBDIRS)

all: $(SUBDIRS)
	@echo "构建完成"

$(SUBDIRS):
	$(MAKE) -C $@

运行命令:

make -j4  # 并行构建 lib、utils → 完成后构建 app → 最后构建 test

5. 错误处理

确保子目录构建失败时,上层 Makefile 立即终止。

示例:严格错误检查
# 顶层 Makefile
SUBDIRS = lib app test

.PHONY: all $(SUBDIRS)

all: $(SUBDIRS)
	@echo "构建成功"

$(SUBDIRS):
	$(MAKE) -C $@ || exit 1

clean:
	for dir in $(SUBDIRS); do \
		$(MAKE) -C $$dir clean || exit 1; \
	done

说明:

  • || exit 1 确保子目录构建失败时,整个流程立即终止


6. 多目标支持(如 clean、install)

通过变量动态传递目标名称,实现灵活的多目标调用。

示例:支持 install 和 distclean
# 顶层 Makefile
SUBDIRS = lib app test
TARGET = all  # 默认目标

.PHONY: $(SUBDIRS) all install distclean

all: $(SUBDIRS)

$(SUBDIRS):
	$(MAKE) -C $@ $(TARGET)

install: TARGET = install
install: $(SUBDIRS)
	@echo "所有子目录安装完成"

distclean: TARGET = distclean
distclean: $(SUBDIRS)
	@echo "彻底清理完成"

子目录 Makefile(示例 lib/Makefile):

.PHONY: all install distclean

all: lib.o
	@echo "lib 构建完成"

install:
	cp lib.o /usr/local/lib  # 需要 sudo 权限的操作

distclean:
	rm -f lib.o

运行命令:

make          # 构建所有子目录
make install  # 安装所有子目录(可能需要 sudo)
make distclean  # 彻底清理

7. 动态子目录发现

自动发现子目录,避免硬编码 SUBDIRS

示例:自动遍历子目录
# 顶层 Makefile
SUBDIRS := $(wildcard */.)  # 匹配所有子目录(例如 lib/. app/. test/.)

.PHONY: all $(SUBDIRS)

all: $(SUBDIRS)
	@echo "构建完成"

$(SUBDIRS):
	$(MAKE) -C $(@D)  # $(@D) 提取目录名(如 lib/. → lib)

clean:
	for dir in $(SUBDIRS); do \
		$(MAKE) -C $$dir clean; \
	done

完整示例:分层构建项目

目录结构:
project/
├── Makefile
├── include/
│   └── common.h
├── lib/
│   ├── Makefile
│   └── src/
│       └── lib.c
├── app/
│   ├── Makefile
│   └── src/
│       └── app.c
└── test/
    ├── Makefile
    └── src/
        └── test.c
顶层 Makefile:
export CFLAGS = -I../include -Wall -O2
SUBDIRS = lib app test

.PHONY: all clean install

all: $(SUBDIRS)
	@echo "=== 构建完成 ==="

# 依赖关系
app: lib
test: app

$(SUBDIRS):
	$(MAKE) -C $@

clean:
	for dir in $(SUBDIRS); do \
		$(MAKE) -C $$dir clean; \
	done

install: all
	@echo "=== 安装到系统目录(需要 sudo)==="
	sudo cp app/bin/app /usr/local/bin
	sudo cp lib/lib.so /usr/local/lib
子目录 Makefile(以 lib/Makefile 为例):
TARGET = lib.so
SRC = src/lib.c
OBJ = $(SRC:.c=.o)

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJ)
	gcc -shared -o $@ $^

%.o: %.c
	gcc $(CFLAGS) -fPIC -c $< -o $@

clean:
	rm -f $(OBJ) $(TARGET)

关键总结

  1. 递归调用使用 $(MAKE) -C 进入子目录执行 Makefile。

  2. 依赖管理:通过目标依赖确保构建顺序(如 app: lib)。

  3. 变量传递export 或命令行显式传递编译选项。

  4. 并行构建:利用 make -jN 加速,但需正确声明依赖。

  5. 错误处理|| exit 1 确保子目录失败时终止。

  6. 多目标支持:通过变量动态传递目标名(如 installdistclean)。

  7. 动态子目录:使用 wildcard 自动发现子目录,避免硬编码。

相关文章:

  • URL结构、HTTP协议报文
  • Redis for Windows 后台服务运行
  • 【6】深入学习http模块(万字)-Nodejs开发入门
  • javascript专题2 ---- 在 JavaScript 列表(数组)的第一个位置插入数据
  • 【Linux C】简单bash设计
  • 重返JAVA之路——面向对象
  • 论文:Generalized Category Discovery with Large Language Models in the Loop
  • 玩转ChatGPT:使用深入研究功能梳理思路
  • 最大公约数和最小倍数 java
  • 【Linux实践系列】:匿名管道收尾+完善shell外壳程序
  • redis linux 安装简单教程(redis 3.0.4)
  • Spring Boot(二十一):RedisTemplate的String和Hash类型操作
  • 基于XGBoost的异烟酸生产收率预测:冠军解决方案解析
  • 七大寻址方式
  • ubuntu 系统安装Mysql
  • 【代码安全】spotbugs编写自定义规则(一) 快速开始
  • 【数据可视化艺术·实战篇】视频AI+人流可视化:如何让数据“动”起来?
  • 每日OJ_牛客_ruby和薯条_排序+二分/滑动窗口_C++_Java
  • vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值。
  • C语言中常用的调试宏和函数总结(__LINE__、__FUNCTION__)
  • 推开“房间”的门:一部“生命存在的舞台” 史
  • 哪条线路客流最大?哪个站点早高峰人最多?上海地铁一季度客流报告出炉
  • 马上评|科学红毯,让科学家成为“最亮的星”
  • 首次带人形机器人走科技节红毯,傅利叶顾捷:没太多包袱,很多事都能从零开始
  • 大陆非遗项目打铁花、英歌舞将在台演出
  • 圆桌丨新能源车超充技术元年,专家呼吁重视电网承载能力可能面临的结构性挑战