跳到内容

OpenHarmony 构建踩坑:带空格的 DevEco 路径让 clang 报 Files\Huawei\DevEco

更新于
约 6 分钟阅读
·
1039 字
OpenHarmony
#Rust#Tauri#OpenHarmony#clang#ohrs#踩坑记录
🛈
本文由 AI 辅助生成

本文部分或全部内容由 AI 辅助撰写。AI 生成的内容可能存在不准确之处,请读者注意甄别。

最近在给 LiveAgent 的移动端 agent-mobile(Tauri v2)做 HarmonyOS 适配,跑 cargo tauri ohos build 的时候撞上了一个非常经典的 Windows 路径问题。最终一路追到了 ohrs 的源码里,顺手给上游提了个 PR。这篇文章把整个排查和根治过程记下来。

报错长这样

构建进行到 ring 这个 crate 的时候,突然挂掉:

clang: error: no such file or directory: 'Files\Huawei\DevEco'

ring 的 build script 是拿 cc 来编译 C 代码的,它把 TARGET_CFLAGS 里的参数按空格切开后喂给 clang。这个报错其实已经剧透了根因:某个路径被空格切成了两半,C:\Program Files\Huawei\DevEco Studio\... 里的 Files\Huawei\DevEco 被当成了一个「文件参数」。

DevEco Studio 的默认安装路径是 C:\Program Files\Huawei\DevEco Studio\带空格——这本身就是个很正常的路径,理论上工具链没理由不支持。

第一轮:以为是环境变量的问题

最开始我以为是 OHOS_NDK_HOME 没设对。因为 ohrs 是靠读这个环境变量来定位 SDK 的。我甚至搞了个 junction(目录软链接)想把它指到一个无空格的路径上,结果:

  • 改了 OHOS_NDK_HOME没用,报错照旧;
  • 连重启电脑都试了,还是没用

后来才发现关键点:构建链路上 cargo tauri ohos build → tauri-cli(open-harmony 分支)→ cargo-mobile2ohrs build,中间 cargo-mobile2 会自己覆盖 OHOS_NDK_HOME

它的 env.rs 里是按这个顺序解析 SDK 路径的:

OHOS_HOME → DEV_ECO_STUDIO_INSTALL_PATH → 硬编码的 C:\Program Files\Huawei\DevEco Studio

再通过 explicit_env()OHOS_HOMEOHOS_NDK_HOME 等统统塞给 ohrs build。所以我只 setx OHOS_NDK_HOME 是白费力气,会被它覆盖回那个带空格的路径。

临时绕过:8.3 短路径

搞清楚覆盖逻辑之后,就有了一个临时绕过方案——把 OHOS_HOMEcargo-mobile2 优先读它)设成 Windows 的 8.3 短路径

setx OHOS_HOME "C:\PROGRA~1\Huawei\DEVECO~1\sdk\default\openharmony"
setx OHOS_NDK_HOME "C:\PROGRA~1\Huawei\DEVECO~1\sdk\default\openharmony"

C:\Program Files 的短名是 C:\PROGRA~1DevEco Studio 的短名是 DEVECO~1,这样整个路径就没空格了。重新开终端再跑,ring 果然编译通过了。

但这只是「绕过」,不是「治根」——8.3 短名是可能被禁用的,而且默认带空格的安装路径本来就该被支持

治根:追到 ohrs 源码

真正的问题出在 ohrscli/cargo-ohrs/src/util/ohos.rs 里的 apply_hms_include_env

let include_flag = format!("-I{}", include);

它生成的是一个没加引号、还带反斜杠的 Windows 路径,然后塞进三个环境变量:

环境变量 消费方 解析方式
TARGET_CFLAGS / TARGET_CXXFLAGS cc-rs(ring 的 build script) 默认 split_ascii_whitespace
BINDGEN_EXTRA_CLANG_ARGS_* bindgen 无条件 shlex::split

两个消费方的解析方式不一样,但都会被这个路径坑到:

  1. cc-rs:默认按 ASCII 空白切分,空格直接把 -IC:\Program Files\... 切成 -IC:\ProgramFiles\... 两个假参数——这就是报错来源。
  2. bindgen:用 shlex 解析,而 shlex 在 POSIX 规则下把 \ 当成转义符,会把反斜杠一个个吃掉。

所以光加引号还不够(shlex 会吃掉 \),得先反斜杠转正斜杠,再用引号包起来,让整个 -I<path> 成为一个 token:

let include_flag = format!("-I\"{}\"", include.replace('\\', "/"));

// 让 cc-rs 也改走 shlex 解析(bindgen 本来就走 shlex)
prepare_env.insert(String::from("CC_SHELL_ESCAPED_FLAGS"), String::from("1"));

改动很小,但完全对症:路径没空格的时候是个 no-op,有空格的时候两个消费方都能正确还原成「一个带空格的参数」。

这里有个容易踩的坑值得单独说一句:链接参数不用改-L / -Wl,-rpath-link 走的是 -Clink-arg= + \x1f 分隔符编码的 CARGO_ENCODED_RUSTFLAGS,rustc 是原样透传给链接器的,不经过 shell,所以空格本来就安全。如果照猫画虎给链接参数也加引号,引号反而会变成字面量传给 ld.lld,指向一个不存在的路径。

收获

这个问题绕了一个挺典型的弯路:现象在 ring/cc,但真正的 bug 在更上游的 ohrs 生成的 flag 字符串上,而触发它的是「带空格的默认安装路径」这个再正常不过的前提。

几个值得记住的点:

  • Windows 上「路径带空格」不是异常,是默认;工具链不该假设路径无空格。
  • 排查时先搞清楚每一层是谁在传值——这里 cargo-mobile2 覆盖环境变量的行为,让「设 OHOS_NDK_HOME」这个直觉解法完全失效。
  • 加引号之前,先确认消费方用的是不是 shlex,以及 shlex 会怎么处理反斜杠。

修复已经提了 PR 给 ohos-rs/ohos-rs,希望下一个撞上这个报错的人能少走点弯路。

CC BY-NC-SA 4.0

非商业转载请注明出处,商业转载请联系作者获得授权。

For non-commercial use, please indicate the source. For commercial use, please contact the author for authorization.

View license

评论