Skip to content

Compile resources in-tree, cut the SDK surface to two binaries, implement product variants - #43

Merged
iamhyc merged 21 commits into
mainfrom
feat/toml-owned-manifests
Nov 8, 2026
Merged

iamhyc merged 21 commits into
mainfrom
feat/toml-owned-manifests

Conversation

@iamhyc

@iamhyc iamhyc commented Nov 8, 2026

Copy link
Copy Markdown
Member

What and why

The SDK surface this tool depends on was 6 platform binaries, and the build path ignored the platform list the npm package itself ships. restool and syscap_tool are now replaced by in-tree implementations (res_ids, res_index_build, res_index_write, rpcid_sc), cmake and ninja come from the host PATH with the SDK copies as fallback, and sdk_es2abc probes build-win, build-mac and build instead of assuming build-mac. Linux SDKs name that directory build, so both Linux targets the npm package ships stopped at es2abc not found.

Alongside that: the npm platform packages are aligned to 26.0.0-8 and all five are declared (two were missing, so those users installed a launcher with no binary behind it), the SDK layout is defined in one place (manage::component_root), product variants are implemented, and the hvigor build leftovers are out of the tree.

Three changes restructure the tool itself. src/ went from a flat layout to domain modules, and the compiler output now matches what hvigor produces, so the two build paths agree (d4677e2, breaking). The CLI verbs are build and migrate instead of assemble and import, with the docs and help text following. hvolt.toml carries routes, widgets, backup and main, the route manifest is generated from it, and the native libraries ship with the package and build at the project's mode.

Two defects made a clean clone unusable: coverage_map.rs read a gitignored path through include_str!, so cargo test could not compile, and 138 golden/modules.abc baselines were excluded by *.abc, leaving abc_records without input.

Checks

  • cargo fmt --all --check is clean
  • cargo clippy --all-targets -- -D warnings finishes without warnings
  • cargo test passes
  • Behaviour changes come with a test
  • Any claim about engine/SDK/device behaviour states how it was measured
  • --help text and comments naming a changed command are updated
  • CHANGELOG.md has an entry if the CLI surface or project format changed

Tested with

hvolt 26.0.0-8, macOS darwin-arm64, SDK v26.0.0.23, device PLR-AL00 (API 26) over the relay host.

cargo fmt --all --check                      → clean
cargo clippy --all-targets -- -D warnings    → 0 errors
cargo test                                   → 533 passed; 0 failed; 4 ignored

hvolt build (clashbox)                       → entry-signed.hap 122033.6 KB
hvolt push --hap C:/tmp/entry-signed.hap -d 192.168.1.2:8710
                                             → install bundle successfully.
hvolt exec 192.168.1.2:8710 --shell "aa start -a EntryAbility -b org.xbgroup.clashboxLTS"
                                             → start ability successfully.
ps -ef | grep xbgroup                        → 36741 org.xbgroup.clashboxLTS
                                               37023 org.xbgroup.clashboxLTS:vpn
hidumper -s WindowManagerService             → clashboxLTS0  pid 36741  1316x2832
CMakeCache.txt                               → CMAKE_COMMAND=/opt/homebrew/bin/cmake
                                               CMAKE_MAKE_PROGRAM=/opt/homebrew/bin/ninja

Behaviour changes carry their own measurement: es2abc_resolves_under_each_host_layout covers the three host layouts, lists_the_records_of_a_golden_abc covers the tracked golden byte-code, and the npm version alignment is checked by the clean-code.md §13 command set (three version strings identical, platform coverage 5 = 5).

iamhyc added 21 commits November 8, 2026 00:55
…with hvigor

`.gitignore`'s unanchored `build/` pattern also matched `src/build/`, so the
compiler's own source tree (63 .rs files) was never committed. Anchor it to
`/build/` and commit the split (`src/{build,cli,device,project,sdk,sign,support}`).

Compiler work in this change:

- `.ets` preprocessing gains the imported `@Builder` names and the file's own
  `struct` names: the `entry` module now lowers 124/124 sources (was 120/124),
  and a bare call to a same-file component keeps its child block rewritten.
- import rewriting: a read in a type position keeps its binding, so the
  `import type` split no longer drops a specifier the lowering erased; an
  explicit `X as X` alias survives oxc_codegen's same-name folding.
- `module.json` writes `routerMap` when the module ships `route_map.json`, and
  the staged `route_map.json` is filled with `ohmurl`/`bundleName`/`moduleName`
  per entry. Without `ohmurl` the installer resolves the route name but crashes
  on the module record.
- `{page,line,col}` carries the source coordinate: the child-block rewrite's
  column widening is subtracted back, and a call site the lowering cannot find
  keeps a real position instead of `0,0`.
- `@Extend` helpers merge by parser coordinate, so an `export default` keeps the
  source order the reference emits.
- the V2 emitter keeps a `readonly` the source wrote.
- full bytecode verdict on clashbox: 298/298 reference files equal on all four
  axes; `cargo test` 461 passed; oracle 135 PASS / 2 FAIL (comment text only).
…'s mode

A flat project produced a HAP with no `.so` at all, so every NAPI module read as
`undefined` on the device (`export objects of native so is undefined, so name is
@Normalized:Y&&&libflclash.so&`) and the proxy core never loaded. Three causes:

- precompiled libraries were never copied: the packer reads one flat
  `libs/<abi>/`, while a module keeps its own under `<module>/libs/<abi>/`.
- the flat layout's native sources were never compiled: `build_all` only looked
  for `libs/<name>/CMakeLists.txt`, not `<module>/cpp/CMakeLists.txt`.
- the C++ runtime was never shipped: `xb_components` builds with
  `CMAKE_CXX_STANDARD 20` and cannot be dlopen-ed without `libc++_shared.so`.

Also build native code at the project's own build mode. Release inlined away
every `NapiUtils::toNapiValue<…>` instantiation, which halved
`libxb_components.so` (92 552 vs 206 608 bytes) and dropped 178 exported symbols.

After: all four libraries are packed; `libproxy_core.so`, `libxb_components.so`
and `libc++_shared.so` match the reference byte-for-byte in size, and all four
have an identical defined-symbol set (42/275/122, md5 equal).
…laims

The CLI has been calling these commands build and migrate for a while; the
documents still described the old names and two flows that no longer exist
(the hvigorw handoff, the lint section).

Also records what the build actually produces: build/*-unsigned.hap rather
than output/, hvolt device list rather than device targets, and run's real
semantics (push+launch; build+sign only when [app.signing] is set).
Behaviour is unchanged: the variant, the dispatch arm and the progress line
carry the new name. The old one described a one-way copy, which the command
is not — it folds the hvigor manifests into hvolt.toml.
A migrated project kept four hvigor manifests beside its sources:
route_map.json, form_config.json, backup_config.json and the main entry in
oh-package.json5. They are fields on the module table now, so the only
configuration a project carries is hvolt.toml; migrate fills them from the
originals and reports the keys it could not express.

Nothing reads them yet — the next commits wire the consumers.
A migrated project no longer carries route_map.json, so the build has to
produce it and the lowering has to register the same routes. Both now read
one resolver (deps::routes): the table when the module declares one, the
manifest on disk otherwise.

Resolution refuses rather than guesses. A route names one page and one
builder; a file can declare several global builders (Log.ets has
LogBuilder and LogDetailsBuilder), so 'the file's first builder' would
register LogDetails against LogBuilder and open the wrong page with no
output saying so. Every ambiguous case is an error naming the entry.

Measured on clashbox-flat: 19/19 routes resolve to the manifest's page and
buildFunction, 19 files carry NavigationBuilderRegister.

Also in this file set: the resource stage is rebuilt (and app-stage with
it) instead of merged into the previous run, so a deleted resource stops
reappearing in the HAP; empty resource directories are skipped because
restool rejects a directory with nothing under it.
A native module lived at libs/<name> by convention, so a dependency
declared as path = "mylib" was never built — the HAP shipped without its
.so and nothing said so. build_all now takes the resolved directory and
refuses when it holds no CMakeLists.txt.

examples/hello-native now packs libs/arm64-v8a/libhello.so (6168 B);
before this change the archive held only libc++_shared.so.
Three classes of repair, all found by walking the CLI as someone who has
never seen it:

- Messages that sent the reader somewhere useless: 'run hvolt build first'
  after a successful build that produced only an unsigned package, 'run
  hvolt dep sync' for a command that does not exist, and a signing remedy
  that reopened the staleness it was meant to fix. The unsigned path now
  says build → sign → push in that order.

- Silent staleness: a signed package older than the newest build, or older
  than the sources, is refused instead of installed. project-mode
  sign --local refuses before signing, because re-signing refreshes the
  mtime and defeats every mtime check downstream.

- Tooling that reported the wrong thing: doctor claimed all checks passed
  while printing warnings, sdk list showed one installation as three rows,
  check printed Chinese among English output, and the help's first example
  could not run.

Also gates the parse/USB trace output behind HVOLT_DEBUG_PARSE and
HVOLT_USB_DEBUG.
coverage_map.rs (165 lines) had no callers: it read
.scratch/hvigor-oracle/coverage-map.json through include_str!, and .scratch/
is gitignored, so a clean clone could not compile the test target at all.

examples/*/build and examples/*/.hvigor held 183 tracked files (11.2 MB) of
hvigor output; /examples/*/build/ is now ignored next to the anchored /build/.
res_ids reproduces restool's id allocation and res_index_build +
res_index_write reproduce resources.index byte for byte; both agree with the
tool on a real project. rpcid.sc is encoded by rpcid_sc.rs instead of
syscap_tool.

The SDK tool stays reachable from cfg(test) only, so the comparison channel
survives while a release build has no path to it.
- cmake and ninja now come from the host PATH; the SDK copies are the fallback
- es2abc is probed under build-win, build-mac and build, replacing the
  hard-coded build-mac that made every Linux build stop at 'es2abc not found'
  (the npm package ships linux-x64 and linux-arm64)
- cli/build.rs calls ets_assemble::sdk_es2abc instead of keeping a second copy
  of the same resolution, so one place decides where the compiler is
The five platform packages and the optionalDependencies entries were still at
26.0.0-7 while Cargo.toml and the launcher package were 26.0.0-8, so
installing 26.0.0-8 resolved 26.0.0-7 binaries.

optionalDependencies also listed three platforms while npm/stage.mjs packs
five: darwin-x64 and linux-arm64 users got the launcher with no binary behind
it.
- ADR: es2abc stays (no compiler backend in this crate); the data files stay
  version-bound to the SDK, because hvolt supports several SDK versions
- SDK surface report re-measured: 6 platform binaries -> 2 (es2abc, llvm-strip)
- clean-code 13: the semver mirrors the SDK version and six places must agree
- .gitignore: examples/*/build/ beside the anchored /build/
[products.<name>] overrides the artifact identity (bundle / name / version /
version_code / vendor / icon), the signing config, and the resource
directories; unwritten fields inherit from the root table. sdk_version,
runtime_os, members and dependencies describe the project rather than the
artifact, so they cannot vary per product.

migrate carries products and their signing configs over from the root
build-profile.json5, and doctor takes --product to validate the one a build
would select.
- device flow: when the build host cannot reach the device, relay the signed
  HAP to a host that can instead of rebuilding or re-signing elsewhere
- skill reference: hvolt log / screenshot / exec, including that exec --shell
  is the way to start an installed package and run rebuilds it
- changelog: dep sync, abc-records, and the build no longer reading a hvigor
  manifest
The decision predates the implementation; the file was never tracked.
res_ids reproduces restool's id allocation, res_index_build + res_index_write
reproduce resources.index, rpcid_sc encodes rpcid.sc, abc_records lists
modules.abc records without ark_disasm, sdk_bytecode carries the API level to
bytecode version table, id_check compares the in-tree index with the SDK tool.

res_compile.rs referenced these from an earlier commit but none of them had
ever been tracked, so that commit did not build from a clean clone.
*.abc kept every golden modules.abc out of the repository, so on a clean
clone fixtures/foreach/golden/modules.abc was missing and the abc_records
test had nothing to read.

The golden files are baselines rather than build artifacts, so they are
tracked now; each fixture's actual/ stays ignored.
Five strings carried a run of spaces where a line had been joined, so the
message a user saw read 'hvolt builds the same module              set for
every product'.
The build path spelled sdk_root/default/openharmony/<comp> by hand in
sdk_es2abc, native_root, the toolchains lookup, syscap's device-define dir and
rpcid_sc's encoder search. manage::component_dir already owned the version-dir
form of that knowledge, but it serves the download path only.

component_root(sdk_root, comp) is now the single place that knows the shape and
the five callers name a component instead. sdk_facts::platform_root keeps its
three-shape probe on purpose: accepting an SDK that is not laid out as
sdk_root/default is what that module is for.
real_file_scan read a file under a private home directory, so the test
failed anywhere but one machine. It now reads a tracked fixture.

a_system_resource_resolves_to_the_sdk_id resolved through sysResource.js,
which a clean checkout and CI do not have; without it every system
reference is reported missing. Skip when that table is absent and say how
to run it (HVOLT_SYS_RESOURCE).
@iamhyc
iamhyc merged commit 5f48d70 into main Nov 8, 2026
4 checks passed
@iamhyc
iamhyc deleted the feat/toml-owned-manifests branch November 8, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant