Skip to content

fix(v3): unblock iOS and Android cross-compiles (5 build-tag/API bugs) - #5908

Closed
mgueregath wants to merge 3 commits into
wailsapp:masterfrom
mgueregath:fix/mobile-build-tags
Closed

fix(v3): unblock iOS and Android cross-compiles (5 build-tag/API bugs)#5908
mgueregath wants to merge 3 commits into
wailsapp:masterfrom
mgueregath:fix/mobile-build-tags

Conversation

@mgueregath

@mgueregath mgueregath commented Aug 5, 2026

Copy link
Copy Markdown

Fixes #5907.

Five small, independent bugs that keep GOOS=ios and GOOS=android from compiling at all on v3.0.0-alpha.98, found while cross-compiling a real third-party app (Go service backend + plain HTML/JS frontend, nothing exotic).

What's fixed

  • pkg/application/linux_cgo.c / linux_cgo.h: build tag was linux && !gtk3 && !server, missing && !android. Go implicitly sets the linux tag when GOOS=android (Android is Linux-based under the hood), so these GTK-dependent cgo files were pulled into Android builds and failed on a missing gtk/gtk.h. The .go counterparts (application_linux.go, linux_cgo.go) already excluded !android correctly — only the cgo files were missed.
  • pkg/application/messageprocessor_ios.go: added the (empty) androidMethodNames map. messageprocessor.go's Debug-logging switch references both iosMethodNames and androidMethodNames unconditionally, but the iOS file only defined its own map, leaving androidMethodNames undefined on iOS builds — even though processAndroidMethod already has an iOS-side stub right next to it.
  • pkg/application/messageprocessor_android.go: same fix, mirrored — added the empty iosMethodNames map so Android builds don't fail on the same reference in reverse.
  • pkg/events/events.go: exported iOSIOS. events_common_ios.go references events.IOS.ApplicationDidFinishLaunching, but the package only defined the lowercase, unexported iOS — every sibling (Common, Linux, Mac, Windows) is already exported, so this reads like a partial rename that never got finished. Confirmed no other internal references to the lowercase name exist.
  • pkg/events/events.go: added a minimal Android events var (androidEvents{ ActivityCreated }) — events_common_android.go references events.Android.ActivityCreated, but no Android var existed at all in pkg/events. Kept intentionally minimal (just the one field that's actually referenced) rather than guessing at a fuller Android lifecycle event set.
  • pkg/application/application_android.go: wired globalApplication.impl.GetFlags(globalApplication.options) into the JNI page-finished handler's runtime.Core(...) call. Every other platform (webview_window_darwin.go, webview_window_windows.go, webview_window_linux.go) already calls runtime.Core(globalApplication.impl.GetFlags(globalApplication.options)); the Android call site called runtime.Core() with no arguments, which doesn't match runtime.Core(flags map[string]any) string's signature. androidApp.GetFlags already existed, just wasn't being used here.

Verification

Not just compile-clean — built and ran both targets end to end:

  • iOS: go build -buildmode=c-archive -tags ios,debug ... succeeds, linked into a .app with clang, installed and launched in the iOS Simulator (xcrun simctl install/launch) via codesign --sign -. simctl logs show the app boots, application_ios.go's processApplicationEvent fires for ApplicationDidBecomeActive (the exact events.IOS.* path from bug Port init #4) with no crash.
  • Android: go build -buildmode=c-shared -tags android,debug ... succeeds against NDK r27 (aarch64-linux-android21-clang), packaged into a debug APK via the generated Gradle project, installed and launched on an arm64-v8a emulator (Pixel 9 Pro API 35). adb logcat shows WailsBridge initializing, the JNI nativeOnPageFinished callback firing, and runtime.Core()'s log line executing successfully — the exact call site from bug Initial Port #6 — with no FATAL/crash.

Both apps render a blank page in this test (an unrelated debug-mode asset-serving issue, WailsPathHandler: Asset not found: /index.html — happening because I built the c-archive/c-shared manually instead of through wails3 dev's asset pipeline, not something this PR touches), but the Go runtime itself boots and the native bridge handshake completes cleanly on both platforms, which is what these five bugs were blocking.

Notes

  • Bug 1 from the linked issue (wails3 ios overlay:gen failing outside the Wails monorepo, in internal/commands/ios_overlay_gen.go's repoRoot()) is not fixed by this PR — it's a CLI tooling issue, not a build-tag bug, and felt like a separate fix. Happy to take a pass at that in a follow-up if useful.
  • Didn't attempt to flesh out a fuller events.Android lifecycle event set beyond ActivityCreated since I don't have visibility into what the Android-side native code actually emits — kept the fix scoped to what's referenced today.

Summary by CodeRabbit

  • New Features

    • Added Android activity-created event support for improved application lifecycle integration.
    • Improved Android runtime configuration handling so application settings are applied consistently.
    • iOS webviews now load the configured start URL when available.
  • Bug Fixes

    • Improved cross-platform compatibility and diagnostic behavior on Android and iOS.
    • Prevented platform-specific build components from being included in unsupported environments.
    • Corrected iOS event naming for clearer and more consistent API usage.
    • Improved iOS application packaging to identify the correct executable during builds.

Five small build-tag/API-mismatch bugs kept `GOOS=ios` and `GOOS=android`
builds from compiling at all, discovered while cross-compiling a real
third-party app against v3.0.0-alpha.98:

- pkg/application/linux_cgo.{c,h}: build tag was missing `&& !android`.
  Go implicitly sets the `linux` tag for GOOS=android (Android is
  Linux-based), so these GTK-dependent cgo files were being pulled into
  Android builds and failing on a missing gtk/gtk.h.
- pkg/application/messageprocessor_ios.go /
  messageprocessor_android.go: messageprocessor.go references both
  `androidMethodNames` and `iosMethodNames` unconditionally (for its
  Debug logging switch), but each platform-specific file only defined
  its own map. Added the missing empty map on each side, matching the
  existing stub pattern for `processAndroidMethod`/`processIOSMethod`.
- pkg/events/events.go: events_common_ios.go references the exported
  `events.IOS`, but the package only defined the unexported `iOS` — an
  apparent partial rename. Exported it.
- pkg/application/application_android.go: the Android JNI page-finished
  handler called `runtime.Core()` with no arguments, while every other
  platform (darwin/windows/linux) calls
  `runtime.Core(globalApplication.impl.GetFlags(globalApplication.options))`.
  `androidApp.GetFlags` already existed but wasn't wired up here.
- pkg/events/events.go: events_common_android.go references
  `events.Android.ActivityCreated`, but no `Android` events var existed
  at all. Added a minimal `androidEvents` struct with just the field
  that's actually referenced.

Verified end-to-end, not just compile-only: built both targets, packaged
an iOS .app and installed+launched it in the iOS Simulator, and built an
Android debug APK and installed+launched it in an arm64 emulator. Logs on
both platforms show the app boots, the Go runtime starts, and (on
Android) the exact previously-crashing `runtime.Core()` call site
executes successfully via the real JNI nativeOnPageFinished callback.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The change fixes Android and iOS cross-compilation and startup issues. It updates platform build constraints, mobile symbols, Android runtime flags, iOS webview URL loading, and iOS bundle executable settings.

Changes

Mobile cross-compilation

Layer / File(s) Summary
Platform symbols and build selection
v3/pkg/application/linux_cgo.c, v3/pkg/application/linux_cgo.h, v3/pkg/application/messageprocessor_android.go, v3/pkg/application/messageprocessor_ios.go, v3/pkg/events/events.go
Linux CGO files exclude Android builds. Platform logging maps are defined for both mobile targets. The iOS registry is exported as IOS. The Android registry defines ActivityCreated with ID 1300.
Android runtime injection
v3/pkg/application/application_android.go
Android runtime JavaScript generation receives application flags and options.
iOS webview startup
v3/pkg/application/webview_window_ios.go
The iOS webview resolves the configured start URL and loads it after native webview creation.
iOS bundle executable settings
v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl, v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl
Both iOS plist templates use $(EXECUTABLE_NAME) for CFBundleExecutable.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue 5020 — Covers the Android runtime, method-name map, event registry, and build compatibility fixes addressed here.

Possibly related PRs

Suggested reviewers: leaanthony

Poem

A rabbit checks the mobile gate,
Android flags now propagate.
iOS names and events align,
Build tags keep each path in line.
Xcode finds the executable.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: fixing iOS and Android cross-compilation failures.
Description check ✅ Passed The description explains the fixes, linked issue, scope, and detailed iOS and Android verification, but omits template checkboxes and wails doctor output.
Linked Issues check ✅ Passed The PR addresses all cross-compilation coding objectives in [#5907]; the separate ios overlay:gen issue is explicitly excluded as follow-up work.
Out of Scope Changes check ✅ Passed The plist and iOS webview changes support the documented iOS installation and startup objectives, and no unrelated code changes are evident.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/mobile-build-tags
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…lsapp"

Both Info.plist.tmpl and Info.dev.plist.tmpl render CFBundleExecutable
from {{.BinaryName}}, but ios_xcode_gen.go never wires that field to
anything config-derived ("BinaryName remains default unless we later
add config support") — it's permanently the literal "wailsapp" default,
regardless of the app's actual product/binary name.

Since the app's real executable name always matches whatever
PRODUCT_NAME/EXECUTABLE_NAME the Xcode build produces (which IS
correctly derived from build/config.yml via ProductName), the robust
fix is to stop hardcoding a Go-side value at generation time and use
Xcode's own $(EXECUTABLE_NAME) build variable instead — the standard
approach any Xcode-created project uses, and it can never drift out of
sync with the actual build output.

Without this fix, `xcrun devicectl device install app` (and Xcode's own
device install/run) fails with:

    The item at <name>.app is not a valid bundle.
    Failure Reason: The path to the provided bundle's main executable
    could not be determined.

because CFBundleExecutable names a file ("wailsapp") that doesn't exist
anywhere in the bundle. Reproduced and fixed while installing a real app
(product name "tacataca-udp-poc") on a physical iPhone — confirmed
`plutil -p .../Info.plist` now reports the correct executable name and
`xcrun devicectl device install app` + `device process launch` both
succeed.
@github-actions github-actions Bot added the cli label Aug 6, 2026
@mgueregath

Copy link
Copy Markdown
Author

Added one more fix to this branch (pushed as a follow-up commit) while actually installing and running the iOS build on a physical device, not just the simulator.

CFBundleExecutable is hardcoded to "wailsapp" in both Info.plist.tmpl and Info.dev.plist.tmpl. They render it from {{.BinaryName}}, but ios_xcode_gen.go never wires that field to anything config-derived — see the comment right above it: // BinaryName remains default unless we later add config support. It's permanently the literal default, regardless of the app's actual product name.

This breaks real-device installs outright:

The item at <name>.app is not a valid bundle.
Failure Reason: The path to the provided bundle's main executable could not be determined.

xcrun devicectl device install app (and Xcode's own Run) can't find an executable named wailsapp inside the bundle, because the actual binary is named after PRODUCT_NAME/EXECUTABLE_NAME (correctly derived from build/config.yml) — just the Info.plist disagreed with what's actually in the bundle.

Fixed by switching both templates to Xcode's own $(EXECUTABLE_NAME) build variable instead of a Go-side field, so it can never drift out of sync with what the linker actually produces — same approach any Xcode-generated project uses. Verified: installed and launched a real app on a physical iPhone (xcrun devicectl device install app + device process launch, both succeeded) after this fix; failed with the error above before it.

…y blank

iosWebviewWindow.run() creates the native WKWebView (ios_create_webview_with_id)
and applies the background colour, but never tells it to navigate anywhere.
Every other platform's run() does this — see webview_window_darwin.go, which
resolves options.URL via assetserver.GetStartURL() and calls w.setURL()
right after creating the native window.

Without this, the WKWebView is created (so BackgroundColour shows) but sits
permanently blank regardless of whether the Go asset server is serving
correctly — confirmed via full log capture (AssetFileServerFS logs show
index.html/style.css/assets/*.js all being served successfully, and the JS
runtime even sends wails:runtime:ready), the page was never actually loaded
because nothing ever called WKWebView loadRequest.

Fixed by mirroring macOS's run(): resolve the start URL and call setURL()
immediately after creating the native handle. Verified end-to-end: built,
installed, and launched a real app in the iOS Simulator — screenshot
confirms the app's actual UI (not a blank page) renders correctly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@v3/pkg/application/webview_window_ios.go`:
- Around line 228-231: Update the iOS initialization flow around
assetserver.GetStartURL to handle non-nil errors through the existing iOS
fatal-error handler, matching the macOS error path and stopping initialization
before attempting setURL. Preserve setURL only for successfully resolved start
URLs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 00c8bfc7-abde-4aa6-9810-debe9f13bd0b

📥 Commits

Reviewing files that changed from the base of the PR and between 81c8e79 and 1319e70.

📒 Files selected for processing (1)
  • v3/pkg/application/webview_window_ios.go

Comment on lines +228 to +231
startURL, err := assetserver.GetStartURL(w.parent.options.URL)
if err == nil {
w.setURL(startURL)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle GetStartURL errors before continuing.

assetserver.GetStartURL returns an error for malformed FRONTEND_DEVSERVER_URL or options.URL. The current if err == nil branch discards that error. setURL is then skipped, so the iOS application can remain blank with no startup diagnostic. Follow the macOS error path and route the error through the iOS fatal-error handler before stopping initialization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/pkg/application/webview_window_ios.go` around lines 228 - 231, Update the
iOS initialization flow around assetserver.GetStartURL to handle non-nil errors
through the existing iOS fatal-error handler, matching the macOS error path and
stopping initialization before attempting setURL. Preserve setURL only for
successfully resolved start URLs.

@mgueregath

Copy link
Copy Markdown
Author

Two more fixes on this branch, both found while actually getting a real app to render (not just compile) on iOS Simulator and an Android emulator — both were stuck on a permanently blank/white screen even though everything else (bindings, native bridge handshake) worked.

iOS: run() never calls setURL()

iosWebviewWindow.run() (webview_window_ios.go) creates the native WKWebView (ios_create_webview_with_id) and applies the background colour, but never tells it to navigate anywhere. Every other platform's run() does this — see webview_window_darwin.go, which resolves options.URL via assetserver.GetStartURL() and calls w.setURL() right after creating the native window. iOS's setURL() itself is fine (mirrors macOS's), it's just never invoked from run().

Symptom: the WKWebView gets created (so BackgroundColour shows) but sits permanently blank — confirmed via full log stream capture that AssetFileServerFS is serving index.html/style.css/the JS bundle successfully and the JS runtime even sends wails:runtime:ready, but nothing ever called loadRequest on the webview, so there was never any page to begin with.

Fix: mirror macOS's run() — resolve the start URL and call setURL() immediately after creating the native handle.

Not a library bug, but related: examples/android/main_android.go's placement matters and isn't obvious

Separate from the two PRs' actual library changes — while chasing an analogous blank-screen symptom on Android, found that RegisterAndroidMain(main) must be called from an init() in a file that lives in the project root (same directory as main.go), matching examples/android/main_android.go. If that registration never runs, signalAppReady() is never called, and every single asset request blocks for the full waitForAppReady(10 * time.Second) timeout in application_android.go before failing — same "looks like it's working but the page never loads" symptom as the iOS bug above, just with a 10s stall first. Not filing this as a library bug since the mechanism is intentional and the example does show the right placement — just flagging it here since it cost real time to track down, in case a wails3 android scaffolding/doctor check ever wants to catch a misplaced main_android.go.

Verified both platforms end-to-end after the setURL fix: built, installed, and launched real apps — iOS Simulator and an arm64-v8a Android emulator both now render the actual app UI (buttons, styling, everything), not a blank page. Screenshots taken to confirm, not just log inspection.

@mgueregath

Copy link
Copy Markdown
Author

Closing this — while working through the merge conflicts I found that #5562/#5602 (merged since this PR was opened against v3.0.0-alpha.98) already fix everything reported here:

  • linux_cgo.c/.h GTK build tags now correctly exclude Android.
  • messageprocessor_ios.go/messageprocessor_android.go both define their empty stub maps.
  • pkg/events now has a full Android event set.
  • application_android.go's JNI page-finished handler no longer manually calls runtime.Core() at all — it was rewritten to forward a proper window event instead.
  • iOS's run() now calls setURL() after creating the native handle (same fix I'd made here, apparently done independently).

Verified directly: built and ran real apps on both platforms against v3.0.0-beta.4 with none of this branch's patches applied, and both worked. Filed a follow-up issue for one remaining real bug found in the process (missing -ObjC linker flag in the generated Xcode project — separate from anything in this PR).

Thanks for the mobile rewrite — sorry for the noise on an already-fixed issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v3 mobile: iOS and Android cross-compiles fail to build (5 distinct build-tag/API bugs)

1 participant