Makefile 中出现 $ARCH 变量未设置,通常不是 Make 本身无法识别变量,而是Make 变量、Shell 环境变量以及命令行参数之间的作用域和语法混用导致的。尤其是在编译 Linux 内核、交叉编译项目、C/C++ 工程或者使用不同 CPU 架构进行构建时,ARCH 是一个非常常见的变量。
如果 Makefile 中直接使用 $ARCH,但执行 make 后发现它为空,甚至进一步导致编译路径、工具链或目标架构判断错误,可以从变量定义方式、引用语法和环境传递三个方面进行排查。
一、Makefile 中的 $ARCH 到底是什么
ARCH 通常用于表示目标平台架构,例如:
MakefileARCH = x86_64
或者:
MakefileARCH = arm64
然后根据架构执行不同的编译逻辑:
Makefileifeq ($(ARCH), arm64) CC = aarch64-linux-gnu-gcc else CC = gcc endif
需要特别注意,Makefile 中推荐使用:
Makefile$(ARCH)
而不是:
Makefile$ARCH
虽然 GNU Make 支持某些单字符变量引用形式,但 $ARCH 并不等价于 Shell 中常见的 $ARCH 写法。
Make 对 $ 后面的单字符进行变量展开,因此:
Makefile$A
表示变量 A,而不是变量 ARCH。
也就是说:
Makefile$ARCH
很可能会被 Make 解析成:
$A + RCH
如果变量 A 没有定义,最终结果就可能变成:
RCH
这也是很多人排查 $ARCH 未设置问题时容易忽略的地方。
正确写法应该是:
Makefile$(ARCH)
例如:
Makefileall: echo $(ARCH)
二、最直接的解决方法:显式定义 ARCH
如果项目要求使用固定架构,可以直接在 Makefile 中定义:
MakefileARCH := x86_64
完整示例:
MakefileARCH := x86_64 all: @echo "Target architecture: $(ARCH)"
执行:
Bashmake
输出:
Target architecture: x86_64
如果目标平台是 ARM64,则可以改成:
MakefileARCH := arm64
这种方式简单直接,适合架构不会随着构建环境变化的项目。
不过,如果同一份 Makefile 需要同时支持 x86、ARM、RISC-V 等架构,就不建议把架构硬编码在 Makefile 中。
三、推荐通过命令行传递 ARCH
更灵活的方式是执行 make 时指定变量:
Bashmake ARCH=x86_64
Makefile:
Makefileall: @echo "ARCH=$(ARCH)"
执行:
Bashmake ARCH=arm64
即可得到:
ARCH=arm64
这种方式特别适合跨平台编译。
例如:
Bashmake ARCH=arm64
或者:
Bashmake ARCH=riscv
也可以根据实际项目支持的架构选择对应值。
命令行变量通常比 Makefile 内部普通赋值具有更高的优先级,因此可以方便地覆盖默认配置。
四、给 ARCH 设置默认值
如果希望用户不指定 ARCH 时自动使用某个架构,可以使用 ?=:
MakefileARCH ?= x86_64
完整示例:
MakefileARCH ?= x86_64 all: @echo "ARCH=$(ARCH)"
直接运行:
Bashmake
得到:
ARCH=x86_64
指定其他架构:
Bashmake ARCH=arm64
得到:
ARCH=arm64
这种方式通常比直接使用:
MakefileARCH := x86_64
更加合理,因为它既提供了默认值,又允许外部覆盖。
五、不要混淆 Make 变量和 Shell 变量
这是 $ARCH 问题中最常见的原因之一。
例如:
MakefileARCH = arm64 all: echo $ARCH
很多人以为会输出:
arm64
但实际上并不是这么回事。
Make 首先处理配方中的 $,而 $A 会按照 Make 的单字符变量规则进行解析。因此应该写成:
Makefileall: echo $(ARCH)
或者,如果确实需要让 Shell 读取环境变量,则应该使用:
Makefileall: echo $$ARCH
这里有一个非常重要的区别:
Makefile$(ARCH)
表示 Make 变量。
Makefile$$ARCH
表示把 $ARCH 原样传递给 Shell。
例如:
Makefileexport ARCH=arm64 all: echo $(ARCH) echo $$ARCH
两个命令最终都可以获得相应的值,但变量展开发生的阶段不同。
六、为什么 Shell 中的 $ARCH 可能为空
假设 Makefile:
Makefileall: echo $$ARCH
执行:
Bashmake
如果当前 Shell 环境中没有设置 ARCH:
Bashecho $ARCH
自然不会得到任何内容。
可以先检查环境变量:
Bashecho $ARCH
如果没有输出,再执行:
Bashexport ARCH=arm64
然后:
Bashmake
此时:
Bashecho $ARCH
就会得到:
arm64
也可以在执行 Make 时直接传递:
BashARCH=arm64 make
这种写法只对当前命令有效,不会永久修改当前 Shell 环境。
七、使用 export 让 ARCH 传递给子进程
Make 中还有一个容易被忽略的问题:Make 自己知道 ARCH,并不意味着 Shell 子进程一定能通过环境变量读取它。
例如:
MakefileARCH := arm64 all: echo $$ARCH
这里的:
$(ARCH)
是 Make 变量,而:
$$ARCH
要求 Shell 环境中存在 ARCH。
如果需要将 Make 变量导出到环境,可以使用:
Makefileexport ARCH
例如:
MakefileARCH := arm64 export ARCH all: echo $(ARCH) echo $$ARCH
这样 Make 和其启动的子进程都可以使用 ARCH。
也可以直接写:
Makefileexport ARCH := arm64
这是一种比较简洁的写法。
八、检查 ARCH 是否真正传入 Make
遇到变量未设置问题时,可以先使用:
Bashmake -pn | grep '^ARCH'
查看 Make 当前解析到的变量。
也可以在 Makefile 中临时添加:
Makefile$(info ARCH=$(ARCH))
例如:
MakefileARCH ?= x86_64 $(info ARCH=$(ARCH)) all: @echo "build..."
执行:
Bashmake
如果输出:
ARCH=x86_64
说明 Make 已经正确获得变量。
如果输出:
ARCH=
则需要继续检查变量来源。
九、检查变量来源和覆盖关系
Make 中 ARCH 可能来自多个地方:
-
Makefile
-
命令行
-
Shell 环境
-
include 文件
-
上层 Makefile
-
构建脚本
-
CI/CD 环境变量
因此不要只检查当前 Makefile。
例如:
MakefileARCH ?= x86_64
如果运行:
Bashmake ARCH=arm64
那么最终:
ARCH=arm64
如果使用:
MakefileARCH := x86_64
则项目内部可能固定为:
ARCH=x86_64
排查时尤其要关注多个 Makefile 之间是否存在变量覆盖。
十、注意递归调用 make 时的变量传递
大型项目经常存在这样的结构:
Makefileall: $(MAKE) -C subdir
如果子目录 Makefile 也需要使用 ARCH,应该确保变量能够正确传递。
使用:
MakefileARCH ?= arm64 all: $(MAKE) -C subdir
通常可以利用 MAKEFLAGS 等机制传递命令行变量。
也可以明确导出:
Makefileexport ARCH
然后:
Makefileall: $(MAKE) -C subdir
子 Makefile 中即可使用:
Makefileall: @echo "sub ARCH=$(ARCH)"
需要注意,递归调用 Make 时最好使用:
Makefile$(MAKE)
而不是直接写:
make
因为 $(MAKE) 能让 GNU Make 更好地处理递归构建和相关参数传递。
十一、Linux 内核编译中的 ARCH 特别容易出问题
ARCH 在 Linux 内核构建中非常常见。
例如编译 ARM64 内核时通常会涉及:
Bashmake ARCH=arm64
如果同时使用交叉编译器,还可能需要:
Bashmake ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
这里:
ARCH
用于指定目标架构,而:
CROSS_COMPILE
用于指定交叉编译工具链前缀。
如果 Makefile 中错误地写成:
Makefileecho $ARCH
就可能导致架构判断异常。
应该使用:
Makefileecho $(ARCH)
或者在需要 Shell 环境变量时:
Makefileecho $$ARCH
两者不要混用。
十二、常见错误写法与正确写法
错误写法一
MakefileARCH = arm64 all: echo $ARCH
建议修改为:
MakefileARCH = arm64 all: echo $(ARCH)
错误写法二
MakefileARCH = arm64 all: echo $$ARCH
如果没有:
Makefileexport ARCH
Shell 可能无法获取 Make 中的 ARCH。
可以修改为:
Makefileexport ARCH = arm64 all: echo $$ARCH
错误写法三
MakefileARCH ?= arm64 build: make -C kernel
建议使用:
MakefileARCH ?= arm64 build: $(MAKE) -C kernel
如果子进程还需要通过环境变量访问,则可以增加:
Makefileexport ARCH
十三、推荐的 Makefile 写法
对于一个需要兼容不同架构的项目,可以采用下面的结构:
MakefileARCH ?= x86_64 ifeq ($(ARCH),arm64) CC := aarch64-linux-gnu-gcc else ifeq ($(ARCH),riscv64) CC := riscv64-linux-gnu-gcc else CC := gcc endif all: @echo "ARCH=$(ARCH)" @echo "CC=$(CC)"
构建默认架构:
Bashmake
指定 ARM64:
Bashmake ARCH=arm64
指定 RISC-V:
Bashmake ARCH=riscv64
这种设计可以把架构选择和编译器选择关联起来,避免用户频繁修改 Makefile。
十四、如何快速定位 $ARCH 未设置
实际排查时,可以按照下面的顺序处理。
首先检查 Shell:
Bashecho $ARCH
然后检查 Make:
Bashmake -pn | grep '^ARCH'
接着搜索项目中的定义:
Bashgrep -R "ARCH" Makefile */Makefile 2>/dev/null
重点查看是否存在:
MakefileARCH =
或者:
MakefileARCH :=
以及:
MakefileARCH ?=
然后检查代码中是否错误使用:
$ARCH
如果是在 Makefile 中,优先改成:
$(ARCH)
如果是希望 Shell 展开,则使用:
$$ARCH
最后检查是否需要:
Makefileexport ARCH
这样基本可以覆盖大多数 $ARCH 变量为空、架构识别失败以及交叉编译配置异常的问题。
十五、总结
解决 Makefile 中 $ARCH 变量未设置问题,最关键的并不是简单地给 ARCH 赋值,而是先判断变量究竟由谁管理、在哪个阶段展开。
最常用的正确方式可以归纳为:
MakefileARCH ?= arm64
Makefile 中引用:
Makefile$(ARCH)
需要传递给 Shell 时:
Makefile$$ARCH
需要让子进程通过环境变量获取时:
Makefileexport ARCH
命令行指定架构时:
Bashmake ARCH=arm64
如果只是记住一个原则,可以记住:Makefile 中的 ARCH 是 Make 变量,优先使用 $(ARCH);只有明确需要 Shell 展开时,才使用 $$ARCH。 正确区分这两种变量引用方式,通常就能解决大部分 $ARCH 未设置或取值异常的问题。