从头开始写项目Makefile(六):参数传递、条件判断、include

本文介绍如何在Makefile中实现变量传递、条件语句的应用及如何提取公共规则到独立文件,提高Makefile的可维护性和复用性。
【版权声明:转载请保留出处:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】
在多个Makefile嵌套调用时,有时我们需要传递一些参数给下一层Makefile。比如我们在顶层Makefile里面定义的打开调试信息变量DEBUG_SYMBOLS,我们希望在进入子目录执行子Makefile时该变量仍然有效,这是需要将该变量传递给子Makefile,那怎么传递呢?这里有两种方法:
1.     在上层Makefile中使用”export”关键字对需要传递的变量进行声明。比如:
DEBUG_SYMBOLS = TRUE
export DEBUG_SYMBOLS
当不希望将一个变量传递给子 make 时,可以使用指示符 “unexport”来声明这个变量。
export一般用法是在定义变量的同时对它进行声明。如下:
export DEBUG_SYMBOLS = TRUE
2.     在命令行上指定变量。比如:
$(MAKE) -C xxx DEBUG_SYMBOLS = TRUE
这样在进入子目录xxx执行make时该变量也有效。
 
像编程语言一样,Makefile也有自己的条件语句。条件语句可以根据一个变量值来控制make的执行逻辑。比较常用的条件语句是ifeq –else-endif、ifneq-else-endif、ifdef-else-endif。
ifeq关键字用来判断参数是否相等。
比如判断是否生成调试信息可以这么用:
ifeq ($(DEBUG_SYMBOLS), TRUE)
>---CFLAGS += -g -Wall -Werror -O0
else
>---CFLAGS += -Wall -Werror -O2
endif
 
Ifneq和ifeq作用相反,此关键字是用来判断参数是否不相等。
ifdef关键字用来判断一个变量是否已经定义。
后两个关键字用法和ifeq类似。
 
现在我们继续改进我们上一节的Makefile,上一节的Makefile完成Makefile的嵌套调用,每一个模块都有自己的Makefile。其实每个模块的Makefile都大同小异,只需要改改最后编译成生成的目标名称或者编译链接选项,规则都差不多,那么我们是否可以考虑将规则部分提取出来,每个模块只需修改各自变量即可。这样是可行的,我们将规则单独提取出来,写一个Makefile.rule,将他放在顶层Makefile同目录下,其他模块内部的Makefile只需要include该Makefile就可以了。如下:
include $(SRC_BASE)/Makefile.rule
include类似于C语言的头文件包含,你把它理解为为本替换就什么都明白了。
这样以后规则有修改的话我们直接修改该Makefile就可以了,就不用进入每一个模块去修改,这样也便于维护。
这样我们今天顶层Makefile稍作修改:
# Top Makefile for C program                                                                                                                                                             
# Copyright (C) 2014 shallnew \at 163 \dot com
 
export DEBUG_SYMBOLS = TRUE
 
DIR = src
MODULES = $(shell ls $(DIR))
# MODULES = ipc main tools
 
all : $(MODULES)
 
$(MODULES):
>---$(MAKE) -C $(DIR)/$@
 
main:tools ipc
 
clean :
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done
 
distclean:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done
 
tags:
>---ctags -R
 
help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all              - (==make) compile and link"
>---@echo " clean            - clean target"
>---@echo " distclean        - clean target and otherinformation"
>---@echo " tags             - create ctags for vimeditor"
>---@echo " help             - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.2 ======================="
 
.PHONY : all clean distclean tags help
 
目前我们顶层目录下的目录树为:
.
├── include
│   ├── common.h
│   ├── ipc
│   │   └── ipc.h
│   └── tools
│       ├── base64.h
│       ├── md5.h
│       └── tools.h
├── libs
├── Makefile
├── Makefile.rule
└── src
    ├── ipc
    │  ├──inc
    │  ├──Makefile
    │  └──src
    │       └── ipc.c
    ├── main
    │  ├──inc
    │  ├──Makefile
    │  └──src
    │       ├── main.c
    │       └── main.c~
    └── tools
        ├── inc
        ├── Makefile
        └── src
            ├── base64.c
            ├── md5.c
            └── tools.c
 
14 directories, 16 files
 
每个子模块下的Makefile删除规则后修改为如下:
 
SRC_BASE = ../..
 
CFLAGS +=
CPPFLAGS += -I. -I./inc -I$(SRC_BASE)/include
 
# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = libtools.a
 
include $(SRC_BASE)/Makefile.rule
而处于顶层目录下的Makefile.rule专门处理各模块编译链接时需要的规则。内容如下:
# Copyright (C) 2014 shallnew \at 163 \dot com                                                                                                                                           
 
ifeq ($(DEBUG_SYMBOLS), TRUE)
>---CFLAGS += -g -Wall -Werror -O0
else
>---CFLAGS += -Wall -Werror -O2
endif
 
all : $(SRC_BIN) $(SRC_LIB)
 
ifneq ($(SRC_BIN),)
$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)
endif
 
ifneq ($(SRC_LIB),)
$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs $@ $^
>---cp $@ $(SRC_BASE)/libs
endif
        
# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) $(SRC_BIN)$(SRC_BIN).exe
 
distclean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) $(SRC_BIN)$(SRC_BIN).exe $(SRC_BASE)/libs/* $(SRC_BASE)/tags *~
 
.PHONY : all clean disclean
~
我们将Makefile.rule放在顶层有可能会一不小心在命令行上面执行了该Makefile,如下:
# make -f Makefile.rule
make: Nothing tobe done for `all'.
#
由于我们没有定义变量$(SRC_BIN)和$(SRC_LIB),伪目标all没有任何依赖,所以编译是无法成功的。这里我们我们应该禁止直接执行该Makefile。

在make里面有这样一个变量:MAKELEVEL,它在多级调用的 make 执行过程中。变量代表了调用的深度。在 make 一级级的执行过程中变量MAKELEVEL的值不断的发生变化,通过它的值我们可以了解当前make 递归调用的深度。顶层的MAKELEVEL的值为“0” 、下一级时为“1” 、再下一级为“2”.......,所以我们希望一个子目录的Makefile必须被上层 make 调用才可以执行,而不允许直接执行,我们可以判断变量MAKELEVEL来控制。所以我们这一节最终的Makefile.rule为:


# Copyright (C)2014 shallnew \at 163 \dot com
 
ifeq ($(DEBUG_SYMBOLS),TRUE)
>---CFLAGS +=-g -Wall -Werror -O0
else
>---CFLAGS +=-Wall -Werror -O2
endif
 
ifeq($(MAKELEVEL), 0)                                                                                                                                                                   
all : msg
else
all : $(SRC_BIN)$(SRC_LIB)
endif
 
ifneq ($(SRC_BIN),)
$(SRC_BIN) :$(SRC_OBJ)
>---$(CC) -o $@$^ $(LDFLAGS)
endif
 
ifneq($(SRC_LIB),)
$(SRC_LIB) :$(SRC_OBJ)
>---$(AR) rcs$@ $^
>---cp $@$(SRC_BASE)/libs
endif
 
msg:
>---@echo"You cannot directily execute this Makefile! This Makefile should calledby toplevel Makefile."
 
# clean target
clean:
>---$(RM)$(SRC_OBJ) $(SRC_LIB) $(SRC_BIN) $(SRC_BIN).exe
 
distclean:
>---$(RM)$(SRC_OBJ) $(SRC_LIB) $(SRC_BIN) $(SRC_BIN).exe $(SRC_BASE)/libs/*$(SRC_BASE)/tags *~
 
.PHONY : all cleandisclean 

此时再直接执行该Makefile:

# make -f Makefile.rule
You cannot directily execute this Makefile! This Makefile should called by toplevel Makefile.
# 

### 是否应该 `make clean` 取决于 Makefile 的修改类型 在大型工程项目中,**修改了 Makefile 文件之后是否需要执行 `make clean`,取决于你对 Makefile 做了什么类型的更改**。 --- ### ✅ 一、需要 `make clean` 的情况(必须清理) 当你修改的 Makefile 内容 **影响编译结果** 时,旧的目标文件(`.o`)已经“过期”或不一致,必须重新从头构建。 #### 🔺 常见需要 clean 的修改: | 修改内容 | 为什么需要 clean | |--------|----------------| | 添加/修改 `CFLAGS`, `CPPFLAGS`, `KBUILD_CFLAGS` 等编译标志 | 影响编译器行为(如宏定义 `-DDEBUG`、优化等级 `-O2`),旧 `.o` 文件是用不同参数编译的 | | 修改头文件搜索路径:`-Iinclude/`, `-I./src` | 头文件查找路径变了,可能导致包含不同的头文件 | | 修改宏定义:`-DCONFIG_FOO=1` → `-DCONFIG_FOO=2` | 宏值变化会导致代码逻辑不同,但依赖检测通常无法识别这种变化 | | 修改架构或平台相关配置:`-mcpu=cortex-m4` | 编译目标变了,生成的机器码不兼容 | > ⚠️ 即使 `make` 检测到 Makefile 被修改,也**不会自动知道这些编译参数的变化会影响所有源文件**,所以增量构建可能出错。 📌 **结论:这类修改后应执行 `make clean && make`** --- ### ❌ 二、不需要 `make clean` 的情况(可增量构建) 如果你只修改了 Makefile 中不影响最终二进制输出的部分,可以不用 clean。 #### 🔻 常见无需 clean 的修改: | 修改内容 | 说明 | |--------|------| | 修改 `all:` 目标顺序 | 不影响编译过程本身 | | 添加新的自定义 target,比如 `flash:` 或 `debug:` | 只增加功能,不影响已有 `.o` 文件 | | 修改链接脚本路径但未改变内存布局 | 如果 `.o` 未变,链接仍有效 | | 优化 Makefile 结构(变量重命名、提取公共部分) | 只要最终传递给 gcc 的参数不变 | 👉 这些情况下,`make` 会检测到 Makefile 更改,并自动重新构建受影响的部分(通常是全部重新链接),**无需 clean**。 --- ### 🛠️ 三、推荐实践(安全策略) 在大型项目中,建议遵循以下原则: #### ✅ 最佳实践流程: ```bash # Step 1: 修改 Makefile # ... 编辑 Makefile ... # Step 2: 判断是否涉及编译参数变更 if [ "修改了 CFLAGS、宏定义、include 路径等" ]; then make clean fi # Step 3: 重新构建 make ``` #### 💡 小技巧:让 Makefile 自动触发 clean 你可以添加一个“脏标记”,当关键变量被修改时自动清理: ```makefile # 记录上次使用的 CFLAGS CFLAGS_LAST := $(shell cat .cflags.last 2>/dev/null || echo "") ifneq ($(CFLAGS),$(CFLAGS_LAST)) $(warning CFLAGS changed, forcing clean...) clean:: ; @echo "$(CFLAGS)" > .cflags.last endif # 或者用 timestamp 方式控制 ``` 但这较复杂,一般用于高度自动化系统。 --- ### 🔍 四、如何判断是否“真的需要 clean”? 你可以通过以下方式辅助判断: | 方法 | 命令示例 | 说明 | |------|---------|------| | 查看实际编译命令 | `make V=1` 或 `make VERBOSE=1` | 观察 gcc 命令行参数是否变化 | | 比较前后编译参数 | 记录日志对比 | 参数变了就必须 clean | | 使用 `ninja` 构建系统 | —— | Ninja 能更精确地追踪构建规则依赖,减少手动 clean 需求 | --- ### 🧩 五、特殊情况:内核模块 / Linux Kernel 在 Linux 内核或模块开发中: - 修改 `.config` 或 Kconfig 导致 `AUTOCONF_H` 变化 - 即使只是改了一个宏,也需要 `make clean`(尤其是驱动模块) - 否则可能出现:**加载模块时报符号缺失或版本不匹配** 👉 推荐: ```bash make clean && make -j$(nproc) ``` --- ### ✅ 总结 | 修改类型 | 是否需要 `make clean` | 原因 | |--------|---------------------|------| | 修改 `CFLAGS`, `-D`, `-I`, 优化选项等 | ✅ 是 | 编译参数变,旧 `.o` 不再可信 | | 修改链接脚本、LDFLAGS | ✅ 建议 | 链接行为改变,避免残留问题 | | 添加新 target 或结构调整 | ❌ 否 | 不影响已有编译产物 | | 修复 Makefile 语法错误 | ❌ 否(如果参数没变) | make 会自动重建 | > 📌 **保守建议:只要改动涉及编译器命令行参数,就 `make clean`。宁可多清一次,也不要留下潜在 bug。** ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值