A
Air官方
【Air 系列 · 鸿蒙 PC 适配全记录】48 / 56
■ 应用:轻统计(from PSPP)(AirStat)
■ 定位:GNU 统计分析(PSPP,SPSS 同类工具)
■ 上游:PSPP | 路线:GTK3 gtk3-stack | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 轻统计
统计分析是高校教学与科研中的高频刚需:频率表、描述统计、交叉分析、回归检验,这些工作长期由商业统计软件垄断,而 GNU PSPP 作为 SPSS 语法兼容的自由软件替代品,提供了数据编辑器、语法执行与输出查看器的完整体系。把 PSPP 带到鸿蒙 PC 与平板上,学生与研究者可以在鸿蒙设备上直接打开 .sav 数据文件、跑一遍完整的分析流程,这对教育场景的价值直接而具体。
轻统计(AirStat)是 PSPP 2.1.2 的鸿蒙原生移植工程,路线为 ArkTS 薄壳加 XComponent 加 GTK3 OHOS GDK 后端,与 AirTerm、AirRaw、AirPhoto 同栈。工程按移植手册的里程碑推进:2026-09-22 一天内连续完成 M1 新编依赖装栈、M2 pspp 交叉编译、M3 core 链接、M4 HAP 构建签名、M5 真机首启出主窗口、M6 打开示例 .sav 并端到端跑通 FREQUENCIES 频率分析,主验证机为 HUAWEI MatePad 11.5,关键节点均有像素与 OCR 双通道截图留痕。
当前工程包名 com.airstat.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,申请 ohos.permission.INTERNET 权限,ability 为 multiton 启动模式。module.json5 中显式设置 compressNativeLibs: false,注释标明原因:native 库压缩会破坏 PT_LOAD 对齐导致 musl dlopen 段错误。Native ABI 为 arm64-v8a,M4 里程碑记录签名 HAP 约 66MB,42 个依赖库经双通道打包。
▍二、先划清边界:不把编译通过当成适配完成
PSPP 原生是 GTK3 桌面程序,假设有窗口管理器、桌面字体配置与完整的 gettext 运行时。鸿蒙侧三者皆无,界面能不能显示、中文能不能出、对话框能不能管,都与编译成功无关。工程用里程碑制回答"适配到哪了":M5 只宣称首启出主窗口且菜单可点开,M6 只宣称示例数据端到端频率分析,触控与 IME 等交互深度适配全部划入未完成的 M7,不提前计分。
层次 原项目实现(桌面 Linux) 鸿蒙侧当前方案
应用入口 桌面 main() 进程 Stage 模型 EntryAbility,psppire main 改名后由 core 线程调入
窗口承载 X11 + WM XComponent(TEXTURE) + GTK3 OHOS GDK 后端合成
统计引擎 pspp 本体进程 libairstat_pspp.so(psppire 改库,4.9MB),全内部 .a 静态内嵌
数据表格 系统内的 ssw 控件 spread-sheet-widget 0.10 交叉编译随栈带入
语法高亮 GtkSourceView gtksourceview-4 8.4 版交叉编译,禁 doc 与 introspection
统计函数 系统科学库 gsl 2.8 交叉编译(autotools 四板斧)
配置存储 GSettings/dconf psppire-conf 走 GKeyFile(HOME 指向 filesDir),全源码无 g_settings_new
最后两行体现的是选型阶段的取证习惯:交叉编译前先确认配置体系不踩 GSettings 的 dconf 运行时依赖,风险在编码前就排除,而不是链接报错后再补救。
▍三、鸿蒙版的整体架构
工程是标准的薄壳加 GTK 栈结构:ArkTS 层负责 ability 生命周期、share 资源部署(stamp 幂等)与示例数据、autostart 种子文件的下发;薄 NAPI 模块 libairstatbridge.so 只链系统库,运行时按依赖拓扑序 dlopen GTK3 与 pspp 全部依赖,再加载 libairstat_core.so;core 层完成环境设置(FONTCONFIG_FILE 指向沙箱 fonts.conf)、gtk_init、顶层窗铺满轮询,最后以 {"psppire","-q","-n"} 参数调入改名的 airstat_psppire_main;界面全部由 GTK3 OHOS GDK 后端合成到 XComponent 表面。
text
EntryAbility(ArkTS 壳)
├── share 部署:rawfile/pspp-share → files/share(stamp 幂等)
├── 示例数据:rawfile/sample-data → files/sample-data
├── autostart 种子:rawfile/airstat_autostart → files/(.sav 绝对路径)
├── 部署完成后 TSFN 通知 core 放行(10s 兜底)
└── Index.ets / XComponent(TEXTURE)
▼
libairstatbridge.so(薄 NAPI:拓扑序 dlopen GTK3/pspp 依赖,仅链系统库)
▼
libairstat_core.so(gtk_init + FONTCONFIG_FILE + poll_ui_ready 铺满 + 调入引擎)
├── libairstat_pspp.so(psppire 改库:数据编辑器/语法/输出查看器)
└── GTK3 3.24.53(OHOS GDK 后端)+ gsl 2.8 + gtksourceview 4.8.4 + ssw 0.10
主要目录如下:
text
AirStat/
├── AppScope/app.json5 # com.airstat.app、版本
├── PORTING.md # 移植手册:依赖矩阵、补丁清单、里程碑
├── ai_log/ # 每轮过程档案与截图留痕(shots/)
├── native/
│ ├── scripts/ # env.sh、build_m1_deps、build_pspp、deploy_stack、elf_edit
│ ├── sources/ # gsl 2.8 / gtksourceview 4.8.4 / ssw 0.10 / pspp 2.1.2
│ ├── install/ # AirTerm 栈硬链前缀(同 inode 纪律)
│ └── install_pspp/ # PSPP 专用安装前缀
└── entry/src/main/
├── module.json5 # compressNativeLibs:false、INTERNET 权限
├── ets/
│ ├── entryability/EntryAbility.ets # 部署 stamp、种子下发、安全区布局
│ ├── pages/Index.ets # XComponent 宿主
│ └── utils/FileManager.ets # 沙箱文件管理
├── resources/rawfile/ # pspp-share、sample-data、airstat_autostart
└── cpp/
├── napi_init.cpp # 薄 NAPI:LoadCoreLibraries 拓扑序 dlopen
└── airstat_core.cpp # 环境设置、poll_ui_ready、引擎调入
▍四、核心适配过程
### 1. 栈复用与四件新编依赖
GTK 运行时直接复用 AirTerm 栈:gtk+-3.0 3.24.53(含 OHOS GDK 后端)、glib 2.80.4、cairo/pango、libxml2、真实 zlib 与 airlibintl 共 42 库,经硬链同 inode 共享,遵守"不改栈内既有文件"纪律。PSPP 缺的四件在本仓新编:gsl 2.8 统计函数库(autotools,--host=aarch64-linux-gnu)、gtksourceview-4 8.4(meson,禁 gtk_doc 与 introspection)、spread-sheet-widget 0.10 数据表格控件、pspp 2.1.2 本体(autotools 加 gnulib)。PSPP 的 configure 选项保留 GUI 开启,libpq 探测自然落空,统计引擎与图形界面一次成型。
### 2. PSPP 改库与入口改名
上游 main 无法直接进动态库,P1 补丁在 src/ui/gui/main.c 以 #ifdef __OHOS__ 把 main 改名为 airstat_psppire_main 并由 libairstat_pspp.so 导出,core 层以 psppire 加 -q -n 参数调入。运行期路径在 configure 阶段烙定:--datadir/--localedir 直指设备侧 /data/storage/el2/base/haps/entry/files/share[.../locale],gl/configmake.h 把 PKGDATADIR 与 LOCALEDIR 写死,make install 经 DESTDIR 收割进 rawfile。这套做法把"运行时找资源"变成"编译期对准沙箱",首启即从正确路径加载菜单定义、翻译与语法说明。
### 3. 打包五件套与双通道部署
交叉编译产物进 HAP 的路径按五件套固定:hvigor 只编薄 NAPI 桥;nativeLib.strip: false 与 compressNativeLibs: false 双配置防 strip 与压缩破坏预编译库;产物 .so 经 elf_edit.py 去版本号、去 libc NEEDED 并由 fix_elf_align.py 修对齐;运行时 dlopen 模块走 entry/libs/arm64-v8a/ 模块级通道;dlopen 按 LoadCoreLibraries 清单的依赖拓扑序执行,PSPP 插件重定位依赖的库最先全局加载。M4 里程碑的 66MB HAP 即由该链产出。
### 4. 端到端种子通道
PSPP 的 GUI 程序没有命令行执行语法的选项,端到端验证只能走界面或启动参数。工程设计了种子通道:rawfile 里的 airstat_autostart 文件写一行 .sav 绝对路径,EntryAbility 部署到沙箱后 core 把它追加为 psppire 的位置参数,HANDLES_OPEN 机制打开数据文件。M6 验证即由此完成:autostart 载入示例 horticulture.sav,界面上经分析菜单执行描述统计的频率过程(treatment 与 yield 两变量),输出查看器全屏渲染出完整频率表。这条通道同时成了回归测试的固定入口,任何一轮改动后重跑种子即可复验端到端链路。
### 5. 顶层窗治理与输出查看器铺满
PSPP 打开数据即产生输出,输出查看器是首个输出提交时才惰性创建的第二个顶层窗口,无 WM 环境下它以 236x114 的自然尺寸合成且堆叠在主窗之下。core 层的 poll_ui_ready 改为永续轮询:每个新出现的普通顶层窗执行 set_default_size 加 maximize 加 present 置顶,已处理窗口经 weak pointer 槽表幂等跳过、销毁自动清槽。取证侧配套了 GDK 后端追踪旗标,窗口叠放与几何问题先拉 files/gimp_trace.txt,hdc touch 一个空文件即可开启,免重打包。
▍五、适配中遇到的几个难点
### 难点1:hvigor 闭包按 DT_SONAME 建图的静默丢弃
手工链接的 core .so 明明被 NEEDED 引用,打包后却不在 HAP 里。定位结论是 hvigor 按动态节的 DT_SONAME 字段建依赖图,手工链接产物没有 SONAME 就不进图,被引用也静默丢弃。对策是链接必加 -Wl,-soname,且发现 .cxx 目录的陈旧缓存会把已修复的链接结果藏回去——改链后必须删 entry/.cxx 与 intermediates/libs 强制重链重收。这个坑让 M3 的"链接成功"与 M4 的"打包齐全"之间多了一次系统排查,也成了打包五件套第三、五条的由来。
### 难点2:musl 内核结构与 PSPP 内部结构撞名
PSPP 源码树内自定义了 struct file_handle,而 musl 的 fcntl.h 在 _GNU_SOURCE 下定义内核同名结构(name_to_handle_at 家族),编译期直接冲突。最初设想用宏把名字统一改掉,分析后确认不可行:宏会把内核结构一起改名,撞名依旧。最终方案是全源码树把内部结构改名为 pspp_file_handle,61 个文件 207 处替换,因该结构是纯指针 API,ABI 不受影响。这是一类典型的 musl 差异问题:glibc 下编译通过≠musl 下无冲突,名字空间的边界必须逐一审计。
### 难点3:沉浸式与自由窗口态的安全区冲突
首启主窗口的菜单栏与工具栏被状态栏永久遮挡约 120px。原因是 setWindowLayoutFullScreen(true) 的全出血布局在 MatePad 自由窗口态下不做避让,GL 内容顶到了状态栏之下。对策是改用安全区布局(setWindowLayoutFullScreen(false)),由系统把窗口内容约束在状态栏与手势条之内,EntryAbility 中落点明确并经真机回归。对一个菜单驱动的 GTK 应用,菜单栏可达性是可用性的底线,这条配置差异在平板形态上必须显式处理。
### 难点4:meson 版本化布局顶掉归一产物
新装的 gtksourceview 是版本化布局(.so.0、.so.0.0.0,SONAME 带 .0,NEEDED libc.so),hvigor 解析 CMake link.txt 时把 install 目录的原始库直接拷进 HAP,顶掉了已经归一的版本。对策是把 normalize 前置成装栈后的固定动作:elf_edit 去版本号、去 libc NEEDED、SONAME 改无版本、fix_elf_align 修对齐,并删除版本化文件与 symlink,让闭包图里只剩无版本名字。与难点1 合并看,HAP 内 so 集合的正确性既取决于链接参数也取决于 install 布局,任何一环松动都会静默换库。
### 难点5:第二顶层窗的合成与交互残留
输出查看器未铺满时的破碎与遮挡问题已由永续轮询解决铺满与置顶,但交互侧仍有两类残留登记在案:频率对话框存在间歇性不重绘(点击在模型层生效、画面停留旧帧,两轮实测 12 次交互复发 2 次,疑对话框未绑定回退合成路径的 damage 传播);输出查看器内容区的触摸滚动无响应(数据编辑器网格正常)。工程的处理是把它们如实列入 M7 待修清单并给出排查入口(GDK present 与 damage 路径、standards 触控条目),同时记录切换窗口时偶发的 GDK_IS_SCREEN 断言为无崩溃观察项,不为赶进度把未收敛的交互说成已适配。
▍六、编译、安装与启动
Native 侧按 native/scripts/ 的固定顺序构建:env.sh 载入交叉编译环境,build_m1_deps.sh 完成 gsl、gtksourceview、ssw 三件新编依赖的装栈与归一,build_pspp.sh 交叉编译 PSPP 本体并产出 libairstat_pspp.so,deploy_stack.sh 把栈库与 PSPP 闭包分流部署(cpp/libs 供链接、entry/libs/arm64-v8a 供运行时),elf_edit.py 与 fix_elf_align.py 在其间完成归一。ArkTS 与 HAP 打包使用标准 hvigorw 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于:
text
entry/build/default/outputs/default/entry-default-signed.hap
连接设备后安装并启动(M4 里程碑记录该 HAP 约 66MB):
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airstat.app
工程档案记载的部署方式为 devecocli run 指定 MatePad 11.5 设备序列号,改 .so 后需 force-stop 再验证;签名走 devecocli 云端(自建 CA 曾被安装侧拒绝,有留档)。M5 与 M6 的验证证据(数据编辑器、中文菜单栏、频率表全屏渲染)在 ai_log/shots/ 下有像素与 OCR 双通道留痕,可从源码复现构建后按同一通道核验。
▍七、当前功能边界
当前鸿蒙版已覆盖的能力(M1 到 M6 里程碑):
- 可安装 HAP 与薄壳四层架构(ArkTS 壳、薄 NAPI、core、libairstat_pspp 改库加 GTK3 栈);
- MatePad 11.5 真机首启主窗口:数据编辑器、中文菜单栏、工具栏与数据网格,菜单可点开;
- 示例 .sav 数据端到端分析:autostart 种子载入 horticulture.sav,界面执行描述统计的频率过程,输出查看器全屏渲染完整频率表;
- psppire 界面体系随库带入:gtksourceview 语法高亮、spread-sheet-widget 数据表格、GKeyFile 配置(沙箱 HOME);
- share 资源 stamp 幂等部署、字体配置指向沙箱、42 库双通道打包与拓扑序 dlopen。
尚未覆盖的能力(PORTING.md M7 与过程坑登记):触控与 IME 深度适配(standards 逐项清单)、菜单 ArkTS 化与 Tip 弹窗中文化补齐、频率对话框间歇性不重绘的修复(复发已量化)、输出查看器内容区触摸滚动(数据网格正常,输出区不滚)、切换窗口时偶发 GDK_IS_SCREEN 断言观察项、autostart 打开时输出查看器抢占前台的观感优化。
因此,当前版本更准确的定位是"PSPP 鸿蒙移植的端到端基线版":从构建、安装到打开数据、执行分析、查看输出的主链路已在真机走通,交互深度与观感治理全部显式留在 M7,不把里程碑外的能力计入围栏。
▍八、总结
轻统计的移植把 Air 系列的 GTK 栈路线推到了科学计算领域,也验证了这条路线的可复制性:栈级复用让 42 个依赖零成本就位,四件新编依赖各走各的构建系统一次通过,入口改名加路径烙定加种子通道三件事把"桌面程序进动态库、资源进沙箱、验证可自动化"串成闭环。从立项到端到端频率表,档案记录的每一步都有可复查的产物与截图,里程碑制让"走到哪了"成为一个可以外部核验的问题。
后续工作的重心集中在 M7:触控与 IME 的深度适配决定平板形态的日常可用性,菜单 ArkTS 化与弹窗中文化补齐界面层短板,两个已量化的交互残留(对话框重绘与输出区滚动)沿排查入口收敛。统计场景对输出正确性的要求高于交互花哨,工程把 FREQUENCIES 端到端作为固定回归入口的做法将延续下去,每一轮改动都以同一份示例数据、同一条分析路径重新计分。
■ 应用:轻统计(from PSPP)(AirStat)
■ 定位:GNU 统计分析(PSPP,SPSS 同类工具)
■ 上游:PSPP | 路线:GTK3 gtk3-stack | 版本:1.0.0
■ 市场状态:开发中,即将上架
■ 官网(免费下载): https://www.shunidc.cn/
————————————————————
开源鸿蒙 PC 社区: https://harmonypc.csdn.net/
PC 社区项目申请: https://atomgit.com/OpenHarmonyPCDeveloper
适配开源地址:(AtomGit 仓库建立后回填)
环境搭建文章:https://blog.csdn.net/weixin_52908342/article/details/161343743
▍一、为什么要适配 轻统计
统计分析是高校教学与科研中的高频刚需:频率表、描述统计、交叉分析、回归检验,这些工作长期由商业统计软件垄断,而 GNU PSPP 作为 SPSS 语法兼容的自由软件替代品,提供了数据编辑器、语法执行与输出查看器的完整体系。把 PSPP 带到鸿蒙 PC 与平板上,学生与研究者可以在鸿蒙设备上直接打开 .sav 数据文件、跑一遍完整的分析流程,这对教育场景的价值直接而具体。
轻统计(AirStat)是 PSPP 2.1.2 的鸿蒙原生移植工程,路线为 ArkTS 薄壳加 XComponent 加 GTK3 OHOS GDK 后端,与 AirTerm、AirRaw、AirPhoto 同栈。工程按移植手册的里程碑推进:2026-09-22 一天内连续完成 M1 新编依赖装栈、M2 pspp 交叉编译、M3 core 链接、M4 HAP 构建签名、M5 真机首启出主窗口、M6 打开示例 .sav 并端到端跑通 FREQUENCIES 频率分析,主验证机为 HUAWEI MatePad 11.5,关键节点均有像素与 OCR 双通道截图留痕。
当前工程包名 com.airstat.app,版本 1.0.0,deviceTypes 覆盖 tablet、2in1 与 phone,申请 ohos.permission.INTERNET 权限,ability 为 multiton 启动模式。module.json5 中显式设置 compressNativeLibs: false,注释标明原因:native 库压缩会破坏 PT_LOAD 对齐导致 musl dlopen 段错误。Native ABI 为 arm64-v8a,M4 里程碑记录签名 HAP 约 66MB,42 个依赖库经双通道打包。
▍二、先划清边界:不把编译通过当成适配完成
PSPP 原生是 GTK3 桌面程序,假设有窗口管理器、桌面字体配置与完整的 gettext 运行时。鸿蒙侧三者皆无,界面能不能显示、中文能不能出、对话框能不能管,都与编译成功无关。工程用里程碑制回答"适配到哪了":M5 只宣称首启出主窗口且菜单可点开,M6 只宣称示例数据端到端频率分析,触控与 IME 等交互深度适配全部划入未完成的 M7,不提前计分。
层次 原项目实现(桌面 Linux) 鸿蒙侧当前方案
应用入口 桌面 main() 进程 Stage 模型 EntryAbility,psppire main 改名后由 core 线程调入
窗口承载 X11 + WM XComponent(TEXTURE) + GTK3 OHOS GDK 后端合成
统计引擎 pspp 本体进程 libairstat_pspp.so(psppire 改库,4.9MB),全内部 .a 静态内嵌
数据表格 系统内的 ssw 控件 spread-sheet-widget 0.10 交叉编译随栈带入
语法高亮 GtkSourceView gtksourceview-4 8.4 版交叉编译,禁 doc 与 introspection
统计函数 系统科学库 gsl 2.8 交叉编译(autotools 四板斧)
配置存储 GSettings/dconf psppire-conf 走 GKeyFile(HOME 指向 filesDir),全源码无 g_settings_new
最后两行体现的是选型阶段的取证习惯:交叉编译前先确认配置体系不踩 GSettings 的 dconf 运行时依赖,风险在编码前就排除,而不是链接报错后再补救。
▍三、鸿蒙版的整体架构
工程是标准的薄壳加 GTK 栈结构:ArkTS 层负责 ability 生命周期、share 资源部署(stamp 幂等)与示例数据、autostart 种子文件的下发;薄 NAPI 模块 libairstatbridge.so 只链系统库,运行时按依赖拓扑序 dlopen GTK3 与 pspp 全部依赖,再加载 libairstat_core.so;core 层完成环境设置(FONTCONFIG_FILE 指向沙箱 fonts.conf)、gtk_init、顶层窗铺满轮询,最后以 {"psppire","-q","-n"} 参数调入改名的 airstat_psppire_main;界面全部由 GTK3 OHOS GDK 后端合成到 XComponent 表面。
text
EntryAbility(ArkTS 壳)
├── share 部署:rawfile/pspp-share → files/share(stamp 幂等)
├── 示例数据:rawfile/sample-data → files/sample-data
├── autostart 种子:rawfile/airstat_autostart → files/(.sav 绝对路径)
├── 部署完成后 TSFN 通知 core 放行(10s 兜底)
└── Index.ets / XComponent(TEXTURE)
▼
libairstatbridge.so(薄 NAPI:拓扑序 dlopen GTK3/pspp 依赖,仅链系统库)
▼
libairstat_core.so(gtk_init + FONTCONFIG_FILE + poll_ui_ready 铺满 + 调入引擎)
├── libairstat_pspp.so(psppire 改库:数据编辑器/语法/输出查看器)
└── GTK3 3.24.53(OHOS GDK 后端)+ gsl 2.8 + gtksourceview 4.8.4 + ssw 0.10
主要目录如下:
text
AirStat/
├── AppScope/app.json5 # com.airstat.app、版本
├── PORTING.md # 移植手册:依赖矩阵、补丁清单、里程碑
├── ai_log/ # 每轮过程档案与截图留痕(shots/)
├── native/
│ ├── scripts/ # env.sh、build_m1_deps、build_pspp、deploy_stack、elf_edit
│ ├── sources/ # gsl 2.8 / gtksourceview 4.8.4 / ssw 0.10 / pspp 2.1.2
│ ├── install/ # AirTerm 栈硬链前缀(同 inode 纪律)
│ └── install_pspp/ # PSPP 专用安装前缀
└── entry/src/main/
├── module.json5 # compressNativeLibs:false、INTERNET 权限
├── ets/
│ ├── entryability/EntryAbility.ets # 部署 stamp、种子下发、安全区布局
│ ├── pages/Index.ets # XComponent 宿主
│ └── utils/FileManager.ets # 沙箱文件管理
├── resources/rawfile/ # pspp-share、sample-data、airstat_autostart
└── cpp/
├── napi_init.cpp # 薄 NAPI:LoadCoreLibraries 拓扑序 dlopen
└── airstat_core.cpp # 环境设置、poll_ui_ready、引擎调入
▍四、核心适配过程
### 1. 栈复用与四件新编依赖
GTK 运行时直接复用 AirTerm 栈:gtk+-3.0 3.24.53(含 OHOS GDK 后端)、glib 2.80.4、cairo/pango、libxml2、真实 zlib 与 airlibintl 共 42 库,经硬链同 inode 共享,遵守"不改栈内既有文件"纪律。PSPP 缺的四件在本仓新编:gsl 2.8 统计函数库(autotools,--host=aarch64-linux-gnu)、gtksourceview-4 8.4(meson,禁 gtk_doc 与 introspection)、spread-sheet-widget 0.10 数据表格控件、pspp 2.1.2 本体(autotools 加 gnulib)。PSPP 的 configure 选项保留 GUI 开启,libpq 探测自然落空,统计引擎与图形界面一次成型。
### 2. PSPP 改库与入口改名
上游 main 无法直接进动态库,P1 补丁在 src/ui/gui/main.c 以 #ifdef __OHOS__ 把 main 改名为 airstat_psppire_main 并由 libairstat_pspp.so 导出,core 层以 psppire 加 -q -n 参数调入。运行期路径在 configure 阶段烙定:--datadir/--localedir 直指设备侧 /data/storage/el2/base/haps/entry/files/share[.../locale],gl/configmake.h 把 PKGDATADIR 与 LOCALEDIR 写死,make install 经 DESTDIR 收割进 rawfile。这套做法把"运行时找资源"变成"编译期对准沙箱",首启即从正确路径加载菜单定义、翻译与语法说明。
### 3. 打包五件套与双通道部署
交叉编译产物进 HAP 的路径按五件套固定:hvigor 只编薄 NAPI 桥;nativeLib.strip: false 与 compressNativeLibs: false 双配置防 strip 与压缩破坏预编译库;产物 .so 经 elf_edit.py 去版本号、去 libc NEEDED 并由 fix_elf_align.py 修对齐;运行时 dlopen 模块走 entry/libs/arm64-v8a/ 模块级通道;dlopen 按 LoadCoreLibraries 清单的依赖拓扑序执行,PSPP 插件重定位依赖的库最先全局加载。M4 里程碑的 66MB HAP 即由该链产出。
### 4. 端到端种子通道
PSPP 的 GUI 程序没有命令行执行语法的选项,端到端验证只能走界面或启动参数。工程设计了种子通道:rawfile 里的 airstat_autostart 文件写一行 .sav 绝对路径,EntryAbility 部署到沙箱后 core 把它追加为 psppire 的位置参数,HANDLES_OPEN 机制打开数据文件。M6 验证即由此完成:autostart 载入示例 horticulture.sav,界面上经分析菜单执行描述统计的频率过程(treatment 与 yield 两变量),输出查看器全屏渲染出完整频率表。这条通道同时成了回归测试的固定入口,任何一轮改动后重跑种子即可复验端到端链路。
### 5. 顶层窗治理与输出查看器铺满
PSPP 打开数据即产生输出,输出查看器是首个输出提交时才惰性创建的第二个顶层窗口,无 WM 环境下它以 236x114 的自然尺寸合成且堆叠在主窗之下。core 层的 poll_ui_ready 改为永续轮询:每个新出现的普通顶层窗执行 set_default_size 加 maximize 加 present 置顶,已处理窗口经 weak pointer 槽表幂等跳过、销毁自动清槽。取证侧配套了 GDK 后端追踪旗标,窗口叠放与几何问题先拉 files/gimp_trace.txt,hdc touch 一个空文件即可开启,免重打包。
▍五、适配中遇到的几个难点
### 难点1:hvigor 闭包按 DT_SONAME 建图的静默丢弃
手工链接的 core .so 明明被 NEEDED 引用,打包后却不在 HAP 里。定位结论是 hvigor 按动态节的 DT_SONAME 字段建依赖图,手工链接产物没有 SONAME 就不进图,被引用也静默丢弃。对策是链接必加 -Wl,-soname,且发现 .cxx 目录的陈旧缓存会把已修复的链接结果藏回去——改链后必须删 entry/.cxx 与 intermediates/libs 强制重链重收。这个坑让 M3 的"链接成功"与 M4 的"打包齐全"之间多了一次系统排查,也成了打包五件套第三、五条的由来。
### 难点2:musl 内核结构与 PSPP 内部结构撞名
PSPP 源码树内自定义了 struct file_handle,而 musl 的 fcntl.h 在 _GNU_SOURCE 下定义内核同名结构(name_to_handle_at 家族),编译期直接冲突。最初设想用宏把名字统一改掉,分析后确认不可行:宏会把内核结构一起改名,撞名依旧。最终方案是全源码树把内部结构改名为 pspp_file_handle,61 个文件 207 处替换,因该结构是纯指针 API,ABI 不受影响。这是一类典型的 musl 差异问题:glibc 下编译通过≠musl 下无冲突,名字空间的边界必须逐一审计。
### 难点3:沉浸式与自由窗口态的安全区冲突
首启主窗口的菜单栏与工具栏被状态栏永久遮挡约 120px。原因是 setWindowLayoutFullScreen(true) 的全出血布局在 MatePad 自由窗口态下不做避让,GL 内容顶到了状态栏之下。对策是改用安全区布局(setWindowLayoutFullScreen(false)),由系统把窗口内容约束在状态栏与手势条之内,EntryAbility 中落点明确并经真机回归。对一个菜单驱动的 GTK 应用,菜单栏可达性是可用性的底线,这条配置差异在平板形态上必须显式处理。
### 难点4:meson 版本化布局顶掉归一产物
新装的 gtksourceview 是版本化布局(.so.0、.so.0.0.0,SONAME 带 .0,NEEDED libc.so),hvigor 解析 CMake link.txt 时把 install 目录的原始库直接拷进 HAP,顶掉了已经归一的版本。对策是把 normalize 前置成装栈后的固定动作:elf_edit 去版本号、去 libc NEEDED、SONAME 改无版本、fix_elf_align 修对齐,并删除版本化文件与 symlink,让闭包图里只剩无版本名字。与难点1 合并看,HAP 内 so 集合的正确性既取决于链接参数也取决于 install 布局,任何一环松动都会静默换库。
### 难点5:第二顶层窗的合成与交互残留
输出查看器未铺满时的破碎与遮挡问题已由永续轮询解决铺满与置顶,但交互侧仍有两类残留登记在案:频率对话框存在间歇性不重绘(点击在模型层生效、画面停留旧帧,两轮实测 12 次交互复发 2 次,疑对话框未绑定回退合成路径的 damage 传播);输出查看器内容区的触摸滚动无响应(数据编辑器网格正常)。工程的处理是把它们如实列入 M7 待修清单并给出排查入口(GDK present 与 damage 路径、standards 触控条目),同时记录切换窗口时偶发的 GDK_IS_SCREEN 断言为无崩溃观察项,不为赶进度把未收敛的交互说成已适配。
▍六、编译、安装与启动
Native 侧按 native/scripts/ 的固定顺序构建:env.sh 载入交叉编译环境,build_m1_deps.sh 完成 gsl、gtksourceview、ssw 三件新编依赖的装栈与归一,build_pspp.sh 交叉编译 PSPP 本体并产出 libairstat_pspp.so,deploy_stack.sh 把栈库与 PSPP 闭包分流部署(cpp/libs 供链接、entry/libs/arm64-v8a 供运行时),elf_edit.py 与 fix_elf_align.py 在其间完成归一。ArkTS 与 HAP 打包使用标准 hvigorw 流程:
bash
export JAVA_HOME=/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
hvigorw --mode module -p module=entry assembleHap --no-daemon
签名产物位于:
text
entry/build/default/outputs/default/entry-default-signed.hap
连接设备后安装并启动(M4 里程碑记录该 HAP 约 66MB):
bash
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.airstat.app
工程档案记载的部署方式为 devecocli run 指定 MatePad 11.5 设备序列号,改 .so 后需 force-stop 再验证;签名走 devecocli 云端(自建 CA 曾被安装侧拒绝,有留档)。M5 与 M6 的验证证据(数据编辑器、中文菜单栏、频率表全屏渲染)在 ai_log/shots/ 下有像素与 OCR 双通道留痕,可从源码复现构建后按同一通道核验。
▍七、当前功能边界
当前鸿蒙版已覆盖的能力(M1 到 M6 里程碑):
- 可安装 HAP 与薄壳四层架构(ArkTS 壳、薄 NAPI、core、libairstat_pspp 改库加 GTK3 栈);
- MatePad 11.5 真机首启主窗口:数据编辑器、中文菜单栏、工具栏与数据网格,菜单可点开;
- 示例 .sav 数据端到端分析:autostart 种子载入 horticulture.sav,界面执行描述统计的频率过程,输出查看器全屏渲染完整频率表;
- psppire 界面体系随库带入:gtksourceview 语法高亮、spread-sheet-widget 数据表格、GKeyFile 配置(沙箱 HOME);
- share 资源 stamp 幂等部署、字体配置指向沙箱、42 库双通道打包与拓扑序 dlopen。
尚未覆盖的能力(PORTING.md M7 与过程坑登记):触控与 IME 深度适配(standards 逐项清单)、菜单 ArkTS 化与 Tip 弹窗中文化补齐、频率对话框间歇性不重绘的修复(复发已量化)、输出查看器内容区触摸滚动(数据网格正常,输出区不滚)、切换窗口时偶发 GDK_IS_SCREEN 断言观察项、autostart 打开时输出查看器抢占前台的观感优化。
因此,当前版本更准确的定位是"PSPP 鸿蒙移植的端到端基线版":从构建、安装到打开数据、执行分析、查看输出的主链路已在真机走通,交互深度与观感治理全部显式留在 M7,不把里程碑外的能力计入围栏。
▍八、总结
轻统计的移植把 Air 系列的 GTK 栈路线推到了科学计算领域,也验证了这条路线的可复制性:栈级复用让 42 个依赖零成本就位,四件新编依赖各走各的构建系统一次通过,入口改名加路径烙定加种子通道三件事把"桌面程序进动态库、资源进沙箱、验证可自动化"串成闭环。从立项到端到端频率表,档案记录的每一步都有可复查的产物与截图,里程碑制让"走到哪了"成为一个可以外部核验的问题。
后续工作的重心集中在 M7:触控与 IME 的深度适配决定平板形态的日常可用性,菜单 ArkTS 化与弹窗中文化补齐界面层短板,两个已量化的交互残留(对话框重绘与输出区滚动)沿排查入口收敛。统计场景对输出正确性的要求高于交互花哨,工程把 FREQUENCIES 端到端作为固定回归入口的做法将延续下去,每一轮改动都以同一份示例数据、同一条分析路径重新计分。
楼主
8 天前