解决Makefile中$ARCH变量未设置的问题

0 次阅读

Makefile 中出现 $ARCH 变量未设置,通常不是 Make 本身无法识别变量,而是Make 变量、Shell 环境变量以及命令行参数之间的作用域和语法混用导致的。尤其是在编译 Linux 内核、交叉编译项目、C/C++ 工程或者使用不同 CPU 架构进行构建时,ARCH 是一个非常常见的变量。

如果 Makefile 中直接使用 $ARCH,但执行 make 后发现它为空,甚至进一步导致编译路径、工具链或目标架构判断错误,可以从变量定义方式、引用语法和环境传递三个方面进行排查。

一、Makefile 中的 $ARCH 到底是什么

ARCH 通常用于表示目标平台架构,例如:

Makefile
ARCH = x86_64

或者:

Makefile
ARCH = arm64

然后根据架构执行不同的编译逻辑:

Makefile
ifeq ($(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)

例如:

Makefile
all:
	echo $(ARCH)

二、最直接的解决方法:显式定义 ARCH

如果项目要求使用固定架构,可以直接在 Makefile 中定义:

Makefile
ARCH := x86_64

完整示例:

Makefile
ARCH := x86_64

all:
	@echo "Target architecture: $(ARCH)"

执行:

Bash
make

输出:

Target architecture: x86_64

如果目标平台是 ARM64,则可以改成:

Makefile
ARCH := arm64

这种方式简单直接,适合架构不会随着构建环境变化的项目。

不过,如果同一份 Makefile 需要同时支持 x86、ARM、RISC-V 等架构,就不建议把架构硬编码在 Makefile 中。

三、推荐通过命令行传递 ARCH

更灵活的方式是执行 make 时指定变量:

Bash
make ARCH=x86_64

Makefile:

Makefile
all:
	@echo "ARCH=$(ARCH)"

执行:

Bash
make ARCH=arm64

即可得到:

ARCH=arm64

这种方式特别适合跨平台编译。

例如:

Bash
make ARCH=arm64

或者:

Bash
make ARCH=riscv

也可以根据实际项目支持的架构选择对应值。

命令行变量通常比 Makefile 内部普通赋值具有更高的优先级,因此可以方便地覆盖默认配置。

四、给 ARCH 设置默认值

如果希望用户不指定 ARCH 时自动使用某个架构,可以使用 ?=

Makefile
ARCH ?= x86_64

完整示例:

Makefile
ARCH ?= x86_64

all:
	@echo "ARCH=$(ARCH)"

直接运行:

Bash
make

得到:

ARCH=x86_64

指定其他架构:

Bash
make ARCH=arm64

得到:

ARCH=arm64

这种方式通常比直接使用:

Makefile
ARCH := x86_64

更加合理,因为它既提供了默认值,又允许外部覆盖。

五、不要混淆 Make 变量和 Shell 变量

这是 $ARCH 问题中最常见的原因之一。

例如:

Makefile
ARCH = arm64

all:
	echo $ARCH

很多人以为会输出:

arm64

但实际上并不是这么回事。

Make 首先处理配方中的 $,而 $A 会按照 Make 的单字符变量规则进行解析。因此应该写成:

Makefile
all:
	echo $(ARCH)

或者,如果确实需要让 Shell 读取环境变量,则应该使用:

Makefile
all:
	echo $$ARCH

这里有一个非常重要的区别:

Makefile
$(ARCH)

表示 Make 变量

Makefile
$$ARCH

表示把 $ARCH 原样传递给 Shell

例如:

Makefile
export ARCH=arm64

all:
	echo $(ARCH)
	echo $$ARCH

两个命令最终都可以获得相应的值,但变量展开发生的阶段不同。

六、为什么 Shell 中的 $ARCH 可能为空

假设 Makefile:

Makefile
all:
	echo $$ARCH

执行:

Bash
make

如果当前 Shell 环境中没有设置 ARCH

Bash
echo $ARCH

自然不会得到任何内容。

可以先检查环境变量:

Bash
echo $ARCH

如果没有输出,再执行:

Bash
export ARCH=arm64

然后:

Bash
make

此时:

Bash
echo $ARCH

就会得到:

arm64

也可以在执行 Make 时直接传递:

Bash
ARCH=arm64 make

这种写法只对当前命令有效,不会永久修改当前 Shell 环境。

七、使用 export 让 ARCH 传递给子进程

Make 中还有一个容易被忽略的问题:Make 自己知道 ARCH,并不意味着 Shell 子进程一定能通过环境变量读取它。

例如:

Makefile
ARCH := arm64

all:
	echo $$ARCH

这里的:

$(ARCH)

是 Make 变量,而:

$$ARCH

要求 Shell 环境中存在 ARCH

如果需要将 Make 变量导出到环境,可以使用:

Makefile
export ARCH

例如:

Makefile
ARCH := arm64
export ARCH

all:
	echo $(ARCH)
	echo $$ARCH

这样 Make 和其启动的子进程都可以使用 ARCH

也可以直接写:

Makefile
export ARCH := arm64

这是一种比较简洁的写法。

八、检查 ARCH 是否真正传入 Make

遇到变量未设置问题时,可以先使用:

Bash
make -pn | grep '^ARCH'

查看 Make 当前解析到的变量。

也可以在 Makefile 中临时添加:

Makefile
$(info ARCH=$(ARCH))

例如:

Makefile
ARCH ?= x86_64

$(info ARCH=$(ARCH))

all:
	@echo "build..."

执行:

Bash
make

如果输出:

ARCH=x86_64

说明 Make 已经正确获得变量。

如果输出:

ARCH=

则需要继续检查变量来源。

九、检查变量来源和覆盖关系

Make 中 ARCH 可能来自多个地方:

  • Makefile

  • 命令行

  • Shell 环境

  • include 文件

  • 上层 Makefile

  • 构建脚本

  • CI/CD 环境变量

因此不要只检查当前 Makefile。

例如:

Makefile
ARCH ?= x86_64

如果运行:

Bash
make ARCH=arm64

那么最终:

ARCH=arm64

如果使用:

Makefile
ARCH := x86_64

则项目内部可能固定为:

ARCH=x86_64

排查时尤其要关注多个 Makefile 之间是否存在变量覆盖。

十、注意递归调用 make 时的变量传递

大型项目经常存在这样的结构:

Makefile
all:
	$(MAKE) -C subdir

如果子目录 Makefile 也需要使用 ARCH,应该确保变量能够正确传递。

使用:

Makefile
ARCH ?= arm64

all:
	$(MAKE) -C subdir

通常可以利用 MAKEFLAGS 等机制传递命令行变量。

也可以明确导出:

Makefile
export ARCH

然后:

Makefile
all:
	$(MAKE) -C subdir

子 Makefile 中即可使用:

Makefile
all:
	@echo "sub ARCH=$(ARCH)"

需要注意,递归调用 Make 时最好使用:

Makefile
$(MAKE)

而不是直接写:

make

因为 $(MAKE) 能让 GNU Make 更好地处理递归构建和相关参数传递。

十一、Linux 内核编译中的 ARCH 特别容易出问题

ARCH 在 Linux 内核构建中非常常见。

例如编译 ARM64 内核时通常会涉及:

Bash
make ARCH=arm64

如果同时使用交叉编译器,还可能需要:

Bash
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

这里:

ARCH

用于指定目标架构,而:

CROSS_COMPILE

用于指定交叉编译工具链前缀。

如果 Makefile 中错误地写成:

Makefile
echo $ARCH

就可能导致架构判断异常。

应该使用:

Makefile
echo $(ARCH)

或者在需要 Shell 环境变量时:

Makefile
echo $$ARCH

两者不要混用。

十二、常见错误写法与正确写法

错误写法一

Makefile
ARCH = arm64

all:
	echo $ARCH

建议修改为:

Makefile
ARCH = arm64

all:
	echo $(ARCH)

错误写法二

Makefile
ARCH = arm64

all:
	echo $$ARCH

如果没有:

Makefile
export ARCH

Shell 可能无法获取 Make 中的 ARCH

可以修改为:

Makefile
export ARCH = arm64

all:
	echo $$ARCH

错误写法三

Makefile
ARCH ?= arm64

build:
	make -C kernel

建议使用:

Makefile
ARCH ?= arm64

build:
	$(MAKE) -C kernel

如果子进程还需要通过环境变量访问,则可以增加:

Makefile
export ARCH

十三、推荐的 Makefile 写法

对于一个需要兼容不同架构的项目,可以采用下面的结构:

Makefile
ARCH ?= 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)"

构建默认架构:

Bash
make

指定 ARM64:

Bash
make ARCH=arm64

指定 RISC-V:

Bash
make ARCH=riscv64

这种设计可以把架构选择和编译器选择关联起来,避免用户频繁修改 Makefile。

十四、如何快速定位 $ARCH 未设置

实际排查时,可以按照下面的顺序处理。

首先检查 Shell:

Bash
echo $ARCH

然后检查 Make:

Bash
make -pn | grep '^ARCH'

接着搜索项目中的定义:

Bash
grep -R "ARCH" Makefile */Makefile 2>/dev/null

重点查看是否存在:

Makefile
ARCH =

或者:

Makefile
ARCH :=

以及:

Makefile
ARCH ?=

然后检查代码中是否错误使用:

$ARCH

如果是在 Makefile 中,优先改成:

$(ARCH)

如果是希望 Shell 展开,则使用:

$$ARCH

最后检查是否需要:

Makefile
export ARCH

这样基本可以覆盖大多数 $ARCH 变量为空、架构识别失败以及交叉编译配置异常的问题。

十五、总结

解决 Makefile 中 $ARCH 变量未设置问题,最关键的并不是简单地给 ARCH 赋值,而是先判断变量究竟由谁管理、在哪个阶段展开

最常用的正确方式可以归纳为:

Makefile
ARCH ?= arm64

Makefile 中引用:

Makefile
$(ARCH)

需要传递给 Shell 时:

Makefile
$$ARCH

需要让子进程通过环境变量获取时:

Makefile
export ARCH

命令行指定架构时:

Bash
make ARCH=arm64

如果只是记住一个原则,可以记住:Makefile 中的 ARCH 是 Make 变量,优先使用 $(ARCH);只有明确需要 Shell 展开时,才使用 $$ARCH 正确区分这两种变量引用方式,通常就能解决大部分 $ARCH 未设置或取值异常的问题。