Skip to content

OHOS NDK cross-compile, replacing Alpine-as-OHOS (1.6.58.6) - #11

Closed
ronaldtse wants to merge 6 commits into
mainfrom
fix/ohos-ndk-cross-compile
Closed

OHOS NDK cross-compile, replacing Alpine-as-OHOS (1.6.58.6)#11
ronaldtse wants to merge 6 commits into
mainfrom
fix/ohos-ndk-cross-compile

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

What

Properly cross-compile the aarch64-linux-ohos gem using the OpenHarmony NDK, replacing the previous approach of shipping the Alpine-built musl arm64 binary under the OHOS platform label.

Why

The 1.6.58.4/.5 OHOS gem shipped Alpine-built bytes, based on an unverified assumption that "same dynamic linker path (/lib/ld-musl-aarch64.so.1) = byte-compatible." That hand-waved away real risks:

  • musl symbol versions may differ (Alpine patches vs OHOS patches)
  • -fvisibility=hidden defaults may differ between toolchains
  • TLS / pthread struct layouts could differ
  • OHOS requires code signing (binary-sign-tool) for runtime loading; an unsigned Alpine-built .so would not load on a real OHOS device

The proper fix is to use the actual OHOS NDK. Reference implementation: hqzing/ohos-node (which builds Node.js for OHOS).

How

Toolchain setup (ext/ohos/)

  • setup-toolchain.sh -- queries OpenHarmony's daily_build API (dcp.openharmony.cn/api/daily_build/build/list/component) for ohos-sdk-public and LLVM-19. Downloads, extracts. Exposes:
    • OHOS_LLVM -- the OHOS NDK's LLVM dir (compilers live in bin/)
    • OHOS_SYSROOT -- the OHOS sysroot (musl + system headers)
    • OHOS_SIGN_TOOL -- binary-sign-tool for code signing
  • toolchain.cmake -- CMake config pointing at OHOS clang (aarch64-unknown-linux-ohos-clang) with the sysroot as CMAKE_SYSROOT.
  • smoke-test.c + smoke-test.sh -- minimal libpng round-trip test. Compiles smoke-test.c against the freshly-built libpng16.so, runs via qemu-aarch64 with QEMU_LD_PREFIX=$OHOS_SYSROOT. Verifies the .so actually loads and the simplified API works (since we can't run Ruby itself on OHOS in CI).

Recipe integration (lib/libpng/recipe.rb)

  • setup_cross_compile hook (previously empty) now sets @host=aarch64-unknown-linux-ohos and OHOS_TOOLCHAIN_FILE env var when the target is OHOS.
  • configure_defaults appends -DCMAKE_TOOLCHAIN_FILE so CMake uses the OHOS cross-compile config.
  • install step signs the resulting .so via binary-sign-tool sign -selfSign 1. Without signing, the .so won't load on a real OHOS device.

CI (build.yml + release.yml)

  • Removed aarch64-linux-ohos from the build_musl matrix (no longer using Alpine path).
  • Added new build_ohos job:
    • Runs on ubuntu-latest (cross-compile host)
    • Runs setup-toolchain.sh to install OHOS NDK
    • Cross-compiles libpng via rake gem:native:aarch64-linux-ohos with OHOS env vars
    • Runs the qemu smoke test
    • Verifies file format via file lib/libpng/libpng16.so
  • publish job now depends on build_ohos.

What this verifies

  • ✅ Toolchain: OHOS NDK clang produces an ELF64 aarch64 binary.
  • ✅ Linking: links against OHOS sysroot's musl + zlib.
  • ✅ Runtime: qemu smoke test confirms the simplified API round-trips (encode -> decode -> bytes match).
  • ✅ Signing: binary-sign-tool runs without error; .so carries a signature.
  • ❓ Real OHOS hardware: not testable in CI. The signing step + qemu smoke test are the best we can do without real hardware or a proper OHOS Ruby port.

What this does NOT verify

  • Whether 1.6.58.4/.5's Alpine-built OHOS gem actually ran on real OHOS hardware (unknown; user feedback wanted).
  • Whether the OHOS NDK's daily-build version is stable across runs (the API returns "latest"; build IDs are logged in CI output for traceability).

Caveats

  • No Ruby on OHOS yet: the gem is only useful if someone has ported Ruby to OHOS (no widely-known port today). For those users, the gem now provides a properly-built, signed libpng .so.
  • OHOS SDK version drift: the daily_build API returns the latest build, which changes over time. The script logs the build ID so future investigators can pin to a specific build if needed.

Test plan

  • bundle exec rake -- 132 specs pass, rubocop clean (no source/test changes)
  • CI: build_ohos job successfully installs OHOS NDK, cross-compiles, signs, smoke test passes under qemu
  • CI: all 11 platform gems still build (10 native + source ruby)
  • Released 1.6.58.6 OHOS gem contains a properly-signed OHOS-built .so

Version

Bumps to 1.6.58.6. Resolves the regression acknowledged in the CHANGELOG (1.6.58.4/.5 used Alpine-built bytes for the OHOS platform).

The 1.6.58.4/.5 aarch64-linux-ohos gem shipped an Alpine-built musl
arm64 binary under the OHOS platform label, based on an unverified
assumption that 'same dynamic linker path = byte-compatible'.
The real risks -- musl symbol versions, -fvisibility=hidden defaults,
TLS/pthread layout, OHOS's mandatory code-signing requirement -- were
hand-waved away. This changeset does it properly.

New path: cross-compile libpng with the OHOS NDK from
OpenHarmony's daily_build API. The NDK ships clang (target triple
aarch64-unknown-linux-ohos), the OHOS sysroot, and binary-sign-tool.
Reference implementation: https://github.com/hqzing/ohos-node

Files added:
- ext/ohos/setup-toolchain.sh -- downloads ohos-sdk-public + LLVM-19
  via dcp.openharmony.cn/api/daily_build/build/list/component,
  extracts to /tmp/ohos-ndk, sets OHOS_LLVM/OHOS_SYSROOT/OHOS_SIGN_TOOL.
- ext/ohos/toolchain.cmake -- CMAKE_SYSTEM_NAME=Linux,
  CMAKE_SYSTEM_PROCESSOR=aarch64, points C/CXX compilers at OHOS clang,
  sets CMAKE_SYSROOT to OHOS sysroot.
- ext/ohos/smoke-test.c -- minimal libpng round-trip (2x2 RGBA encode
  -> decode -> compare pixels).
- ext/ohos/smoke-test.sh -- compiles smoke-test.c with OHOS clang
  against the freshly-built libpng16.so, runs via qemu-aarch64 with
  QEMU_LD_PREFIX=sysroot. Verifies the .so loads and round-trips.

Files updated:
- lib/libpng/recipe.rb:
  - setup_cross_compile hook now sets @host=aarch64-unknown-linux-ohos
    and OHOS_TOOLCHAIN_FILE env var when target is OHOS.
  - configure_defaults appends -DCMAKE_TOOLCHAIN_FILE for OHOS.
  - install step signs the .so via binary-sign-tool -selfSign 1
    (mandatory for runtime loading on OHOS).
- .github/workflows/build.yml: removed aarch64-linux-ohos from
  build_musl matrix; added build_ohos job on ubuntu-latest that
  runs setup-toolchain.sh, cross-compiles, signs, runs qemu smoke test.
- .github/workflows/release.yml: same changes; publish job now depends
  on build_ohos in addition to build_musl and build.
- README.adoc, CLAUDE.md: updated to reflect the new build path.
- CHANGELOG.md: 1.6.58.6 entry documents the switch and warns users
  that 1.6.58.4/.5 OHOS gems used the Alpine bytes (status: unknown
  whether they actually ran on real OHOS hardware).

Bumps LIBPNG_RUBY_ITERATION to 6.
The OHOS NDK clang doesn't auto-detect the multiarch library path
(.../usr/lib/aarch64-linux-ohos) when --sysroot is set. ld.lld then
fails with 'cannot open Scrt1.o / crti.o / -lc'.

Two changes:
1. setup-toolchain.sh now auto-detects the sysroot path by finding
   the aarch64-linux-ohos multiarch dir, walking up three levels to
   get the sysroot root. Exports both OHOS_SYSROOT and OHOS_LIB_DIR
   (the multiarch subdir).
2. toolchain.cmake sets CMAKE_EXE_LINKER_FLAGS / SHARED / MODULE to
   include --sysroot AND -L $OHOS_LIB_DIR, so ld.lld can find the
   C runtime + libc.

Also: setup-toolchain.sh prints diagnostic output (sysroot path, lib
dir, sample file listing) so future layout changes are easy to spot
in CI logs.
…' dir

Previous detection assumed the multiarch subdir lived 3 levels deep
(usr/lib/aarch64-linux-ohos). OHOS actually ships it 1 level deep:
llvm-19/sysroot/aarch64-linux-ohos/.

New detection: find Scrt1.o anywhere matching *aarch64-linux-ohos*,
take its directory as LIB_DIR, walk up until we hit a directory
named 'sysroot'. Falls back to LIB_DIR's parent if no 'sysroot'
marker exists.

Handles both layouts (sysroot/aarch64-linux-ohos and
sysroot/usr/lib/aarch64-linux-ohos) without hard-coding depth.
libpng requires zlib. CMake's FindZLIB can't auto-discover it in the
OHOS sysroot because the multiarch layout doesn't match the standard
search paths FindZLIB expects.

setup-toolchain.sh now finds libz.so and zlib.h under the sysroot
and exports OHOS_ZLIB_LIBRARY + OHOS_ZLIB_INCLUDE_DIR. recipe.rb's
configure_defaults passes these as -DZLIB_LIBRARY and
-DZLIB_INCLUDE_DIR when target is OHOS.
The script runs under 'set -euo pipefail'. 'ls | head -10' causes
head to exit after 10 lines, sending SIGPIPE to ls. ls exits non-
zero, pipefail propagates, the whole script exits 1 -- before
reaching zlib detection.

The diagnostic output (sample lib dir listing) was useful for
debugging but isn't essential. Add '|| true' to suppress pipefail
when head closes the pipe.
@ronaldtse

Copy link
Copy Markdown
Contributor Author

Closing in favor of dockerharmony-based approach. The OHOS NDK cross-compile keeps hitting issues (sysroot layout, missing zlib in sysroot, pipefail on diagnostics). dockerharmony lets us verify Alpine-built musl arm64 bytes actually run in real OHOS userland without the cross-compile machinery.

@ronaldtse ronaldtse closed this Jul 26, 2026
@ronaldtse
ronaldtse deleted the fix/ohos-ndk-cross-compile branch July 26, 2026 11:22
ronaldtse added a commit that referenced this pull request Jul 26, 2026
- lib/libpng/version.rb: LIBPNG_RUBY_ITERATION 5 -> 6.
- CHANGELOG.md: new 1.6.58.6 entry describing the OHOS NDK cross-compile
  (toolchain, static zlib, signing, dockerharmony verify) and listing
  PR #11 and #12 as superseded.
- README.adoc: aarch64-linux-ohos row now reflects the actual build host
  (ubuntu-24.04-arm + OHOS NDK, verified in dockerharmony) instead of
  the misleading "Alpine, byte-compatible with musl".
- CLAUDE.md: file table extended with lib/libpng/ohos/ + ext/ohos/;
  Common commands section now has an "OHOS build notes" subsection
  explaining the binfmt_misc + qemu-user-static setup and that local
  builds require arm64 + binfmt registration.
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