
OpenHarmony 构建踩坑:带空格的 DevEco 路径让 clang 报 Files\Huawei\DevEco
本文部分或全部内容由 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-mobile2 → ohrs 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_HOME、OHOS_NDK_HOME 等统统塞给 ohrs build。所以我只 setx OHOS_NDK_HOME 是白费力气,会被它覆盖回那个带空格的路径。
临时绕过:8.3 短路径
搞清楚覆盖逻辑之后,就有了一个临时绕过方案——把 OHOS_HOME(cargo-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~1,DevEco Studio 的短名是 DEVECO~1,这样整个路径就没空格了。重新开终端再跑,ring 果然编译通过了。
但这只是「绕过」,不是「治根」——8.3 短名是可能被禁用的,而且默认带空格的安装路径本来就该被支持。
治根:追到 ohrs 源码
真正的问题出在 ohrs 的 cli/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 |
两个消费方的解析方式不一样,但都会被这个路径坑到:
- cc-rs:默认按 ASCII 空白切分,空格直接把
-IC:\Program Files\...切成-IC:\Program和Files\...两个假参数——这就是报错来源。 - 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,希望下一个撞上这个报错的人能少走点弯路。
非商业转载请注明出处,商业转载请联系作者获得授权。
For non-commercial use, please indicate the source. For commercial use, please contact the author for authorization.
View license