如何让Makefile成功终止不执行后续任务并返回成功退出码
问题场景
我有一个配置了应急引导步骤的Makefile:若运行任务前未完成关键环境初始化,会自动执行环境引导流程,相关代码如下:
bootstrap: ensure_opam_switch_once build .ONESHELL: $(FRONTEND_OPAM_SWITCH): @echo '' @echo "$(bold)[make bootstrap]$(sgr0) An opam-switch doesn't exist for frontend/, creating one ..." OPAMYES=true $(MAKE) init opam exec --sw="${FRONTEND_DIR}" -- $(MAKE) bootstrap @EXIT_CODE=$$? @exit $$EXIT_CODE ensure_opam_switch_once: | $(FRONTEND_OPAM_SWITCH) init: # this does critical first-time setup build: # ... everything else happens here
现存问题
Makefile的后续执行流程依赖多个shell环境变量,这些变量的具体内容无法预先获知:opam会通过eval配置部分环境变量,机制与rbenv、nvm、pyenv等版本管理工具完全一致。
当前临时处理方案为:完成环境初始化后,在已配置好对应shell变量的调用环境中重新执行make任务,即上述示例中的opam exec行。但该方案存在明确缺陷:如果子make执行成功退出,编写的@exit $$EXIT_CODE语句并不会终止Make的运行,Make会继续执行后续的依赖任务;而外层Make调用并没有配置必要的shell环境,必然会导致执行失败。
核心诉求:是否有方法可以让Make以成功的退出码终止运行,不再执行后续任务?
解决方案
问题根因
原有写法中@exit $$EXIT_CODE仅会终止执行$(FRONTEND_OPAM_SWITCH)目标配方的shell进程,Make收到该shell的退出码后,会判定当前目标已执行完成,继续调度后续依赖目标,根本不会主动退出。
最优实现(无副作用)
使用shell的exec内置命令直接替换当前进程,不要给外层Make留下继续执行的机会。修改$(FRONTEND_OPAM_SWITCH)目标的配方即可:
.ONESHELL: $(FRONTEND_OPAM_SWITCH): @echo '' @echo "$(bold)[make bootstrap]$(sgr0) An opam-switch doesn't exist for frontend/, creating one ..." OPAMYES=true $(MAKE) init exec opam exec --sw="${FRONTEND_DIR}" -- $(MAKE) bootstrap
实现逻辑
- shell执行到
exec开头的命令时,会直接用后面的命令进程替换当前shell进程,不会再创建新的子进程 - 等带正确环境变量的子make执行完成后,整个进程会直接退出,外层原Make进程完全没有机会继续调度后续的
build等依赖目标 - 子make的退出码会直接作为整个Make调用的最终退出码:执行成功返回0,执行失败返回对应非0错误码,完全符合预期
- 原有写法中捕获退出码、手动exit的逻辑完全多余,可以直接删掉
避坑说明
不要尝试用Make内置的$(error)函数实现终止逻辑,该函数无论传入什么参数,都会让Make返回非0的错误退出码,无法实现成功退出的需求。
内容的提问来源于stack exchange,提问作者ELLIOTTCABLE
相关产品推荐
相关产品推荐

