Skip to content
雲里
里雾

Android Studio 在 Apple Silicon Mac 上无法找到 node 导致 Gradle 同步失败

帽子云 帽子云

Android Studio 在 Apple Silicon Mac 上无法找到 node 导致 Gradle 同步失败

问题描述

在 Apple Silicon Mac 上用 Android Studio 打开 React Native 项目的 android/ 目录,触发 Gradle Sync 时报错:

A problem occurred evaluating project ':react-native-gesture-handler'.
> A problem occurred starting process 'command 'node''

Caused by: java.io.IOException: Cannot run program "node"
(in directory ".../android"): error=2, No such file or directory

后续还会出现类似的错误,换成其他 native 模块(如 react-native-camera-roll):

A problem occurred evaluating project ':react-native-camera-roll_camera-roll'.
> /.../camera-roll/node_modules/react-native/ReactAndroid/gradle.properties
  (No such file or directory)

根本原因

React Native 的 native 模块(gesture-handler、camera-roll 等)在 Gradle 配置阶段需要调用 node 命令来定位 react-native 包的路径,大致逻辑如下:

commandLine("node", "--print", "require.resolve('react-native/package.json')")

问题在于:macOS GUI 应用启动时,不会 source 任何 shell 配置文件.zshrc.zprofile 等),它从 launchd 继承一个极简的环境变量,其中的 PATH 不包含任何包管理器(nvm、Homebrew 在 Apple Silicon 上)注册的路径。具体来说:

因此 Android Studio 的 Gradle daemon 启动后,执行 commandLine("node", ...) 时找不到可执行文件,构建失败。

Intel Mac 上用 Homebrew 安装 node 通常不受影响,因为 Intel Homebrew 的路径是 /usr/local/bin/,这个路径在系统默认 PATH 中。Apple Silicon 换用了 /opt/homebrew/bin/,不再有这个优待。

尝试过的方案

创建 /usr/local/bin/node symlink

sudo ln -sf $(which node) /usr/local/bin/node

没有解决问题。原因是 /usr/local/bin 虽然在 /etc/paths 里,但 macOS Sequoia 的 launchd 用户域并不通过 path_helper 读取 /etc/paths,GUI 应用实际拿到的 PATH 仍然不包含 /usr/local/bin

gradle.properties 中设置 REACT_NATIVE_NODE_MODULES_DIR

REACT_NATIVE_NODE_MODULES_DIR=../node_modules/react-native

修复了第一个报错(gesture-handler),却引出第二个。原因是相对路径被 camera-roll 的 build.gradle 按照它自己的项目目录(node_modules/@react-native-camera-roll/camera-roll/android/)来解析,最终拼出了一个不存在的路径。

LaunchAgent 注入 PATH

创建 ~/Library/LaunchAgents/dev.hatcloud.set-gui-path.plist,在登录时调用 launchctl setenv PATH ...。在 macOS Sequoia 上没有生效,原因是新版本 launchctl setenv 的行为发生了变化,不再可靠地影响后续启动的 GUI 应用。

最终解决方案

在用户级别的 Gradle 全局 init 脚本中注入路径。

创建文件 ~/.gradle/init.d/react-native-node-path.gradle

allprojects {
    def rnDir = rootProject.file("../node_modules/react-native")
    if (rnDir.exists()) {
        ext.REACT_NATIVE_NODE_MODULES_DIR = rnDir.absolutePath
    }
}

~/.gradle/init.d/ 是 Gradle 的全局初始化目录,其中的脚本会在每次构建开始前自动执行,无论是命令行还是 Android Studio。

rnDir.exists() 的判断保证了这段逻辑只对真正的 React Native 项目生效,普通 Android 项目不受影响。rootProject.file(...).absolutePath 确保路径是绝对路径,不会被各子模块按自己的目录相对解析。

为什么这个方案有效

React Native 的 native 模块 build.gradle 在调用 node 之前都会先检查 rootProject.ext 中是否存在 REACT_NATIVE_NODE_MODULES_DIR,有就直接用,跳过 commandLine("node", ...) 这一步。init 脚本在项目 build.gradle 执行前就把这个 ext 属性设好了,所以所有模块都能命中这个快捷路径。

其他人是怎么解决的

大多数人没有遇到这个问题,因为 React Native 官方文档推荐用 Homebrew 安装 node,在 Intel Mac 上 Homebrew 的路径 /usr/local/bin/ 恰好在系统 PATH 中。Apple Silicon 上使用 nvm 或关注到这个区别的人不多,相关讨论散落在各处,没有形成一个统一的官方答案。

常见的临时方案是从终端启动 Android Studioopen -a "Android Studio"),这样 Android Studio 会继承终端的 PATH。缺点是每次都要记得用命令行打开,从 Dock 启动就又失效了。

Gradle 全局 init 脚本的方案在这种场景下是最干净的:不需要修改任何项目代码,不依赖环境变量,一次配置,所有 RN 项目都受益。


分享这篇文章:
分享到微博 分享到 QQ 分享到 X

Next Post
桃花