Skip to content

Align the Android library to 16 KB pages - #25

Merged
jcant0n merged 1 commit into
mainfrom
fix/android-16kb-page-size
Sep 1, 2026
Merged

Align the Android library to 16 KB pages#25
jcant0n merged 1 commit into
mainfrom
fix/android-16kb-page-size

Conversation

@jcant0n

@jcant0n jcant0n commented Sep 1, 2026

Copy link
Copy Markdown
Member

The problem, measured

Google Play requires 64-bit native libraries to support 16 KB memory pages, and from
1 February 2027 it refuses updates that do not
(docs). The requirement is
64-bit only — armeabi-v7a is explicitly out of scope, which is moot here since this repo
builds arm64-v8a alone.

Reading the program headers of the libCesiumNativeC.so currently committed in
Cesium.NET@main:

CesiumC android-arm64   ELF64   LOAD align = [0x1000, 0x1000, 0x1000]   -> NOT COMPLIANT

For contrast, the other two packages in the fleet that ship Android binaries:

Package RID min LOAD align
JoltPhysics.NET android-arm64 0x4000 compliant
Vuforia.NET android-arm64 0x4000 compliant (third-party binary)
Cesium.NET android-arm64 0x1000 not compliant

So today an app that consumes Evergine.Bindings.CesiumNative cannot be published to Play
once the deadline lands.

Why it is 4 KB

Nothing in this repository asked for it. max-page-size, common-page-size and 16384
appear in no blob on main. The alignment came from the toolchain: the android-arm64 leg
links with whatever NDK ubuntu-latest happens to carry, the last green run used
27.3.13750724, and 16 KB alignment only became the default in r28. One runner image
bump either way and the answer changes silently — which is the actual defect, more than the
flag being absent.

The fix

if(ANDROID)
    target_link_options(CesiumNativeC PRIVATE "-Wl,-z,max-page-size=16384")
endif()

Same flag the draco fork uses, and guarded the same way — its first attempt applied it
unconditionally and 9f675442 moved it behind if(ANDROID) the same day.

In CMake rather than the workflow's extra-cmake, for two reasons beyond taste. Anyone
building this locally gets the same binary CI does, which is not true of a flag living in a
matrix row. And -DCMAKE_SHARED_LINKER_FLAGS= replaces the variable rather than
appending, so the first other link flag anyone needed would quietly drop this one.

Not a triplets/arm64-android-release.cmake overlay. build.yml:130-136 already
records why Android has no overlay: one replaces the community triplet instead of
extending it, and would drop VCPKG_MAKE_BUILD_TRIPLET, VCPKG_CMAKE_SYSTEM_VERSION and
VCPKG_CMAKE_CONFIGURE_OPTIONS. It would not reach this link anyway — a triplet governs
how vcpkg builds ports, not how the project consuming them is linked.

Only the final link needs it. arm64-android-release sets VCPKG_LIBRARY_LINKAGE static, so the thirty dependencies are archives, and an archive carries no program headers
to align. There is exactly one ELF in the output.

Verified

The guard was checked against a minimal project reproducing the construct: the flag reaches
the link line with -DANDROID=1 and is absent without it, so the other eight legs are
untouched.

The real check is this PR's own android-arm64 artifact — its LOAD alignment should read
0x4000. Worth confirming before merge, since the whole point is a property of the output
rather than of the source.

After merge

The flag alone changes nothing that ships. The .so lives in Cesium.NET and only moves
when a release here flows into it:

  1. cut a release here — workflow_dispatch with release-tag: v0.63.0-cesiumc.5;
  2. notify-downstream fires and Cesium.NET's CD commits the new .so on its own — but
    will not publish, because it decides on generated-code changes and release assets
    are deliberately excluded from that (fetch_upstream.py:768-773: "a rebuilt native
    library would read as a changed API and publish a package whose managed surface is
    identical"
    ). Correct in general; here the binary is the point;
  3. so the NuGet needs a workflow_dispatch of Cesium.NET's CD with publish-only: true,
    which ships the reviewed tree and refuses if regenerating would change it.

Google Play requires 64-bit native libraries to support 16 KB memory pages, and
from 1 February 2027 it refuses updates that do not. The shipped
libCesiumNativeC.so does not: reading its program headers back gives p_align
0x1000 on every LOAD segment.

Nothing in this repository asked for 4 KB. The alignment came from the runner:
the android-arm64 leg links with whatever NDK ubuntu-latest happens to carry, the
last green run used 27.3.13750724, and 16 KB alignment only became the default in
r28. So the flag has to be explicit rather than inherited.

In CMake rather than in the workflow's extra-cmake, for two reasons beyond taste.
Anyone building this locally gets the same binary CI does, which is not true of a
flag that lives in a matrix row. And -DCMAKE_SHARED_LINKER_FLAGS= replaces the
variable rather than appending to it, so the first other link flag anyone needed
would silently drop this one.

Not a triplets/arm64-android-release.cmake overlay either. build.yml already
records why Android has no overlay: one replaces the community triplet instead of
extending it, and would drop VCPKG_MAKE_BUILD_TRIPLET,
VCPKG_CMAKE_SYSTEM_VERSION and VCPKG_CMAKE_CONFIGURE_OPTIONS. It would not reach
this link anyway -- a triplet governs how vcpkg builds ports, not how the project
consuming them is linked.

Only the final link needs it. arm64-android-release sets VCPKG_LIBRARY_LINKAGE
static, so the thirty dependencies are archives, and an archive carries no
program headers to align.
@jcant0n

jcant0n commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Verified on the binary this PR produces

All nine legs green. Downloaded this run's CesiumNativeC-android-arm64 artifact and read
its program headers:

fichero      : libCesiumNativeC.so  (27,082,048 bytes)
clase        : ELF64  machine=0xb7 (aarch64)
LOAD p_align : ['0x4000', '0x4000', '0x4000']

RESULTADO    : CUMPLE 16 KB

Against 0x1000 on the library currently committed in Cesium.NET@main. The property is
one of the output rather than of the source, so this is the check that counts.

@jcant0n
jcant0n merged commit aaed4d4 into main Sep 1, 2026
11 checks passed
@jcant0n
jcant0n deleted the fix/android-16kb-page-size branch September 1, 2026 22:14
jcant0n added a commit to EvergineTeam/Evergine.Bindings that referenced this pull request Sep 1, 2026
#67)

Google Play requires 64-bit native libraries to support 16 KB memory pages and
refuses updates that do not from 1 February 2027. Cesium.NET has been shipping a
4 KB-aligned arm64 library for as long as it has shipped Android, and every check
this fleet has was satisfied by it.

That is the part worth fixing here rather than in the wrapper. The property lives
in the ELF program headers: each PT_LOAD segment carries a p_align, and a linker
writes 0x1000 unless asked for 0x4000. Nothing else looks. The architecture check
reads the machine field and is happy. The symbol check finds every symbol
present. The smoke test installs the package and runs it on a runner whose pages
are 4 KB, where a 4 KB-aligned library is perfectly loadable. So the build goes
green, the package publishes, and the rejection arrives months later at whoever
tries to put an application built on it into the store.

The new checker is the architecture check's neighbour in every sense: same
manifest, same shipped_natives() from native_paths.py, same file, three fields
further into it.

Two decisions in it worth stating. A 32-bit library is skipped and said so, not
failed -- the requirement is 64-bit only and armeabi-v7a is excluded by name, so
failing there would be noise that teaches people to ignore the check. And a
library that parses but declares no PT_LOAD segments is a failure rather than a
pass, for the reason native_paths.py already records elsewhere: a platform nobody
could look at should be named out loud instead of counted as verified.

A package that ships no Android binary prints one line and exits zero, which is
most of the fleet.

Run against the four repositories that could answer:

  Cesium.NET        exit=1   android-arm64 aligned to 0x1000
  JoltPhysics.NET   exit=0   android-arm64 0x4000, android-arm skipped as 32-bit
  Vuforia.NET       exit=0   android-arm64 0x4000
  Vulkan.NET        exit=0   ships no Android binaries

The one failure is real and is fixed in EvergineTeam/CesiumC#25; it reaches this
package once that release flows downstream. Nothing here takes effect until the
v1 tag moves, so the order is: land the wrapper fix, publish it, then move the
tag.
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