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)
关键总结
递归调用:使用
$(MAKE) -C
进入子目录执行 Makefile。依赖管理:通过目标依赖确保构建顺序(如
app: lib
)。变量传递:
export
或命令行显式传递编译选项。并行构建:利用
make -jN
加速,但需正确声明依赖。错误处理:
|| exit 1
确保子目录失败时终止。多目标支持:通过变量动态传递目标名(如
install
、distclean
)。动态子目录:使用
wildcard
自动发现子目录,避免硬编码。