Skip to content

build: make assembleRelease work, align libwnsteam.so to 16 KB pages - #801

Open
laoqijun2 wants to merge 6 commits into
WinNative-Emu:mainfrom
laoqijun2:build/16kb-aligned-release
Open

laoqijun2 wants to merge 6 commits into
WinNative-Emu:mainfrom
laoqijun2:build/16kb-aligned-release

Conversation

@laoqijun2

Copy link
Copy Markdown

Why

Every workflow in this repository builds Debug APKs (assembleStandardDebug in release.yml, the tag/lint workflows too), so the release build type has never been exercised. Running :app:assembleStandardRelease locally fails twice before it produces anything:

> Execution failed for task ':app:buildCMakeRelWithDebInfo[arm64-v8a]'.
  CMake Error at app/src/main/cpp/proot/loader-info.cmake:14 (message):
    pokedata_workaround or _start missing from .../libproot-loader.so
> Execution failed for task ':app:minifyStandardReleaseWithR8'.
  ERROR: Missing classes detected while running R8.
  Missing class java.beans.BeanInfo (referenced from: org.yaml.snakeyaml.introspector.PropertyUtils)

Changes

  1. proot loader (app/src/main/cpp/proot/CMakeLists.txt) — release links add -Wl,--gc-sections, which drops the unreferenced pokedata_workaround stub from src/loader/assembly.S. loader-info.cmake measures that stub's offset from _start, so the build aborted. -Wl,--undefined=pokedata_workaround keeps it as a GC root on every build type.

  2. R8 (app/proguard-rules.pro) — snakeyaml's optional JavaBeans introspection path references java.beans.*, which does not exist on Android. Suppressed with -dontwarn; the code path is unreachable at runtime.

  3. 16 KB page size (app/src/main/cpp/wn-steam-client/CMakeLists.txt) — Gradle already passes -Wl,-z,max-page-size=16384 to CMake targets, but cargo drives the NDK linker itself for libwnsteam.so, so that library did not inherit the flag and was the only ELF in the APK built for 4 KB pages:

before: libwnsteam.so align=0x1000
all other APK libs:          0x4000
after:  libwnsteam.so align=0x4000

Result

  • :app:assembleStandardRelease completes: BUILD SUCCESSFUL in 3m 59s, app-standard-release.apk (303 MB).
  • The APK is not debuggable (aapt2 dump badging has no application-debuggable line), which is what actually stops Android 15+ from showing the 16 KB page-size compatibility dialog — the platform only shows it for debuggable builds.
  • 35 of 38 bundled ELFs are now 16 KB aligned.

Known limitation

libcrypto.so, libssl.so and libcurl.so still come from the com.android.ndk.thirdparty prefab artefacts (openssl:1.1.1q-beta-1, curl:7.85.0-beta-1). Those are prebuilt for 4 KB pages and Google has published no newer release (maven-metadata.xml still lists 1.1.1q-beta-1 as latest), so they cannot be realigned without rebuilding OpenSSL and curl from source. The APK works on the 4 KB-page devices that make up essentially the whole install base, but a device booted with a 16 KB page-size kernel would fail to load those three libraries.

A follow-up workflow that builds this release variant and publishes it as a GitHub Release is prepared separately (it needs a token with the workflow scope to land).

The release build type was never exercised: every workflow in this repository
builds Debug APKs, so two latent failures only show up on assembleRelease.

1. proot loader link. Release builds add -Wl,--gc-sections, which discards the
   unreferenced pokedata_workaround stub in src/loader/assembly.S. The
   loader-info.cmake step measures that stub's offset from _start and aborted
   the build with "pokedata_workaround or _start missing". Force the symbol
   into the image with -Wl,--undefined=pokedata_workaround.

2. R8. snakeyaml's optional JavaBeans introspection path references
   java.beans.*, which is not part of the platform, so minification failed with
   "Missing classes detected while running R8". Suppress those warnings; the
   code path is unreachable on Android.

Also align libwnsteam.so to 16 KB pages. Gradle already passes
-Wl,-z,max-page-size=16384 to the CMake targets, but cargo drives the NDK
linker itself for the Rust library, so it was the only ELF in the APK still
built for 4 KB pages. Verified: LOAD align is now 0x4000.

Shipping the release variant also removes the Android 15+ 16 KB compatibility
dialog, which the platform only shows for debuggable builds.
No workflow in this repository produces a *release* (non-debuggable) APK:
release.yml, tag-apk-artifacts.yml and pr-ci.yml all invoke assemble*Debug.
Debug builds are the only ones Android shows the 16 KB page-size compatibility
dialog for, so a release variant is what users actually want to sideload.

Adds a workflow that builds :app:assembleStandardRelease on GitHub's runners and
publishes the APK as a GitHub Release:

- triggers: manual dispatch (optional tag input) or a push of a fork-v* tag
- installs NDK 27.3.13750724 / CMake 3.22.1 / platform 35, pulls LFS assets and
  adds the aarch64-linux-android Rust target before building
- signs with the KEYSTORE_BASE64 / KEYSTORE_PASSWORD / KEY_ALIAS / KEY_PASSWORD
  secrets when they exist, otherwise generates an ephemeral key and says so in
  the release notes, because a different key means users must uninstall first
- fails the run if the produced APK is debuggable, or if libwnsteam.so lost its
  16 KB LOAD alignment (cargo bypasses Gradle's CMake linker flags, so that
  regression is easy to reintroduce)
android-actions/setup-android@v3 fails on current ubuntu-latest images: it
invokes sdkmanager to install the "tools" package, which no longer exists
("Warning: Failed to find package 'tools'"), so the job aborted before the
build ever started. The other workflows in this repository already rely on the
SDK that ships with the runner image; do the same, resolve sdkmanager from it,
and fall back to letting Gradle install the NDK when it is absent.
The previous run built the release APK successfully but the verification step
exited 74 without printing anything, so the log gave no clue which check
failed. Drop `set -e` in favour of explicit checks with messages, resolve
aapt2/llvm-readelf through globs (the find|sort|tail pipelines can abort under
pipefail), and echo each decision so a failure is diagnosable from the log.
The first release APK built from this branch crashed as soon as the setup
wizard tried to extract imagefs:

    F runtime.cc: Throwing new exception 'no non-static method
      "Lcom/winlator/cmod/shared/util/OnExtractFileListener;.mapsExtractedFiles()Z"'
    NoSuchMethodError: no non-static method
      "Lcom/winlator/cmod/shared/util/OnExtractFileListener;.onExtractFileProgress(Ljava/io/File;J)V"

R8 cannot see calls that cross the JNI boundary, so it treats the callbacks that
only native code invokes as dead code and drops them. `-dontobfuscate` alone was
not enough: OnExtractFileListener.onExtractFileProgress, mapsExtractedFiles,
reportsExtractedBytesOnly and onExtractedBytes were still missing from the dex.

Keep the app's own classes intact and let R8 shrink only third-party code. The
JNI surface is wide in both directions: libwinlator looks methods up with
FindClass/GetMethodID (OnExtractFileListener, WaylandCompositor's static frame
callbacks, XConnectorEpoll's ConnectionHandler, GPUImage.setStride, the
Downloader progress listener) and the Rust Steam client calls Kotlin listeners
by name (onStateChanged, onAuthResult, onComplete, onProgress, ...).

Also fixes the CI verification step: it ran under `bash -e` and died with exit
74 while piping llvm-readelf into `awk 'exit'` (SIGPIPE). Write the headers to a
file instead, add an ERR trap that names the failing line, and keep the explicit
not-debuggable and 16 KB alignment assertions.
Once release builds actually run, EncryptedSharedPreferences construction fails
because Tink registers its algorithm implementations reflectively and R8 strips
them. Keep the crypto/Tink classes so the encrypted preference store works in a
minified build instead of falling back to plain preferences.

This branch has not been deployed

No deployments
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.

2 participants