Skip to content

Commit 11e274f

Browse files
authored
✨ feat: static versioning via gradle.properties (#6)
* 🚧 chore: seed gradle.properties with current version values * ✨ feat: read app version from gradle.properties instead of git describe * 🔥 chore: remove buildSrc/ — no longer needed with static versioning * 👷 ci: bump+tag gradle.properties on main, read static on PR * 👷 ci: drop QUIRE_VERSION_FALLBACK from release job, checkout the freshly-pushed tag * 👷 ci(codeql): drop QUIRE_VERSION_FALLBACK and fetch-tags * 📝 docs: rewrite release.md for static gradle.properties versioning
1 parent a062764 commit 11e274f

8 files changed

Lines changed: 65 additions & 194 deletions

File tree

.github/workflows/android-ci.yaml

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ concurrency:
3737
jobs:
3838
build:
3939
runs-on: ubuntu-latest
40+
permissions:
41+
contents: write # to push the version-bump commit and tag
4042
outputs:
4143
version: ${{ steps.version.outputs.version }}
4244
steps:
@@ -54,23 +56,38 @@ jobs:
5456
- uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
5557
- id: version
5658
name: Compute version
59+
env:
60+
# Use the GitHub-provided token to push the bump commit and tag.
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5762
run: |
5863
if [ "$GITHUB_REF" = "refs/heads/main" ]; then
59-
# On main: bump CalVer (date + CI run number) and tag the HEAD
60-
# locally so Gradle's git-describe finds an exact-match tag.
61-
# The release job pushes this tag when it creates the Release.
64+
# On main: compute the next CalVer from date + run number,
65+
# write it into gradle.properties, commit + tag, then push
66+
# both before Gradle runs. The build then reads the bumped
67+
# values directly from gradle.properties.
6268
BUILD_DATE=$(date -u +%Y-%m-%d)
63-
VERSION="${BUILD_DATE//-/.}.${GITHUB_RUN_NUMBER}"
64-
git tag "v$VERSION"
69+
NEW_NAME="${BUILD_DATE//-/.}.${GITHUB_RUN_NUMBER}"
70+
YY=$(date -u +%y)
71+
MM=$(date -u +%m)
72+
DD=$(date -u +%d)
73+
NEW_CODE=$(( (10#$YY * 10000 + 10#$MM * 100 + 10#$DD) * 100 + GITHUB_RUN_NUMBER % 100 ))
74+
75+
sed -i "s/^VERSION_NAME=.*/VERSION_NAME=$NEW_NAME/" gradle.properties
76+
sed -i "s/^VERSION_CODE=.*/VERSION_CODE=$NEW_CODE/" gradle.properties
77+
78+
git config user.name "github-actions[bot]"
79+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
80+
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
81+
git add gradle.properties
82+
git commit -m ":bookmark: chore: release v$NEW_NAME"
83+
git tag "v$NEW_NAME"
84+
git push origin "HEAD:$GITHUB_REF" "v$NEW_NAME"
85+
86+
VERSION="$NEW_NAME"
6587
else
66-
# On PRs / feature branches: derive from existing tag history.
67-
# Post-tag dev format is fine for non-release artifact names.
68-
if ! git describe --tags --match 'v*' --abbrev=0 >/dev/null 2>&1; then
69-
echo "::error::No matching v* tag in history. Build will fail."
70-
git tag -l 'v*' | head
71-
exit 1
72-
fi
73-
VERSION=$(git describe --tags --match 'v*' --always | sed 's/^v//')
88+
# On PRs / feature branches: just read the static value from
89+
# gradle.properties — no bump, no tag, no commit.
90+
VERSION=$(grep '^VERSION_NAME=' gradle.properties | cut -d= -f2)
7491
fi
7592
echo "VERSION_NAME=$VERSION" >> "$GITHUB_ENV"
7693
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
@@ -102,6 +119,7 @@ jobs:
102119
steps:
103120
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
104121
with:
122+
ref: v${{ needs.build.outputs.version }}
105123
fetch-depth: 0
106124
fetch-tags: true
107125
- uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
@@ -119,11 +137,6 @@ jobs:
119137
KEYSTORE_B64: ${{ secrets.QUIRE_RELEASE_KEYSTORE_B64 }}
120138
- name: Assemble release APK
121139
env:
122-
# The build job created the tag locally on its runner so Gradle's
123-
# git-describe found an exact match. Each release-job runner gets
124-
# a fresh checkout with no local tag, so feed the same CalVer in
125-
# via the parser's fallback path.
126-
QUIRE_VERSION_FALLBACK: ${{ needs.build.outputs.version }}
127140
QUIRE_RELEASE_KEYSTORE: ${{ secrets.QUIRE_RELEASE_KEYSTORE_B64 != '' && format('{0}/release.keystore', runner.temp) || '' }}
128141
QUIRE_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.QUIRE_RELEASE_KEYSTORE_PASSWORD }}
129142
QUIRE_RELEASE_KEY_ALIAS: ${{ secrets.QUIRE_RELEASE_KEY_ALIAS }}

.github/workflows/codeql.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ jobs:
3232
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
3333
with:
3434
fetch-depth: 0
35-
fetch-tags: true
3635
- name: Initialize CodeQL
3736
uses: github/codeql-action/init@0daab03d71ff584ef619d027a3fd9146679c5d84 # v3
3837
with:
@@ -47,11 +46,6 @@ jobs:
4746
with:
4847
packages: "platform-tools platforms;android-34 build-tools;34.0.0"
4948
- if: matrix.language == 'java-kotlin'
50-
env:
51-
# CodeQL doesn't need a meaningful version — fall back to a fixed
52-
# placeholder so the version derivation in app/build.gradle.kts
53-
# succeeds even when no v* tag is reachable from this commit.
54-
QUIRE_VERSION_FALLBACK: 2026.05.08.0
5549
run: ./gradlew assembleDebug -x test --stacktrace
5650
- if: matrix.language == 'python'
5751
uses: github/codeql-action/autobuild@0daab03d71ff584ef619d027a3fd9146679c5d84 # v3

app/build.gradle.kts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,10 @@ plugins {
55
alias(libs.plugins.aboutlibraries)
66
}
77

8-
// Tag-driven CalVer. The build reads `git describe --tags --match 'v*'`
9-
// and parses it via buildSrc/Version.kt. Set QUIRE_VERSION_FALLBACK
10-
// (e.g. "2026.05.08.29") if building from a shallow clone with no tags.
11-
fun gitDescribe(): String =
12-
try {
13-
val process = ProcessBuilder("git", "describe", "--tags", "--match", "v*", "--always")
14-
.directory(rootDir)
15-
.redirectErrorStream(true)
16-
.start()
17-
val output = process.inputStream.bufferedReader().readText().trim()
18-
if (process.waitFor() == 0) output else ""
19-
} catch (_: Exception) {
20-
""
21-
}
22-
23-
val versionInfo = Version.fromGitDescribe(
24-
output = gitDescribe(),
25-
fallback = System.getenv("QUIRE_VERSION_FALLBACK")
26-
)
8+
// Static version. Source of truth: gradle.properties (VERSION_NAME, VERSION_CODE).
9+
// CI bumps these on every push to main; local devs see the most recent release.
10+
val appVersionName: String = project.property("VERSION_NAME") as String
11+
val appVersionCode: Int = (project.property("VERSION_CODE") as String).toInt()
2712

2813
// Drop the build timestamp from the AboutLibraries-generated license JSON
2914
// so the resource is byte-identical across rebuilds (matters for F-Droid's
@@ -40,8 +25,8 @@ android {
4025
applicationId = "io.theficos.quire"
4126
minSdk = 26
4227
targetSdk = 34
43-
versionCode = versionInfo.code
44-
versionName = versionInfo.name
28+
versionCode = appVersionCode
29+
versionName = appVersionName
4530
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
4631
}
4732
compileOptions {

buildSrc/build.gradle.kts

Lines changed: 0 additions & 17 deletions
This file was deleted.

buildSrc/src/main/kotlin/Version.kt

Lines changed: 0 additions & 43 deletions
This file was deleted.

buildSrc/src/test/kotlin/VersionTest.kt

Lines changed: 0 additions & 63 deletions
This file was deleted.

docs/release.md

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# Release process
22

3-
Tag-driven. The `build` job in `android-ci.yaml` already pushes a
4-
`vYYYY.MM.DD.<run>` tag on every push to `main`. The tag push fires the
5-
workflow again, and the `release` job builds and signs a release APK
6-
and attaches it to a GitHub Release.
3+
Push-driven. Every push to `main` triggers `android-ci.yaml`. The
4+
`build` job bumps `VERSION_NAME` / `VERSION_CODE` in `gradle.properties`
5+
to the next CalVer (`YYYY.MM.DD.<run>` / `yyMMdd*100 + run%100`),
6+
commits with a `[bot]` author, tags `vYYYY.MM.DD.<run>`, and pushes
7+
both before Gradle runs. The `release` job then builds and signs a
8+
release APK from the freshly-pushed tag and attaches it to a GitHub
9+
Release.
710

811
## One-time keystore setup
912

@@ -35,18 +38,21 @@ testers but should not be your `latest` release.
3538

3639
## Cutting a release
3740

38-
The `build` job pushes `vYYYY.MM.DD.<run>` tags on every push to
39-
`main`. The tag-trigger run does the rest. Nothing manual needed.
41+
Land a commit on `main`. The `build` job's *Compute version* step picks
42+
the next CalVer, writes it into `gradle.properties`, commits + tags +
43+
pushes, then builds. Nothing manual needed.
4044

41-
To cut an out-of-band release, push a tag manually:
42-
43-
```sh
44-
git tag v2026.05.07.0
45-
git push origin v2026.05.07.0
46-
```
45+
To cut an out-of-band release, push a no-op commit (e.g. `git commit
46+
--allow-empty -m ":bookmark: chore: trigger release"` followed by `git
47+
push`). Manually-pushed tags are not used by this workflow.
4748

4849
## Local release builds
4950

51+
`gradle.properties` carries the version (`VERSION_NAME` and
52+
`VERSION_CODE`). Whatever's committed at HEAD is what the local build
53+
reports — if you want a specific version locally, edit those values
54+
before running.
55+
5056
```sh
5157
export QUIRE_RELEASE_KEYSTORE=/abs/path/to/quire-release.keystore
5258
export QUIRE_RELEASE_KEYSTORE_PASSWORD=...
@@ -98,17 +104,8 @@ it reports differences in `classes*.dex`, the build is non-reproducible
98104
— check JDK version, AGP version, and `gradle.properties` flags in
99105
the fdroidserver VM vs the CI runner.
100106

101-
If `git describe` returns nothing inside the fdroidserver VM (it does
102-
a non-shallow clone, but on rare runs tags might not propagate),
103-
set `QUIRE_VERSION_FALLBACK` in the recipe's `Builds:` block:
104-
105-
```yaml
106-
Builds:
107-
- versionName: 2026.05.08.30
108-
versionCode: 26050830
109-
commit: v2026.05.08.30
110-
subdir: app
111-
gradle: [ yes ]
112-
env:
113-
QUIRE_VERSION_FALLBACK: 2026.05.08.30
114-
```
107+
The version values come from `gradle.properties` (`VERSION_NAME` and
108+
`VERSION_CODE`), which CI bumps on every push to `main` before the
109+
build runs. fdroidserver reads them via the recipe's `UpdateCheckData`
110+
line, so each tag's APK metadata is statically derivable from source
111+
without running Gradle.

gradle.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ org.gradle.caching=true
44
android.useAndroidX=true
55
android.nonTransitiveRClass=true
66
kotlin.code.style=official
7+
8+
# App version. Bumped by CI on every push to main; locally these are the
9+
# values of the most recent release tag.
10+
VERSION_NAME=2026.05.08.43
11+
VERSION_CODE=26050843

0 commit comments

Comments
 (0)