最近在给一台鸿蒙PC做命令行工具适配折腾下来发现整个工作流最舒服的组合就是Windows侧开WSL Ubuntu负责交叉编译编译出来的命令通过鸿蒙自带的hdc工具直接推到鸿蒙PC上运行。这条路走通之后从写完代码到在鸿蒙PC上看到输出基本能控制在十几秒以内整个开发节奏完全不一样了。这篇文章就围绕“WSL Ubuntu下交叉编译的命令高效传输到鸿蒙PC”这一条主线把环境搭建、工具链验证、传输方案对比、脚本化工作流和实际踩坑完整过一遍。适合正在做鸿蒙PC命令行工具移植、自研小工具部署或者被“编译完怎么送过去”这个环节卡住的开发者参考。1. 为什么是WSL Ubuntu这套环境选型的背后逻辑1.1 先搞清楚“命令适配”适配的是什么很多人第一眼看到“鸿蒙PC命令行适配”会觉得有点抽象。说得直白一点鸿蒙PC上跑的是鸿蒙系统它的内核形态和文件系统布局跟普通Linux发行版有差异系统里自带的命令行工具集也跟Ubuntu那套不完全一样。你平时在Linux服务器上用得顺手的那些自编命令、自研小工具大概率没法直接拷过去跑——不是缺动态库就是架构不匹配再或者系统里压根没有对应的shell环境。我这次要做的是把一个在Linux下开发好的命令行工具移植到鸿蒙PC上。这个工具本身是用C写的依赖不多但必须能在这个设备的命令行环境里直接执行。开发机是Windows笔记本目标机是鸿蒙PC这就引出了两个绕不开的问题第一编译环境放在哪第二编译出来的东西怎么送过去。1.2 WSL Ubuntu比虚拟机、双系统强在哪如果目标只是“有个Linux环境能编译”那选择其实不少装个虚拟机、搞双系统、用Docker Desktop、或者开WSL。但真正跑起来之后WSL的优势会变得非常明显。首先是资源占用。虚拟机要分配固定内存和CPU双系统还得重启切换而WSL2是轻量级的Ubuntu 22.04跑起来之后内存占用大概几百兆跟Windows侧完全共存随时开一个终端就能进Linux环境不需要关机重启也不用为虚拟机预留磁盘空间。其次是文件互通。WSL2里可以直接访问Windows的磁盘目录路径是/mnt/c/、/mnt/d/这种形式。鸿蒙SDK如果放在Windows的D盘在WSL里直接就能引用。反过来Windows侧的程序也可以被WSL调用——比如鸿蒙的hdc工具是Windows下的exeWSL里可以直接带上完整路径去执行它。第三是网络和内核。WSL2跑在Hyper-V虚拟化层上有自己的完整Linux内核网络行为和普通Linux基本一致对于交叉编译这种场景完全够用。不过这里有个非常重要的实操坑编译项目文件别放在/mnt/c/或/mnt/d/下。WSL2访问Windows文件系统要经过9P协议转换IO性能非常差编译大项目时能明显感觉到慢。正确做法是项目放在WSL自己的文件系统里比如~/workspace/Windows侧的文件通过软链接或拷贝方式引进来。1.3 交叉编译的三个关键要素交叉编译这个概念用一句话说就是在一台机器上编译出另一台机器能运行的程序。我现在是x86_64架构的WindowsWSL环境目标鸿蒙PC是aarch64架构所以需要的是“x86编译、aarch64运行”。交叉编译能否成功核心看三件事编译器必须是对应目标架构的编译器或者一个支持--target参数指定目标架构的编译器比如clang。目标三元组target triple告诉编译器“我要生成什么架构的代码”例如aarch64-linux-ohos。sysroot目标系统的头文件和库文件集合。交叉编译时编译器默认找的是本机的头文件和库但本机是x86_64的必须让它去指定的sysroot目录里找aarch64的头文件和库否则编译能过、链接必挂。这三件事缺一不可。我后面用鸿蒙SDK自带的clang工具链一条命令就把这三件事全解决了这也是整个工作流能跑通的基础。2. 交叉编译工具链的安装与最小验证2.1 把鸿蒙SDK和WSL环境准备利索第一步先在Windows上把WSL功能打开装一个Ubuntu 22.04。这个流程网上资料很多简单说就是管理员PowerShell里执行wsl --install然后跟着提示建用户名密码就行。装好之后建议先执行一遍系统更新sudo apt update sudo apt upgrade -y然后装一些基础工具后面会用到sudo apt install -y build-essential file tar gzip curl wget第二步搞定鸿蒙SDK。打开鸿蒙配套的IDE在设置里找到SDK管理把Native工具链那一部分下载下来。下载完的SDK目录里有一个关键子目录叫native里面通常能看到llvm和sysroot这两个目录。llvm下是交叉编译器sysroot下是aarch64的头文件、运行时库和链接脚本。拿到SDK路径后在WSL的~/.bashrc里加两个环境变量后面用起来会省很多事export OHOS_SDK_HOME/mnt/d/ohos-sdk export OHOS_NATIVE_HOME$OHOS_SDK_HOME/native注意/mnt/d/ohos-sdk是按我自己的存放位置写的你装在哪就改成哪。加完执行source ~/.bashrc让它生效。这里建议把SDK放在Windows侧路径而不是WSL里原因是SDK体积不小而且鸿蒙IDE在Windows侧也会用到放Windows侧两边共享省一份磁盘空间。代价是访问速度慢一点但编译时头文件读取量不大体感不明显。2.2 用一行clang命令完成首次交叉编译环境就绪之后写一个最简单的C程序验证工具链是否正常。新建hello.c#include stdio.h int main() { printf(Hello from HarmonyOS PC\n); return 0; }然后执行交叉编译$OHOS_NATIVE_HOME/llvm/bin/clang \ --targetaarch64-linux-ohos \ --sysroot$OHOS_NATIVE_HOME/sysroot \ -O2 \ -o hello hello.c命令不长但信息量很大。--targetaarch64-linux-ohos是告诉编译器目标架构--sysroot是指定鸿蒙系统的头文件和库位置-O2是优化级别-o hello是输出文件名。编译完成后用file命令验证产物架构file hello如果一切正常会输出类似这样的信息hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked看到ARM aarch64就说明交叉编译成功了。这个aarch64不是我自己拍脑袋定的是先通过hdc shell uname -m确认了目标鸿蒙PC确实是这个架构。你实际操作时先确认目标机架构再选target别默认跟我一样。2.3 静态链接与动态链接怎么选编译小工具的时候我强烈建议优先考虑静态链接。在编译命令后面加一个-static参数就能让编译出来的可执行文件不再依赖任何动态库单独一个文件拷贝过去就能跑。$OHOS_NATIVE_HOME/llvm/bin/clang \ --targetaarch64-linux-ohos \ --sysroot$OHOS_NATIVE_HOME/sysroot \ -O2 -static \ -o hello hello.c静态链接的好处是省心。鸿蒙PC系统的动态库跟Ubuntu不全一样你本机能链接的libc.so.6版本目标机上不一定对得上。静态链接等于把所有依赖打包进了可执行文件彻底绕开动态库兼容问题。代价就是体积变大一个原本几十KB的程序可能变成几百KB甚至上兆。但对于命令行工具来说这个体积完全可以接受。如果非要动态链接那就得做好传输动态库的准备这个坑我在第5章会专门讲。3. 给鸿蒙PC传文件的几种实操方式与效果对比编译产物有了接下来就是“高效传输”这个核心问题。我在不同阶段试过四种方式各有各的适用场景。3.1 hdc file send最常规也最稳的方案hdc是鸿蒙系统的设备连接管理工具功能类似Android的adb文件传输、远程shell、日志抓取都能干。日常开发中最顺手的传输方案就是它。先确认设备连接状态。在Windows的命令行或WSL里执行hdc list targets能列出目标设备就说明连接正常。如果列不出来大概率是hdc服务没启动先执行hdc start再试。文件传输命令非常简单hdc file send ./hello /data/local/tmp/hello前面是本地文件路径后面是鸿蒙PC上的目标路径。传输完成后接着做两件事加执行权限、运行验证hdc shell chmod x /data/local/tmp/hello hdc shell /data/local/tmp/hello这里有个细节如果在WSL里直接执行hdc命令提示找不到说明hdc是Windows侧的工具WSL里没有。解决办法是直接用Windows侧hdc.exe的全路径调用比如/mnt/c/tools/hdc/hdc.exe file send ./hello /data/local/tmp/hello或者把Windows侧hdc.exe所在目录加到WSL的PATH里这样两边命令都能用。鸿蒙SDK配套工具目录下的hdc位置每个人不一样自己找一下。3.2 局域网HTTP下载适合大文件批量包当需要传输的不只是单个命令而是一整个包比如带配置、带依赖库、带多个脚本的发布包的时候用hdc file send一遍遍传就觉得笨重了。这时候我更喜欢起一个临时HTTP服务让鸿蒙PC自己来拉。在WSL里找到要传输的目录执行python3 -m http.server 8080然后进hdc shell用wget或curl拉取hdc shell wget http://Windows主机的局域网IP:8080/release.tar.gz -O /data/local/tmp/release.tar.gz这里需要确认鸿蒙PC的命令行环境里带了wget或curl。我实测的这台设备有但不同设备情况不同先跑一下which wget确认没有的话就得回到hdc方案。还需要确认WSL和鸿蒙PC在同一个局域网内WSL2默认是NAT网络一般在Windows能ping通鸿蒙PC的局域网里WSL也能通但偶尔会有网络模式问题。遇到WSL里python服务起来、鸿蒙PC却访问不了的情况可以用Windows侧开启HTTP服务代替先把传输跑通再回来折腾网络。3.3 四种传输方式横评对比把试过的方式放一起对比结论就清晰了传输方式依赖条件单次操作复杂度传输大文件效率适合场景hdc file send设备已连接hdc并授权低一条命令一般日常单文件、集成到脚本局域网HTTP下载鸿蒙PC自带wget/curl低起服务拉取高带宽吃满批量包、大文件发布scp远程拷贝鸿蒙PC运行ssh服务端中需要ip和账号高设备具备完整Linux用户态时Windows共享目录鸿蒙PC支持挂载CIFS高挂载配置复杂高长期频繁同步但配置成本高我的使用习惯是开发迭代期单文件命令直接用hdc打发布包或批量传工具集就用HTTP方式scp和共享目录用得少因为很多鸿蒙PC默认不跑sshd共享目录挂载又要改设备配置开发阶段不值得折腾。核心原则是“把链路缩短把变量减少”传输方式越简单出问题的概率越低。4. 从手动传文件到一键同步工作流脚本化手动执行编译、传输、运行这三步一次两次还行迭代几十次之后就会非常痛苦。我的做法是把整条链路写进脚本让“编译传输远程执行”变成一条命令的事。4.1 编译-传输-运行三步合一的构建脚本先写一个基础的构建脚本build.sh#!/bin/bash set -e OHOS_CLANG$OHOS_NATIVE_HOME/llvm/bin/clang SYSROOT$OHOS_NATIVE_HOME/sysroot TARGETaarch64-linux-ohos echo [1/3] 交叉编译 $OHOS_CLANG --target$TARGET --sysroot$SYSROOT -O2 -static -o build/hello hello.c echo [2/3] 传输到鸿蒙PC hdc file send build/hello /data/local/tmp/hello echo [3/3] 远程运行 hdc shell chmod x /data/local/tmp/hello hdc shell /data/local/tmp/hello这个脚本每次会先清空旧编译产物、重新编译然后传输、运行。set -e保证任何一步失败就立刻退出避免传了个坏文件过去还往下走。实际用下来最影响效率的反而不是编译而是hdc在每次连接时的握手延迟。如果传输完紧接着执行远程命令hdc可能有一段连接初始化时间体感上会多等几秒。我的处理方式是脚本里做一次hdc start确保hdc服务已就绪然后再执行file send和shell命令整体顺畅很多。4.2 基于文件校验的增量跳过逻辑开发进入稳定期后经常出现“代码没改只是单纯想跑一下看看结果”的情况。每次都重编重传虽然命令不复杂但耗时和等待会慢慢消磨耐心。我给脚本加了一个增量判断先编译出两个版本的产物对比与上一次的差异只有变化了才做传输。最简单可靠的方式是用校验值对比if [ -f build/hello.md5 ] md5sum -c build/hello.md5 --status; then echo 编译产物无变化跳过传输 else hdc file send build/hello /data/local/tmp/hello md5sum build/hello build/hello.md5 fi原理不复杂上一次传输成功后把产物哈希记到build/hello.md5文件里这次构建完成后先算新哈希跟记录比一下一样就跳过传输。因为开发期改动往往是单文件小改动编译本身很快而hdc传输加设备握手可能比编译还慢所以这个跳过逻辑对体验的提升非常明显。4.3 把高频命令做成别名脚本是给“整条链路”用的但实际开发中还有很多零碎操作比如只传不动、只连不进、只抓日志。这些都值得做成短命令减少键盘输入量。在~/.bashrc里加几个函数function hp() { hdc shell $ } function hpush() { hdc file send $1 /data/local/tmp/ echo 已传输到 /data/local/tmp/文件名: $(basename $1) }之后想进鸿蒙PC命令行就敲hp ls /data/local/tmp想传个文件就敲hpush ./hello。看似只是省了几个字母但高频操作下这种小优化累计起来能省下不少时间和心智负担。5. 传输链路里的隐藏坑权限、动态库、缓存与路径整个工作流跑通之后我原以为就完事了。真到用起来才发现传输本身只是第一步传输之后能不能跑、跑得对不对才是真正消耗时间的环节。5.1 传输成功却无法执行的三个常见原因文件明明传过去了hdc shell ls也能看到但一执行就给“Permission denied”或者“No such file or directory”这种问题我至少遇到三次。按排查优先级排列最常见的原因有三个一是缺执行权限。hdc file send只负责把文件内容拷过去不会保留可执行权限。传到鸿蒙PC上之后文件的权限位是什么样完全取决于目标目录和家长目录的默认umask。解决办法很简单每次传完执行一次chmod x脚本里别忘了这一步。二是动态依赖缺失。如果编译时没加-static目标机上又恰好没有对应的动态库执行时会直接报错。这个问题的排查链路我单独放在5.2讲。三是架构不匹配。传了个x86_64的二进制到aarch64的鸿蒙PC上file命令一看就知道。这个问题看起来低级但容易发生在“之前编译过一个x86版本后来又给aarch64编译过两个build目录混了”的情况下所以脚本里我固定了输出目录build/并且每次编译前先清理避免旧产物残留。5.2 动态依赖库缺失的完整排查链路有段时间我出于体积考虑编译时没用静态链接结果在鸿蒙PC上执行时冒出类似“cannot find shared library”的错误。那次排查让我把动态库依赖的链路彻底摸清了。第一步在WSL里对编译产物执行ldd hello查看它的动态库依赖ldd hello会看到一连串libc.so.6 ...、libm.so.6 ...之类的输出。注意WSL是x86_64环境ldd看到的是本机对应库的信息但依赖列表本身是有参考价值的。重点看它到底依赖哪些库然后去鸿蒙PC的sysroot里找对应文件。第二步在鸿蒙PC上确认这些库是否存在。用hdc进入目标机到动态库目录里查找hdc shell find / -name libc.so.6 2/dev/null如果某个库在目标机上确实不存在有两个选择把库里缺失的.so文件单独传上去放到程序能找到的目录然后设置LD_LIBRARY_PATH指向该目录或者干脆改用静态链接把整个环节彻底砍掉。我最终选择了-static静态链接因为对于命令行小工具来说体积增加完全能接受但“动态库缺失”这一类问题直接清零。开发期求稳发布时再根据场景优化体积这个思路更务实。5.3 缓存、路径与“玄学问题”排查链路走完之后还剩两类问题它们不常出现但一旦出现非常迷惑人。一类是hdc服务自身状态异常。表现为文件send命令提示成功但远程目录看不到新文件或者设备列表时有时无。这时候先执行hdc kill再hdc start让服务重新初始化。绝大多数后续问题都能靠这个操作解决它相当于给hdc一次“重启”很多莫名奇妙的缓存状态会被清掉。另一类是路径习惯带来的问题。Windows侧路径分隔符是\WSL里是/鸿蒙PC命令行里也是/。在脚本里拼接路径时一个不小心就会拼出带反斜杠的路径传到远端后程序找不到文件。我的建议是脚本里统一用正斜杠并给目标路径加双引号防止空格和特殊字符导致路径被拆开。另外提醒一句开发期传到/data/local/tmp很省事但那个目录是临时性质的设备重启后文件可能会丢。如果工具需要长期保留在鸿蒙PC上应该放到系统里专门的应用程序目录并注意目录权限。开发期就无所谓重启丢了重新推一下就好。最后分享一个我自己习惯的小细节构建产物的目录和文件名始终保持固定比如统一放在build/目录、固定叫hello。这样脚本里所有路径都是确定的不会出现一会儿叫hello一会儿叫hello_v2、最后分不清哪个是最新版本的情况。整个工作流跑顺之后核心体验就是“命令越短越好、路径越稳定越好、链路越短越好”。这套WSL交叉编译加hdc高效传输的思路在鸿蒙PC命令行工具开发里目前用下来是最平衡的选择。
阅读完成 · 觉得有帮助?