diff --git a/.github/workflows/fw-lite.yaml b/.github/workflows/fw-lite.yaml index 97a07232d1..ff227b014b 100644 --- a/.github/workflows/fw-lite.yaml +++ b/.github/workflows/fw-lite.yaml @@ -247,10 +247,25 @@ jobs: path: backend/FwLite/artifacts/publish/FwLiteWeb/* publish-android: - name: Publish FW Lite app for Android + name: Publish FW Lite app for Android (${{ matrix.variant }}) needs: [ build-and-test, frontend ] timeout-minutes: 30 runs-on: macos-latest + # Two bundles for one Play listing: CoreCLR (64-bit) + Mono (armeabi-v7a) — they can't share an AAB + # (a bundle boots a single runtime). 64-bit devices' abilist also includes armeabi-v7a, so they match + # BOTH bundles; the versionCode bands CoreCLR above Mono so they resolve to CoreCLR and only 32-bit-only + # devices get Mono. See create-release for the upload side. + strategy: + fail-fast: false + matrix: + include: + # RIDs come from the csproj, keyed on UseMonoRuntime (see FwLiteMaui.csproj). + - variant: coreclr + runtimeArgs: '' + versionOffset: 100000000 + - variant: mono + runtimeArgs: '-p:UseMonoRuntime=true' + versionOffset: 0 steps: - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 @@ -266,6 +281,10 @@ jobs: - name: Setup Maui run: dotnet workload install maui + - name: Compute Android version code + id: versionCode + shell: bash + run: echo "CODE=$(( ${{ matrix.versionOffset }} + ${{ github.run_number }} ))" >> ${GITHUB_OUTPUT} - name: Decode Android Keystore id: decodeKeystore env: @@ -281,9 +300,9 @@ jobs: KEYSTORE_PASS: ${{ secrets.FW_LITE_KEYSTORE_PASS }} KEYSTORE_ALIAS: ${{ vars.FW_LITE_KEYSTORE_UPLOAD_KEY_ALIAS }} run: | - dotnet publish -f net10.0-android -p:BuildApple=false --artifacts-path ../artifacts \ + dotnet publish -f net10.0-android -p:BuildApple=false --artifacts-path ../artifacts ${{ matrix.runtimeArgs }} \ -p:ApplicationDisplayVersion=${{ needs.build-and-test.outputs.semver-version }} \ - -p:ApplicationVersion=${{ github.run_number }} \ + -p:ApplicationVersion=${{ steps.versionCode.outputs.CODE }} \ -p:InformationalVersion=${{ needs.build-and-test.outputs.version }} \ -p:AndroidKeyStore=true \ -p:AndroidSigningKeyStore=${KEYSTORE_PATH} \ @@ -295,10 +314,11 @@ jobs: - name: Upload FWLite App artifacts uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: - name: fw-lite-android + name: fw-lite-android-${{ matrix.variant }} if-no-files-found: error -# path looks like this: backend/FwLite/artifacts/publish/FwLiteMaui/release_net10.0-android/org.sil.fwlitemaui-signed.apk - path: backend/FwLite/artifacts/publish/FwLiteMaui/release_net10.0-android/* + # Single-RID (mono) publishes to release_net10.0-android_android-arm; multi-RID + # (coreclr) to release_net10.0-android. The trailing * tolerates both. + path: backend/FwLite/artifacts/publish/FwLiteMaui/release_net10.0-android*/* publish-win: name: Publish FW Lite app for Windows @@ -399,17 +419,29 @@ jobs: path: fw-lite-web-linux - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: - name: fw-lite-android - path: fw-lite-android + name: fw-lite-android-coreclr + path: fw-lite-android-coreclr + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + name: fw-lite-android-mono + path: fw-lite-android-mono - name: Zip artifacts run: | zip -r fw-lite-portable.zip fw-lite-portable chmod +x fw-lite-web-linux/*/FwLiteWeb fw-lite-web-linux/*/*.sh zip -r fw-lite-web-linux.zip fw-lite-web-linux - - name: Rename Installer + - name: Rename Installer and stage Android bundles run: | mv fw-lite-msix/FwLiteMaui.msixbundle fw-lite-msix/FieldWorksLiteInstaller.msixbundle + # Both variants share the org.sil.FwLiteMaui basename, so rename per arch before + # attaching (release asset names must be unique). Upload BOTH .aab to one Play release. + mkdir -p fw-lite-android + for v in coreclr mono; do + case $v in coreclr) arch=64bit ;; mono) arch=arm32 ;; esac + cp fw-lite-android-$v/*.aab fw-lite-android/FieldWorksLite-$arch.aab + cp fw-lite-android-$v/*.apk fw-lite-android/FieldWorksLite-$arch.apk 2>/dev/null || true + done - name: Create Release uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1 diff --git a/Taskfile.yml b/Taskfile.yml index 7fb944ce14..8f85a323f7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -152,3 +152,8 @@ tasks: aliases: [android-release-dev] desc: Build a Release "Dev" APK and adb-install it on the connected USB device deps: [fw-lite:install-maui-android-release-dev] + + fw-lite-android-release-arm32: + aliases: [android-release-arm32] + desc: Build & install a Release Mono arm32 (armeabi-v7a) APK on the connected USB device + deps: [fw-lite:install-maui-android-release-arm32] diff --git a/backend/FwLite/FwLiteMaui/FwLiteMaui.csproj b/backend/FwLite/FwLiteMaui/FwLiteMaui.csproj index 69624a297c..ef106a0187 100644 --- a/backend/FwLite/FwLiteMaui/FwLiteMaui.csproj +++ b/backend/FwLite/FwLiteMaui/FwLiteMaui.csproj @@ -12,7 +12,11 @@ The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated; either BOTH runtimes must be indicated or ONLY macatalyst-x64. --> - android-arm;android-arm64;android-x86;android-x64 + + android-arm64;android-x64 + android-arm Exe FwLiteMaui true diff --git a/backend/FwLite/FwLiteMaui/Services/MauiTroubleshootingService.cs b/backend/FwLite/FwLiteMaui/Services/MauiTroubleshootingService.cs index 685a9bbea4..a3f58daefc 100644 --- a/backend/FwLite/FwLiteMaui/Services/MauiTroubleshootingService.cs +++ b/backend/FwLite/FwLiteMaui/Services/MauiTroubleshootingService.cs @@ -22,6 +22,9 @@ public class MauiTroubleshootingService( [JSInvokable] public Task GetCanShare() => Task.FromResult(true); + [JSInvokable] + public Task GetProcessArchitecture() => Task.FromResult(RuntimeInfoHelper.ProcessArchitecture()); + [JSInvokable] public async Task TryOpenDataDirectory() { diff --git a/backend/FwLite/FwLiteShared/Services/ITroubleshootingService.cs b/backend/FwLite/FwLiteShared/Services/ITroubleshootingService.cs index 50be6fad49..3a9813af96 100644 --- a/backend/FwLite/FwLiteShared/Services/ITroubleshootingService.cs +++ b/backend/FwLite/FwLiteShared/Services/ITroubleshootingService.cs @@ -3,6 +3,7 @@ namespace FwLiteShared.Services; public interface ITroubleshootingService { Task GetCanShare(); + Task GetProcessArchitecture(); Task TryOpenDataDirectory(); Task GetDataDirectory(); Task OpenLogFile(); diff --git a/backend/FwLite/FwLiteShared/Services/RuntimeInfoHelper.cs b/backend/FwLite/FwLiteShared/Services/RuntimeInfoHelper.cs new file mode 100644 index 0000000000..8ef9a1c6fd --- /dev/null +++ b/backend/FwLite/FwLiteShared/Services/RuntimeInfoHelper.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace FwLiteShared.Services; + +public static class RuntimeInfoHelper +{ + // On Android the process architecture reveals which bundle a device installed (Mono/arm32 vs CoreCLR/64-bit). + public static string ProcessArchitecture() + { + var bits = Environment.Is64BitProcess ? "64-bit" : "32-bit"; + return $"{RuntimeInformation.ProcessArchitecture} ({bits})"; + } +} diff --git a/backend/FwLite/FwLiteWeb/Services/WebTroubleshootingService.cs b/backend/FwLite/FwLiteWeb/Services/WebTroubleshootingService.cs index 64bd1ec086..89778108dd 100644 --- a/backend/FwLite/FwLiteWeb/Services/WebTroubleshootingService.cs +++ b/backend/FwLite/FwLiteWeb/Services/WebTroubleshootingService.cs @@ -13,6 +13,9 @@ public class WebTroubleshootingService( [JSInvokable] public Task GetCanShare() => Task.FromResult(false); + [JSInvokable] + public Task GetProcessArchitecture() => Task.FromResult(RuntimeInfoHelper.ProcessArchitecture()); + [JSInvokable] public Task GetDataDirectory() { diff --git a/backend/FwLite/Taskfile.yml b/backend/FwLite/Taskfile.yml index c76a165132..b453f88a9b 100644 --- a/backend/FwLite/Taskfile.yml +++ b/backend/FwLite/Taskfile.yml @@ -121,6 +121,14 @@ tasks: vars: { FLAVOR: 'Dev' } - adb -d install -r bin/Release/net10.0-android/org.sil.FwLiteMaui.dev-Signed.apk + install-maui-android-release-arm32: + # Production builds are 64-bit (CoreCLR); this builds & runs the Mono/armeabi-v7a bundle to test + # the 32-bit app on a connected USB device. + desc: Build, install & run a Release Mono arm32 (armeabi-v7a) "Dev" APK on the connected USB device + dir: ./FwLiteMaui + deps: [ ui:build-viewer ] + cmd: dotnet build -f net10.0-android -c Release -t:Run -p:FwLiteFlavor=Dev -p:UseMonoRuntime=true -p:RuntimeIdentifiers=android-arm -p:RuntimeIdentifier=android-arm -p:AndroidPackageFormat=apk -p:AdbTarget=-d + build-mini-lcm-sdk: desc: Builds the sdk, a zip with the FwLiteWeb server with a project and config to run locally dir: ./FwLiteWeb diff --git a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/ITroubleshootingService.ts b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/ITroubleshootingService.ts index 9cd162a39e..566504e172 100644 --- a/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/ITroubleshootingService.ts +++ b/frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/ITroubleshootingService.ts @@ -6,6 +6,7 @@ export interface ITroubleshootingService { getCanShare() : Promise; + getProcessArchitecture() : Promise; tryOpenDataDirectory() : Promise; getDataDirectory() : Promise; openLogFile() : Promise; diff --git a/frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte b/frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte index 46c24b702e..7932e62c0d 100644 --- a/frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte +++ b/frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte @@ -27,6 +27,7 @@ const config = useFwLiteConfig(); let projectCode = $state(); let canShare = resource(() => service, async (s) => await s?.getCanShare()); + let architecture = resource(() => service, async (s) => await s?.getProcessArchitecture()); // Mobile platforms keep app data in private storage that no file manager can open. const canOpenDataDirectory = $derived(config.os !== FwLitePlatform.Android && config.os !== FwLitePlatform.iOS); @@ -45,7 +46,7 @@
-
+

{$t`FieldWorks Lite version`}: @@ -54,15 +55,22 @@

{$t`Platform`}: {config.os}

+ {#if architecture.current} +

+ {$t`Architecture`}: + {architecture.current} +

+ {/if}
{#if service && (canOpenDataDirectory || $isDev)}
diff --git a/frontend/viewer/src/locales/en.po b/frontend/viewer/src/locales/en.po index 7678587387..436979a58c 100644 --- a/frontend/viewer/src/locales/en.po +++ b/frontend/viewer/src/locales/en.po @@ -227,6 +227,10 @@ msgstr "Any semantic domain" msgid "Any Ws" msgstr "Any Ws" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "Architecture" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/es.po b/frontend/viewer/src/locales/es.po index 880e5da21e..c822fb0fd5 100644 --- a/frontend/viewer/src/locales/es.po +++ b/frontend/viewer/src/locales/es.po @@ -232,6 +232,10 @@ msgstr "Cualquier dominio semántico" msgid "Any Ws" msgstr "Cualquier W" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/fr.po b/frontend/viewer/src/locales/fr.po index 485d6334c9..01348581f3 100644 --- a/frontend/viewer/src/locales/fr.po +++ b/frontend/viewer/src/locales/fr.po @@ -232,6 +232,10 @@ msgstr "N'importe quel domaine sémantique" msgid "Any Ws" msgstr "Tous les Systèmes d'Ecriture" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/id.po b/frontend/viewer/src/locales/id.po index 215f56abd7..065026748a 100644 --- a/frontend/viewer/src/locales/id.po +++ b/frontend/viewer/src/locales/id.po @@ -232,6 +232,10 @@ msgstr "Domain semantik apa pun" msgid "Any Ws" msgstr "Setiap Ws" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/ko.po b/frontend/viewer/src/locales/ko.po index d84aaca2d1..64f00e437a 100644 --- a/frontend/viewer/src/locales/ko.po +++ b/frontend/viewer/src/locales/ko.po @@ -232,6 +232,10 @@ msgstr "모든 시맨틱 도메인" msgid "Any Ws" msgstr "모든 W" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/ms.po b/frontend/viewer/src/locales/ms.po index 86edeaa7f4..6389e08d1e 100644 --- a/frontend/viewer/src/locales/ms.po +++ b/frontend/viewer/src/locales/ms.po @@ -232,6 +232,10 @@ msgstr "Mana-mana domain semantik" msgid "Any Ws" msgstr "Mana-mana Ws" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/sw.po b/frontend/viewer/src/locales/sw.po index ddc5f77608..3dd55a1c44 100644 --- a/frontend/viewer/src/locales/sw.po +++ b/frontend/viewer/src/locales/sw.po @@ -232,6 +232,10 @@ msgstr "Kikoa chochote cha kumkutania" msgid "Any Ws" msgstr "Ws yoyote" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?" diff --git a/frontend/viewer/src/locales/vi.po b/frontend/viewer/src/locales/vi.po index 74bb968e9f..fe6ce41142 100644 --- a/frontend/viewer/src/locales/vi.po +++ b/frontend/viewer/src/locales/vi.po @@ -232,6 +232,10 @@ msgstr "Bất kỳ miền ngữ nghĩa nào" msgid "Any Ws" msgstr "Bất kỳ hệ chữ nào" +#: src/lib/troubleshoot/TroubleshootDialog.svelte +msgid "Architecture" +msgstr "" + #. Confirmation prompt #: src/lib/entry-editor/DeleteDialog.svelte msgid "Are you sure you want to delete {0}?"