From 8dc95da01c2275d7e9056570e5bfe8b9ee493fc7 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 21 Sep 2026 13:43:14 +0200 Subject: [PATCH] ci: fail the build when a changelog will not fit F-Droid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sync_changelog_to_fastlane.sh` has been printing "note: >500 chars — F-Droid may truncate this changelog in-client" for three releases, and every release since 0.2.0 has sailed past it: 1824, 696, 916 characters. A note nobody acts on is not a check. F-Droid truncates the in-client "What's New" box, so everything past the limit is written for nobody — the reader gets a sentence cut mid-word and no way to expand it. The limit is now a hard failure: the script exits non-zero, with a message naming the section to shorten, and `MAX_CHARS` is a variable so the bound lives in one place rather than being copied into the workflows. Enforced in two places, for two different failures: - `.forgejo/workflows/ci.yaml` runs it as an always-on guard beside the reproducible-release invariant, then checks `git status --porcelain` over the changelogs directory. That second half catches a CHANGELOG.md edit whose generated fastlane file was never committed — which until now degraded silently into the official F-Droid listing showing the *previous* version's notes, exactly as RELEASING.md step 3 warns. Porcelain rather than `git diff --exit-code`, so a brand-new file for a bumped versionCode counts as dirty instead of being missed as untracked. - `.gitea/workflows/release.yaml` gets the same call in the cheap `detect` gate. The release job already regenerated the file, but only at step 15 of 24 — after the build, the signing and the keystore setup. Failing in `detect` costs one bash invocation and publishes nothing. --- .forgejo/workflows/ci.yaml | 20 ++++++++++++++++++++ .gitea/workflows/release.yaml | 10 ++++++++++ scripts/sync_changelog_to_fastlane.sh | 23 +++++++++++++++++++++-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index af678bc..a89ab4e 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -37,6 +37,26 @@ jobs: - name: Reproducible-release invariant run: bash scripts/check_reproducible_release.sh + # Also cheap, also always-on. Two failures in one: the script exits + # non-zero if this version's changelog is over the character limit + # F-Droid truncates at, and the porcelain check below catches a + # CHANGELOG.md edit whose generated fastlane file was never committed — + # which used to degrade silently into "the official listing shows the + # previous version's notes". + - name: Changelog fits F-Droid, and is committed + run: | + set -e + bash scripts/sync_changelog_to_fastlane.sh + DIRTY=$(git status --porcelain fastlane/metadata/android/en-US/changelogs) + if [ -n "$DIRTY" ]; then + echo "$DIRTY" + echo "ERROR: the generated fastlane changelog is not what is committed." >&2 + echo "Run scripts/sync_changelog_to_fastlane.sh and commit the result, so" >&2 + echo "the official F-Droid listing shows this version's notes rather than" >&2 + echo "the previous one's." >&2 + exit 1 + fi + # Decide whether anything that affects the app build changed. Docs, store # metadata, licence texts and forge housekeeping don't, so those PRs skip # the SDK + Gradle work below but still report a green `ci`. diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 38d729b..4a26230 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -110,6 +110,16 @@ jobs: ;; esac + # Before a single Gradle task runs: F-Droid truncates the in-client + # changelog, so an over-long one would reach users cut off mid-sentence. + # The script exits non-zero past the limit. Cheap enough to sit in the + # gate job, where failing costs nothing and publishes nothing — the step + # further down that regenerates the file for the repo would otherwise be + # the first thing to notice, after the build and the signing. + - name: Changelog fits F-Droid + if: steps.v.outputs.is_release == 'true' + run: bash scripts/sync_changelog_to_fastlane.sh + # Releases: build + sign + publish, then mint the tag and Gitea release. # Also runs on manual dispatch, where it skips the build and just re-signs and # re-uploads the existing index (recovery path). diff --git a/scripts/sync_changelog_to_fastlane.sh b/scripts/sync_changelog_to_fastlane.sh index f6d0e93..48d0749 100755 --- a/scripts/sync_changelog_to_fastlane.sh +++ b/scripts/sync_changelog_to_fastlane.sh @@ -20,6 +20,10 @@ MAJOR=${VERSION%%.*}; rest=${VERSION#*.}; MINOR=${rest%%.*}; PATCH=${rest##*.} MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0} VERSION_CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH )) +# F-Droid's in-client changelog box truncates past roughly this length; CI reads +# this value rather than hardcoding its own copy. +MAX_CHARS=${MAX_CHARS:-500} + CL_DIR="fastlane/metadata/android/en-US/changelogs" mkdir -p "$CL_DIR" OUT="$CL_DIR/${VERSION_CODE}.txt" @@ -36,6 +40,21 @@ fi CHARS=$(wc -m < "$OUT" | tr -d ' ') echo "Wrote $OUT (version $VERSION, code $VERSION_CODE, ${CHARS} chars)" -if [ "$CHARS" -gt 500 ]; then - echo " note: >500 chars — F-Droid may truncate this changelog in-client." >&2 + +# Hard limit, not a note. F-Droid truncates the "What's New" box in-client, so +# anything past this is written for nobody: the reader sees a sentence cut +# mid-word and no way to expand it. Keep the section in CHANGELOG.md short +# enough to survive that, and put the detail in the commit messages and docs, +# where there is room for it. +# +# CI enforces the same bound (.forgejo/workflows/ci.yaml) so a release cannot +# reach main with a changelog its users cannot read. +if [ "$CHARS" -gt "$MAX_CHARS" ]; then + cat >&2 <