Skip to content

Commit 7192d20

Browse files
release-android: workaround wry getId() ProGuard regression (#8)
Squash-merges #11. Closes #8. Codex review chain: 4 findings → all fixed → CLEAN. CI: backend + frontend both pass.
1 parent d5352bc commit 7192d20

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

.github/workflows/release-android.yml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,131 @@ jobs:
144144
- name: Build APK (release, all ABIs)
145145
run: cargo tauri android build --apk
146146

147+
- name: Apply wry getId() ProGuard workaround (issue #8)
148+
# `wry <= 0.55.0`'s `proguard-wry.pro` template is missing
149+
# `int getId();` for `WryActivity`. R8 strips the Kotlin auto-
150+
# generated getter, then tao 0.35's JNI bridge calls
151+
# `activity.getId()` at onActivityCreate, gets NoSuchMethodError,
152+
# panics, and the app crashes before its first frame.
153+
#
154+
# Upstream fix: tauri-apps/wry#1721 (merged 2026-05-04, awaiting
155+
# release). Until that flows in transitively, patch the rule
156+
# post-init and force R8 to re-run. Two complications:
157+
#
158+
# 1. `cargo tauri android init` (run by `cargo tauri android
159+
# build`) regenerates the file from wry's bundled template
160+
# every time, so the patch only sticks between init and the
161+
# next R8 invocation.
162+
# 2. Gradle's R8 task does not declare the proguard file as an
163+
# input, so editing it in place doesn't invalidate R8's
164+
# incremental cache. We have to wipe R8 outputs to force a
165+
# re-run.
166+
#
167+
# Sequence: patch → wipe R8 + dex outputs → re-run gradle
168+
# `assembleUniversalRelease`, excluding rust tasks since the
169+
# `.so`s are already on disk from the previous build step.
170+
# Once the next wry release lands, delete this whole step.
171+
shell: bash
172+
run: |
173+
set -euo pipefail
174+
175+
# `-print -quit` avoids the SIGPIPE-on-`find | head` risk
176+
# under pipefail. Guard the search root so a missing tree
177+
# surfaces a clear error rather than an opaque empty result.
178+
if [ ! -d src-tauri/gen/android ]; then
179+
echo "::error::src-tauri/gen/android missing — Tauri Android init did not run"
180+
exit 1
181+
fi
182+
PROGUARD=$(find src-tauri/gen/android -name "proguard-wry.pro" -path "*/generated/*" -print -quit)
183+
if [ -z "$PROGUARD" ]; then
184+
echo "::error::proguard-wry.pro not found under src-tauri/gen/android — Tauri's Android scaffolding may have changed"
185+
exit 1
186+
fi
187+
echo "Patching $PROGUARD"
188+
189+
if grep -q "int getId();" "$PROGUARD"; then
190+
echo "::notice::proguard-wry.pro already has 'int getId();' — wry release likely shipped the upstream fix; this workflow step can be removed"
191+
else
192+
python3 - "$PROGUARD" <<'PY'
193+
import pathlib, re, sys
194+
p = pathlib.Path(sys.argv[1])
195+
s = p.read_text()
196+
# Match the WryActivity keep block for any package and insert
197+
# `int getId();` between `getAppClass(...)` and `getVersion()`,
198+
# mirroring the position in tauri-apps/wry#1721.
199+
new, n = re.subn(
200+
r'(-keep class \S+\.WryActivity \{[^}]*?\n)(\s+java\.lang\.String getVersion\(\);)',
201+
r'\1 int getId();\n\2',
202+
s,
203+
count=1,
204+
flags=re.DOTALL,
205+
)
206+
if n != 1:
207+
sys.stderr.write("could not locate WryActivity keep block in proguard-wry.pro — wry template shape may have changed\n")
208+
sys.exit(1)
209+
p.write_text(new)
210+
PY
211+
grep -q "int getId();" "$PROGUARD" || { echo "::error::patch did not apply"; exit 1; }
212+
echo "Patch applied."
213+
fi
214+
215+
# Wipe R8 outputs so the next gradle run actually re-runs R8
216+
# (Gradle's incremental check misses our edit).
217+
rm -rf src-tauri/gen/android/app/build/outputs/apk
218+
rm -rf src-tauri/gen/android/app/build/outputs/mapping
219+
rm -rf src-tauri/gen/android/app/build/intermediates/dex
220+
rm -rf src-tauri/gen/android/app/build/intermediates/r8_dex_archive_for_full_bundle
221+
222+
# Re-run gradle assemble, skipping the rust-build tasks (the
223+
# `.so` files are already in jniLibs from the prior build).
224+
# Skipping is required because those tasks invoke `cargo tauri`
225+
# standalone, which fails outside of the Tauri parent process
226+
# (it tries to connect to a WebSocket the parent owns).
227+
(
228+
cd src-tauri/gen/android
229+
./gradlew :app:assembleUniversalRelease \
230+
-x :app:rustBuildArm64Release \
231+
-x :app:rustBuildArmRelease \
232+
-x :app:rustBuildX86_64Release \
233+
-x :app:rustBuildX86Release \
234+
--no-daemon --console=plain
235+
)
236+
237+
# Sanity-check: confirm getId() landed inside the WryActivity
238+
# class in the dex. This is the workaround's load-bearing
239+
# invariant — anything that prevents the check itself from
240+
# running is treated as a hard failure, not a silent skip.
241+
DEXDUMP=$(find "$ANDROID_HOME/build-tools" -name dexdump -type f -print -quit 2>/dev/null || true)
242+
if [ -z "$DEXDUMP" ] || [ ! -x "$DEXDUMP" ]; then
243+
echo "::error::dexdump not found in $ANDROID_HOME/build-tools — cannot verify the workaround"
244+
exit 1
245+
fi
246+
shopt -s nullglob
247+
apks=(src-tauri/gen/android/app/build/outputs/apk/universal/release/*-unsigned.apk)
248+
if [ "${#apks[@]}" -eq 0 ]; then
249+
echo "::error::no universal-release APK produced after re-running gradle"
250+
exit 1
251+
fi
252+
UNIVERSAL="${apks[0]}"
253+
# Scope the grep to the WryActivity class block so we don't get
254+
# a false-positive from any other class that defines getId.
255+
# dexdump emits class descriptor lines with leading spaces
256+
# (" Class descriptor : ..."), so anchor the block-end
257+
# match with [[:space:]]*. Without that, in_block stays
258+
# true past WryActivity and a later RustWebView.getId would
259+
# satisfy the check even if WryActivity.getId was stripped.
260+
if LC_ALL=C "$DEXDUMP" "$UNIVERSAL" 2>/dev/null | LC_ALL=C awk '
261+
/Class descriptor.*WryActivity;/ { in_block=1; next }
262+
in_block && /^[[:space:]]*Class descriptor/ { in_block=0 }
263+
in_block && /name *: .getId./ { found=1 }
264+
END { exit !found }
265+
'; then
266+
echo "✓ WryActivity.getId() present in dex"
267+
else
268+
echo "::error::workaround failed — WryActivity.getId() still missing from dex"
269+
exit 1
270+
fi
271+
147272
- name: Sign APKs with Android debug keystore
148273
# `cargo tauri android build --apk` produces unsigned release
149274
# APKs, which Android refuses to install

0 commit comments

Comments
 (0)