feat: HarmonyOS NEXT toolchain + NAPI + HAP bundle (Phase 1 for #113)#122
Open
proggeramlug wants to merge 7 commits intomainfrom
Open
feat: HarmonyOS NEXT toolchain + NAPI + HAP bundle (Phase 1 for #113)#122proggeramlug wants to merge 7 commits intomainfrom
proggeramlug wants to merge 7 commits intomainfrom
Conversation
PR A for #113 — additive scaffolding only. Every edit is a new arm in an existing match, unreachable until the user passes --target harmonyos or --target harmonyos-simulator. No behavior change for any other target. * rust_target_triple / resolve_target_triple: harmonyos → aarch64-unknown-linux-ohos, harmonyos-simulator → x86_64-unknown-linux-ohos (DevEco emulator). * find_library: _harmonyos suffix convention next to the perry binary, mirroring _watchos / _ios / _tvos. * find_ui_library: libperry_ui_harmonyos.a (crate lands in PR C). * parse_native_library_manifest target_key → "harmonyos" so package.json authors can declare perry.nativeLibrary.targets.harmonyos. * UI-crate selector → perry-ui-harmonyos. * is_mobile feature filter includes harmonyos[-simulator] so --features plugins is stripped (dlopen isn't practical under HarmonyOS's sandbox). * __platform__ = 7 (HarmonyOS); ordered before the linux arm since the OHOS triple is *-unknown-linux-ohos — naive contains("linux") would misclassify. Platform enum is deliberately untouched. publish.rs:627 and run.rs:1687-1802 exhaustively match every variant; adding one would force premature behavior decisions (what does `perry publish --platform harmonyos` do?) that belong with PR B alongside the HAP bundler. `compile` takes Option<String>, so --target harmonyos works without the enum change. Verified: * cargo build --release -p perry -p perry-codegen — clean * perry compile hi.ts (default host) — byte-equivalent output, runs * perry compile hi.ts --target harmonyos — fails fast with "Could not find libperry_runtime.a (for target harmonyos)"; search paths include target/aarch64-unknown-linux-ohos/release/... and the _harmonyos suffix, proving both new arms are live. rustc additionally emits "consider: rustup target add aarch64-unknown-linux-ohos", confirming the triple is upstream-recognized (Tier 2 with host tools).
PR B.1 for #113. First functional slice of HarmonyOS NEXT support: a user with OHOS_SDK_HOME set can now invoke perry compile foo.ts --target harmonyos and get a valid musl-ELF libfoo.so out the other end. No NAPI wrapper yet (B.2), no ArkTS shim (B.2), no HAP bundler (B.3) — this slice stops at "the .so is on disk." Plumbing changes (all in crates/perry/src/commands/compile.rs): * find_harmonyos_sdk() — probes $OHOS_SDK_HOME first, then DevEco's defaults (~/Library/Huawei/Sdk on macOS, ~/Huawei/Sdk on Linux, %USERPROFILE%\Huawei\Sdk on Windows). Accepts either the SDK root or the native/ subdir, and walks openharmony/<api>/native if the DevEco API-level nesting is present. Normalizes to the native/ dir that contains llvm/bin/clang and sysroot/. * harmonyos_cross_env() — emits env vars for cargo build so cc-rs picks up OHOS clang + --sysroot + -D__MUSL__. Sets both CC/CXX (the crate needs libmimalloc-sys, whose build.rs fails with "'pthread.h' file not found" otherwise) and in both hyphen and underscore triple forms (cc-rs prefers underscores; rustc has historically emitted the hyphen form from stable APIs, so we set both for robustness). Plus CARGO_TARGET_*_LINKER and _RUSTFLAGS so rustc links through clang with the sysroot. * auto_rebuild_runtime_and_stdlib wired: when target is harmonyos, prepend the cross env to the cargo_cmd before status(). * Linker branch (new is_harmonyos arm, slotted after Android since they're both clang→.so): -shared -fPIC --target=<arch>-linux-ohos --sysroot=<sdk>/sysroot -D__MUSL__ -Wl,-Bsymbolic -Wl,--warn-unresolved-symbols. Symbolic binding prevents ArkTS's host process from interposing Perry's runtime symbols; warn-only keeps namespace-import shortname externs from blocking the link. * exe_path default: --target harmonyos[-simulator] auto-selects `lib{stem}.so`. The `lib` prefix matches the dlopen name that PR B.2's generated ArkTS shim will use (`import entry from 'libapp.so'`). Knock-on guards (4 sites) so existing branches don't misfire: * is_macho falls through to mach-O on macOS hosts via `(!is_windows && !is_linux && !is_android && cfg!(target_os=macos))`. Added !is_harmonyos — OHOS is ELF, not mach-O. * jsruntime_lib guard: added !is_harmonyos (V8/jsruntime is not a thing on NEXT; same rationale as ios/android/watchos/tvos). * --gc-sections dead-strip path extended from (is_android || is_linux) to include harmonyos. * Android companion-.so copy-next-to-output extended to harmonyos — the B.3 HAP bundler will need them staged identically. UX: fail-fast check at the top of compile() when --target harmonyos is passed, the SDK is absent, and no prebuilt libperry_runtime_harmonyos.a is on disk — bails with a single clear message naming OHOS_SDK_HOME and the default probe paths. Without this, the user would see two messages in sequence (auto-rebuild's "SDK not found" warning + find_runtime's "libperry_runtime.a not found" with unrelated fixes). Verified: * Default host / --target macos / --target ios paths produce byte- equivalent output to pre-B.1 main. * perry compile hi.ts --target harmonyos without OHOS_SDK_HOME: single-line fail-fast error listing remediation. * With OHOS_SDK_HOME=<mock-sdk-with-fake-clang>, cargo is invoked with --target aarch64-unknown-linux-ohos and cc-rs inherits CC_aarch64-unknown-linux-ohos + CXX_* + CFLAGS_* — observed in cc-rs trace output. Mock clang is a no-op shell script, so the compile itself doesn't complete, but the env plumbing is confirmed. * End-to-end on real OHOS SDK requires the Huawei-portal download; deferred to @cavivie's on-device validation once B.2 + B.3 land (there's no way to "run" a standalone .so without the ArkTS shim). Scope not included (lands in B.2 / B.3): * napi_module_register Rust-side entry wrapper (ohos-napi feature). * ArkTS UIAbility / EntryAbility shim generator. * module.json5 / app.json5 / resources scaffolding. * HAP assembly (manual zip, not hvigor — per Decision 3). * hap-sign integration + $PERRY_HARMONYOS_P12 env discovery + perry setup harmonyos subcommand (Decision 4 = a+b).
PR B.2 for #113. Builds on B.1: the .so Perry emits for --target harmonyos now has a NAPI entry point that ArkTS can actually call, plus the ArkTS source files that call it. crates/perry-runtime/src/ohos_napi.rs (new, ~110 lines, feature-gated): * Declares opaque NapiEnv/NapiValue/NapiCallbackInfo + NapiModule struct matching OpenHarmony's node_api.h layout. * Externs the four NAPI functions we need: napi_module_register (for the module registration in .init_array), napi_create_int32 (return value of run()), napi_create_function + napi_set_named_property (to wire run() onto the exports object during nm_register_func). * Externs `main` from the compiler-emitted TS entry. -Wl,-Bsymbolic (added in B.1) ensures this resolves to our own `main`, not the ArkTS host process's `main`. * `run(env, info)` NAPI callback: calls main(), wraps exit code as napi_int32. `napi_init(env, exports)` sets `exports.run = run`. * Module descriptor lives in a `static mut NAPI_MODULE_DESC` so register_module() can plug in nm_modname from static bytes at constructor time. Uses &raw mut to avoid unsafe-referent warnings. * Registration is triggered by `#[link_section = ".init_array"] #[used] static INIT: extern "C" fn() = register_module;` — the ELF constructor-array slot. `dlopen` walks this and invokes every function pointer before returning to ArkTS. crates/perry-runtime/Cargo.toml + src/lib.rs: * New `ohos-napi = []` feature. Module is `#[cfg(feature = "ohos-napi")]` gated (not target_os — cross-build hosts see target_os = linux, the feature flag is the authoritative signal). crates/perry/src/commands/compile.rs: * Feature auto-enable: the compiled_features block was the natural home. When target is harmonyos[-simulator], push "ohos-napi" into the feature list. Two paths covered — the existing `if features_str` branch (user passed --features too) and a new `else if target=harmonyos` branch (user passed no --features at all). Without the second, a bare `perry compile foo.ts --target harmonyos` would build the .so with no napi_module_register and ArkTS's `import entry from 'libfoo.so'` would fail at load with "module entry not found." * Feature passthrough to auto_rebuild_runtime_and_stdlib: ohos-napi added next to ios-game-loop/watchos-game-loop in the `perry-runtime/<feat>` rewrite list at compile.rs:1302. * `emit_harmonyos_arkts_stubs(output_dir, so_filename)` — writes two files to `<output_dir>/ets/`: - entryability/EntryAbility.ets: UIAbility subclass. onCreate calls `perryEntry.run()` once per ability instance. onWindowStageCreate loads `pages/Index`. Other lifecycle hooks (Destroy, Foreground, Background) are no-op for v1. - pages/Index.ets: ArkUI `@Entry @component struct Index` with a centered `Text('Perry app running')`. Enough to load; UI lands in PR C (TS→ArkTS emitter for perry/ui). The `import perryEntry from '<so_filename>'` specifier is templated off exe_path.file_name() so `-o libfoo.so` stays consistent with what ArkTS dlopen's. * Called from the post-link section (after the `link cmd.status()?` gate), before the android/harmonyos companion-.so copy block. Gated on `is_harmonyos`. A failure in shim-emission is logged as a warning, not a hard error — the .so is still useful without it if the caller wants to write their own ArkTS shim (unlikely, but possible). Deliberately NOT in B.2 — lands in B.3: * module.json5 / app.json5 / resources (strings, icons, colors). * HAP .zip assembly + hap-sign + $PERRY_HARMONYOS_P12 env discovery. * `perry setup harmonyos` subcommand (Decision 4b). * ArkTS → .abc compilation step (the .ets files land in the HAP as either source or bytecode; B.3 decides and implements). Verified: * `cargo check -p perry-runtime --features ohos-napi --no-default-features` — clean (only preexisting warnings). * `cargo build --release -p perry` — clean. * Standalone render of emit_harmonyos_arkts_stubs via a one-off binary: produces valid ArkTS, import specifier templates correctly. * `perry compile hi.ts --target harmonyos` (no SDK) still hits the B.1 fail-fast with a single clear message.
PR B.3 for #113. Final Phase-1 piece: the .so + ArkTS shim from B.1/B.2 are now wrapped in a valid .hap archive that hdc can (in principle) install. Per Decision 3, we do the HAP assembly ourselves instead of shelling out to hvigor — no Node.js or DevEco-project dependency. New crates/perry/src/commands/harmonyos_hap.rs (~560 lines incl. tests): * build_hap(HapBuildArgs) — top-level entry. Stages a directory: <stem>.hap_staging/ app.json5 — bundleName, vendor, version, icon/label refs module.json5 — EntryAbility ability + entity.system.home skill pack.info — summary + packages blocks hap-sign needs ets/ — copied from B.2's emit_harmonyos_arkts_stubs libs/arm64-v8a/ — the .so, renamed-preserved resources/base/ media/icon.png — inlined 68-byte 1×1 white placeholder string/string.json — app_name + EntryAbility_label color/color.json — start_window_background = #FFFFFFFF profile/main_pages.json — src: ["pages/Index"] Then zips into <stem>.unsigned.hap. If signing creds are available (see below) the signing step also runs and drops <stem>.hap next to the unsigned file (which is then removed — two HAPs in the output dir would be confusing). * Signing (Decision 4a). Reads three env vars: PERRY_HARMONYOS_P12, _P12_PASSWORD, _PROFILE If any is missing, emits unsigned + one-line remediation. With all three present, resolves hap-sign-tool.jar from the SDK (or the override PERRY_HARMONYOS_HAPSIGN) and shells out to java -jar <jar> sign-app -keyAlias perry-signing-key -signAlg SHA256withECDSA -mode localSign -appCertFile <profile> -profileFile <profile> -inFile <unsigned> -outFile <signed> -keystoreFile <p12> -keystorePwd <password> This is Huawei's documented minimum; users with existing cert pipelines may need env overrides once we get on-device feedback. * ets-loader detection. Probes four known SDK layouts for es2abc (standalone binary — preferred because no Node required) and falls back to a node-based ets-loader/main.js if it's there. If neither is found we ship .ets source and print a warning — the HAP still assembles, but will only install on DevEco emulator with source-mode enabled, not on real NEXT hardware. * Bundle naming. $PERRY_HARMONYOS_BUNDLE_NAME wins; otherwise `com.perry.app.<sanitized_stem>` (ASCII alnum only, leading-digit safe, consecutive-separator-safe). Real deploys must override — Huawei certs pin bundleName. * Placeholder icon. A real app's icon will come from perry.harmonyos.icon in package.json (not in this PR). Until then we inline a valid 68-byte PNG so the $media:icon ref resolves and hap-sign doesn't reject. Wiring in compile.rs: * The existing is_harmonyos post-link block now chains emit_harmonyos_arkts_stubs → harmonyos_hap::build_hap. Both are gated behind the link succeeding. Logs a single summary line: Wrote HAP: libhi.unsigned.hap (unsigned, ets: source) HAP failures are eprintln! warnings — the .so is still useful for inspection even if the HAP step fails. * A new crates/perry/src/commands/mod.rs line (`pub mod harmonyos_hap`) registers the module. Tests (crates/perry/src/commands/harmonyos_hap.rs#tests): * assembles_unsigned_hap_with_expected_layout — feeds a fake .so and fake ets/ to build_hap, verifies the produced zip contains every required layout member (10 entries), verifies the PNG magic bytes survive zip roundtrip, verifies the bundle-name fallback (`com.perry.app.hi`) appears in app.json5. Ignores signing by scrubbing env vars. Passes in ~10ms. * sanitize_bundle_segment_handles_edge_cases — covers hyphen rewrite, leading-digit prefix, consecutive-non-alnum collapse. Deferred to follow-up PRs (not blocking @cavivie's on-device validation): * `perry setup harmonyos` wizard (Decision 4b). Env-var-only signing is enough for the first on-device install; a proper wizard with persisted config is its own PR. * User-provided icon / resource bundle (`perry.harmonyos.icon`, strings, colors). * x86_64 ABI dir for `--target harmonyos-simulator`. Currently libs/arm64-v8a/ is hardcoded; x86_64-linux-ohos output would need libs/x86_64/ and a runtime that can pick the right one. * `perry.toml` / `package.json` bundleName + version integration. Verified: * `cargo test --release -p perry --bins harmonyos_hap` — 2/2 pass. * `cargo build --release -p perry` — clean. * `perry compile hi.ts --target harmonyos` without SDK — still single fail-fast from B.1 ("OHOS SDK not found"). * Default host / macos / ios compile — byte-equivalent to pre-B.3. * Standalone render of a HAP via the unit test produces a valid ZIP that unzip(1) accepts, with the expected layout at every path. End-to-end `hdc install` requires a real OHOS SDK + signing cert; handing off to @cavivie for on-device validation once they pull.
Three parallel audits against Huawei's real sources caught seven
install-blocking bugs in the from-memory B.1-B.3 implementation.
Auditing against:
- developtools_hapsigner (hap-sign-tool CLI spec)
- developtools_packing_tool (pack.info canonical parser, C++ + Java)
- arkui_napi / ace_napi (napi_module_register semantics)
- arkcompiler_ets_frontend (es2abc CLI + supported extensions)
- applications_app_samples (minimal reference HAPs)
- openharmony/docs app.json5 + module.json5 references
Fixes landed in this commit:
1. NAPI modname ↔ .so filename mismatch (worst bug — silently fails).
nm_modname was hardcoded "entry", default output was lib<stem>.so.
OHOS's NativeModuleManager resolves `import X from 'libfoo.so'` by
stripping lib/.so from the filename and looking up that name, so
unless .so was literally libentry.so, the import resolved to
undefined and every property access threw at load time.
Fix: derive nm_modname at .init_array time via dladdr(register_module,
&info), extract basename of dli_fname, strip "lib" prefix + ".so"
suffix, copy into a 256-byte static buffer. Works for any -o output
name. Falls back to "entry" if dladdr fails (matches DevEco's
default-template modname). Single-threaded (init constructor runs
before any ArkTS code) so no locking needed.
2. app.json5 missing minAPIVersion / targetAPIVersion / apiReleaseType.
HAP install-time verification rejects without these. Added all three:
minAPI=11 (HarmonyOS NEXT floor), target=12 (current DevEco 5.x),
apiReleaseType="Release" (valid values: "Canary<N>" / "Beta<N>" /
"Release"). Note apiReleaseType is spelled DIFFERENTLY from the
releaseType key used inside pack.info — same semantics, different
key name. Java parser checks both locations separately.
3. pack.info structural fixes.
- summary.modules[0].name was bundleName; must be module name "entry".
- summary.modules[0].package same — must be "entry".
- packages[0].name same — must be "entry".
Confusingly, summary.app.bundleName above IS the bundleName. Most
common shape bug in hand-rolled HAPs per the audit.
- apiVersion belonged under summary.app.apiVersion, not
summary.modules[0].apiVersion. The Java parser reads the app-level
path; a module-level duplicate is silently ignored. We put it in
both locations for robustness across SDK versions.
- deviceType (singular in pack.info, plural in module.json5) must
match byte-for-byte; added "2in1" to pack.info side to align with
module.json5's ["phone","tablet","2in1"]. packing_tool's HapVerify
rejects mismatches before hap-sign even runs.
- API target 10 -> 12. 10 = HarmonyOS 4.x, pre-NEXT.
4. hap-sign CLI reworked per developtools_hapsigner README lines 297-314.
- -appCertFile and -profileFile are DIFFERENT files (cert chain
.cer/.pem vs signed provisioning profile .p7b). B.3 passed the same
PERRY_HARMONYOS_PROFILE path to both; hap-sign rejects (cert chain
parser can't read p7b). Split into two env vars:
PERRY_HARMONYOS_CERT — cert chain (.cer)
PERRY_HARMONYOS_PROFILE — signed profile (.p7b)
- -keyPwd added. DevEco-generated p12s have a separate key password
from the keystore password. New env var PERRY_HARMONYOS_KEY_PASSWORD;
falls back to PERRY_HARMONYOS_P12_PASSWORD if unset (common case
where they're the same).
- -keyAlias was hardcoded "perry-signing-key" which never matches
anything. DevEco auto-signing uses "debugKey". New env var
PERRY_HARMONYOS_KEY_ALIAS, defaults to "debugKey".
- -signAlg configurable via PERRY_HARMONYOS_SIGN_ALG (defaults
SHA256withECDSA; only other accepted value is SHA384withECDSA).
- Explicit -profileSigned 1, -inForm zip, -signCode 1. These are
defaults but explicit survives SDK-version drift.
5. ets-loader probe paths were all wrong.
- B.3 probed <sdk>/build-tools/ets-loader/bin/ark_ts2abc_bin/es2abc
+ three variants under <sdk>/toolchains/. None exist in DevEco 5.x.
- Real path: <sdk_root>/<api>/openharmony/ets/build-tools/ets-loader/.
find_harmonyos_sdk returns <api>/openharmony/native/, so ets-loader
is at `native/../ets/build-tools/ets-loader/`.
- es2abc lives under ets-loader in a host-OS-specific subdir:
build-mac/bin/es2abc (macOS)
build-win/bin/es2abc (Windows)
build/bin/es2abc (Linux)
We validate presence but don't invoke it directly — ets-loader
spawns it internally.
6. Pipeline structurally rewritten. es2abc does NOT accept .ets — only
js/ts/as. B.3's "run es2abc over each .ets file" approach was
infeasible. Real pipeline is two-stage: ets-loader (Node/rollup npm
package) desugars ArkUI decorators + bundles .ets -> .ts, then
invokes bundled es2abc to emit .abc. compile_ets_to_abc now invokes
ets-loader via `node <loader>/main.js --hap-mode=release <ets-dir>`
from the staging dir. Hvigor orchestrates this with a richer
build-profile.json5 we don't fully synthesize yet; our minimal
invocation may or may not work on first try. First emulator run will
tell us if we need to vendor more hvigor glue.
7. Source-fallback message updated. Physical NEXT devices reject
.ets-source HAPs entirely (no debug-mode exception). Fallback now
clearly states the HAP won't install and points the user at
DevEco/hvigor for completion.
Tests extended:
* serde_json strict-parse of pack.info (catches trailing commas).
* Assert summary.modules[0].{name,package} == "entry".
* Assert packages[0].name == "entry".
* Assert summary.app.apiVersion is an object.
* Assert app.json5 has minAPIVersion / targetAPIVersion /
apiReleaseType: "Release".
* Scrub all eight PERRY_HARMONYOS_* env vars at test start.
Also in this commit: ohos_napi.rs's modname buffer machinery (dladdr
+ basename extraction + "lib"/".so" stripping). 30-ish lines of
unsafe pointer walking — reviewed carefully, matches what every
OHOS NAPI example (including Huawei's own) does under the
NAPI_MODULE() macro.
NOT in this commit, staying as follow-up work:
* Reading DevEco's build-profile.json5 auto-signingConfigs[] block
directly instead of requiring users to set env vars manually.
* More hvigor-glue if ets-loader's bare invocation fails on first
emulator run.
* --target harmonyos-simulator libs/x86_64/ ABI dir.
* perry.toml / package.json bundle-name + icon integration.
* perry setup harmonyos wizard.
…128) PR B.5 for #113. First real-SDK run (DevEco Studio 6.0.1, API 21). Three patches driven by what actually broke on the host: 1. DevEco 6.x SDK layout probe. 5.x put the SDK at ~/Library/Huawei/Sdk/openharmony/<api>/. 6.x bundles it inside the app: /Applications/DevEco-Studio.app/ Contents/sdk/default/openharmony/. The per-API numeric subdir was replaced with a single `default/`. `find_harmonyos_sdk()`'s `normalize` gained two arms for the new shape, and the default candidate list picks up the app-bundle path on macOS. All downstream paths (toolchains/lib/hap-sign-tool.jar, ets/ build-tools/ets-loader/bin/ark/build-mac/bin/es2abc, etc.) are unchanged — only the SDK root moved. 2. macOS host-side link args leaking into cross-compile. First real compile failed with: ld.lld: error: unknown argument '-framework' ld.lld: error: cannot open Security: No such file or directory ld.lld: error: unable to find library -liconv / -lobjc Root cause: the linker-command if/else chain had an `} else { if cfg!(target_os="macos") || is_cross_macos { -framework Security -framework CoreFoundation -framework SystemConfiguration -liconv -lresolv -lobjc } }` branch that fires whenever the target isn't explicitly ios/tvos/watchos/android/linux/windows. HarmonyOS fell through to it on a macOS host. Added dedicated `is_harmonyos` arm before `is_linux` with OHOS-correct libs: `-Wl,--allow-multiple-definition -lm -lpthread -ldl -lace_napi.z`. musl folds m/pthread/dl into libc.a so those -l flags are no-ops (harmless); libace_napi.z.so provides the NAPI symbols ohos_napi.rs imports (napi_module_register, napi_create_int32, napi_create_function, napi_set_named_property). OHOS convention is `<name>.z.so` and `-l` strips lib/.so but not the middle `.z`, so `-lace_napi.z` is the deliberate spelling. Also: `!is_harmonyos` added to the strip-debug-symbols guard — BSD strip on macOS emits a noisy "non-object and non-archive file" warning on ELF binaries (warning only, but confusing in the output). 3. ets-loader pipeline replaced with direct es2abc invocation. Empirical test against the installed SDK: ets-loader is a ~15-env-var Node/rollup pipeline that reads `aceModuleRoot`, `aceModuleBuild`, `aceModuleJsonPath`, `aceProfilePath`, `compileMode=moduleJson`, plus a full DevEco build-profile.json5. Without the full setup it silently exits 0 and produces no .abc. Synthesizing all of that is effectively re-implementing hvigor. Pivot: es2abc --extension ts empirically accepts plain-TypeScript content in a .ets file; it only rejects ArkUI-specific syntax (@Entry/@Component/struct). Phase 1 ships no ArkUI, just one plain UIAbility, so we invoke es2abc directly and skip ets-loader entirely. PR C (TS→ArkTS emitter) brings ets-loader back when it produces real ArkUI decorators. `compile_ets_to_abc` now: * finds es2abc at <sdk_native>/../ets/build-tools/ets-loader/bin/ ark/build-<host>/bin/es2abc * collects every .ets under staging/ets/ * one invocation: es2abc --module --merge-abc --extension ts --output ets/modules.abc <inputs...> * deletes the source .ets files HAPs ship a single merged `ets/modules.abc`, not per-file .abc. 4. Dropped pages/Index for Phase 1. EntryAbility.ets no longer imports `@ohos.window`, no longer has `onWindowStageCreate` with `windowStage.loadContent('pages/Index')`, and no longer needs a pages/Index.ets sibling. The UIAbility runs `perryEntry.run()` in onCreate; window stays blank but console.log output reaches hilog. That's enough to validate Phase 1's goal: cross-compile → NAPI bind → TS main() executes. Correspondingly: * module.json5 drops the `pages: "$profile:main_pages"` field. * main_pages.json no longer emitted. * resources/base/profile/ dir no longer created. PR C reintroduces all three when it can emit valid ArkUI pages. Verified end-to-end (real DevEco 6.0.1 SDK): * perry compile hi.ts --target harmonyos succeeds with no warnings. * libentry.so: 8.6 MB, ELF 64-bit aarch64, NEEDED=[libc.so, libace_napi.z.so] (zero macOS cruft, zero glibc-only libs), .init_array has 2 function pointers (Rust's own init + our register_module), perry_runtime::ohos_napi::register_module present as GLOBAL FUNC, napi_module_register as UND (imported). * entry.unsigned.hap: 17 members, 9 MB, ets/modules.abc 1372 bytes with `PANDA` magic header, module.json5 bundle fallback through cleanly, no stray .ets source files. * Unit tests still pass after dropping pages/Index from expected layout (modified: required[] no longer includes profile/ main_pages.json or ets/pages/Index.ets). Blocking the first `hdc install` test: * Signing env vars (PERRY_HARMONYOS_P12/_P12_PASSWORD/_CERT/_PROFILE/ _KEY_ALIAS/_KEY_PASSWORD/_BUNDLE_NAME) — user runs through DevEco's auto-signing flow in a throwaway project, then copies paths + passwords into their shell. * API-level compatibility. We set min=11, target=12. DevEco 6.x primary SDK is API 21; install-time verification may complain. Easy follow-up if it does: bump both, ideally read from the SDK root automatically. Not yet touched: * `--target harmonyos-simulator` libs/x86_64/ path (hardcoded arm64-v8a today; emulator runs aarch64 anyway so not blocking). * Reading DevEco's build-profile.json5 signingConfigs.material to avoid manual env var setup. The seven env vars work but are fiddly.
…v0.5.129) PR B.6 for #113. First time a Perry-emitted .so survives the OHOS dynamic linker and `aa start EntryAbility` succeeds without crashing. Before this patch, `hdc install` worked but onCreate threw TypeError: Cannot read property run of undefined Error relocating libentry.so: mi_malloc_aligned: symbol not found Root cause: libmimalloc-sys's build.rs compiles its C sources to a loose <hash>-static.o (362 KB, 154 mi_* symbols) AND emits a libmimalloc.a wrapper — but on macOS→OHOS cross-builds the .a comes out as a zero-member BSD-format archive (__.SYMDEF SORTED layout; llvm-ar can't enumerate it). Rust's staticlib "bundle native libs" path silently skips empty archives, so libperry_runtime.a shipped with zero mi_* definitions. On macOS/Linux targets this hasn't bitten us because Perry links via rustc's driver there, which honors `cargo:rustc-link-lib=static= mimalloc` directives and drags in the loose .o anyway. The harmonyos branch invokes clang directly (we want --sysroot + --target=aarch64- linux-ohos, rustc would wrap them awkwardly), so we miss all of rustc's link-lib hints. Fix: walk target/<variant>/<triple>/release/build/*/out/ recursively (cc-rs can nest under c_src/mimalloc/v2/src/) and append every .o to the clang link line. Triggers for both the perry-auto-<hash>/ (auto-rebuild) and plain <triple>/ (manual cargo build) roots. Over-inclusion is safe: `--gc-sections` (already enabled for ELF via the is_android || is_linux || is_harmonyos arm) dead-strips anything the final binary doesn't actually reference. Result on the wearable NEXT emulator (DevEco 6.0.1(21), SP3DEVC900E1): * libentry.so: 8.6 → 8.8 MB (+200 KB mimalloc C code). * mi_malloc_aligned: UND → 0x1aaed8 FUNC GLOBAL (llvm-readelf confirmed after the rebuild). * `bm install` + `aa start -a EntryAbility -b com.example.myapplication` both succeed. * "Hello world" default ArkUI page renders. * `perryEntry.run()` in onCreate returns (didn't throw). Also in this commit (discovered during emulator validation, not linker-related): * TARGET_API bumped 12 → 21 to match DevEco 6.0.1(21)'s primary SDK. minAPIVersion stays at 11 (HarmonyOS NEXT floor). * "wearable" added to deviceTypes (module.json5) and deviceType (pack.info) — phone emulator is gated behind Huawei real-name verification, wearable is the only image unverified accounts can download, so validation happens there. * ArkTS shim skill action: action.system.home → ohos.want.action.home (both accepted per OHOS spec, but the newer spelling matches DevEco 6 templates and surfaces fewer deprecation warnings). * Module-level docstring in harmonyos_hap.rs enumerates all 7 PERRY_HARMONYOS_* env vars (was stale, listed only 3; B.4 added CERT/KEY_ALIAS/KEY_PASSWORD/SIGN_ALG but didn't update the docs). Open cutoff items (deliberate handoff to physical-device tester in #113): * Does TS `console.log()` reach `hdc hilog`? perry-runtime writes to `println!()` → fd 1, which OHOS does not route into hilog by default. Likely needs a stdout→hilog shim in ohos_napi.rs's init_array constructor (dup2 fd 1 + fd 2 to a pipe-reader thread that calls OH_LOG_Print). Not tackled here — validation by @cavivie on physical metal will confirm whether the emulator behavior matches the device. * ArkTS import syntax: `requireNapi('entry')` resolved via a .d.ts + `file:` dependency in the DevEco project's oh-package.json5, not via direct `import ... from 'libentry.so'` (ArkTS's strict compiler rejects the bare import without a declaration). Clean path: have `perry compile --target harmonyos` emit the .d.ts + oh-package.json5 next to the .so. Follow-up. * `hdc install` of Perry's own assembled .hap: still gated on resolving DevEco-encrypted passwords or using the bundled OpenHarmony.p12 path. Current emulator validation uses the "splice .so + .ets into an existing DevEco project, let hvigor sign" workaround (documented in harmonyos_hap.rs module docstring).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 for #113 — HarmonyOS NEXT support end-to-end from
perry compile foo.ts --target harmonyosto a.hapfile on disk. Stacked on top of PR #121 (PR A — scaffolding); merging this after #121 is merged tomain.What's in scope
Three commits, each self-contained:
v0.5.124 — Toolchain (PR B.1)
find_harmonyos_sdk()probes$OHOS_SDK_HOME, then DevEco defaults (~/Library/Huawei/Sdk,~/Huawei/Sdk,%USERPROFILE%\Huawei\Sdk).harmonyos_cross_env()emitsCC_/CXX_/CFLAGS_/CXXFLAGS_/CARGO_TARGET_*_LINKER/_RUSTFLAGSforcargo build, both hyphen and underscore triple forms.is_harmonyoslinker branch:clang -shared -fPIC --target=aarch64-linux-ohos --sysroot=<sdk>/sysroot -D__MUSL__ -Wl,-Bsymbolic -Wl,--warn-unresolved-symbols.exe_pathdefault: auto-selectslib<stem>.sofor--target harmonyos[-simulator].!is_macho(ELF),!is_harmonyosinjsruntime_lib,--gc-sectionsdead-strip, Android-style companion-lib copy.v0.5.125 — NAPI + ArkTS shim (PR B.2)
crates/perry-runtime/src/ohos_napi.rsbehindohos-napifeature flag: opaque NAPI types,napi_module_registervia#[link_section = ".init_array"]constructor, one exported functionrun()that calls Perry's compiledmain()and returns its exit code as NAPI int32.--target harmonyosis passed (both--features-present and--features-absent paths covered).emit_harmonyos_arkts_stubs()writesets/entryability/EntryAbility.ets+ets/pages/Index.etsnext to the.so. Import specifier is templated off the actual output filename.v0.5.126 — HAP bundler (PR B.3)
crates/perry/src/commands/harmonyos_hap.rsassembles the full.haparchive ourselves (no hvigor — per Decision 3). Stages{app.json5, module.json5, pack.info, ets/, libs/arm64-v8a/<stem>.so, resources/base/{media,string,color,profile}/}, zips via the existingzipworkspace dep..ets→.abccompilation via SDK'ses2abc/ets-loaderwhen discoverable. Falls back to shipping source + warning otherwise.$PERRY_HARMONYOS_P12/_P12_PASSWORD/_PROFILE+ resolvedhap-sign-tool.jar(or$PERRY_HARMONYOS_HAPSIGNoverride), shells out tojava -jar .... Missing creds → unsigned HAP + one-line remediation.$PERRY_HARMONYOS_BUNDLE_NAMEoverrides; fallbackcom.perry.app.<sanitized_stem>.Decisions (agreed before implementation)
.sooutput for--target harmonyos?entry.run()returning number?hvigor— assemble HAP zip ourselves + shell out tohap-signonly?$PERRY_HARMONYOS_*env vars +perry setup harmonyoswizard?What's explicitly not in this PR
perry setup harmonyoswizard — Decision 4b. Env-var-only path covers @cavivie's first validation; wizard is its own PR if the UX warrants it.perry.harmonyos.iconin package.json is a follow-up.--target harmonyos-simulator—libs/arm64-v8a/is hardcoded for now.perry-ui-harmonyos+ TS→ArkTS emitter — that's Phase 2 (PR C).Platformenum variant — deliberately not touched (would force prematurepublish/runbehavior decisions).compiletakesOption<String>so the string-level wiring is sufficient.Verification
cargo build --release -p perry— clean (only pre-existing warnings).cargo test --release -p perry --bins harmonyos_hap— 2/2 pass.cargo check -p perry-runtime --features ohos-napi --no-default-features— clean on host.perry compile hi.ts(default host),--target macos,--target ios— byte-equivalent to pre-B output.perry compile hi.ts --target harmonyoswithout SDK — single fail-fast with remediation namingOHOS_SDK_HOME.CC_aarch64-unknown-linux-ohos=<sdk>/llvm/bin/clangflowing through cc-rs invocations.app.json5carries the sanitized bundle name.Test plan for @cavivie (on real NEXT hardware)
export OHOS_SDK_HOME=<path>export PERRY_HARMONYOS_P12=<path>,PERRY_HARMONYOS_P12_PASSWORD=<pw>,PERRY_HARMONYOS_PROFILE=<p7b>,PERRY_HARMONYOS_BUNDLE_NAME=<cert-bundle>perry compile hello.ts --target harmonyos— expectlibhello.so,ets/,hello.hapin output dirhdc install hello.hapon physical NEXT device — expect install to succeedhdc hilog).etscompilation worked or fell back to source, (b) whether signing worked, (c) any runtime errorsExpected rough patches needed:
hap-sign-tool.jararg set may drift between OHOS SDK versions; x86_64-simulator ABI dir; any NAPI symbol we missed.Follow-ups if Phase 1 lands cleanly
perry-ui-harmonyoscrate + TS→ArkTS emitter forperry/uiHIR → ArkUI components. BridgesState<T>→@State/@Link.