Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9e29ad46c |
@@ -37,29 +37,11 @@ jobs:
|
|||||||
- name: Reproducible-release invariant
|
- name: Reproducible-release invariant
|
||||||
run: bash scripts/check_reproducible_release.sh
|
run: bash scripts/check_reproducible_release.sh
|
||||||
|
|
||||||
# The committed versionName must parse (X.Y.Z or X.Y.Z-beta.N) and the
|
|
||||||
# committed versionCode must be the one it derives: the official F-Droid
|
|
||||||
# repo builds the tag as committed. And a beta must never reach main,
|
|
||||||
# where the release pipeline would ship it to F-Droid and Play; betas are
|
|
||||||
# cut from release/* branches (docs/RELEASING.md).
|
|
||||||
- name: Committed version is well-formed
|
|
||||||
env:
|
|
||||||
BASE: ${{ github.base_ref }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
bash scripts/version_info.sh --check
|
|
||||||
if [ "${BASE#refs/heads/}" = "main" ] && [ "$(bash scripts/version_info.sh channel)" = "beta" ]; then
|
|
||||||
echo "ERROR: versionName $(bash scripts/version_info.sh version) is a beta." >&2
|
|
||||||
echo "Set the stable version (and its versionCode) before merging into main." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Also cheap, also always-on. Two failures in one: the script exits
|
# Also cheap, also always-on. Two failures in one: the script exits
|
||||||
# non-zero if this version's changelog is over the 500-character limit,
|
# non-zero if this version's changelog is over the 500-character limit,
|
||||||
# and the porcelain check below catches a version with no committed
|
# and the porcelain check below catches a version with no committed
|
||||||
# changelog, which would otherwise ship the CHANGELOG.md section instead
|
# changelog, which would otherwise ship the CHANGELOG.md section instead
|
||||||
# of a hand-written summary. A beta version passes both: it ships no
|
# of a hand-written summary.
|
||||||
# What's New, so the script writes nothing for it.
|
|
||||||
- name: Changelog fits the stores, and is committed
|
- name: Changelog fits the stores, and is committed
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
name: Beta — Codeberg pre-release
|
|
||||||
|
|
||||||
# A beta is cut by pushing a release branch whose committed versionName is
|
|
||||||
# X.Y.Z-beta.N (see docs/RELEASING.md). Same model as release.yaml: the
|
|
||||||
# committed version is the trigger and the vX.Y.Z-beta.N tag is an output. If
|
|
||||||
# its Codeberg pre-release doesn't carry the APK yet, this runs the unit tests, builds and
|
|
||||||
# signs the APK with the app key, records a Gitea pre-release (with the R8
|
|
||||||
# mapping) and publishes a Codeberg pre-release with the APK + SHA-256.
|
|
||||||
#
|
|
||||||
# Deliberately nothing else. A beta never reaches the F-Droid repos or Play:
|
|
||||||
# Obtainium hides pre-releases unless a user opts in, the official F-Droid
|
|
||||||
# recipe only picks up `^v[0-9.]+$` tags, and Codeberg's "latest release"
|
|
||||||
# skips pre-releases. Pushes of a release branch carrying a stable version
|
|
||||||
# (the usual state) fall through `detect` and do nothing.
|
|
||||||
#
|
|
||||||
# Lives in .gitea/workflows next to release.yaml for the same reason: it needs
|
|
||||||
# the app signing key, which only the self-hosted Gitea instance holds.
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ['release/**']
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: beta
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect:
|
|
||||||
# Gitea only; see the same guard in release.yaml.
|
|
||||||
if: github.repository_owner == 'makiolaj'
|
|
||||||
runs-on: docker
|
|
||||||
outputs:
|
|
||||||
is_beta: ${{ steps.v.outputs.is_beta }}
|
|
||||||
version: ${{ steps.v.outputs.version }}
|
|
||||||
version_code: ${{ steps.v.outputs.version_code }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Resolve version and whether it still needs publishing
|
|
||||||
id: v
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
INFO=$(bash scripts/version_info.sh)
|
|
||||||
echo "$INFO"
|
|
||||||
echo "$INFO" >> "$GITHUB_OUTPUT"
|
|
||||||
if [ "$(bash scripts/version_info.sh channel)" != beta ]; then
|
|
||||||
echo "Not a beta — nothing to do."
|
|
||||||
echo "is_beta=false" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
# Done only once its Codeberg pre-release carries the APK, so a failed
|
|
||||||
# publish is redone by the next push; refused unless newer than the
|
|
||||||
# latest stable release.
|
|
||||||
GATE=$(bash scripts/release_gate.sh)
|
|
||||||
echo "is_beta=${GATE#cut=}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
beta:
|
|
||||||
needs: detect
|
|
||||||
if: needs.detect.outputs.is_beta == 'true'
|
|
||||||
runs-on: docker
|
|
||||||
env:
|
|
||||||
ANDROID_HOME: /opt/android-sdk
|
|
||||||
ANDROID_SDK_ROOT: /opt/android-sdk
|
|
||||||
VERSION: ${{ needs.detect.outputs.version }}
|
|
||||||
VERSION_CODE: ${{ needs.detect.outputs.version_code }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'zulu'
|
|
||||||
java-version: '17'
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
with:
|
|
||||||
packages: ''
|
|
||||||
|
|
||||||
- name: Setup Android SDK cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /opt/android-sdk
|
|
||||||
key: ${{ runner.os }}-android-sdk-37-36.0.0
|
|
||||||
|
|
||||||
- name: Install Android SDK packages
|
|
||||||
run: |
|
|
||||||
yes | sdkmanager --licenses >/dev/null || true
|
|
||||||
sdkmanager \
|
|
||||||
"platform-tools" \
|
|
||||||
"platforms;android-37.0" \
|
|
||||||
"build-tools;36.0.0"
|
|
||||||
|
|
||||||
- name: Setup Gradle cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.gradle/caches
|
|
||||||
~/.gradle/wrapper
|
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-gradle-
|
|
||||||
|
|
||||||
- name: Install jq
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
SUDO=""
|
|
||||||
if command -v sudo >/dev/null 2>&1; then SUDO="sudo"; fi
|
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
|
||||||
$SUDO apt-get update
|
|
||||||
$SUDO apt-get install -y jq
|
|
||||||
elif command -v apk >/dev/null 2>&1; then
|
|
||||||
$SUDO apk add --no-cache jq
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
|
||||||
run: chmod +x ./gradlew
|
|
||||||
|
|
||||||
- name: Pin versionCode to versionName
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
sed -i "s/versionCode = .*/versionCode = $VERSION_CODE/" app/build.gradle.kts
|
|
||||||
grep -E 'versionName|versionCode' app/build.gradle.kts
|
|
||||||
|
|
||||||
- name: Unit tests
|
|
||||||
run: ./gradlew testDebugUnitTest
|
|
||||||
|
|
||||||
# The real app key, same as a stable release: a beta has to update in
|
|
||||||
# place to the next beta and to the stable version.
|
|
||||||
- name: Setup Android Keystore
|
|
||||||
env:
|
|
||||||
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
|
||||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
||||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
||||||
run: |
|
|
||||||
mkdir -p app
|
|
||||||
echo "$KEYSTORE_BASE64" | base64 --decode > app/upload-keystore.jks
|
|
||||||
cat > key.properties <<EOF
|
|
||||||
storePassword=$KEY_PASSWORD
|
|
||||||
keyPassword=$KEY_PASSWORD
|
|
||||||
keyAlias=$KEY_ALIAS
|
|
||||||
storeFile=upload-keystore.jks
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Build release APK
|
|
||||||
run: ./gradlew assembleRelease
|
|
||||||
|
|
||||||
# Notes = a `## [X.Y.Z-beta.N]` section if there is one, else
|
|
||||||
# `## [Unreleased]`. Gitea creates the tag at this commit.
|
|
||||||
- name: Create tag + Gitea pre-release
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
||||||
SHA: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
bash scripts/release_notes.sh "$VERSION" > release-notes.md
|
|
||||||
cat release-notes.md
|
|
||||||
TAG="v$VERSION" PRERELEASE=true NOTES_FILE=release-notes.md \
|
|
||||||
MAPPING=app/build/outputs/mapping/release/mapping.txt \
|
|
||||||
bash scripts/publish_gitea_release.sh
|
|
||||||
|
|
||||||
# The point of the whole workflow, so NOT continue-on-error.
|
|
||||||
- name: Publish pre-release to Codeberg
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ secrets.CODEBERG_RELEASE_TOKEN }}
|
|
||||||
API: https://codeberg.org/api/v1/repos/jlmakiola/agendula
|
|
||||||
SHA: ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
TAG="v$VERSION" PRERELEASE=true NOTES_FILE=release-notes.md \
|
|
||||||
APK=app/build/outputs/apk/release/app-release.apk \
|
|
||||||
bash scripts/publish_codeberg_release.sh
|
|
||||||
+211
-38
@@ -7,9 +7,7 @@ name: Release — F-Droid repo + Gitea/Codeberg release + Play
|
|||||||
# the release on Codeberg with the signed APK + a SHA-256 checksum as a
|
# the release on Codeberg with the signed APK + a SHA-256 checksum as a
|
||||||
# direct-download channel — the tag is an output of the pipeline, not its
|
# direct-download channel — the tag is an output of the pipeline, not its
|
||||||
# trigger. Ordinary merges (no version bump) fall through `detect` and do
|
# trigger. Ordinary merges (no version bump) fall through `detect` and do
|
||||||
# nothing. Betas (X.Y.Z-beta.N) never come through here: beta.yaml cuts them
|
# nothing.
|
||||||
# from release/* branches as Codeberg-only pre-releases, and `detect` refuses
|
|
||||||
# one that reaches main.
|
|
||||||
#
|
#
|
||||||
# A trailing `play` job then uploads the App Bundle to Google Play. It is last
|
# A trailing `play` job then uploads the App Bundle to Google Play. It is last
|
||||||
# and separate because Play can reject a good build for reasons the pipeline
|
# and separate because Play can reject a good build for reasons the pipeline
|
||||||
@@ -62,32 +60,60 @@ jobs:
|
|||||||
|
|
||||||
- name: Resolve version and whether it is a new release
|
- name: Resolve version and whether it is a new release
|
||||||
id: v
|
id: v
|
||||||
|
env:
|
||||||
|
# Tags are read from Codeberg, which is canonical — deliberately NOT
|
||||||
|
# from the Gitea API this workflow runs on. The Codeberg -> Gitea sync
|
||||||
|
# is a push mirror, i.e. `git push --mirror`, which deletes refs the
|
||||||
|
# source does not have. A tag minted here on Gitea is therefore wiped
|
||||||
|
# by the next sync (Codeberg does not have it yet) and only reappears
|
||||||
|
# once the tag push at the end of this workflow propagates back.
|
||||||
|
# Asking Gitea inside that window would report "no tag" for a release
|
||||||
|
# that already shipped, and cut it a second time.
|
||||||
|
# Public repo, so this read needs no token.
|
||||||
|
TAG_API: https://codeberg.org/api/v1/repos/jlmakiola/agendula
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
# versionName -> versionCode, channel and the pre-release flag (set
|
VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts)
|
||||||
# while MAJOR was 0) all come from the one script beta.yaml uses too.
|
if [ -z "$VERSION" ]; then echo "No versionName in app/build.gradle.kts" >&2; exit 1; fi
|
||||||
INFO=$(bash scripts/version_info.sh)
|
MAJOR=$(echo "$VERSION" | cut -d. -f1); MINOR=$(echo "$VERSION" | cut -d. -f2); PATCH=$(echo "$VERSION" | cut -d. -f3)
|
||||||
echo "$INFO"
|
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
|
||||||
echo "$INFO" >> "$GITHUB_OUTPUT"
|
VERSION_CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
||||||
VERSION=$(echo "$INFO" | sed -n 's/^version=//p')
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
CHANNEL=$(echo "$INFO" | sed -n 's/^channel=//p')
|
echo "version_code=$VERSION_CODE" >> "$GITHUB_OUTPUT"
|
||||||
|
# Pre-1.0 is not stable yet: mark the Gitea release as a pre-release
|
||||||
|
# while MAJOR is 0. Graduates to a stable release automatically at 1.0.0.
|
||||||
|
if [ "$MAJOR" = "0" ]; then PRERELEASE=true; else PRERELEASE=false; fi
|
||||||
|
echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved version $VERSION (code $VERSION_CODE, prerelease=$PRERELEASE)"
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
echo "Manual dispatch — re-sign path, not a release."
|
echo "Manual dispatch — re-sign path, not a release."
|
||||||
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
# Betas ship from release/* branches via beta.yaml and must never be
|
# A tag for this version already existing means the release shipped on
|
||||||
# cut here: this path publishes to F-Droid and Play. CI blocks such a
|
# an earlier push; do nothing. Absent => this merge cuts the release.
|
||||||
# PR into main; this is the backstop if one gets through anyway.
|
#
|
||||||
if [ "$CHANNEL" != "stable" ]; then
|
# Anything other than a clean 200/404 is treated as fatal rather than
|
||||||
echo "versionName $VERSION on main is a beta. Set the stable version before merging to main." >&2
|
# as "no tag". A Codeberg outage or a network blip would otherwise
|
||||||
|
# read as absent and re-cut a release that has already shipped —
|
||||||
|
# republishing to F-Droid. Failing here is recoverable; a duplicate
|
||||||
|
# release is not.
|
||||||
|
STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$TAG_API/git/refs/tags/v$VERSION" || echo 000)
|
||||||
|
case "$STATUS" in
|
||||||
|
200)
|
||||||
|
echo "Tag v$VERSION already exists on Codeberg — nothing to release."
|
||||||
|
echo "is_release=false" >> "$GITHUB_OUTPUT"
|
||||||
|
;;
|
||||||
|
404)
|
||||||
|
echo "No tag for v$VERSION on Codeberg yet — cutting the release."
|
||||||
|
echo "is_release=true" >> "$GITHUB_OUTPUT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Codeberg tag lookup for v$VERSION returned HTTP $STATUS." >&2
|
||||||
|
echo "Refusing to guess: treating this as 'no tag' could re-cut a shipped release." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
;;
|
||||||
# Tags are read from Codeberg, the canonical forge, by exact name: its
|
esac
|
||||||
# git/refs/tags API matches by prefix, so v1.1.0-beta.1 would read as
|
|
||||||
# v1.1.0. A lookup error is fatal rather than read as "no tag".
|
|
||||||
GATE=$(bash scripts/release_gate.sh)
|
|
||||||
echo "is_release=${GATE#cut=}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Before a single Gradle task runs: F-Droid truncates the in-client
|
# 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.
|
# changelog, so an over-long one would reach users cut off mid-sentence.
|
||||||
@@ -170,8 +196,8 @@ jobs:
|
|||||||
run: chmod +x ./gradlew
|
run: chmod +x ./gradlew
|
||||||
|
|
||||||
# The committed versionName is the source of truth. Pin versionCode to the
|
# The committed versionName is the source of truth. Pin versionCode to the
|
||||||
# value scripts/version_info.sh derives from it, so the published APK's
|
# value derived from it so the published APK's code is always
|
||||||
# code follows the scheme even if the committed code was forgotten.
|
# MAJOR*10000 + MINOR*100 + PATCH even if the committed code was forgotten.
|
||||||
- name: Pin versionCode to versionName
|
- name: Pin versionCode to versionName
|
||||||
if: env.IS_RELEASE == 'true'
|
if: env.IS_RELEASE == 'true'
|
||||||
run: |
|
run: |
|
||||||
@@ -306,8 +332,6 @@ jobs:
|
|||||||
# Creating it with target_commitish makes Gitea create the vX.Y.Z tag at
|
# Creating it with target_commitish makes Gitea create the vX.Y.Z tag at
|
||||||
# this commit, so the tag only ever marks a fully-shipped release (and a
|
# this commit, so the tag only ever marks a fully-shipped release (and a
|
||||||
# failure before here leaves no tag, so re-running the workflow retries).
|
# failure before here leaves no tag, so re-running the workflow retries).
|
||||||
# Also attaches the R8 mapping (best-effort) so user crash stacktraces
|
|
||||||
# stay deobfuscatable. Notes = this version's CHANGELOG section.
|
|
||||||
- name: Create tag + Gitea release
|
- name: Create tag + Gitea release
|
||||||
if: env.IS_RELEASE == 'true'
|
if: env.IS_RELEASE == 'true'
|
||||||
env:
|
env:
|
||||||
@@ -316,17 +340,85 @@ jobs:
|
|||||||
SHA: ${{ github.sha }}
|
SHA: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
bash scripts/release_notes.sh "$VERSION" > release-notes.md
|
TAG="v$VERSION"
|
||||||
TAG="v$VERSION" NOTES_FILE=release-notes.md \
|
# Notes = this version's CHANGELOG section.
|
||||||
MAPPING=app/build/outputs/mapping/release/mapping.txt \
|
awk -v ver="$VERSION" '
|
||||||
bash scripts/publish_gitea_release.sh
|
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
|
||||||
|
/^## \[/ { flag = 0 }
|
||||||
|
flag' CHANGELOG.md > release-notes.md
|
||||||
|
sed -i -e '/./,$!d' release-notes.md
|
||||||
|
if [ ! -s release-notes.md ]; then
|
||||||
|
echo "_No changelog entry for ${VERSION} — see CHANGELOG.md._" > release-notes.md
|
||||||
|
fi
|
||||||
|
python3 - "$TAG" "$SHA" "$PRERELEASE" <<'PY' > payload.json
|
||||||
|
import json, sys
|
||||||
|
print(json.dumps({
|
||||||
|
"tag_name": sys.argv[1],
|
||||||
|
"target_commitish": sys.argv[2],
|
||||||
|
"name": sys.argv[1],
|
||||||
|
"body": open("release-notes.md").read(),
|
||||||
|
"draft": False,
|
||||||
|
# Pre-1.0 releases are flagged as pre-releases (see detect job).
|
||||||
|
"prerelease": sys.argv[3] == "true",
|
||||||
|
}))
|
||||||
|
PY
|
||||||
|
# Upsert (re-run safe): PATCH if a release for the tag already exists,
|
||||||
|
# else POST a new one (which also creates the tag at target_commitish).
|
||||||
|
curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" > existing.json
|
||||||
|
ID=$(jq -r '.id // empty' existing.json 2>/dev/null || true)
|
||||||
|
if [ -n "$ID" ]; then
|
||||||
|
CODE=$(curl -s -o response.json -w '%{http_code}' -X PATCH \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d @payload.json "$API/releases/$ID")
|
||||||
|
OK=200
|
||||||
|
else
|
||||||
|
CODE=$(curl -s -o response.json -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d @payload.json "$API/releases")
|
||||||
|
OK=201
|
||||||
|
fi
|
||||||
|
cat response.json
|
||||||
|
if [ "$CODE" != "$OK" ]; then
|
||||||
|
echo "Release upsert failed with HTTP $CODE (expected $OK)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Created/updated release $TAG at $SHA"
|
||||||
|
|
||||||
|
# Archive the R8 mapping so user crash stacktraces stay deobfuscatable.
|
||||||
|
# Attached to the release (it's not an APK, so it fits the no-binaries
|
||||||
|
# rule). Best-effort: never fail a release over it.
|
||||||
|
- name: Attach R8 mapping to Gitea release
|
||||||
|
if: env.IS_RELEASE == 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
MAP="app/build/outputs/mapping/release/mapping.txt"
|
||||||
|
if [ ! -f "$MAP" ]; then echo "No mapping.txt (R8 off?) — skipping."; exit 0; fi
|
||||||
|
TAG="v$VERSION"
|
||||||
|
ASSET="mapping-${VERSION}.txt.gz"
|
||||||
|
gzip -c "$MAP" > "/tmp/$ASSET"
|
||||||
|
ID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty')
|
||||||
|
if [ -z "$ID" ]; then echo "Could not resolve release id — skipping."; exit 0; fi
|
||||||
|
# Replace any prior asset of the same name (re-run safe).
|
||||||
|
OLD=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
|
||||||
|
| jq -r --arg n "$ASSET" '.[] | select(.name==$n) | .id')
|
||||||
|
[ -n "$OLD" ] && curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$OLD" >/dev/null || true
|
||||||
|
curl -s -X POST -H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@/tmp/$ASSET" \
|
||||||
|
"$API/releases/$ID/assets?name=$ASSET" -o /dev/null -w "asset upload HTTP %{http_code}\n"
|
||||||
|
|
||||||
# Publish the release on Codeberg, which is canonical for tags and
|
# Publish the release on Codeberg, which is canonical for tags and
|
||||||
# releases (see docs/RELEASING.md): push the tag there and attach the
|
# releases (see docs/RELEASING.md). Codeberg push-mirrors branches + tags
|
||||||
# signed APK plus a SHA-256 checksum as the direct-download channel for
|
# to Gitea, but releases aren't git objects and don't sync in either
|
||||||
# users who don't want F-Droid. The APK is identical to the F-Droid one
|
# direction — so this step pushes the tag straight to Codeberg and creates
|
||||||
# (same app key), so this adds no trust surface. Needs the
|
# the release there over the API, attaching the signed APK plus a SHA-256
|
||||||
# CODEBERG_RELEASE_TOKEN secret; skips cleanly if unset.
|
# checksum as the direct-download channel for users who don't want
|
||||||
|
# F-Droid. The APK is identical to the F-Droid one (same app key), so this
|
||||||
|
# adds no trust surface. Needs the CODEBERG_RELEASE_TOKEN secret; skips
|
||||||
|
# cleanly if unset.
|
||||||
- name: Publish release to Codeberg
|
- name: Publish release to Codeberg
|
||||||
if: env.IS_RELEASE == 'true'
|
if: env.IS_RELEASE == 'true'
|
||||||
# NOT continue-on-error: this step reported green through 0.2.1, 0.2.2,
|
# NOT continue-on-error: this step reported green through 0.2.1, 0.2.2,
|
||||||
@@ -339,10 +431,91 @@ jobs:
|
|||||||
SHA: ${{ github.sha }}
|
SHA: ${{ github.sha }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
[ -s release-notes.md ] || bash scripts/release_notes.sh "$VERSION" > release-notes.md
|
if [ -z "${TOKEN:-}" ]; then
|
||||||
TAG="v$VERSION" NOTES_FILE=release-notes.md \
|
echo "CODEBERG_RELEASE_TOKEN not set — skipping Codeberg publish."
|
||||||
APK=app/build/outputs/apk/release/app-release.apk \
|
exit 0
|
||||||
bash scripts/publish_codeberg_release.sh
|
fi
|
||||||
|
TAG="v$VERSION"
|
||||||
|
APK="app/build/outputs/apk/release/app-release.apk"
|
||||||
|
if [ ! -f "$APK" ]; then echo "No release APK found — skipping." >&2; exit 1; fi
|
||||||
|
ASSET_APK="agendula_v${VERSION}.apk"
|
||||||
|
ASSET_SUM="${ASSET_APK}.sha256"
|
||||||
|
cp "$APK" "/tmp/$ASSET_APK"
|
||||||
|
( cd /tmp && sha256sum "$ASSET_APK" > "$ASSET_SUM" )
|
||||||
|
|
||||||
|
# Release notes: reuse the section extracted for the Gitea release,
|
||||||
|
# fall back to the CHANGELOG entry if that step's file is gone.
|
||||||
|
if [ ! -s release-notes.md ]; then
|
||||||
|
awk -v ver="$VERSION" '
|
||||||
|
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
|
||||||
|
/^## \[/ { flag = 0 }
|
||||||
|
flag' CHANGELOG.md > release-notes.md
|
||||||
|
sed -i -e '/./,$!d' release-notes.md
|
||||||
|
fi
|
||||||
|
[ -s release-notes.md ] || echo "_See CHANGELOG.md for ${VERSION}._" > release-notes.md
|
||||||
|
# Push the tag to Codeberg ourselves. Under Codeberg-canonical the
|
||||||
|
# mirror runs Codeberg -> Gitea, so waiting for a tag to arrive here
|
||||||
|
# from Gitea (what 0.3.2 did) would wait forever. The tag this
|
||||||
|
# pipeline minted on Gitea is in fact *deleted* by the next mirror
|
||||||
|
# sync until Codeberg has it — so pushing it here is what makes it
|
||||||
|
# durable on both forges.
|
||||||
|
#
|
||||||
|
# Pushing the ref first and attaching with NO target_commitish is
|
||||||
|
# deliberate: a release POST carrying a target_commitish for a commit
|
||||||
|
# or tag Codeberg hasn't received yet is what produced the
|
||||||
|
# empty-bodied 500s. Attaching to a ref that already exists doesn't
|
||||||
|
# need the API to write one.
|
||||||
|
git tag -f "$TAG" "$SHA"
|
||||||
|
git push -f "https://jlmakiola:${TOKEN}@codeberg.org/jlmakiola/agendula.git" \
|
||||||
|
"refs/tags/$TAG"
|
||||||
|
python3 - "$TAG" "$PRERELEASE" <<'PY' > cb-payload.json
|
||||||
|
import json, sys
|
||||||
|
tag, pre = sys.argv[1:3]
|
||||||
|
print(json.dumps({
|
||||||
|
"tag_name": tag,
|
||||||
|
"name": tag,
|
||||||
|
"body": open("release-notes.md").read(),
|
||||||
|
"draft": False,
|
||||||
|
# Pre-1.0 releases are flagged as pre-releases (see detect job).
|
||||||
|
"prerelease": pre == "true",
|
||||||
|
}))
|
||||||
|
PY
|
||||||
|
# Create (or update) the release. Codeberg 500s on a POST/GET against a
|
||||||
|
# tag it has only just received — the release request outruns the
|
||||||
|
# indexing of the ref we pushed a moment ago — so a single attempt can
|
||||||
|
# fail even though the very same call succeeds seconds later. Retry
|
||||||
|
# with backoff, and PATCH in place if a release already exists (re-run
|
||||||
|
# safe, so re-running never disturbs a published release). A 5xx body
|
||||||
|
# still exits curl 0, so the loop, not `set -e`, controls the flow.
|
||||||
|
ID=""
|
||||||
|
for attempt in 1 2 3 4 5 6; do
|
||||||
|
EXIST=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty' 2>/dev/null || true)
|
||||||
|
if [ -n "$EXIST" ]; then
|
||||||
|
curl -s -o /dev/null -w "release PATCH HTTP %{http_code}\n" -X PATCH \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d @cb-payload.json "$API/releases/$EXIST"
|
||||||
|
ID="$EXIST"; break
|
||||||
|
fi
|
||||||
|
CODE=$(curl -s -o cb-response.json -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d @cb-payload.json "$API/releases")
|
||||||
|
echo "release POST attempt $attempt HTTP $CODE"
|
||||||
|
ID=$(jq -r '.id // empty' cb-response.json 2>/dev/null || true)
|
||||||
|
[ -n "$ID" ] && break
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
if [ -z "$ID" ]; then echo "Could not resolve Codeberg release id after retries." >&2; exit 1; fi
|
||||||
|
|
||||||
|
# Attach APK + checksum, replacing any prior asset of the same name.
|
||||||
|
for A in "$ASSET_APK" "$ASSET_SUM"; do
|
||||||
|
OLD=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
|
||||||
|
| jq -r --arg n "$A" '.[] | select(.name==$n) | .id')
|
||||||
|
[ -n "$OLD" ] && curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$OLD" >/dev/null || true
|
||||||
|
curl -s -X POST -H "Authorization: token $TOKEN" \
|
||||||
|
-F "attachment=@/tmp/$A" \
|
||||||
|
"$API/releases/$ID/assets?name=$A" -o /dev/null -w "asset $A HTTP %{http_code}\n"
|
||||||
|
done
|
||||||
|
echo "Published $TAG to Codeberg."
|
||||||
|
|
||||||
# Play takes an App Bundle, not the APK: a second artifact from the same
|
# Play takes an App Bundle, not the APK: a second artifact from the same
|
||||||
# source and signing config. Play treats the release key only as the
|
# source and signing config. Play treats the release key only as the
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
name: Renovate
|
name: Renovate
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Every merge to main. Mirror syncs from Codeberg fire push events here (the
|
# Weekly sweep. Mondays 05:00 UTC — this cron owns the cadence; the repo's
|
||||||
# same trigger release.yaml relies on), so a merged Renovate PR is followed
|
# renovate.json5 deliberately has no internal schedule (avoids double-gating).
|
||||||
# within minutes by a run that rebases the sibling PRs it just conflicted —
|
|
||||||
# most bumps touch gradle/libs.versions.toml. Outside renovate.json5's
|
|
||||||
# `schedule` window such a run only maintains existing branches
|
|
||||||
# (updateNotScheduled), it never opens new PRs. Renovate's own rebases push
|
|
||||||
# to renovate/* branches, not main, so this cannot loop.
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
# Weekly sweep for new updates. Mondays 05:00 UTC, inside the schedule window
|
|
||||||
# in renovate.json5 — keep the two in step.
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 5 * * 1'
|
- cron: '0 5 * * 1'
|
||||||
# Manual run for an on-demand sweep from the Actions tab.
|
# Manual run for an on-demand sweep from the Actions tab.
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ All notable changes to this project are documented here. The format follows
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.1] - 2026-09-25
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- First release on Google Play. No changes to the app since 1.0.0.
|
||||||
|
|
||||||
## [1.0.0] - 2026-09-21
|
## [1.0.0] - 2026-09-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -20,14 +20,6 @@ required.</p>
|
|||||||
<a href="https://ko-fi.com/jeanlucmakiola"><img src="https://storage.ko-fi.com/cdn/brandasset/v2/support_me_on_kofi_badge_beige.png" alt="Support me on Ko-fi" height="56"></a>
|
<a href="https://ko-fi.com/jeanlucmakiola"><img src="https://storage.ko-fi.com/cdn/brandasset/v2/support_me_on_kofi_badge_beige.png" alt="Support me on Ko-fi" height="56"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/01.png" alt="Home screen: today's progress, overdue and upcoming tasks, and your lists" width="18%">
|
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/02.png" alt="Onboarding: sync with your own CalDAV server" width="18%">
|
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/03.png" alt="Upcoming list with a task expanded into its subtasks" width="18%">
|
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/04.png" alt="Task detail: a monthly recurring task with a reminder and priority" width="18%">
|
|
||||||
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/05.png" alt="Reminder notification with Done and Snooze actions" width="18%">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Agendula is the task-list sibling to [Calendula](https://codeberg.org/jlmakiola/calendula).
|
Agendula is the task-list sibling to [Calendula](https://codeberg.org/jlmakiola/calendula).
|
||||||
@@ -119,8 +111,6 @@ APK and a `.sha256` checksum attached — the same APK the F-Droid repository
|
|||||||
serves. For automatic updates from there, use
|
serves. For automatic updates from there, use
|
||||||
**[Obtainium](https://github.com/ImranR98/Obtainium)** and
|
**[Obtainium](https://github.com/ImranR98/Obtainium)** and
|
||||||
**[add Agendula in one tap](https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://codeberg.org/jlmakiola/agendula)**.
|
**[add Agendula in one tap](https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://codeberg.org/jlmakiola/agendula)**.
|
||||||
Betas of upcoming versions are published there too, as pre-releases; to test
|
|
||||||
them, switch on *Include prereleases* for Agendula in Obtainium.
|
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ android {
|
|||||||
// These committed values ARE the source of truth for a release: merging
|
// These committed values ARE the source of truth for a release: merging
|
||||||
// a bumped versionName into main triggers .gitea/workflows/release.yaml,
|
// a bumped versionName into main triggers .gitea/workflows/release.yaml,
|
||||||
// which builds this version and then creates the matching vX.Y.Z tag +
|
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||||
// release itself. A versionName of X.Y.Z-beta.N pushed to a release/*
|
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||||
// branch instead cuts a Codeberg-only pre-release (beta.yaml).
|
// PATCH from versionName, e.g. 1.0.0 -> 10000). Releases were flagged as
|
||||||
// versionCode is derived from versionName by scripts/version_info.sh
|
// pre-releases while MAJOR was 0; 1.0.0 is the first stable one, and the
|
||||||
// (1.0.x: 1.0.0 -> 10000; from 1.1.0: 1.1.0-beta.1 -> 1010001,
|
// pipeline graduates it on its own. See docs/RELEASING.md.
|
||||||
// 1.1.0 -> 1010099), and CI fails if the committed one doesn't match.
|
versionCode = 10001
|
||||||
// See docs/RELEASING.md.
|
versionName = "1.0.1"
|
||||||
versionCode = 10000
|
|
||||||
versionName = "1.0.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<string name="lists_header">Listen</string>
|
<string name="lists_header">Listen</string>
|
||||||
<string name="new_task">Neue Aufgabe</string>
|
<string name="new_task">Neue Aufgabe</string>
|
||||||
<string name="lists_failure">Deine Aufgaben konnten nicht gelesen werden.</string>
|
<string name="lists_failure">Deine Aufgaben konnten nicht gelesen werden.</string>
|
||||||
<string name="lists_empty">Keine Aufgaben-Listen bisher.</string>
|
<string name="lists_empty">Keine Aufgaben-Listen bisher. Füge eine in deiner Aufgaben-App hinzu oder mit der + Schaltfläche.</string>
|
||||||
<string name="smart_today">Heute</string>
|
<string name="smart_today">Heute</string>
|
||||||
<string name="smart_overdue">Überfällig</string>
|
<string name="smart_overdue">Überfällig</string>
|
||||||
<string name="smart_upcoming">Anstehend</string>
|
<string name="smart_upcoming">Anstehend</string>
|
||||||
@@ -99,23 +99,23 @@
|
|||||||
<string name="reminder_channel_name">Aufgaben Erinnerungen</string>
|
<string name="reminder_channel_name">Aufgaben Erinnerungen</string>
|
||||||
<string name="reminder_channel_desc">Benachrichtigungen für fällige Aufgaben.</string>
|
<string name="reminder_channel_desc">Benachrichtigungen für fällige Aufgaben.</string>
|
||||||
<string name="onboarding_no_provider_title">Eine Task-App wird benötigt</string>
|
<string name="onboarding_no_provider_title">Eine Task-App wird benötigt</string>
|
||||||
<string name="onboarding_no_provider_body">Es ist keine App installiert, in der man Aufgaben speichern könnte. Installieren OpenTasks oder tasks.org oder wechsel zu Agendulas eigenem Speicher.</string>
|
<string name="onboarding_no_provider_body">Agendula zeigt und bearbeitet die Aufgaben, die in OpenTasks oder tasks.org gespeichert sind und per DAVx5 synchronisiert werden. Installiere eine davon, um zu starten.</string>
|
||||||
<string name="onboarding_permission_title">Erlaube Zugriff auf deine Aufgaben</string>
|
<string name="onboarding_permission_title">Erlaube Zugriff auf deine Aufgaben</string>
|
||||||
<string name="onboarding_permission_body">Agendula benötigt die Berechtigung, deine Aufgaben zu lesen und zu schreiben. Das ist die einzige Berechtigung, die es jemals anfragt.</string>
|
<string name="onboarding_permission_body">Agendula benötigt die Berechtigung, deine Aufgaben zu lesen und zu schreiben. Das ist die einzige Berechtigung, die es jemals anfragt.</string>
|
||||||
<string name="onboarding_permission_button">Zugriff gewähren</string>
|
<string name="onboarding_permission_button">Zugriff gewähren</string>
|
||||||
<string name="reminder_onboarding_title">Verpasse nichts, was fällig ist</string>
|
<string name="reminder_onboarding_title">Verpasse nichts, was fällig ist</string>
|
||||||
<string name="reminder_onboarding_body">Agendula benachrichtigt dich, wenn etwas fällig wird, also muss nichts in deinem Kopf leben.</string>
|
<string name="reminder_onboarding_body">Aufgaben-Apps senden selbst keine Erinnerungen, daher liefert Agendula sie für dich. Schalte sie ein, um eine Benachrichtigung zu erhalten, wenn eine Aufgabe fällig ist.</string>
|
||||||
<string name="reminder_onboarding_enable_button">Erinnerungen aktivieren</string>
|
<string name="reminder_onboarding_enable_button">Erinnerungen aktivieren</string>
|
||||||
<string name="reminder_onboarding_skip_button">Nicht jetzt</string>
|
<string name="reminder_onboarding_skip_button">Nicht jetzt</string>
|
||||||
<string name="reminder_benefit_delivery_title">Agendula erinnert dich</string>
|
<string name="reminder_benefit_delivery_title">Agendula erinnert dich</string>
|
||||||
<string name="reminder_benefit_delivery_body">Jede Aufgaben mit Fälligkeitsdatum bekommt seine eigene Benachrichtigung.</string>
|
<string name="reminder_benefit_delivery_body">Es plant eine Benachrichtigung für jede Aufgabe mit einem Fälligkeitsdatum.</string>
|
||||||
<string name="reminder_benefit_timing_title">Auf deinem Zeitplan</string>
|
<string name="reminder_benefit_timing_title">Auf deinem Zeitplan</string>
|
||||||
<string name="reminder_benefit_timing_body">Wähle, wie weit im Voraus du erinnert werden möchtest — für alles oder pro Liste.</string>
|
<string name="reminder_benefit_timing_body">Wähle in den Einstellungen, wie weit im Voraus du erinnert werden möchtest.</string>
|
||||||
<string name="reminder_benefit_reversible_title">Jederzeit änderbar</string>
|
<string name="reminder_benefit_reversible_title">Jederzeit änderbar</string>
|
||||||
<string name="reminder_benefit_reversible_body">Schalte Erinnerungen aus, wann immer du willst — es ist nur ein Schalter.</string>
|
<string name="reminder_benefit_reversible_body">Schalte Erinnerungen aus, wann immer du willst — es ist nur ein Schalter.</string>
|
||||||
<string name="settings_title">Einstellungen</string>
|
<string name="settings_title">Einstellungen</string>
|
||||||
<string name="settings_section_appearance">Darstellung</string>
|
<string name="settings_section_appearance">Darstellung</string>
|
||||||
<string name="settings_appearance_subtitle">Theme, Farbe und Zeitformat</string>
|
<string name="settings_appearance_subtitle">Theme und Farbe</string>
|
||||||
<string name="settings_section_task_form">Aufgaben-Formular</string>
|
<string name="settings_section_task_form">Aufgaben-Formular</string>
|
||||||
<string name="settings_task_form_subtitle">Standard Felder, Liste und Unter-Aufgaben</string>
|
<string name="settings_task_form_subtitle">Standard Felder, Liste und Unter-Aufgaben</string>
|
||||||
<string name="settings_reminders_subtitle">Benachrichtigungen für fällige Aufgaben</string>
|
<string name="settings_reminders_subtitle">Benachrichtigungen für fällige Aufgaben</string>
|
||||||
@@ -169,244 +169,4 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<string name="settings_translate">Hilf beim Übersetzen</string>
|
<string name="settings_translate">Hilf beim Übersetzen</string>
|
||||||
<string name="settings_translate_hint">Füge eine Sprache auf Weblate hinzu oder verbessere sie</string>
|
<string name="settings_translate_hint">Füge eine Sprache auf Weblate hinzu oder verbessere sie</string>
|
||||||
<string name="task_completed">Fertig</string>
|
|
||||||
<string name="cd_repeats">Wiederholt</string>
|
|
||||||
<string name="shortcut_new_task">Neue Aufgabe</string>
|
|
||||||
<string name="shortcut_today">Heute</string>
|
|
||||||
<string name="qs_tile_new_task">Neue Aufgabe</string>
|
|
||||||
<string name="lists_reorder">Liste umsortieren</string>
|
|
||||||
<string name="lists_reorder_done">Ferig umsortiert</string>
|
|
||||||
<string name="lists_reorder_handle">Ziehen zum Verschieben</string>
|
|
||||||
<string name="sort_title">Sortieren nach</string>
|
|
||||||
<string name="sort_due">Fälligkeitsdatum</string>
|
|
||||||
<string name="sort_priority">Priorität</string>
|
|
||||||
<string name="sort_title_alpha">Titel</string>
|
|
||||||
<string name="sort_created">Kürzlich hinzugefügt</string>
|
|
||||||
<plurals name="clear_completed_body">
|
|
||||||
<item quantity="one">%1$d erledigte Aufgabe in dieser Liste wird gelöscht.</item>
|
|
||||||
<item quantity="other">%1$d erledigte Aufgaben in dieser Liste werden gelöscht.</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="clear_completed_confirm">Löschen</string>
|
|
||||||
<string name="settings_list_notifications">Benachrichtigungen</string>
|
|
||||||
<string name="settings_list_notifications_hint">Töne, Vibration und Wichtigkeit für Erinnerungen der Liste</string>
|
|
||||||
<string name="recurrence_generic">Wiederholende Aufgabe</string>
|
|
||||||
<string name="recurrence_daily">Jeden Tag</string>
|
|
||||||
<string name="recurrence_weekly">Jede Woche</string>
|
|
||||||
<string name="recurrence_monthly">Jedem Monat</string>
|
|
||||||
<string name="recurrence_yearly">Jedes Jahr</string>
|
|
||||||
<plurals name="recurrence_every_n_days">
|
|
||||||
<item quantity="one">Jeden Tag</item>
|
|
||||||
<item quantity="other">Alle %1$d Tage</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="recurrence_every_n_weeks">
|
|
||||||
<item quantity="one">Jede Woche</item>
|
|
||||||
<item quantity="other">Alle %1$d Wochen</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="recurrence_every_n_months">
|
|
||||||
<item quantity="one">Jeden Monat</item>
|
|
||||||
<item quantity="other">Allw %1$d Monate</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="recurrence_every_n_years">
|
|
||||||
<item quantity="one">Jedes Jahr</item>
|
|
||||||
<item quantity="other">Alle %1$d Jahre</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="recurrence_on_days">%1$s am %2$s</string>
|
|
||||||
<string name="recurrence_with_until">%1$s bis %2$s</string>
|
|
||||||
<string name="recurrence_none">Wiederholt sich nicht</string>
|
|
||||||
<string name="recurrence_custom">Benutzerdefiniert</string>
|
|
||||||
<string name="recurrence_unit_days">Tage</string>
|
|
||||||
<string name="recurrence_unit_weeks">Wochen</string>
|
|
||||||
<string name="recurrence_unit_months">Monate</string>
|
|
||||||
<string name="recurrence_unit_years">Jahre</string>
|
|
||||||
<string name="recurrence_incomplete">Gebe eine Nummer zwischen 1 und 999 ein</string>
|
|
||||||
<string name="recurrence_end_never">Nie</string>
|
|
||||||
<string name="recurring_scope_occurrence">Nur diese Aufgabe</string>
|
|
||||||
<string name="recurring_scope_following">Diese und folgende Aufgaben</string>
|
|
||||||
<string name="error_recurrence_without_date">Eine sich wiederholende Aufgabe braucht ein Start- oder Fälligkeitsdatum</string>
|
|
||||||
<string name="edit_location_label">Standort</string>
|
|
||||||
<string name="edit_url_label">Link</string>
|
|
||||||
<string name="edit_url_hint">https://…</string>
|
|
||||||
<string name="edit_discard_title">Änderungen verwerfen?</string>
|
|
||||||
<string name="edit_discard_body">Deine Änderungen an dieser Aufgabe wurden nicht gespeichert.</string>
|
|
||||||
<string name="edit_discard_confirm">Verwerfen</string>
|
|
||||||
<string name="edit_keep_editing">Weiter bearbeiten</string>
|
|
||||||
<string name="edit_reminder_add">Erinnerung hinzufügen</string>
|
|
||||||
<string name="edit_reminder_add_first">Erinnerung hinzufügen (sonst wird Standard angewendet)</string>
|
|
||||||
<string name="edit_reminder_remove">Erinnerung entfernen</string>
|
|
||||||
<string name="subtask_progress">%1$d / %2$d</string>
|
|
||||||
<string name="progress_fraction_compact">%1$d/%2$d</string>
|
|
||||||
<string name="detail_delete_title">Aufgabe löschen?</string>
|
|
||||||
<string name="detail_delete_body">Die Aufgabe wird gelöscht.</string>
|
|
||||||
<plurals name="detail_delete_body_subtasks">
|
|
||||||
<item quantity="one">Diese Aufgabe und ihre Unteraufgabe werden gelöscht.</item>
|
|
||||||
<item quantity="other">Diese Aufgabe und ihre %1$d Unteraufgaben werden gelöscht.</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="detail_status_cancelled">Abgebrochen</string>
|
|
||||||
<string name="detail_status_in_process">In Bearbeitung</string>
|
|
||||||
<string name="more_options">Mehr Optionen</string>
|
|
||||||
<string name="detail_cancel_task">Aufgabe abbrechen</string>
|
|
||||||
<string name="detail_restore">Aufgabe wiederherstellen</string>
|
|
||||||
<string name="detail_duplicate">Duplizieren</string>
|
|
||||||
<string name="lists_empty_action">Liste erstellen</string>
|
|
||||||
<string name="list_add">Neue Liste</string>
|
|
||||||
<string name="list_new_title">Neue Liste</string>
|
|
||||||
<string name="list_edit_title">Liste bearbeiten</string>
|
|
||||||
<string name="list_name_hint">Listenname</string>
|
|
||||||
<string name="list_color">Farbe</string>
|
|
||||||
<string name="list_delete">Liste löschen</string>
|
|
||||||
<string name="list_delete_confirm_title">Liste löschen?</string>
|
|
||||||
<string name="list_where_device">Nur auf diesem Gerät</string>
|
|
||||||
<string name="detail_pane_empty">Wähle eine Aufgabe, um sie hier zu sehen</string>
|
|
||||||
<string name="percent_complete">%1$d%%</string>
|
|
||||||
<string name="reminder_on_the_day">Ab dem Tag</string>
|
|
||||||
<string name="recurrence_next">Als nächstes: %1$s</string>
|
|
||||||
<string name="recurrence_end_until">An einem Tag</string>
|
|
||||||
<plurals name="home_today_remaining_count">
|
|
||||||
<item quantity="one">%1$d übrig</item>
|
|
||||||
<item quantity="other">%1$d übrig</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="reminder_day_today">heute</string>
|
|
||||||
<string name="reminder_day_tomorrow">morgen</string>
|
|
||||||
<string name="reminder_day_yesterday">gestern</string>
|
|
||||||
<string name="reminder_action_done">Fertig</string>
|
|
||||||
<string name="onboarding_welcome_title">Alles, was du machen wolltest</string>
|
|
||||||
<string name="onboarding_welcome_body">Ein ruhiger Ort für deine Aufgaben auf deinem Smartphone, das deins bleibt. Lass es uns einrichten, es braucht eine Minute.</string>
|
|
||||||
<string name="onboarding_welcome_button">Loslegen</string>
|
|
||||||
<string name="onboarding_welcome_lists_title">Listen und Unteraufgaben</string>
|
|
||||||
<string name="onboarding_welcome_reminders_title">Erinnerungen, die ankommen</string>
|
|
||||||
<string name="onboarding_welcome_reminders_body">Eine Benachrichtigung, wenn etwas fällig wird, nach deinem Plan</string>
|
|
||||||
<string name="onboarding_welcome_sync_body">Synchronisiere über CalDAV mit einem Server, den du wählst, oder behalte alles auf diesem Gerät.</string>
|
|
||||||
<string name="onboarding_sync_title">Mit deinem eigenen Server synchronisieren lassen?</string>
|
|
||||||
<string name="onboarding_sync_body">Agendula spricht CalDav — Nextcloud, Radicale, Posteo, Fastmail und alles, was auch CalDAV spricht. Keine Konten bei uns, nie.</string>
|
|
||||||
<string name="onboarding_sync_connect">Konto verbinden</string>
|
|
||||||
<string name="onboarding_sync_skip">Nicht jetzt</string>
|
|
||||||
<string name="onboarding_sync_devices_body">Hake etwas hier ab, und es wird Überfall abgehakt.</string>
|
|
||||||
<string name="onboarding_sync_copy_body">Deine Aufgaben leben auch auf dem Server, also ist ein verlorenes Gerät nicht verlorene Arbeit.</string>
|
|
||||||
<string name="onboarding_list_title">Deine erste Liste</string>
|
|
||||||
<string name="onboarding_list_default_name">Persönlich</string>
|
|
||||||
<string name="onboarding_list_create">Liste erstellen</string>
|
|
||||||
<string name="onboarding_backup_title">Eine Sicherung behalten</string>
|
|
||||||
<string name="onboarding_backup_continue">Fortsetzen</string>
|
|
||||||
<string name="onboarding_quick_add_title">Wie möchtest du Aufgaben hinzufügen?</string>
|
|
||||||
<string name="clear_completed_action">Löschen</string>
|
|
||||||
<string name="clear_completed_title">Erledigte Aufgaben löschen?</string>
|
|
||||||
<string name="sync_notice_retry">Wiederholen</string>
|
|
||||||
<string name="sync_notice_cause_server_newer">Durch die neuere Kopie vom Server ersetzt</string>
|
|
||||||
<string name="sync_sign_in_channel_name">Anmeldung nötig</string>
|
|
||||||
<string name="sync_sign_in_title">Bei %1$s wieder anmelden</string>
|
|
||||||
<string name="sync_notification_channel">Synchronisieren</string>
|
|
||||||
<string name="sync_notification_title">Aufgaben synchronisieren</string>
|
|
||||||
<string name="add_account_server_label">Server-Adresse</string>
|
|
||||||
<string name="add_account_server_hint">https://cloud.example.com</string>
|
|
||||||
<string name="add_account_username_label">Benutzername</string>
|
|
||||||
<string name="add_account_password_label">Passwort</string>
|
|
||||||
<string name="add_account_sign_in">Anmelden</string>
|
|
||||||
<string name="add_account_browser_title">Auf Browser warten</string>
|
|
||||||
<string name="add_account_browser_body">Bestätige Agendula im Browser, komme dann zurück hierher. Dein Passwort gelangt niemals zu dieser App, der Server vergibt ein seperates App-Passwort, das du jederzeit löschen kannst.</string>
|
|
||||||
<string name="add_account_browser_use_password">Stattdessen Passwort nutzen</string>
|
|
||||||
<string name="add_account_browser_retry">Nochmal versuchen</string>
|
|
||||||
<string name="add_account_browser_open">Öffne die Anmeldeseite</string>
|
|
||||||
<string name="add_account_browser_insecure">Diese Anmeldeseite ist unverschlüsselt (http). Alles, was du darin schreibst, auch dein Passwort, wird unverschlüsselt versendet.</string>
|
|
||||||
<string name="add_account_lists_title">Welche Listen sollen synchronisiert werden?</string>
|
|
||||||
<string name="add_account_lists_shared">Mit dir geteilt</string>
|
|
||||||
<string name="add_account_save">Konto hinzufügen</string>
|
|
||||||
<string name="add_account_no_lists_selected">Wähle mindestens eine Liste</string>
|
|
||||||
<string name="widget_tasks_label">Aufgaben</string>
|
|
||||||
<string name="widget_empty">Hier ist nichts zu tun</string>
|
|
||||||
<string name="widget_unavailable">Öffne Agendula und die Einrichtung abzuschließen</string>
|
|
||||||
<string name="widget_config_title">Im Widget zeigen</string>
|
|
||||||
<string name="widget_config_smart_lists">Intelligente Listen</string>
|
|
||||||
<string name="list_color_blue">Blau</string>
|
|
||||||
<string name="list_color_purple">Lila</string>
|
|
||||||
<string name="list_color_pink">Rosa</string>
|
|
||||||
<plurals name="home_today_progress_count">
|
|
||||||
<item quantity="one">%1$d von %2$d abgeschlossen</item>
|
|
||||||
<item quantity="other">%1$d von %2$d abgeschlossen</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="settings_group_look">Aussehen und Verhalten</string>
|
|
||||||
<string name="settings_group_data">Daten</string>
|
|
||||||
<string name="settings_group_app">App</string>
|
|
||||||
<string name="settings_group_about">Über</string>
|
|
||||||
<string name="settings_autofocus_title">Fokusiere den Titel bei Erstellung neuer Aufgaben</string>
|
|
||||||
<plurals name="duration_minutes">
|
|
||||||
<item quantity="one">%d Minute</item>
|
|
||||||
<item quantity="other">%d Minuten</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="duration_hours">
|
|
||||||
<item quantity="one">%d Stunde</item>
|
|
||||||
<item quantity="other">%d Stunden</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="settings_section_accounts">Konten</string>
|
|
||||||
<string name="accounts_empty_title">Noch keine Konten</string>
|
|
||||||
<string name="accounts_add">Konto hinzufügen</string>
|
|
||||||
<string name="accounts_sync_interval">Automatisch synchronisieren</string>
|
|
||||||
<plurals name="accounts_sync_every_minutes">
|
|
||||||
<item quantity="one">Jede %d Minute</item>
|
|
||||||
<item quantity="other">Jede %d Minuten</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="list_save_failed">Liste konnte nicht gespeichert werden.</string>
|
|
||||||
<string name="list_delete_failed">Liste konnte nicht gelöscht werden.</string>
|
|
||||||
<string name="list_save_failed_offline">Server konnte nicht erreicht werden. Versuche es bitte erneut, wenn du wieder online bist.</string>
|
|
||||||
<string name="list_save_failed_unsupported">Dieses Konto lässt Apps keine Aufgabenlisten erstellen.</string>
|
|
||||||
<string name="list_delete_confirm_message">\"%1$s\" und alle Aufgaben davon werden gelöscht. Das kann nicht rückgängig gemacht werden.</string>
|
|
||||||
<string name="list_color_red">Rot</string>
|
|
||||||
<string name="list_color_orange">Orange</string>
|
|
||||||
<string name="list_color_olive">Olive</string>
|
|
||||||
<string name="list_color_green">Grün</string>
|
|
||||||
<string name="list_color_cyan">Cyan</string>
|
|
||||||
<string name="settings_time_format">Zeitformat</string>
|
|
||||||
<string name="settings_time_format_auto">Automatisch</string>
|
|
||||||
<string name="settings_week_start">Woche startet am</string>
|
|
||||||
<string name="settings_week_start_auto">Automatisch</string>
|
|
||||||
<string name="settings_week_start_auto_summary">Momentan: %1$s</string>
|
|
||||||
<string name="settings_licences">Open-Source Lizenzen</string>
|
|
||||||
<string name="settings_licences_subtitle">Die Projekte, auf denen Agendula aufgebaut ist</string>
|
|
||||||
<string name="licences_title">Open-Source Lizenzen</string>
|
|
||||||
<string name="licences_intro">Agendula beinhaltet die unten gelistete Arbeit. Drücke eine, um ihren Quellcode zu erhalten.</string>
|
|
||||||
<string name="settings_privacy">Datenschutzbestimmungen</string>
|
|
||||||
<string name="settings_notifications_blocked">Benachrichtigungen sind deaktiviert</string>
|
|
||||||
<plurals name="import_done">
|
|
||||||
<item quantity="one">%1$d Aufgabe in %2$s importiert</item>
|
|
||||||
<item quantity="other">%1$d Aufgaben in %2$s importiert</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="import_done_skipped_existing">
|
|
||||||
<item quantity="one">%1$d schon vorhanden. Wird übersprungen.</item>
|
|
||||||
<item quantity="other">%1$d schon vorhanden. Werden übersprungen.</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="import_failed_unreadable">Die Datei konnte nicht als iCalendar eingelesen werden</string>
|
|
||||||
<string name="import_failed_no_tasks">Die Datei beinhaltet keine Aufgaben</string>
|
|
||||||
<string name="import_failed_write">Die Aufgabe konnte nicht gespeichert werden</string>
|
|
||||||
<plurals name="accounts_sync_every_hours">
|
|
||||||
<item quantity="one">Jede Stunde</item>
|
|
||||||
<item quantity="other">Alle %d Stunden</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="accounts_remove">Konto entfernen</string>
|
|
||||||
<string name="accounts_generic_provider">CalDAV-Konto</string>
|
|
||||||
<string name="accounts_sync_now">Jetzt synchronisieren</string>
|
|
||||||
<string name="accounts_removing">Entfernen…</string>
|
|
||||||
<string name="accounts_lists">Listen</string>
|
|
||||||
<string name="add_account_step_of">Schritt %1$d von %2$d</string>
|
|
||||||
<string name="add_account_provider_other_summary">Baïkal, DAViCal, SOGo, Radicale oder einen, den du selber laufen lässt</string>
|
|
||||||
<string name="add_account_provider_other_header">Jeglichen CalDAV-Server</string>
|
|
||||||
<string name="add_account_provider_other">Andere CalDAV-Server</string>
|
|
||||||
<string name="add_account_email_title">Was ist dort deine Adresse?</string>
|
|
||||||
<string name="add_account_email_body">Die E-Mail-Adresse, mit der du diesen Dienst nutzt. Agendula findet den Server selbst.</string>
|
|
||||||
<string name="add_account_email_label">E-Mail-Adresse</string>
|
|
||||||
<string name="add_account_summary_title">Du bist eingerichtet</string>
|
|
||||||
<string name="add_account_summary_done">Fertig</string>
|
|
||||||
<string name="add_account_server_title">Wo sind deine Aufgaben?</string>
|
|
||||||
<string name="add_account_credentials_title">Anmelden</string>
|
|
||||||
<string name="add_account_error_not_an_address">Das scheint keine Web-Adresse zu sein. Versuche etwas wie cloud.example.com.</string>
|
|
||||||
<string name="add_account_working_signing_in">Anmelden</string>
|
|
||||||
<string name="add_account_working_reading_lists">Aufgaben auslesen</string>
|
|
||||||
<string name="add_account_working_saving">Konto hinzufügen</string>
|
|
||||||
<string name="add_account_error_google_unsupported">Google Kalendar unterstützt nach eigener Dokumentation nicht Aufgaben über CalDAV, also kann Agendula nicht damit synchronisieren.</string>
|
|
||||||
<string name="add_account_error_already_exists">Konto ist schon eingerichtet.</string>
|
|
||||||
<string name="add_account_error_credentials_rejected">Diser Benutzername oder dieses Passwort wurde nicht akzeptiert.</string>
|
|
||||||
<string name="add_account_browser_error_maintenance">Der Server befindet sich um Wartungsmodus. Versuche es erneut, wenn er wieder zurück ist.</string>
|
|
||||||
<string name="add_account_setup_icloud_title">iCloud braucht ein App-spezifisches Passwort</string>
|
|
||||||
<string name="accounts_sign_in_again">Erneut anmelden</string>
|
|
||||||
<string name="sync_notice_body">%1$s</string>
|
|
||||||
<string name="sync_notices_title">Änderungen durch letzte Synchronisierung</string>
|
|
||||||
<string name="sync_notices_dismiss">Verstanden</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+19
-87
@@ -11,12 +11,9 @@ carries no APK assets.
|
|||||||
Codeberg is the canonical forge; Gitea is build infrastructure. See
|
Codeberg is the canonical forge; Gitea is build infrastructure. See
|
||||||
[Two forges, one repo](#two-forges-one-repo) for how the two are wired.
|
[Two forges, one repo](#two-forges-one-repo) for how the two are wired.
|
||||||
|
|
||||||
Before a stable release you can ship **betas** (`X.Y.Z-beta.N`) from the
|
While Agendula is pre-1.0 (`versionName` starts with `0.`), every release is
|
||||||
release branch. They go to **Codeberg only**, flagged as pre-releases; F-Droid
|
flagged as a **pre-release**. This happens automatically and graduates to a
|
||||||
and Play never see them. See [Cutting a beta](#cutting-a-beta).
|
stable release at `1.0.0` — no manual toggling.
|
||||||
|
|
||||||
While Agendula was pre-1.0 (`versionName` started with `0.`), every release was
|
|
||||||
flagged as a **pre-release** automatically.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,30 +22,16 @@ flagged as a **pre-release** automatically.
|
|||||||
A release is defined by the `versionName`/`versionCode` committed in
|
A release is defined by the `versionName`/`versionCode` committed in
|
||||||
`app/build.gradle.kts` — **not** by a hand-pushed tag:
|
`app/build.gradle.kts` — **not** by a hand-pushed tag:
|
||||||
|
|
||||||
- `versionName` = `MAJOR.MINOR.PATCH` (e.g. `1.1.0`), or
|
- `versionName` = `MAJOR.MINOR.PATCH` (e.g. `0.2.0`)
|
||||||
`MAJOR.MINOR.PATCH-beta.N` for a beta (e.g. `1.1.0-beta.2`)
|
- `versionCode` = `MAJOR*10000 + MINOR*100 + PATCH` (`0.2.0` → `200`,
|
||||||
- `versionCode` is derived from it by `scripts/version_info.sh`:
|
`1.3.4` → `10304`)
|
||||||
|
|
||||||
| `versionName` | `versionCode` | Example |
|
So `MINOR` and `PATCH` each have room for 0–99. The release pipeline reads
|
||||||
| --- | --- | --- |
|
`versionName`, pins `versionCode` to the derived value, builds, publishes, and —
|
||||||
| `X.Y.Z` below 1.1.0 (legacy) | `X*10000 + Y*100 + Z` | `1.0.1` → `10001` |
|
once the APK is live — creates the tag `v<versionName>` at that commit. The tag
|
||||||
| `X.Y.Z-beta.N` (N = 1–98) | `X*1000000 + Y*10000 + Z*100 + N` | `1.1.0-beta.2` → `1010002` |
|
is an **output** of a successful release, not its trigger, so a tag always marks
|
||||||
| `X.Y.Z` from 1.1.0 | `X*1000000 + Y*10000 + Z*100 + 99` | `1.1.0` → `1010099` |
|
a fully-shipped version (and a failure before publish leaves no tag, so
|
||||||
|
re-running the workflow safely retries).
|
||||||
A beta's code sits below its own stable release and above everything before
|
|
||||||
it, so a beta install updates in place to the next beta and then to the stable
|
|
||||||
version. Every 1.0.x keeps the code it already has, and `1.1.0-beta.1`
|
|
||||||
(`1010001`) is above all of them. `MINOR` and `PATCH` each have room for 0–99.
|
|
||||||
Run `scripts/version_info.sh` to see what the committed version resolves to;
|
|
||||||
CI fails a PR whose committed `versionCode` doesn't match, because the official
|
|
||||||
F-Droid repo builds the tag exactly as committed.
|
|
||||||
|
|
||||||
The release pipeline reads `versionName`, pins `versionCode` to the derived
|
|
||||||
value, builds, publishes, and — once the APK is live — creates the tag
|
|
||||||
`v<versionName>` at that commit. The tag is an **output** of a successful
|
|
||||||
release, not its trigger, so a tag always marks a fully-shipped version (and a
|
|
||||||
failure before publish leaves no tag, so re-running the workflow safely
|
|
||||||
retries).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -61,8 +44,8 @@ retries).
|
|||||||
`## [X.Y.Z]` heading (Keep a Changelog format). The text between that heading
|
`## [X.Y.Z]` heading (Keep a Changelog format). The text between that heading
|
||||||
and the next `## [` becomes the Gitea and Codeberg release notes. The
|
and the next `## [` becomes the Gitea and Codeberg release notes. The
|
||||||
heading **must** match the version exactly.
|
heading **must** match the version exactly.
|
||||||
3. **Bump the committed `versionName`** (and `versionCode`, which
|
3. **Bump the committed `versionName`** (and `versionCode`) in
|
||||||
`scripts/version_info.sh` prints) in `app/build.gradle.kts`. **This bump is what triggers the release** when the
|
`app/build.gradle.kts`. **This bump is what triggers the release** when the
|
||||||
branch merges to `main`.
|
branch merges to `main`.
|
||||||
Then write this version's **"What's New"** by hand:
|
Then write this version's **"What's New"** by hand:
|
||||||
`fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt`, one per
|
`fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt`, one per
|
||||||
@@ -96,48 +79,6 @@ retries).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cutting a beta
|
|
||||||
|
|
||||||
A beta is a test build of the next version, published as a **pre-release on
|
|
||||||
Codeberg** and nowhere else. It is signed with the real app key, so testers
|
|
||||||
install it over their stable install and it updates in place to later betas
|
|
||||||
and to the stable release.
|
|
||||||
|
|
||||||
1. **On `release/vX.Y.Z`**, with the features merged in, set
|
|
||||||
`versionName = "X.Y.Z-beta.1"` and the matching `versionCode`
|
|
||||||
(`scripts/version_info.sh` prints it; `1.1.0-beta.1` → `1010001`).
|
|
||||||
No What's New file — betas don't ship to the stores. Notes come from a
|
|
||||||
`## [X.Y.Z-beta.N]` section of `CHANGELOG.md` if you write one, otherwise
|
|
||||||
from `## [Unreleased]`.
|
|
||||||
2. **Optionally verify it on a device** with `scripts/verify-release.sh`, as for
|
|
||||||
a stable release.
|
|
||||||
3. **Push the branch to Codeberg.** When it reaches Gitea, `beta.yaml` sees a
|
|
||||||
beta whose Codeberg pre-release doesn't carry its APK yet, runs the unit
|
|
||||||
tests, builds and signs the APK, creates the `vX.Y.Z-beta.1` tag + a Gitea
|
|
||||||
pre-release (with the R8 mapping) and publishes the **Codeberg
|
|
||||||
pre-release** with the APK + `.sha256`. If that publish fails part-way, the
|
|
||||||
next push of the branch redoes it.
|
|
||||||
4. **Next round:** bump to `-beta.2` (and its `versionCode`) and push again.
|
|
||||||
5. **Going stable:** set `versionName = "X.Y.Z"` and its `versionCode`
|
|
||||||
(`1.1.0` → `1010099`), then continue from step 2 of
|
|
||||||
[Cutting a release](#cutting-a-release).
|
|
||||||
|
|
||||||
Who gets a beta:
|
|
||||||
|
|
||||||
- **Obtainium** users only with *Include prereleases* switched on for the app.
|
|
||||||
That is how a tester opts in; everyone else stays on stable.
|
|
||||||
- **F-Droid** (self-hosted and official) never: `beta.yaml` doesn't touch the
|
|
||||||
self-hosted repo, and the official recipe's `UpdateCheckMode: Tags ^v[0-9.]+$`
|
|
||||||
ignores `-beta` tags.
|
|
||||||
- **Play** never.
|
|
||||||
|
|
||||||
Guards: a beta version can't reach `main` (CI fails the PR, and `release.yaml`'s
|
|
||||||
`detect` refuses one as a backstop), `beta.yaml` refuses a beta that isn't
|
|
||||||
newer than the latest stable release, and betas start at 1.1.0 (the 1.0.x codes have no
|
|
||||||
room below them).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What the pipeline does
|
## What the pipeline does
|
||||||
|
|
||||||
CI and release are split so a change is built once on its PR and only does
|
CI and release are split so a change is built once on its PR and only does
|
||||||
@@ -153,12 +94,10 @@ release work when a merge actually cuts a release:
|
|||||||
`values-*/strings.xml`, so Weblate PRs get fast feedback. Runs on every PR
|
`values-*/strings.xml`, so Weblate PRs get fast feedback. Runs on every PR
|
||||||
without a path filter, so the required `Translations / check` status is always
|
without a path filter, so the required `Translations / check` status is always
|
||||||
reported.
|
reported.
|
||||||
- **`renovate.yml`** (`.gitea/workflows/`, Mondays 05:00 UTC, on push to
|
- **`renovate.yml`** (`.gitea/workflows/`, Mondays 05:00 UTC plus
|
||||||
`main`, plus `workflow_dispatch`, **Gitea**) — the dependency sweep. Runs the
|
`workflow_dispatch`, **Gitea**) — the dependency sweep. Runs the pinned
|
||||||
pinned Renovate image as its job container and targets Codeberg's API. New
|
Renovate image as its job container and targets Codeberg's API; the cadence
|
||||||
PRs only open in the Monday `schedule` window in `renovate.json5` (keep it
|
lives here, not in `renovate.json5`, so the two don't double-gate.
|
||||||
bracketing the cron); the on-merge runs fall outside it and only rebase
|
|
||||||
existing PRs that the merge left conflicted.
|
|
||||||
- **`release.yaml`** (`.gitea/workflows/`, on push to `main` plus
|
- **`release.yaml`** (`.gitea/workflows/`, on push to `main` plus
|
||||||
`workflow_dispatch`, **Gitea**) — a cheap `detect` job reads `versionName` and
|
`workflow_dispatch`, **Gitea**) — a cheap `detect` job reads `versionName` and
|
||||||
checks **Codeberg** for a tag for it. Only when there isn't one does the
|
checks **Codeberg** for a tag for it. Only when there isn't one does the
|
||||||
@@ -171,12 +110,6 @@ release work when a merge actually cuts a release:
|
|||||||
**Codeberg** with the signed APK + a SHA-256 checksum, and finally builds the
|
**Codeberg** with the signed APK + a SHA-256 checksum, and finally builds the
|
||||||
App Bundle. Ordinary merges with no version bump fall through `detect` and do
|
App Bundle. Ordinary merges with no version bump fall through `detect` and do
|
||||||
nothing.
|
nothing.
|
||||||
- **`beta.yaml`** (`.gitea/workflows/`, on push to `release/**`, **Gitea**) —
|
|
||||||
the same `detect` idea for betas: only when the committed `versionName` is
|
|
||||||
`X.Y.Z-beta.N` and Codeberg has no tag for it does the `beta` job run: unit
|
|
||||||
tests, pin `versionCode`, build & sign the release APK with the **app key**,
|
|
||||||
create the tag + a Gitea pre-release with the R8 mapping, and publish the
|
|
||||||
**Codeberg pre-release** with the APK + SHA-256. No F-Droid, no Play.
|
|
||||||
- **`play` job** (same workflow, after `release`) — uploads the App Bundle and
|
- **`play` job** (same workflow, after `release`) — uploads the App Bundle and
|
||||||
every locale's "What's New" to Google Play. Runs last and separately so a Play
|
every locale's "What's New" to Google Play. Runs last and separately so a Play
|
||||||
rejection can't endanger a release that already shipped; skips cleanly until
|
rejection can't endanger a release that already shipped; skips cleanly until
|
||||||
@@ -190,8 +123,7 @@ Releases aren't git objects and don't sync with the push mirror in either
|
|||||||
direction, so the pipeline pushes the `vX.Y.Z` tag straight to Codeberg, creates
|
direction, so the pipeline pushes the `vX.Y.Z` tag straight to Codeberg, creates
|
||||||
the release over the Codeberg API, and attaches `agendula_v<version>.apk` + its
|
the release over the Codeberg API, and attaches `agendula_v<version>.apk` + its
|
||||||
`.sha256`. It's the same APK the F-Droid repo serves (same **app key**), so it
|
`.sha256`. It's the same APK the F-Droid repo serves (same **app key**), so it
|
||||||
adds no trust surface. The logic lives in `scripts/publish_codeberg_release.sh`,
|
adds no trust surface. It skips cleanly if `CODEBERG_RELEASE_TOKEN` is unset,
|
||||||
which `beta.yaml` uses too (with the pre-release flag set). It skips cleanly if `CODEBERG_RELEASE_TOKEN` is unset,
|
|
||||||
but it is **not** `continue-on-error`: through 0.2.1–0.3.2 this step reported
|
but it is **not** `continue-on-error`: through 0.2.1–0.3.2 this step reported
|
||||||
green while never once publishing, which is how a crash-fix release reached
|
green while never once publishing, which is how a crash-fix release reached
|
||||||
F-Droid but not the Codeberg/Obtainium users who needed it. A broken mirror
|
F-Droid but not the Codeberg/Obtainium users who needed it. A broken mirror
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ on 2026-09-24. Its CI rebuilt v1.0.0 and verified it against our published APK
|
|||||||
|
|
||||||
The file here is a copy of the submitted recipe. fdroiddata's copy is the one
|
The file here is a copy of the submitted recipe. fdroiddata's copy is the one
|
||||||
that counts; after the merge F-Droid picks up new `vX.Y.Z` tags on its own
|
that counts; after the merge F-Droid picks up new `vX.Y.Z` tags on its own
|
||||||
(`AutoUpdateMode`), so there is no per-release work there. Beta tags
|
(`AutoUpdateMode`), so there is no per-release work there. Only a change to the
|
||||||
(`vX.Y.Z-beta.N`) don't match `UpdateCheckMode: Tags ^v[0-9.]+$`, so betas stay
|
|
||||||
off the official repo; keep that pattern if the recipe ever changes. Only a change to the
|
|
||||||
recipe itself (a new submodule, a build flag) needs an MR, pinned to a full
|
recipe itself (a new submodule, a build flag) needs an MR, pinned to a full
|
||||||
commit hash, never a tag.
|
commit hash, never a tag.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Agendula gibt es jetzt bei Google Play. An der App hat sich seit 1.0.0 nichts geändert:
|
||||||
|
- CalDAV-Sync eingebaut: Nextcloud, Radicale, Baïkal und mehr.
|
||||||
|
- Agendula speichert deine Aufgaben selbst, keine andere App nötig.
|
||||||
|
- Wiederkehrende Aufgaben, mehrere Erinnerungen, Listen in der App,
|
||||||
|
iCalendar-Import und -Export, ein Widget und eine Schnelleinstellungs-Kachel.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Agendula is now on Google Play. Nothing changed in the app since 1.0.0:
|
||||||
|
- CalDAV sync built in: Nextcloud, Radicale, Baïkal and more.
|
||||||
|
- Agendula keeps your tasks itself, no other app needed.
|
||||||
|
- Repeating tasks, several reminders per task, lists managed in the app,
|
||||||
|
iCalendar import and export, a home-screen widget and a Quick Settings tile.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Agendula is now on Google Play. Nothing changed in the app since 1.0.0:
|
||||||
|
- CalDAV sync built in: Nextcloud, Radicale, Baïkal and more.
|
||||||
|
- Agendula keeps your tasks itself, no other app needed.
|
||||||
|
- Repeating tasks, several reminders per task, lists managed in the app,
|
||||||
|
iCalendar import and export, a home-screen widget and a Quick Settings tile.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
O Agendula agora está no Google Play. Nada mudou no app desde a 1.0.0:
|
||||||
|
- Sincronização CalDAV integrada: Nextcloud, Radicale, Baïkal e outros.
|
||||||
|
- O Agendula guarda suas tarefas sozinho, sem outro app.
|
||||||
|
- Tarefas recorrentes, vários lembretes por tarefa, listas no app, importação e
|
||||||
|
exportação iCalendar, widget e bloco de Configurações rápidas.
|
||||||
+1
-1
Submodule floret-kit updated: 8b33635a85...db4be68dad
@@ -1,13 +1,13 @@
|
|||||||
#This file is generated by updateDaemonJvm
|
#This file is generated by updateDaemonJvm
|
||||||
toolchainUrl.FREE_BSD.AARCH64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-aarch64-b1163.116.tar.gz
|
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||||
toolchainUrl.FREE_BSD.X86_64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-x64-b1163.116.tar.gz
|
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||||
toolchainUrl.LINUX.AARCH64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-aarch64-b1163.116.tar.gz
|
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||||
toolchainUrl.LINUX.X86_64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-x64-b1163.116.tar.gz
|
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||||
toolchainUrl.MAC_OS.AARCH64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-osx-aarch64-b1163.116.tar.gz
|
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/7083b89563e7ce20943037b8cd2b8cc2/redirect
|
||||||
toolchainUrl.MAC_OS.X86_64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-osx-x64-b1163.116.tar.gz
|
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/060bbb778a1f55ea705fdebd2ccfeab9/redirect
|
||||||
toolchainUrl.UNIX.AARCH64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-aarch64-b1163.116.tar.gz
|
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
|
||||||
toolchainUrl.UNIX.X86_64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-linux-x64-b1163.116.tar.gz
|
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
|
||||||
toolchainUrl.WINDOWS.AARCH64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-windows-aarch64-b1163.116.tar.gz
|
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d09679dc60fe5aa05ef7d03efdefac20/redirect
|
||||||
toolchainUrl.WINDOWS.X86_64=https\://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.11-windows-x64-b1163.116.tar.gz
|
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ed4e3bf2f5e7c5d9aabc4cbd8acd555e/redirect
|
||||||
toolchainVendor=JETBRAINS
|
toolchainVendor=JETBRAINS
|
||||||
toolchainVersion=21
|
toolchainVersion=21
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.4.1"
|
agp = "9.2.1"
|
||||||
kotlin = "2.4.20"
|
kotlin = "2.3.21"
|
||||||
ksp = "2.3.11"
|
ksp = "2.3.11"
|
||||||
hilt = "2.60.1"
|
hilt = "2.60.1"
|
||||||
coreKtx = "1.19.0"
|
coreKtx = "1.19.0"
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+2
-4
@@ -1,10 +1,8 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
|
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
retries=0
|
|
||||||
retryBackOffMs=500
|
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# gradlew start up script for POSIX generated by Gradle.
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
#
|
#
|
||||||
# Important for running:
|
# Important for running:
|
||||||
#
|
#
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
# bash, then to run this script, type that shell name before the whole
|
# bash, then to run this script, type that shell name before the whole
|
||||||
# command line, like:
|
# command line, like:
|
||||||
#
|
#
|
||||||
# ksh gradlew
|
# ksh Gradle
|
||||||
#
|
#
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -114,6 +114,7 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -171,6 +172,7 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
|||||||
Vendored
+34
-52
@@ -19,39 +19,12 @@
|
|||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem gradlew startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
@rem Set local scope for the variables with windows NT shell
|
||||||
setlocal EnableExtensions
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
@rem Catch executions from older scripts and ensure they exit cleanly.
|
|
||||||
@rem This can be removed once we can be reasonably confident that few people
|
|
||||||
@rem will be migrating directly to this new wrapper.
|
|
||||||
goto afterSafetyNet
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
goto exitWithErrorLevel
|
|
||||||
:afterSafetyNet
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@@ -72,14 +45,13 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
1>&2 echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
goto fail
|
||||||
goto exitWithErrorLevel
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -87,26 +59,36 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
1>&2 echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
goto fail
|
||||||
goto exitWithErrorLevel
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute gradlew
|
@rem Execute Gradle
|
||||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
@rem which allows us to clear the local environment before executing the java command
|
|
||||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
|
|
||||||
|
|
||||||
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
|
:end
|
||||||
:exitWithErrorLevel
|
@rem End local scope for the variables with windows NT shell
|
||||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
|
|||||||
+3
-10
@@ -52,16 +52,9 @@
|
|||||||
prConcurrentLimit: 5,
|
prConcurrentLimit: 5,
|
||||||
prHourlyLimit: 0,
|
prHourlyLimit: 0,
|
||||||
|
|
||||||
// New branches/PRs only in the Monday window (UTC), which brackets the weekly
|
// Cadence is owned by the Gitea Actions cron (.gitea/workflows/renovate.yml,
|
||||||
// cron in .gitea/workflows/renovate.yml with slack for a late start — keep
|
// Mondays) — no internal `schedule` here, so the two don't double-gate and
|
||||||
// the two in step, or the sweep lands outside the window and finds nothing.
|
// silently skip a run.
|
||||||
// The same workflow also runs on every push to main; those runs fall outside
|
|
||||||
// the window and, via the default updateNotScheduled: true, only maintain
|
|
||||||
// existing branches — chiefly rebasing PRs a merge just conflicted.
|
|
||||||
schedule: ["* 4-6 * * 1"],
|
|
||||||
// Rebase only on conflict, not every time main moves (automerge is off, so
|
|
||||||
// this matches "auto"; spelled out because the on-merge trigger relies on it).
|
|
||||||
rebaseWhen: "conflicted",
|
|
||||||
|
|
||||||
// Workflows are split by forge and neither directory is .github: CI lives in
|
// Workflows are split by forge and neither directory is .github: CI lives in
|
||||||
// .forgejo/workflows (Codeberg) and the release/renovate jobs in
|
// .forgejo/workflows (Codeberg) and the release/renovate jobs in
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ without setup.
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -121,11 +120,10 @@ def check_images(locale_dir):
|
|||||||
|
|
||||||
|
|
||||||
def version_code():
|
def version_code():
|
||||||
"""The committed version's code, or None for a beta (it ships no What's New)."""
|
gradle = (ROOT / "app/build.gradle.kts").read_text()
|
||||||
def info(key):
|
name = re.search(r'versionName\s*=\s*"([^"]+)"', gradle).group(1)
|
||||||
return subprocess.run(["bash", str(ROOT / "scripts/version_info.sh"), key],
|
parts = (name.split(".") + ["0", "0"])[:3]
|
||||||
check=True, capture_output=True, text=True).stdout.strip()
|
return int(parts[0]) * 10000 + int(parts[1]) * 100 + int(parts[2])
|
||||||
return None if info("channel") == "beta" else int(info("version_code"))
|
|
||||||
|
|
||||||
|
|
||||||
def shipped_languages():
|
def shipped_languages():
|
||||||
@@ -185,7 +183,7 @@ def main():
|
|||||||
err(images / "featureGraphic.png", "required for Play (1024x500, no alpha)")
|
err(images / "featureGraphic.png", "required for Play (1024x500, no alpha)")
|
||||||
if len(list((images / "phoneScreenshots").glob("*"))) < 2:
|
if len(list((images / "phoneScreenshots").glob("*"))) < 2:
|
||||||
err(images / "phoneScreenshots", "Play requires at least 2 phone screenshots")
|
err(images / "phoneScreenshots", "Play requires at least 2 phone screenshots")
|
||||||
for loc in REQUIRED_LOCALES if current is not None else []:
|
for loc in REQUIRED_LOCALES:
|
||||||
if not (META / loc / "changelogs" / f"{current}.txt").exists():
|
if not (META / loc / "changelogs" / f"{current}.txt").exists():
|
||||||
err(META / loc / "changelogs" / f"{current}.txt", "no What's New for this version")
|
err(META / loc / "changelogs" / f"{current}.txt", "no What's New for this version")
|
||||||
|
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Publishes TAG on Codeberg with the signed APK and its SHA-256 attached.
|
|
||||||
# Upserts, so re-runs are safe. A missing TOKEN skips a stable release but fails
|
|
||||||
# a pre-release, where Codeberg is the only channel.
|
|
||||||
#
|
|
||||||
# Env: TOKEN, API (.../api/v1/repos/<owner>/<repo>), TAG, SHA, PRERELEASE,
|
|
||||||
# NOTES_FILE, APK
|
|
||||||
set -euo pipefail
|
|
||||||
if [ -z "${TOKEN:-}" ]; then
|
|
||||||
if [ "${PRERELEASE:-}" = true ]; then
|
|
||||||
echo "CODEBERG_RELEASE_TOKEN not set — a pre-release has nowhere else to go." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "CODEBERG_RELEASE_TOKEN not set — skipping Codeberg publish."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
: "${API:?}" "${TAG:?}" "${SHA:?}" "${PRERELEASE:?}" "${NOTES_FILE:?}" "${APK:?}"
|
|
||||||
if [ ! -f "$APK" ]; then echo "No release APK at $APK." >&2; exit 1; fi
|
|
||||||
|
|
||||||
WORK=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$WORK"' EXIT
|
|
||||||
|
|
||||||
ASSET_APK="agendula_${TAG}.apk"
|
|
||||||
ASSET_SUM="${ASSET_APK}.sha256"
|
|
||||||
cp "$APK" "$WORK/$ASSET_APK"
|
|
||||||
( cd "$WORK" && sha256sum "$ASSET_APK" > "$ASSET_SUM" )
|
|
||||||
|
|
||||||
# Push the tag first and create the release without target_commitish: a POST
|
|
||||||
# naming a commit Codeberg hasn't received yet 500s.
|
|
||||||
HOST=${API#https://}; HOST=${HOST%%/*}
|
|
||||||
REPO=${API#*/repos/}
|
|
||||||
git tag -f "$TAG" "$SHA"
|
|
||||||
git push -f "https://${REPO%%/*}:${TOKEN}@${HOST}/${REPO}.git" "refs/tags/$TAG"
|
|
||||||
|
|
||||||
python3 - "$TAG" "$PRERELEASE" "$NOTES_FILE" <<'PY' > "$WORK/payload.json"
|
|
||||||
import json, sys
|
|
||||||
tag, pre, notes = sys.argv[1:4]
|
|
||||||
print(json.dumps({
|
|
||||||
"tag_name": tag,
|
|
||||||
"name": tag,
|
|
||||||
"body": open(notes).read(),
|
|
||||||
"draft": False,
|
|
||||||
"prerelease": pre == "true",
|
|
||||||
}))
|
|
||||||
PY
|
|
||||||
|
|
||||||
# Codeberg 500s on a freshly pushed tag for a few seconds, hence the retries.
|
|
||||||
ID=""
|
|
||||||
for attempt in 1 2 3 4 5 6; do
|
|
||||||
EXIST=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty' 2>/dev/null || true)
|
|
||||||
if [ -n "$EXIST" ]; then
|
|
||||||
CODE=$(curl -s -o /dev/null -w '%{http_code}' -X PATCH \
|
|
||||||
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d @"$WORK/payload.json" "$API/releases/$EXIST" || echo 000)
|
|
||||||
echo "release PATCH HTTP $CODE"
|
|
||||||
if [ "$CODE" = 200 ]; then ID="$EXIST"; break; fi
|
|
||||||
else
|
|
||||||
CODE=$(curl -s -o "$WORK/response.json" -w '%{http_code}' -X POST \
|
|
||||||
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d @"$WORK/payload.json" "$API/releases" || echo 000)
|
|
||||||
echo "release POST attempt $attempt HTTP $CODE"
|
|
||||||
ID=$(jq -r '.id // empty' "$WORK/response.json" 2>/dev/null || true)
|
|
||||||
[ -n "$ID" ] && break
|
|
||||||
fi
|
|
||||||
sleep $((attempt * 10))
|
|
||||||
done
|
|
||||||
if [ -z "$ID" ]; then echo "Could not create or update the Codeberg release." >&2; exit 1; fi
|
|
||||||
|
|
||||||
for A in "$ASSET_APK" "$ASSET_SUM"; do
|
|
||||||
OLD=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
|
|
||||||
| jq -r --arg n "$A" '.[]? | select(.name==$n) | .id' 2>/dev/null || true)
|
|
||||||
for O in $OLD; do
|
|
||||||
curl -s -o /dev/null -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$O" || true
|
|
||||||
done
|
|
||||||
CODE=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Authorization: token $TOKEN" \
|
|
||||||
-F "attachment=@$WORK/$A" "$API/releases/$ID/assets?name=$A" || echo 000)
|
|
||||||
echo "asset $A HTTP $CODE"
|
|
||||||
if [ "$CODE" != 201 ]; then echo "Uploading $A failed." >&2; exit 1; fi
|
|
||||||
done
|
|
||||||
echo "Published $TAG to Codeberg."
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Create (or update) the release for TAG on the Gitea build instance, the
|
|
||||||
# changelog of record there, and attach the R8 mapping. Shared by
|
|
||||||
# .gitea/workflows/release.yaml and beta.yaml.
|
|
||||||
#
|
|
||||||
# Creating it with target_commitish makes Gitea create the tag at SHA, so the
|
|
||||||
# tag only ever marks a fully-shipped version. Upsert, so re-runs are safe.
|
|
||||||
#
|
|
||||||
# Env:
|
|
||||||
# TOKEN, API Gitea token and .../api/v1/repos/<owner>/<repo>
|
|
||||||
# TAG, SHA vX.Y.Z[-beta.N] and the commit it marks
|
|
||||||
# PRERELEASE true | false
|
|
||||||
# NOTES_FILE release notes (scripts/release_notes.sh)
|
|
||||||
# MAPPING optional path to R8's mapping.txt
|
|
||||||
set -euo pipefail
|
|
||||||
: "${TOKEN:?}" "${API:?}" "${TAG:?}" "${SHA:?}" "${PRERELEASE:?}" "${NOTES_FILE:?}"
|
|
||||||
|
|
||||||
WORK=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$WORK"' EXIT
|
|
||||||
|
|
||||||
python3 - "$TAG" "$SHA" "$PRERELEASE" "$NOTES_FILE" <<'PY' > "$WORK/payload.json"
|
|
||||||
import json, sys
|
|
||||||
tag, sha, pre, notes = sys.argv[1:5]
|
|
||||||
print(json.dumps({
|
|
||||||
"tag_name": tag,
|
|
||||||
"target_commitish": sha,
|
|
||||||
"name": tag,
|
|
||||||
"body": open(notes).read(),
|
|
||||||
"draft": False,
|
|
||||||
"prerelease": pre == "true",
|
|
||||||
}))
|
|
||||||
PY
|
|
||||||
|
|
||||||
# PATCH if a release for the tag already exists, else POST a new one (which
|
|
||||||
# also creates the tag at target_commitish).
|
|
||||||
ID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" | jq -r '.id // empty' 2>/dev/null || true)
|
|
||||||
if [ -n "$ID" ]; then
|
|
||||||
CODE=$(curl -s -o "$WORK/response.json" -w '%{http_code}' -X PATCH \
|
|
||||||
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d @"$WORK/payload.json" "$API/releases/$ID")
|
|
||||||
OK=200
|
|
||||||
else
|
|
||||||
CODE=$(curl -s -o "$WORK/response.json" -w '%{http_code}' -X POST \
|
|
||||||
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
|
||||||
-d @"$WORK/payload.json" "$API/releases")
|
|
||||||
OK=201
|
|
||||||
fi
|
|
||||||
cat "$WORK/response.json"; echo
|
|
||||||
if [ "$CODE" != "$OK" ]; then
|
|
||||||
echo "Gitea release upsert failed with HTTP $CODE (expected $OK)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
ID=$(jq -r '.id' "$WORK/response.json")
|
|
||||||
echo "Created/updated Gitea release $TAG at $SHA"
|
|
||||||
|
|
||||||
# The R8 mapping keeps user crash stacktraces deobfuscatable. It's not an APK,
|
|
||||||
# so it fits the no-binaries rule for this release. Best-effort: never fail a
|
|
||||||
# release over it.
|
|
||||||
attach_mapping() {
|
|
||||||
local asset="mapping-${TAG#v}.txt.gz" old
|
|
||||||
gzip -c "$MAPPING" > "$WORK/$asset"
|
|
||||||
# Replace any prior asset of the same name (re-run safe).
|
|
||||||
old=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/$ID/assets" \
|
|
||||||
| jq -r --arg n "$asset" '.[] | select(.name==$n) | .id')
|
|
||||||
if [ -n "$old" ]; then
|
|
||||||
curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$ID/assets/$old" >/dev/null
|
|
||||||
fi
|
|
||||||
curl -s -X POST -H "Authorization: token $TOKEN" \
|
|
||||||
-F "attachment=@$WORK/$asset" \
|
|
||||||
"$API/releases/$ID/assets?name=$asset" -o /dev/null -w "asset $asset HTTP %{http_code}\n"
|
|
||||||
}
|
|
||||||
if [ -z "${MAPPING:-}" ] || [ ! -f "$MAPPING" ]; then
|
|
||||||
echo "No mapping.txt (R8 off?) — skipping."
|
|
||||||
else
|
|
||||||
attach_mapping || echo "warning: could not attach the R8 mapping to $TAG" >&2
|
|
||||||
fi
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Decides whether the committed version still needs publishing; prints cut=true|false.
|
|
||||||
#
|
|
||||||
# stable: no vX.Y.Z tag on Codeberg yet.
|
|
||||||
# beta: its Codeberg pre-release doesn't carry the APK yet, so a failed
|
|
||||||
# publish is redone by the next push. Refused unless it is newer than
|
|
||||||
# every shipped stable release.
|
|
||||||
#
|
|
||||||
# Fails on any lookup error rather than guessing.
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.." # repo root
|
|
||||||
|
|
||||||
REPO=${REPO:-jlmakiola/agendula}
|
|
||||||
VERSION=$(bash scripts/version_info.sh version)
|
|
||||||
BASE=$(bash scripts/version_info.sh base_version)
|
|
||||||
CHANNEL=$(bash scripts/version_info.sh channel)
|
|
||||||
|
|
||||||
# Exact tag names. The Codeberg git/refs/tags/<name> API matches by prefix.
|
|
||||||
TAGS=$(git ls-remote --tags --refs "https://codeberg.org/$REPO.git" | sed 's#.*refs/tags/##')
|
|
||||||
|
|
||||||
if [ "$CHANNEL" = stable ]; then
|
|
||||||
if grep -qxF "v$VERSION" <<<"$TAGS"; then
|
|
||||||
echo "Tag v$VERSION already exists on Codeberg — nothing to release." >&2
|
|
||||||
echo "cut=false"
|
|
||||||
else
|
|
||||||
echo "No tag for v$VERSION on Codeberg yet — cutting the release." >&2
|
|
||||||
echo "cut=true"
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
LATEST=$(grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' <<<"$TAGS" | sed 's/^v//' | sort -V | tail -n1 || true)
|
|
||||||
if [ -n "$LATEST" ] && [ "$(printf '%s\n%s\n' "$BASE" "$LATEST" | sort -V | tail -n1)" = "$LATEST" ]; then
|
|
||||||
echo "Beta $VERSION is not newer than the shipped stable $LATEST — bump the version." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ASSET="agendula_v${VERSION}.apk"
|
|
||||||
BODY=$(mktemp); trap 'rm -f "$BODY"' EXIT
|
|
||||||
STATUS=$(curl -s -o "$BODY" -w '%{http_code}' "https://codeberg.org/api/v1/repos/$REPO/releases/tags/v$VERSION" || echo 000)
|
|
||||||
case "$STATUS" in
|
|
||||||
200)
|
|
||||||
if grep -qF "\"name\":\"$ASSET\"" "$BODY"; then
|
|
||||||
echo "Pre-release v$VERSION already carries $ASSET — nothing to do." >&2
|
|
||||||
echo "cut=false"
|
|
||||||
else
|
|
||||||
echo "Pre-release v$VERSION exists without $ASSET — publishing it again." >&2
|
|
||||||
echo "cut=true"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
404)
|
|
||||||
echo "No pre-release for v$VERSION yet — cutting the beta." >&2
|
|
||||||
echo "cut=true"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Codeberg release lookup for v$VERSION returned HTTP $STATUS — refusing to guess." >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Print the forge release notes for VERSION: its `## [VERSION]` section of
|
|
||||||
# CHANGELOG.md. A beta (X.Y.Z-beta.N) normally has no section of its own, so it
|
|
||||||
# falls back to `## [Unreleased]`, i.e. what the release branch carries so far,
|
|
||||||
# under a line saying what a beta is.
|
|
||||||
#
|
|
||||||
# scripts/release_notes.sh 1.1.0 > release-notes.md
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.." # repo root
|
|
||||||
|
|
||||||
VERSION=${1:?usage: release_notes.sh VERSION}
|
|
||||||
|
|
||||||
section() {
|
|
||||||
awk -v ver="$1" '
|
|
||||||
$0 ~ "^## \\[" ver "\\]" { flag = 1; next }
|
|
||||||
/^## \[/ { flag = 0 }
|
|
||||||
flag' CHANGELOG.md | sed -e '/./,$!d'
|
|
||||||
}
|
|
||||||
|
|
||||||
NOTES=$(section "$VERSION")
|
|
||||||
if [[ "$VERSION" == *-beta.* ]]; then
|
|
||||||
[ -n "$NOTES" ] || NOTES=$(section Unreleased)
|
|
||||||
printf '%s\n\n' "**Beta of ${VERSION%%-*}, for testing.** It updates in place to later betas and to the stable release. Obtainium only offers betas with *Include prereleases* switched on; F-Droid and Play never get them."
|
|
||||||
fi
|
|
||||||
if [ -n "$NOTES" ]; then
|
|
||||||
printf '%s\n' "$NOTES"
|
|
||||||
else
|
|
||||||
echo "_No changelog entry for ${VERSION} — see CHANGELOG.md._"
|
|
||||||
fi
|
|
||||||
@@ -11,15 +11,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")/.." # repo root
|
cd "$(dirname "$0")/.." # repo root
|
||||||
|
|
||||||
VERSION=$(bash scripts/version_info.sh version)
|
VERSION=$(grep -oP 'versionName\s*=\s*"\K[^"]+' app/build.gradle.kts)
|
||||||
VERSION_CODE=$(bash scripts/version_info.sh version_code)
|
[ -n "$VERSION" ] || { echo "No versionName in app/build.gradle.kts" >&2; exit 1; }
|
||||||
|
MAJOR=${VERSION%%.*}; rest=${VERSION#*.}; MINOR=${rest%%.*}; PATCH=${rest##*.}
|
||||||
# Betas only ship to Codeberg, whose notes come from CHANGELOG.md. A What's New
|
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
|
||||||
# file for one would sit in the tree F-Droid and Play read, so none is wanted.
|
VERSION_CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
||||||
if [ "$(bash scripts/version_info.sh channel)" = beta ]; then
|
|
||||||
echo "Beta $VERSION: no store What's New (betas only ship to Codeberg)."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Play rejects a longer "What's New" and F-Droid truncates it in-client.
|
# Play rejects a longer "What's New" and F-Droid truncates it in-client.
|
||||||
MAX_CHARS=${MAX_CHARS:-500}
|
MAX_CHARS=${MAX_CHARS:-500}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# The one place that turns the committed versionName into a versionCode and a
|
|
||||||
# release channel. The release and beta pipelines, the changelog sync and the
|
|
||||||
# store-listing check all read it from here, so the scheme can't drift between
|
|
||||||
# them.
|
|
||||||
#
|
|
||||||
# versionName channel versionCode
|
|
||||||
# X.Y.Z (< 1.1.0) stable X*10000 + Y*100 + Z (legacy)
|
|
||||||
# X.Y.Z-beta.N beta X*1000000 + Y*10000 + Z*100 + N (N = 1..98)
|
|
||||||
# X.Y.Z (>= 1.1.0) stable X*1000000 + Y*10000 + Z*100 + 99
|
|
||||||
#
|
|
||||||
# A beta's code sits below its own stable release and above everything before
|
|
||||||
# it, so a beta install updates in place to the next beta and then to the
|
|
||||||
# stable version. Every 1.0.x keeps the legacy code it already shipped with;
|
|
||||||
# the first new-scheme code (1.1.0-beta.1 -> 1010001) is above all of them.
|
|
||||||
#
|
|
||||||
# scripts/version_info.sh print version, version_code, base_version,
|
|
||||||
# channel and prerelease as key=value lines
|
|
||||||
# (ready for $GITHUB_OUTPUT)
|
|
||||||
# scripts/version_info.sh <key> print just that value
|
|
||||||
# scripts/version_info.sh --check also fail unless the committed versionCode
|
|
||||||
# matches; the official F-Droid repo builds
|
|
||||||
# the tag as committed, so it must
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.." # repo root
|
|
||||||
|
|
||||||
GRADLE="app/build.gradle.kts"
|
|
||||||
NAME=$(grep -oP 'versionName\s*=\s*"\K[^"]+' "$GRADLE" || true)
|
|
||||||
COMMITTED=$(grep -oP 'versionCode\s*=\s*\K[0-9]+' "$GRADLE" || true)
|
|
||||||
[ -n "$NAME" ] || { echo "No versionName in $GRADLE" >&2; exit 1; }
|
|
||||||
|
|
||||||
if [[ ! "$NAME" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-beta\.([1-9][0-9]*))?$ ]]; then
|
|
||||||
echo "versionName '$NAME' is neither X.Y.Z nor X.Y.Z-beta.N" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
MAJOR=${BASH_REMATCH[1]}; MINOR=${BASH_REMATCH[2]}; PATCH=${BASH_REMATCH[3]}
|
|
||||||
BETA=${BASH_REMATCH[5]}
|
|
||||||
BASE="$MAJOR.$MINOR.$PATCH"
|
|
||||||
|
|
||||||
if [ "$MINOR" -gt 99 ] || [ "$PATCH" -gt 99 ]; then
|
|
||||||
echo "versionName '$NAME': MINOR and PATCH each have room for 0-99" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LEGACY=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
|
|
||||||
if [ -n "$BETA" ]; then
|
|
||||||
if [ "$LEGACY" -lt 10100 ]; then
|
|
||||||
echo "versionName '$NAME': betas start at 1.1.0; 1.0.x codes have no room for them" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ "$BETA" -lt 1 ] || [ "$BETA" -gt 98 ]; then
|
|
||||||
echo "versionName '$NAME': beta number must be 1-98 (99 is the stable release)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CHANNEL=beta
|
|
||||||
CODE=$(( MAJOR * 1000000 + MINOR * 10000 + PATCH * 100 + BETA ))
|
|
||||||
elif [ "$LEGACY" -lt 10100 ]; then
|
|
||||||
CHANNEL=stable
|
|
||||||
CODE=$LEGACY
|
|
||||||
else
|
|
||||||
CHANNEL=stable
|
|
||||||
CODE=$(( MAJOR * 1000000 + MINOR * 10000 + PATCH * 100 + 99 ))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Betas are always pre-releases; so was everything before 1.0.0.
|
|
||||||
if [ "$CHANNEL" = beta ] || [ "$MAJOR" = 0 ]; then PRERELEASE=true; else PRERELEASE=false; fi
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
"")
|
|
||||||
printf 'version=%s\nversion_code=%s\nbase_version=%s\nchannel=%s\nprerelease=%s\n' \
|
|
||||||
"$NAME" "$CODE" "$BASE" "$CHANNEL" "$PRERELEASE"
|
|
||||||
;;
|
|
||||||
--check)
|
|
||||||
if [ "$COMMITTED" != "$CODE" ]; then
|
|
||||||
echo "ERROR: $GRADLE has versionCode = ${COMMITTED:-<none>}, but versionName '$NAME' needs $CODE." >&2
|
|
||||||
echo "Set versionCode = $CODE (see docs/RELEASING.md for the scheme)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "OK: versionName $NAME -> versionCode $CODE ($CHANNEL)."
|
|
||||||
;;
|
|
||||||
version) echo "$NAME" ;;
|
|
||||||
version_code) echo "$CODE" ;;
|
|
||||||
base_version) echo "$BASE" ;;
|
|
||||||
channel) echo "$CHANNEL" ;;
|
|
||||||
prerelease) echo "$PRERELEASE" ;;
|
|
||||||
*) echo "Unknown key '$1'" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
Reference in New Issue
Block a user