Makefile中为何始终使用$(MAKE)而非直接使用make?
$(MAKE)而非直接写make? 这个问题问得非常好!其实除了你已经发现的并行构建支持,$(MAKE)还有几个容易被忽略但至关重要的深层机制,结合权威资料和实践经验,我来逐一拆解:
跨环境与工具链的兼容性
不同操作系统和环境下,make这个命令对应的程序可能不一样:比如在BSD系统中默认的是BSD Make,而GNU Make通常叫gmake;在Windows的MinGW环境中,可能是mingw32-make。$(MAKE)是Make工具自身设置的内置变量,它指向当前正在运行的那个Make程序——也就是说,如果你顶层调用的是gmake,子Makefile里的$(MAKE)也会自动用gmake,完全不需要手动修改。如果硬编码写make,在非GNU Make主导的环境里很可能直接构建失败。自动传递命令行参数与全局变量
当你在顶层执行make时带上参数(比如make -j8 CFLAGS=-O2 --warn-undefined-variables),$(MAKE)会自动把这些参数和你设置的全局变量传递给递归调用的子Makefile。比如子目录的编译会继承-j8的并行设置,以及CFLAGS=-O2的编译选项,保证整个项目构建的一致性。如果直接写make,这些参数和变量都不会被传递,子构建会用默认配置,轻则出现编译选项不一致,重则并行构建直接失效。正确处理递归构建的层级逻辑
Make工具内置了MAKELEVEL变量,用来标识当前递归构建的深度(顶层调用时MAKELEVEL=0,每递归一次加1)。很多项目会用这个变量做逻辑判断:比如顶层Makefile负责生成全局依赖文件、打包发布,而子目录的Makefile只负责编译源码。如果用$(MAKE),这个变量会被正确传递和更新;要是硬写make,MAKELEVEL可能不会被正确设置,导致子Makefile的逻辑出错。遵循POSIX标准与行业最佳实践
POSIX规范里明确建议在递归构建中使用$(MAKE),这是保证Makefile可移植性的标准做法。像你提到的O'Reilly书籍、Stack Overflow高赞回答都强调这一点,本质上是因为它能避免各种环境下的隐性问题,让你的构建脚本在不同平台上都能稳定工作。并行构建的深层协调
你已经发现$(MAKE)支持并行构建,但其实它的作用不止是替换命令:顶层Make会通过$(MAKE)来协调所有子进程的并行任务,避免资源冲突(比如多个子Make同时修改同一个全局文件)。如果直接写make,相当于启动了独立的Make进程,顶层Make无法协调这些进程,很可能出现并行构建时的竞争条件,导致构建失败或者生成损坏的文件。
内容的提问来源于stack exchange,提问作者Trevor Boyd Smith

