name: Native platform artifacts on: workflow_dispatch: inputs: include_linux: description: Build Linux packages type: boolean default: true include_windows: description: Build Windows packages type: boolean default: true include_macos: description: Build macOS packages type: boolean default: true run_quality: description: Run the full quality suite (disable only for manual packaging checks) type: boolean default: true include_android: description: Build the signed Android APK type: boolean default: true include_ios: description: Build the unsigned iOS app type: boolean default: true pull_request: paths: - ".github/workflows/native-platforms.yml" - "Cargo.lock" - "Cargo.toml" - "build.rs" - "packaging/**" - "resources/**" - "scripts/**" push: tags: - "v*.*.*" permissions: contents: read concurrency: group: native-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: CARGO_TERM_COLOR: always jobs: quality: needs: metadata if: github.event_name == 'push' || inputs.run_quality name: Release quality gate uses: ./.github/workflows/quality.yml metadata: name: Release preflight runs-on: ubuntu-22.04 outputs: version: ${{ steps.version.outputs.version }} native_version: ${{ steps.version.outputs.native_version }} msix_version: ${{ steps.version.outputs.msix_version }} prerelease: ${{ steps.version.outputs.prerelease }} source_date_epoch: ${{ steps.source.outputs.source_date_epoch }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Use the source commit timestamp id: source run: | epoch="$(git show -s --format=%ct HEAD)" echo "SOURCE_DATE_EPOCH=$epoch" >> "$GITHUB_ENV" echo "source_date_epoch=$epoch" >> "$GITHUB_OUTPUT" - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.11" - name: Install preflight tools run: | sudo apt-get update sudo apt-get install -y appstream desktop-file-utils dpkg-dev gettext - name: Check release metadata and packaging id: version env: IS_TAG_PUSH: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} run: | if [[ "$IS_TAG_PUSH" == true ]]; then bash scripts/check-release.sh --tag "$GITHUB_REF_NAME" else bash scripts/check-release.sh fi - name: Require complete Spanish and Russian translations if: github.event_name == 'push' && github.ref_type == 'tag' run: bash scripts/check-translation-completeness.sh es ru linux: needs: metadata if: github.event_name == 'push' || github.event_name == 'pull_request' || inputs.include_linux name: Linux packages (${{ matrix.arch }}) strategy: fail-fast: false matrix: include: # Ubuntu 22.04 keeps the glibc 2.35 baseline on both architectures. - arch: x64 runner: ubuntu-22.04 - arch: arm64 runner: ubuntu-22.04-arm runs-on: ${{ matrix.runner }} env: SOURCE_DATE_EPOCH: ${{ needs.metadata.outputs.source_date_epoch }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 with: python-version: "3.11" - name: Install native build dependencies run: | sudo apt-get update sudo apt-get install -y \ appstream \ build-essential \ desktop-file-utils \ file \ libdbus-1-dev \ libfontconfig1-dev \ libx11-dev \ libx11-xcb-dev \ libxcb-shape0-dev \ libxcb-xfixes0-dev \ libxcursor-dev \ libxkbcommon-dev \ libxkbcommon-x11-dev \ pkg-config \ python3 \ wget \ xinput \ xvfb - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-on-failure: true key: native-linux-${{ matrix.arch }} # Every pull request can restore caches saved on main. Caches saved by # pull requests or tags only serve those, and they push main's out of # the 10 GB cache limit. save-if: ${{ github.ref == 'refs/heads/main' }} - name: Validate desktop metadata run: | desktop-file-validate resources/com.flectar.mail.desktop # Network checks make private pre-publication repositories look like # broken URLs. Flathub performs its own online validation on submit. appstreamcli validate --no-net resources/com.flectar.mail.metainfo.xml - name: Build release executable env: FLECTAR_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_ID }} FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET }} FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID }} run: cargo build --locked --release --no-default-features --features remote-content,gpu-renderer --bin flectar-mail - name: Build AppImage env: APPIMAGE_EXTRACT_AND_RUN: "1" FLECTAR_SKIP_BUILD: "1" run: ./scripts/build-appimage.sh - name: Build Debian package env: FLECTAR_SKIP_BUILD: "1" run: ./scripts/build-deb.sh - name: Benchmark startup and idle resources run: | xvfb-run -a ./scripts/benchmark-startup.py \ --binary target/release/flectar-mail \ --profile-cache-dir resources/benchmarks/warm-cache \ --rounds 3 \ --idle-seconds 3 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-linux-${{ matrix.arch }} path: | target/appimage/*.AppImage target/deb/*.deb target/startup-benchmark/*.json if-no-files-found: error rpm: needs: metadata if: github.event_name == 'push' || github.event_name == 'pull_request' || inputs.include_linux name: Fedora RPM build (${{ matrix.arch }}, Fedora 43 baseline) strategy: fail-fast: false matrix: include: - arch: x64 runner: ubuntu-24.04 - arch: arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} # Build against the oldest supported Fedora; test this exact artifact below. container: fedora:43 env: SOURCE_DATE_EPOCH: ${{ needs.metadata.outputs.source_date_epoch }} steps: - name: Install Fedora build dependencies run: | dnf install -y \ appstream \ dbus-daemon \ dbus-devel \ desktop-file-utils \ findutils \ fontconfig-devel \ gcc \ gcc-c++ \ git \ libX11-devel \ libXcursor-devel \ libxcb-devel \ libxkbcommon-devel \ libxkbcommon-x11-devel \ make \ pkgconf-pkg-config \ python3 \ rpm-build \ tar \ wayland-devel \ xorg-x11-server-Xvfb - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-on-failure: true key: native-fedora-43-rpm-${{ matrix.arch }} save-if: ${{ github.ref == 'refs/heads/main' }} - name: Build Fedora release executable env: FLECTAR_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_ID }} FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET }} FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID }} run: cargo build --locked --release --no-default-features --features remote-content,gpu-renderer --bin flectar-mail - name: Build and inspect RPM env: FLECTAR_SKIP_BUILD: "1" run: | ./scripts/build-rpm.sh rpm -qip target/rpm/flectar-mail.rpm rpm -qlp target/rpm/flectar-mail.rpm - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-rpm-${{ matrix.arch }} path: target/rpm/flectar-mail.rpm if-no-files-found: error rpm-test: needs: rpm name: RPM runtime (Fedora ${{ matrix.fedora }}, ${{ matrix.arch }}) runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: fedora: [43, 44] arch: [x64, arm64] include: - arch: x64 runner: ubuntu-24.04 - arch: arm64 runner: ubuntu-24.04-arm steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: flectar-mail-rpm-${{ matrix.arch }} path: target/rpm - name: Test published RPM in a clean Fedora container shell: bash env: FEDORA_VERSION: ${{ matrix.fedora }} run: | mkdir -p target/rpm-test docker run --rm \ --mount "type=bind,source=$GITHUB_WORKSPACE,target=/work,readonly" \ --mount "type=bind,source=$GITHUB_WORKSPACE/target/rpm-test,target=/results" \ "fedora:$FEDORA_VERSION" \ bash /work/scripts/test-rpm.sh /work/target/rpm/flectar-mail.rpm /results \ 2>&1 | tee target/rpm-test/transaction.log - name: Preserve RPM runtime diagnostics if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: rpm-test-fedora-${{ matrix.fedora }}-${{ matrix.arch }} path: target/rpm-test if-no-files-found: error flatpak: needs: metadata if: github.event_name == 'push' || github.event_name == 'pull_request' || inputs.include_linux name: Flatpak preview bundle (${{ matrix.arch }}) strategy: fail-fast: false matrix: include: - arch: x64 runner: ubuntu-24.04 - arch: arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} env: SOURCE_DATE_EPOCH: ${{ needs.metadata.outputs.source_date_epoch }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Install Flatpak build tools run: | sudo apt-get update sudo apt-get install -y appstream dbus-x11 desktop-file-utils flatpak flatpak-builder xvfb flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak install --user --noninteractive -y flathub \ org.freedesktop.Platform//25.08 \ org.freedesktop.Sdk//25.08 \ org.freedesktop.Sdk.Extension.rust-stable//25.08 - name: Build Flatpak bundle from locked sources run: ./scripts/build-flatpak.sh - name: Test Flatpak installation and packaged runtime env: EXPECTED_VERSION: ${{ needs.metadata.outputs.version }} run: | flatpak install --user --noninteractive -y target/flatpak/flectar-mail.flatpak flatpak run --command=sh --env=EXPECTED_VERSION="$EXPECTED_VERSION" com.flectar.mail -c ' test -x /app/bin/flectar-mail test -f /app/lib/flectar-mail/libpdfium.so test -x /usr/bin/gpg test -f /app/share/applications/com.flectar.mail.desktop test -f /app/share/metainfo/com.flectar.mail.metainfo.xml ! grep -q X-AppImage /app/share/applications/com.flectar.mail.desktop grep -q "version=\"$EXPECTED_VERSION\"" /app/share/metainfo/com.flectar.mail.metainfo.xml ' flatpak info --user --show-permissions com.flectar.mail set +e timeout 15s dbus-run-session -- xvfb-run -a flatpak run com.flectar.mail \ > target/flatpak/launch.log 2>&1 status=$? set -e if [[ "$status" -ne 124 ]]; then cat target/flatpak/launch.log echo "Flatpak launch smoke test exited unexpectedly with status $status" >&2 exit 1 fi flatpak uninstall --user --noninteractive -y com.flectar.mail - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-flatpak-${{ matrix.arch }} path: target/flatpak/flectar-mail.flatpak if-no-files-found: error windows: needs: metadata if: github.event_name == 'push' || inputs.include_windows name: Windows packages (${{ matrix.arch }}) strategy: fail-fast: false matrix: include: - arch: x64 runner: windows-2025 - arch: arm64 runner: windows-11-arm runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-on-failure: true key: native-windows-${{ matrix.arch }} save-if: ${{ github.ref == 'refs/heads/main' }} - name: Put Clang on the path if: matrix.arch == 'arm64' shell: pwsh run: | # ring and aws-lc compile their Arm64 Windows sources with Clang. if (-not (Get-Command clang -ErrorAction SilentlyContinue)) { $llvm = Join-Path $env:ProgramFiles 'LLVM\bin' if (-not (Test-Path (Join-Path $llvm 'clang.exe'))) { throw 'LLVM is missing from the runner' } Add-Content $env:GITHUB_PATH $llvm } - name: Build release executable shell: bash env: # Keep the portable build independent of an installed VC++ runtime. # RUSTFLAGS replaces .cargo/config.toml, so retain its stack setting. RUSTFLAGS: "-C target-feature=+crt-static -C link-arg=/STACK:8000000" FLECTAR_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_ID }} FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET }} FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID }} run: | mkdir -p target if cargo build --locked --release --no-default-features --features remote-content,gpu-renderer --bin flectar-mail 2>&1 | tee target/windows-build.log; then exit 0 fi # Keep the compiler's final diagnostics available in the check result, # including when a maintainer cannot download authenticated run logs. python - <<'PY' from pathlib import Path details = "\n".join(Path("target/windows-build.log").read_text(errors="replace").splitlines()[-80:]) details = details[-16000:].replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") print(f"::error title=Windows compilation failed::{details}") PY exit 1 - name: Package executable shell: pwsh env: ARCH: ${{ matrix.arch }} run: | New-Item -ItemType Directory -Force target/package/windows | Out-Null New-Item -ItemType Directory -Force "target/package/windows/LICENSES/Google Sans Flex" | Out-Null New-Item -ItemType Directory -Force "target/package/windows/LICENSES/Noto Emoji" | Out-Null python scripts/stage-pdfium.py "win-$env:ARCH" target/package/windows if ($LASTEXITCODE -ne 0) { throw "PDFium staging failed" } Copy-Item target/release/flectar-mail.exe target/package/windows/ Copy-Item README.md target/package/windows/ Copy-Item LICENSE target/package/windows/ Copy-Item LICENSES/* target/package/windows/LICENSES/ -Recurse -Force Copy-Item THIRD_PARTY_NOTICES.md target/package/windows/ Copy-Item resources/fonts/google-sans-flex/OFL.txt "target/package/windows/LICENSES/Google Sans Flex/" Copy-Item resources/fonts/google-sans-flex/README.md "target/package/windows/LICENSES/Google Sans Flex/" Copy-Item resources/fonts/noto-emoji/OFL.txt "target/package/windows/LICENSES/Noto Emoji/" Copy-Item resources/fonts/noto-emoji/README.md "target/package/windows/LICENSES/Noto Emoji/" python scripts/test-pdf-preview.py target/package/windows/flectar-mail.exe if ($LASTEXITCODE -ne 0) { throw "Packaged PDF preview test failed" } Compress-Archive -Path target/package/windows/* -DestinationPath "target/flectar-mail-windows-$env:ARCH.zip" -Force - name: Close the packaged app from its window shell: pwsh run: | python scripts/test-clean-exit.py target/package/windows/flectar-mail.exe if ($LASTEXITCODE -ne 0) { throw "The packaged app did not exit cleanly" } - name: Install pinned Inno Setup compiler shell: pwsh run: | $installer = Join-Path $env:RUNNER_TEMP 'innosetup.exe' Invoke-WebRequest 'https://github.com/jrsoftware/issrc/releases/download/is-6_5_4/innosetup-6.5.4.exe' -OutFile $installer $expected = 'fa73bf47a4da250d185d07561c2bfda387e5e20db77e4570004cf6a133cc10b1' if ((Get-FileHash $installer -Algorithm SHA256).Hash -ne $expected) { throw 'Inno Setup checksum mismatch' } $process = Start-Process $installer -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', '/SP-' -Wait -PassThru if ($process.ExitCode -ne 0) { throw "Inno Setup install failed: $($process.ExitCode)" } - name: Build per-user Windows installer shell: pwsh env: APP_VERSION: ${{ needs.metadata.outputs.version }} NATIVE_VERSION: ${{ needs.metadata.outputs.native_version }} ARCH: ${{ matrix.arch }} run: | $compiler = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" if (-not (Test-Path $compiler)) { throw "Inno Setup 6 is missing from the runner" } & $compiler "/DAppVersion=$env:APP_VERSION" "/DNativeVersion=$env:NATIVE_VERSION" "/DArch=$env:ARCH" platform/windows/installer.iss if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed with exit code $LASTEXITCODE" } - name: Verify unattended installation and uninstall shell: pwsh env: ARCH: ${{ matrix.arch }} run: | $destination = Join-Path $env:RUNNER_TEMP 'Flectar Mail install test' $installer = (Resolve-Path "target/flectar-mail-windows-$env:ARCH-setup.exe").Path $arguments = @('/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', "/DIR=`"$destination`"") $process = Start-Process $installer -ArgumentList $arguments -Wait -PassThru if ($process.ExitCode -ne 0) { throw "Installation failed: $($process.ExitCode)" } if (-not (Test-Path "$destination/flectar-mail.exe")) { throw 'Installed executable is missing' } $process = Start-Process "$destination/unins000.exe" -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART' -Wait -PassThru if ($process.ExitCode -ne 0) { throw "Uninstall failed: $($process.ExitCode)" } if (Test-Path "$destination/flectar-mail.exe") { throw 'Uninstall left the executable behind' } - name: Build Microsoft Store package shell: pwsh env: ARCH: ${{ matrix.arch }} MSIX_VERSION: ${{ needs.metadata.outputs.msix_version }} run: | python scripts/build-msix.py pack --arch $env:ARCH --version $env:MSIX_VERSION target/package/windows "target/msix/flectar-mail-$env:ARCH.msix" if ($LASTEXITCODE -ne 0) { throw "Store packaging failed" } - name: Install, start and remove the Store package shell: pwsh env: ARCH: ${{ matrix.arch }} run: | powershell -NoProfile -ExecutionPolicy Bypass -File scripts/test-msix.ps1 -Package "target/msix/flectar-mail-$env:ARCH.msix" if ($LASTEXITCODE -ne 0) { throw "Store package test failed" } - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: msix-${{ matrix.arch }} path: target/msix/flectar-mail-${{ matrix.arch }}.msix if-no-files-found: error - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-windows-${{ matrix.arch }} path: | target/flectar-mail-windows-${{ matrix.arch }}.zip target/flectar-mail-windows-${{ matrix.arch }}-setup.exe if-no-files-found: error microsoft-store: needs: [metadata, windows] name: Microsoft Store bundle runs-on: windows-2025 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: pattern: msix-* path: target/msix merge-multiple: true # Partner Center takes this one file and serves each device its architecture. - name: Bundle the Store packages shell: pwsh env: APP_VERSION: ${{ needs.metadata.outputs.version }} MSIX_VERSION: ${{ needs.metadata.outputs.msix_version }} run: | python scripts/build-msix.py bundle --version $env:MSIX_VERSION "target/flectar-mail-$env:APP_VERSION.msixbundle" target/msix/flectar-mail-x64.msix target/msix/flectar-mail-arm64.msix if ($LASTEXITCODE -ne 0) { throw "Store bundling failed" } - name: Install, start and remove the Store bundle shell: pwsh env: APP_VERSION: ${{ needs.metadata.outputs.version }} run: | powershell -NoProfile -ExecutionPolicy Bypass -File scripts/test-msix.ps1 -Package "target/flectar-mail-$env:APP_VERSION.msixbundle" if ($LASTEXITCODE -ne 0) { throw "Store bundle test failed" } - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: microsoft-store-package path: target/flectar-mail-${{ needs.metadata.outputs.version }}.msixbundle if-no-files-found: error macos: needs: metadata if: github.event_name == 'push' || inputs.include_macos name: macOS application (${{ matrix.name }}) strategy: fail-fast: false matrix: include: - name: Apple silicon runner: macos-15 architecture: arm64 - name: Intel runner: macos-15-intel architecture: x64 runs-on: ${{ matrix.runner }} env: MACOSX_DEPLOYMENT_TARGET: "14.0" steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-on-failure: true key: native-macos-${{ matrix.architecture }} save-if: ${{ github.ref == 'refs/heads/main' }} - name: Build release application env: FLECTAR_GOOGLE_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_ID }} FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET: ${{ secrets.FLECTAR_GOOGLE_DESKTOP_CLIENT_SECRET }} FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_DESKTOP_CLIENT_ID }} run: cargo build --locked --release --no-default-features --features remote-content,gpu-renderer --bin flectar-mail - name: Create ad-hoc signed app bundle run: | mkdir -p target/macos if ./scripts/package-macos.sh \ target/release/flectar-mail target/macos \ "${{ matrix.architecture }}" 2>&1 | tee target/macos/package.log; then exit 0 fi python3 - <<'PY' from pathlib import Path details = "\n".join(Path("target/macos/package.log").read_text(errors="replace").splitlines()[-80:]) details = details[-16000:].replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") print(f"::error title=macOS packaging failed::{details}") PY exit 1 - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-macos-${{ matrix.architecture }} path: | target/macos/flectar-mail-macos-${{ matrix.architecture }}.zip target/macos/flectar-mail-macos-${{ matrix.architecture }}.dmg if-no-files-found: error android: needs: metadata if: >- github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.include_android) name: Android signed APK (arm64) runs-on: ubuntu-24.04 environment: android-release steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: "21" - name: Restore shared app signing key env: FLECTAR_ANDROID_KEYSTORE_BASE64: ${{ secrets.FLECTAR_ANDROID_KEYSTORE_BASE64 }} FLECTAR_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.FLECTAR_ANDROID_KEYSTORE_PASSWORD }} FLECTAR_ANDROID_KEY_ALIAS: ${{ secrets.FLECTAR_ANDROID_KEY_ALIAS }} FLECTAR_ANDROID_KEY_PASSWORD: ${{ secrets.FLECTAR_ANDROID_KEY_PASSWORD }} run: bash scripts/restore-android-keystore.sh - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable targets: aarch64-linux-android - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: workspaces: platform/android -> ../../target cache-on-failure: true key: native-android-arm64 save-if: ${{ github.ref == 'refs/heads/main' }} - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4 - name: Configure Android NDK compiler run: | toolchain="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" test -x "$toolchain/aarch64-linux-android26-clang" { echo "CC_aarch64_linux_android=$toolchain/aarch64-linux-android26-clang" echo "CXX_aarch64_linux_android=$toolchain/aarch64-linux-android26-clang++" echo "AR_aarch64_linux_android=$toolchain/llvm-ar" echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$toolchain/aarch64-linux-android26-clang" } >> "$GITHUB_ENV" - name: Build signed release APK env: RELEASE_PRERELEASE: ${{ needs.metadata.outputs.prerelease }} FLECTAR_ANDROID_VERSION_NAME: ${{ needs.metadata.outputs.version }} FLECTAR_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.FLECTAR_ANDROID_KEYSTORE_PASSWORD }} FLECTAR_ANDROID_KEY_ALIAS: ${{ secrets.FLECTAR_ANDROID_KEY_ALIAS }} FLECTAR_ANDROID_KEY_PASSWORD: ${{ secrets.FLECTAR_ANDROID_KEY_PASSWORD }} FLECTAR_GOOGLE_ANDROID_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_ANDROID_CLIENT_ID }} FLECTAR_MICROSOFT_ANDROID_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_ANDROID_CLIENT_ID }} FLECTAR_MICROSOFT_ANDROID_REDIRECT_URI: ${{ secrets.FLECTAR_MICROSOFT_ANDROID_REDIRECT_URI }} run: | args=() if [[ "$RELEASE_PRERELEASE" == true ]]; then args+=(--preview); fi bash scripts/build-android-release.sh "${args[@]}" - name: Verify release APK manifest, ABI, and signature env: FLECTAR_ANDROID_VERSION_NAME: ${{ needs.metadata.outputs.version }} run: bash scripts/verify-android-apk.sh target/android/release/flectar-mail.apk production - name: Record public app signing identity run: ./scripts/android-signing-identities.sh target/android/release/flectar-mail.apk > target/android/release/signing-identities.txt - name: Remove restored signing key if: always() run: | if [[ -n "${FLECTAR_ANDROID_KEYSTORE:-}" ]]; then rm -f "$FLECTAR_ANDROID_KEYSTORE"; fi - name: Enable Android emulator acceleration run: sudo chmod a+rw /dev/kvm - name: Exercise packaged JNI callbacks uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2 env: FLECTAR_ANDROID_KEYSTORE: "" with: # This Google APIs image supports ARM64 native translation, so tests # exercise the same stripped Rust library staged for the release APK. api-level: 30 target: google_apis arch: x86_64 disable-animations: true script: ./platform/android/gradle/gradlew --no-daemon -p platform/android/gradle :app:connectedDebugAndroidTest - name: Upload JNI test results if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: android-jni-test-results path: | target/android/gradle/app/reports/androidTests/ target/android/gradle/app/outputs/androidTest-results/ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-android-signed-arm64 path: | target/android/release/flectar-mail.apk target/android/release/signing-identities.txt if-no-files-found: error ios: needs: metadata if: github.event_name == 'push' || inputs.include_ios name: iOS app (arm64, unsigned) runs-on: macos-15 env: SOURCE_DATE_EPOCH: ${{ needs.metadata.outputs.source_date_epoch }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable targets: aarch64-apple-ios - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-on-failure: true key: native-ios-arm64 save-if: ${{ github.ref == 'refs/heads/main' }} - name: Install Xcode project generator run: brew install xcodegen # Unsigned, so people can sideload it with their own Apple ID. - name: Build the IPA # Google gives iOS its own OAuth client, while one Microsoft # registration serves Android and iOS (platform/ios/README.md). env: FLECTAR_GOOGLE_IOS_CLIENT_ID: ${{ secrets.FLECTAR_GOOGLE_IOS_CLIENT_ID }} FLECTAR_MICROSOFT_IOS_CLIENT_ID: ${{ secrets.FLECTAR_MICROSOFT_ANDROID_CLIENT_ID }} run: bash scripts/package-ios.sh target/ios - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: flectar-mail-ios-arm64 path: target/ios/flectar-mail-ios-arm64.ipa if-no-files-found: error release: name: Publish GitHub Release # Every published channel must include an APK with the shared identity. if: >- !cancelled() && github.event_name == 'push' && github.ref_type == 'tag' && needs.metadata.result == 'success' && needs.quality.result == 'success' && needs.linux.result == 'success' && needs.rpm.result == 'success' && needs.rpm-test.result == 'success' && needs.flatpak.result == 'success' && needs.windows.result == 'success' && needs.microsoft-store.result == 'success' && needs.macos.result == 'success' && needs.android.result == 'success' && needs.ios.result == 'success' needs: [metadata, quality, linux, rpm, rpm-test, flatpak, windows, microsoft-store, macos, android, ios] runs-on: ubuntu-24.04 permissions: actions: read artifact-metadata: write attestations: write contents: write id-token: write steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 # Each package artifact unpacks into release-assets/. # The staging script picks the expected files from those folders. - name: Download packages uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: pattern: flectar-mail-* path: release-assets - name: Prepare release downloads env: RELEASE_VERSION: ${{ needs.metadata.outputs.version }} run: python3 scripts/prepare-release.py - name: Attest release downloads uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 with: subject-path: dist/* - name: Publish tagged release env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: bash scripts/publish-release.sh