Katyusha 1. 前言 起因,是在 第五次龙架构双周会 上看到了一个 悬赏任务:移植 国家中小学智慧教育平台 的桌面端客户端到龙架构,使龙芯平台能够原生运行。 该任务的奖励是一台龙芯 3A6000 笔记本,或一台龙芯 3A6000E 主机。由于我的确非常想要一台 3A6000 笔记本,遂决定接下该任务。 任务并不复杂,但我在移植中也遇到了诸多问题,花费了一周中的的晚间时间。这是我第一次接触 Electron 应用开发,若有纰漏,欢迎大家指正 感谢在移植过程中为我提供帮助的各位。 2. 检查应用 经过观察,发现该应用是一个典型的 Electron 应用。 使用开源工具 imhex 打开应用目录下的 智慧中小学.exe 二进制文件。 也可使用 WinHEX 等十六进制查看工具。 搜索 nodejs 关键字,结果如下: 搜索 Electron 关键字,结果如下: 该应用使用 Nodejs 16.17.1 与 Electron 22.3.27 框架。 3. 旧世界(ABI1.0)移植 3.1. 安装 Nodejs 该部分可参考龙芯官网相关文档。 链接:https://www.loongnix.cn/zh/api/nodejs/ 下载: $ wget http://ftp.loongnix.cn/nodejs/LoongArch/dist/v16.20.2/node-v16.20.2-linux-loong64.tar.gz 解压: $ sudo mkdir /usr/local/nodejs $ sudo tar -zxvf node-v16.20.2-linux-loong64.tar.gz -C /usr/local/nodejs 设置环境变量: 可在终端直接执行 也可以写入文件,如 ~/.bashrc,或 /etc/profile $ export PATH="/usr/local/nodejs/node-v16.20.2-linux-loong64/bin:$PATH" 验证安装: $ node --version v16.20.2 $ npm --version 8.19.4 3.2. 安装 Electron 该部分可参考龙芯官网相关文档。 链接:https://docs.loongnix.cn/electron/doc/list/02.%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E.html 创建一个空文件夹并进入: $ mkdir -p ~/port/clean-build-abi1.0 $ cd ~/port/clean-build-abi1.0 设置 npm 镜像仓库地址: $ npm config set registry https://registry.loongnix.cn:4873 验证设置是否成功: $ npm get config registry config=undefined registry=https://registry.loongnix.cn:4873/ 若不成功,可删除 ~/.npmrc 文件后再试。 设置环境变量: $ export ELECTRON_MIRROR=http://ftp.loongnix.cn/electron/LoongArch/ $ export electron_use_remote_checksums=1 安装: $ npm install electron@22.3.27 --save-dev 检查安装: $ npx electron --version v22.3.27 3.3. 解包应用 将应用在 Windows 平台上安装后,拷贝 app.asar 文件与 asset 文件夹到之前创建的目录中。 位置:C:\Program Files\zxxedu\resources 安装 asar 工具: $ npm install asar --save-dev 解包应用: 将 app.asar 文件内打包的内容物解压到 source 文件夹中 $ npx asar extract app.asar source 为了使解包后的应用能找到正确的文件路径,需要修改目录结构: 创建一个名为 prebuild 的文件夹。 将 source/dist 中的两个文件夹移出到当前目录下的 prebuild 文件夹中。 将 prebuild 文件夹中原本命名为 main 的文件夹改名为 dist。 $ mkdir prebuild $ mv source/dist/* prebuild/ $ mv prebuild/main/ prebuild/dist 应用目前的目录结构应当如下所示: source/package.json 文件记录了部分原应用的信息,可参考期内容用于打包应用。 source 文件夹可删除。 app.asar 文件可删除。 node_modules 文件夹可省略。 $ sudo apt install tree $ tree -L 2 . . ├── asset │ ├── app │ ├── edudoc.ico │ ├── edupdf.ico │ ├── eduppt.ico │ ├── entitlements.mac.plist │ ├── icon.ico │ ├── message │ └── transparent ├── node_modules │ └── (省略) ├── package.json ├── package-lock.json └── prebuild ├── dist └── renderer 3.4. 尝试运行 修改 package.json 文件,在最外层大括号内添加如下内容: "main": "./prebuild/dist/main.js", "scripts": { "start": "electron ." }, 尝试运行: $ npm start 3.4.1. 问题1:日志中的 Error 观察日志,发现出现两个Error。 查找资源文件的路径出错,多往上查找了一层。 这是由于原应用打包后,资源文件的位置与解包后的不同。 在后文中的 package.json 中,通过设置使 asset 目录在打包后放入 resources 目录,解决问题。 23:15:03.239 › Unhandled rejection Error: Failed to load image from path '/home/katyusha/port/asset/app/32.png' at /home/katyusha/port/clean-build-abi1.0/prebuild/dist/webpack:/x-edu-electron-main/src/main/app/tray/index.js:30:22 23:15:03.416 › unhandled promise error catched: Error: Failed to load image from path '/home/katyusha/port/asset/app/32.png' at /home/katyusha/port/clean-build-abi1.0/prebuild/dist/webpack:/x-edu-electron-main/src/main/app/tray/index.js:30:22 3.4.2. 问题2:托盘菜单无法弹出 观察应用,发现在 Windows 上的程序托盘图标,在 Linux 平台上不出现。 原因:资源文件路径错误,导致程序找不到托盘图标所在的路径。 观察应用,发现在 Windows 上的程序托盘右键菜单,在 Linux 平台上无法呼出。 搜索 Windows 平台上菜单项的关键字,并结合 Electron 的 Tray 对象分析。 检查 prebuild/dist/main.js 文件内容,发现一处疑点: ...AppTray.tray.on('right-click')... 可能是由于下方的 AppTray.tray.popUpContextMenu(contextMenu) 方法未生效导致的。 也有可能是 right-click(右键单击) 事件未获取到导致的。 此时,查询 Electron 官方文档: tray.popUpContextMenu([menu, position]) right-click 很遗憾地发现,这两处代码都不支持 Linux 平台。 文档标题旁仅有 Windows 和 macOS 的字样。 因此,该函数必须重写,以正确唤起托盘右键菜单。 经查阅文档,找到解决办法: 将唤起菜单的代码加入 click(左键单击) 事件中,以达到目的。 使用 screen.getCursorScreenPoint() 方法获取鼠标坐标。 使用 menu.popup() 方法唤起托盘菜单,并传入鼠标坐标。 修改后的完整的代码如下: 可在 main.js 中搜索 click 关键字找到相关逻辑。 其中的 external_electron_ 等价于 require() 方法。 TrayWindow.appTray.tray.on('click', () => { TrayWindow.toggleWindow({ showMainWinWhenEmpty: true }); # 以下为新增代码 const defaultMenu = [{ label: '打开主界面', click: () => { window.show(); window.focus(); } }, { label: '重新启动', click: () => { external_electron_.app.relaunch(); external_electron_.app.exit(0); } }, { label: '退出', click: () => { external_electron_.app.exit(0); } }]; const contextMenu = external_electron_.Menu.buildFromTemplate(defaultMenu); const cursorPosition = external_electron_.screen.getCursorScreenPoint(); const { x, y } = cursorPosition; contextMenu.popup({ x: Math.round(x), y: Math.round(y), }); }); 为了方便起见,我将代码整合为一个补丁,0000-fix-tray-menu-can-not-pop-up-on-linux.patch 文件,并放在本文末尾。 将该补丁文件与 prebuild/dist/main.js 文件放置在一起,并执行: $ patch main.js < 0000-fix-tray-menu-can-not-pop-up-on-linux.patch 补丁的制作方法: # main.js 为修改前的文件 # main.js.modified 为修改后的文件 # main.js.patch 为输出的补丁文件 $ diff -u main.js main.js.modified > main.js.patch 3.5. 安装 electron-builder 该部分可参考龙芯官网相关文档。 链接:https://docs.loongnix.cn/electron/doc/list/04.electron-builder%E7%9A%84%E4%BD%BF%E7%94%A8.html 设置环境变量: $ export ELECTRON_MIRROR=http://ftp.loongnix.cn/electron/LoongArch/ 安装: $ npm install electron-builder@22.14.13 --save-dev 检查安装: $ npx electron-builder --version 22.14.13 使用包管理器安装 fpm 运行环境: $ sudo apt install ruby ruby-dev rubygems build-essential rpm 安装 fpm: $ sudo gem install --no-document fpm 设置环境变量,使用系统 fpm: $ export USE_SYSTEM_FPM="true" 3.6. 打包 编辑 package.json 文件: { "name": "zxxedu", "version": "1.3.6", "description": "智慧中小学", "license": "ISC", "homepage":"https://basic.smartedu.cn", "author": { "name": "moe.edu.cn", "email": "ncetbgs@moe.edu.cn" }, "main": "prebuild/dist/main.js", "scripts": { "start": "electron .", "build": "electron-builder" }, "build": { "appId": "zxxedu", "productName": "智慧中小学", "extraResources": [ { "from": "asset", # 将当前目录下的 asset 文件夹放入打包后的 resource 文件夹 "to": "asset" }], "linux": { "target": [ "deb", # 生成 deb 包 "rpm" # 生成 rpm 包 ], "icon": "asset/app/", # 使用原应用的图标,文件名中必须带有图片尺寸信息,如:32、32x32 "category":"Network" # 应用分类 }, "electronVersion": "22.3.27", "electronDownload": { "mirror": "http://ftp.loongnix.cn/electron/LoongArch/", # 使用龙芯提供的 electron 镜像 "customDir": "v22.3.27" } } } 打包: $ npm run build 3.7. 安装 安装: $ sudo dpkg -i dist/zxxedu_1.3.6_loongarch64.deb 4. 新世界(ABI2.0)移植 4.1. 安装 Nodejs 由于 Nodejs 16 上游源码未合并龙架构支持,直接编译会遇到一些问题。 为了方便起见,这里选择 白老师(xen0n) 移植的代码仓库。 拉取代码: git clone https://github.com/loongson-community/node-16 由于 Loongnix 25 自带的 Python 版本为 3.12 ,晚于 Nodejs 16 发布时间,直接编译时会提示不支持 Python 3.12 ,因此修改一下构建脚本: $ cd node-16/ $ vim configure # 将第26行改为如下内容: acceptable_pythons = ((3, 12), (3, 11), (3, 10), (3, 9), (3, 8), (3, 7), (3, 6)) 由于 Nodejs 16 依赖的 openssl 版本未合并龙架构支持,直接编译时会遇到 openssl 的错误,cc: error: unrecognized command-line option ‘-m64’。 这里选择使用系统自带的 libssl-dev 库,采用动态链接的方式编译: $ sudo apt install python3-pip $ pip3 install setuptools --break-system-packages $ sudo apt install libssl-dev $ ./configure --shared-openssl \ --shared-openssl-includes=/usr/include \ --shared-openssl-libpath=/usr/lib \ --prefix=/usr/local/nodejs $ make -j$(nproc) $ sudo make install 3A6000编译耗时约30分钟。 real 30m5.358s user 229m50.043s sys 3m30.977s 设置环境变量: $ export PATH="/usr/local/nodejs/bin:$PATH" 验证安装: $ node --version v16.20.2 $ npm --version 8.19.4 4.2. 安装 Electron 龙芯的工程师在新世界(ABI2.0)上移植了一份 Electron v22.3.27,并放出了二进制。 地址:https://github.com/fedora-remix-loongarch/electron-bin/releases/ 为了方便安装,我暂时将其二进制文件上传到自己的博客服务器上,作为一个临时的镜像仓库。 地址:https://katyusha.net/temp/electron/LoongArch/ > “自豪地采用 WordPress,并由 龙芯3C6000 提供强劲动力” 若想尝试自行编译新世界 Electron,可参考以下文档: 龙芯官方源码仓库(≤25):https://github.com/loongson/electron/ 龙芯工程师交叉编译文档:https://xiao-tao.github.io/ 社区开发者 darkyzhou 的原生编译文档:https://github.com/darkyzhou/electron-loong64 安装 Electron 的过程可参考旧世界文档,并简单调整。 注意,需替换 npm 镜像仓库的链接,以及 Electron 镜像仓库的链接 地址:https://docs.loongnix.cn/electron/doc/list/02.%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E.html 创建一个空文件夹并进入: $ mkdir -p ~/port/clean-build-abi2.0 $ cd ~/port/clean-build-abi2.0 设置 npm 镜像仓库地址: 注意,新世界 npm 镜像仓库的端口号为 5783 而非 4873。 $ npm config set registry https://registry.loongnix.cn:5873 验证设置是否成功: $ npm get config registry config=undefined registry=https://registry.loongnix.cn:5873/ 若不成功,可删除 ~/.npmrc 文件后再试。 设置环境变量: Electron 二进制文件为临时托管,该地址日后可能会发生改变,建议自行搭建 http 服务托管。 $ export ELECTRON_MIRROR=https://katyusha.net/temp/electron/LoongArch/ $ export electron_use_remote_checksums=1 安装: $ npm install electron@22.3.27 --save-dev 检查安装: $ npx electron --version v22.3.27 4.3. 解包应用 该步骤与 3.3. 解包应用 完全一致。 4.4. 尝试运行并修复bug 该步骤与 3.4. 尝试运行 完全一致。 4.5. 安装 electron-builder 安装 electron-builder 的过程可参考旧世界文档,并简单调整。 注意,需替换 Electron 镜像仓库的链接。 再次确认设置环境变量 Electron 二进制文件为临时托管,该地址日后可能会发生改变,建议自行搭建 http 服务托管。 $ export ELECTRON_MIRROR=https://katyusha.net/temp/electron/LoongArch/ 安装: $ npm install electron-builder@22.14.13 --save-dev 检查安装: $ npx electron-builder --version 22.14.13 使用包管理器安装 fpm 运行环境: $ sudo apt install ruby ruby-dev rubygems build-essential rpm 安装 fpm: $ sudo gem install --no-document fpm 设置环境变量,使用系统 fpm: $ export USE_SYSTEM_FPM="true" 4.6. 打包 编辑 package.json 文件: 注意,需替换 Electron 镜像仓库的链接为 https://katyusha.net/temp/electron/LoongArch/ 其余部分与 2.6. 打包 内容相同 { "name": "zxxedu", "version": "1.3.6", "description": "智慧中小学", "license": "ISC", "homepage":"https://basic.smartedu.cn", "author": { "name": "moe.edu.cn", "email": "ncetbgs@moe.edu.cn" }, "main": "prebuild/dist/main.js", "scripts": { "start": "electron .", "build": "electron-builder" }, "build": { "appId": "zxxedu", "productName": "智慧中小学", "extraResources": [ { "from": "asset", # 将当前目录下的 asset 文件夹放入打包后的 resource 文件夹 "to": "asset" }], "linux": { "target": [ "deb", "rpm" ], "icon": "asset/app/", # 使用原应用的图标,文件名中必须带有图片尺寸信息,如:32、32x32 "category":"Network" # 应用分类 }, "electronVersion": "22.3.27", "electronDownload": { "mirror": "https://katyusha.net/temp/electron/LoongArch/", "customDir": "v22.3.27" } } } 4.7. 安装 安装: $ sudo dpkg -i dist/zxxedu_1.3.6_loongarch64.deb 5. 遇到的问题 5.1. Loongnix 25 兼容层误判二进制文件 5.1.2. 现象 在新世界使用 electron-build 打包时,出现了以下错误信息: /lib/loongarch64-linux-gnu/libc.so.6: version `GLIBC_2.36' not found 观察日志,发现该错误是执行一个名为 app-builder 的二进制文件是发生的。 路径:/home/katyusha/port/clean-build-abi2.0/node_modules/app-builder-bin/linux/loong64/app-builder 使用 file 检查该应用的 glibc 信息: $ file /home/katyusha/port/clean-build-abi2.0/node_modules/app-builder-bin/linux/loong64/app-builder ELF 64-bit LSB executable, LoongArch, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-loongarch-lp64d.so.1, BuildID[sha1]=a25f647fbc8c1a67ca47928e51cade981c951bac, for GNU/Linux 4.15.0, stripped 使用 ldd 检查系统的 glibc 信息: $ ldd --version ldd (Debian GLIBC 2.40-2) 2.40 这非常奇怪,因为 glibc 是向下兼容的,即系统中使用的高版本 glibc 兼容应用的低版本 glibc 。 结合龙架构的新旧世界问题,猜想该问题是由于 Loongnix 25 自带的旧世界兼容层导致的。 5.1.3. 排查 首先,尝试在 debian-port 执行该二进制文件,发现一切正常、 使用镜像:https://cdimage.debian.org/cdimage/ports/snapshots/2024-12-24/debian-12.0.0-loong64-NETINST-1.iso 经群友 Icenowy 指点,使用 strace 工具追踪该二进制使用的系统调用,发现存在可疑信息: openat(AT_FDCWD, "/usr/local/abi-compat/lib/loongarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 6 猜想该旧世界兼容层是以软件包的形式安装在系统中的。 检查 dpkg 信息,发现系统中安装了一个名为 abi-compat 的软件包: $ dpkg --list | grep abi ii abi-compat 0.1 loong64 abi-compat ...... 5.1.4. 解决 卸载该软件包,并重启系统: $ sudo apt purge abi-compat $ reboot 顺利解决! (小广告:建议使用由安同开源社区开发者 王邈 开发的 liblol 旧世界兼容层,实现更优雅,体积更小。现已预装至 AOSC OS 系统,并支持 Deabin、deepin、Arch Linux、Loongnix 等系统。) 6. 运行效果 6.1. Loongnix 20 系统(旧世界) 6.2. Kylin 2403 系统(旧世界) 6.3. UOS 1070 系统(旧世界) 6.4. Loongnix 25 系统(新世界) 7. 附件 阿里云盘: 链接: https://www.alipan.com/s/t8g3tKmT4cm 提取码:bd03 百度网盘: 链接: https://pan.baidu.com/s/1qcEak8tVVbY3hUmtpVTC2A?pwd=av4t 提取码: av4t