Files
mail/.github/workflows/quality.yml
T

328 lines
14 KiB
YAML

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