name: Quality and security on: push: branches: - main pull_request: schedule: - cron: "17 4 * * *" workflow_dispatch: workflow_call: permissions: contents: read concurrency: group: quality-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: CARGO_TERM_COLOR: always # Slint's generated application is large; simultaneous lib/test rustc jobs # can exhaust runner memory before any tests start. CARGO_BUILD_JOBS: "1" jobs: quality: if: github.event_name != 'schedule' name: Format, lint, and test runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Free runner disk space # This job peaks at about 33 GiB of disk. Linux runners only promise # 14 GB, and some now have a 72 GB disk instead of 145 GB, which leaves # too little (actions/runner-images#15238). On those, delete # preinstalled SDKs, browsers and Docker images the job never uses. shell: bash run: | df -h / if (( $(df -k --output=avail / | tail -1) < 50 * 1024 * 1024 )); then sudo rm -rf \ /usr/local/lib/android \ /usr/share/dotnet \ /usr/local/.ghcup \ /usr/share/swift \ "${RUNNER_TOOL_CACHE:?}/CodeQL" \ /usr/local/share/powershell \ /opt/microsoft \ /usr/local/julia* \ /usr/local/share/chromium sudo docker image prune --all --force > /dev/null || echo "::warning::Could not remove the preinstalled Docker images." df -h / fi - name: Install native build dependencies run: | sudo apt-get update sudo apt-get install -y \ build-essential \ dbus-daemon \ gettext \ gnupg \ 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 \ ripgrep - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable components: clippy,rustfmt - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: key: quality-default # 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: Check formatting run: cargo fmt --all -- --check - name: Leave translation catalogs to Weblate # Weblate's msgmerge add-on owns lang/*/LC_MESSAGES; edits from other # branches conflict with its pending commits and lock the component. if: >- github.event_name == 'pull_request' && !(github.head_ref == 'weblate-flectar-mail-mail' && github.event.pull_request.head.repo.full_name == github.repository) shell: bash run: | set -euo pipefail # The checkout is the pull request's merge commit; its first parent # is the base branch. git fetch --quiet --deepen=1 origin "$GITHUB_SHA" changed=$(git diff --name-only HEAD^1 HEAD -- 'lang/*/LC_MESSAGES/*.po') if [[ -n $changed ]]; then printf '%s\n' "$changed" echo "::error::Only Weblate edits lang/*/LC_MESSAGES. Commit lang/flectar-mail.pot alone (scripts/update-translation-template.sh) and translate on https://translate.flectar.com/." exit 1 fi - name: Validate repository support files shell: bash run: | set -euo pipefail find scripts -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n python3 - <<'PY' import ast import json import xml.etree.ElementTree as ET from pathlib import Path for path in Path("scripts").glob("*.py"): ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) for path in Path(".").glob("**/*.json"): if "target" not in path.parts: json.loads(path.read_text(encoding="utf-8")) for path in Path(".").glob("**/*.xml"): if "target" not in path.parts: ET.parse(path) PY python3 scripts/test-benchmark-startup.py python3 ui/icons/lucide/generate.py --check python3 resources/app-icon/generate.py --check - name: Keep operating-system names in the platform layer run: python3 scripts/check-platform-gates.py - name: Verify the Blitz patch queue run: bash scripts/blitz-patches.sh verify - name: Verify and test the Parley backport run: | python3 scripts/verify-parley.py cargo test --manifest-path crates/parley/Cargo.toml --lib --locked --target-dir target/parley-tests - name: Verify the Slint winit clipboard backport run: python3 scripts/verify-slint-winit.py - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: "21" - name: Test release validation and publication run: | python3 scripts/test-release.py python3 scripts/test-pdf-packaging.py python3 scripts/test-android-jni.py python3 scripts/test-android-oauth.py python3 scripts/test-android-signing.py - name: Validate Android workspace lockfile run: cargo metadata --manifest-path platform/android/Cargo.toml --locked --format-version 1 > /dev/null - name: Reject unused direct dependencies run: | cargo install cargo-machete --locked --version 0.9.2 cargo machete --with-metadata - name: Check every default target run: cargo check --workspace --all-targets --locked - name: Check the capability-minimal application run: cargo check -p flectar-mail --no-default-features --locked - name: Check the Flatpak application profile run: cargo check -p flectar-mail --no-default-features --features remote-content,gpu-renderer,flatpak --locked - name: Check the CPU-only renderer build run: cargo check -p flectar-mail --no-default-features --features remote-content --locked - name: Lint without warnings run: cargo clippy --workspace --all-targets --locked -- -D warnings - name: Validate translations shell: bash run: | cargo install slint-tr-extractor --version 1.18.1 --locked bash scripts/check-translations.sh - name: Run the default test suite run: cargo test --workspace --all-targets --locked - name: Test the vendored Blitz patches run: cargo test -p blitz-dom --lib --locked - name: Exercise the mobile PDF renderer with bundled PDFium on the host run: | python3 scripts/stage-pdfium.py linux-x64 target/pdfium-mobile-test python3 scripts/test-pdf-preview.py --fixture "$RUNNER_TEMP/mobile-preview.pdf" FLECTAR_TEST_PDFIUM="$PWD/target/pdfium-mobile-test/libpdfium.so" \ FLECTAR_TEST_PDF="$RUNNER_TEMP/mobile-preview.pdf" \ cargo test -p flectar-mail --lib --locked mobile_native_pages_and_rejection -- --ignored platforms: if: github.event_name != 'schedule' # Shared code compiles differently per target. Lint every supported # platform so a change cannot silently break a build nobody ran locally. name: Lint ${{ matrix.name }} strategy: fail-fast: false matrix: include: - name: Windows runner: windows-2025 command: cargo clippy --workspace --all-targets --locked -- -D warnings - name: macOS runner: macos-15 command: cargo clippy --workspace --all-targets --locked -- -D warnings - name: iOS runner: macos-15 target: aarch64-apple-ios # The app's deployment target (platform/ios/project.yml). command: >- IPHONEOS_DEPLOYMENT_TARGET=17.0 cargo clippy --locked --target aarch64-apple-ios --bin flectar-mail --no-default-features --features remote-content,ios -- -D warnings - name: Android runner: ubuntu-24.04 target: aarch64-linux-android # The shared application is a path dependency of this separate # workspace, so `-- -D warnings` alone would not cover it. command: >- RUSTFLAGS="-D warnings" cargo clippy --manifest-path platform/android/Cargo.toml --locked --target aarch64-linux-android -- -D warnings runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable components: clippy targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: key: quality-${{ matrix.name }} save-if: ${{ github.ref == 'refs/heads/main' }} # Slint's Android backend compiles its Java helper during the build. - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 if: matrix.name == 'Android' with: distribution: temurin java-version: "21" - name: Configure Android NDK compiler if: matrix.name == 'Android' 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: Lint without warnings shell: bash run: ${{ matrix.command }} # rustc links without compiler-rt, so an Objective-C `@available` check # above the deployment target would only fail when the release build # links the app. - name: Keep the Objective-C bridges linkable if: matrix.name == 'iOS' run: | archive="" for candidate in target/aarch64-apple-ios/debug/build/flectar-mail-*/out/libflectar_documents.a; do if [[ -z "$archive" || "$candidate" -nt "$archive" ]]; then archive="$candidate"; fi done undefined="$(nm -u "$archive")" if grep -E '___is(Platform|OS)VersionAtLeast' <<<"$undefined"; then echo "::error::Use a runtime check instead of @available in platform/ios; rustc doesn't link compiler-rt." exit 1 fi optional-embeddings: if: github.event_name != 'schedule' name: Optional embedding runtime runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Install native core dependencies run: | sudo apt-get update sudo apt-get install -y build-essential libdbus-1-dev pkg-config gnupg - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable components: clippy - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: key: quality-embeddings save-if: ${{ github.ref == 'refs/heads/main' }} - name: Lint the opt-in model runtime run: cargo clippy -p flectar-mail-core --all-targets --locked --features local-embeddings -- -D warnings - name: Test the opt-in model runtime run: cargo test -p flectar-mail-core --lib --locked --features local-embeddings rustsec: name: RustSec advisory scan runs-on: ubuntu-24.04 steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: cache-targets: false key: quality-rustsec save-if: ${{ github.ref == 'refs/heads/main' }} - name: Install pinned cargo-audit run: cargo install cargo-audit --locked --version 0.22.2 - name: Audit the locked dependency graph # The first four exceptions are unmaintained notices in optional/build # or Slint/Blitz typography dependencies. The next three cover crates # folded into zbus 6: remove them when Slint, ashpd, and ksni migrate # from zbus 5. The last is pin-utils, which async-imap and Slint's # spin_on still use: remove it when both drop it. These are maintenance # notices, not vulnerability waivers. Deny every other warning and all # known vulnerabilities. run: | lockfiles="Cargo.lock platform/android/Cargo.lock" # Tier 3 ports are audited daily and when their own workflow runs, # not on pull requests (platform/README.md). case "$GITHUB_EVENT_NAME" in schedule | workflow_dispatch) lockfiles="$lockfiles platform/ohos/Cargo.lock" ;; esac for lockfile in $lockfiles; do cargo audit --file "$lockfile" --deny warnings \ --ignore RUSTSEC-2025-0141 \ --ignore RUSTSEC-2024-0436 \ --ignore RUSTSEC-2026-0206 \ --ignore RUSTSEC-2026-0192 \ --ignore RUSTSEC-2026-0370 \ --ignore RUSTSEC-2026-0371 \ --ignore RUSTSEC-2026-0372 \ --ignore RUSTSEC-2026-0441 done