build: stamp git-describe build version into every artifact

Add scripts/build-version.sh — a single source of truth for the
effective version string:

  * HEAD on tag vX.Y.Z          -> "X.Y.Z"
  * N commits past tag          -> "X.Y.Z-N-gSHA"
  * working tree dirty          -> additional "-dirty" suffix
  * no git / no matching tag    -> VERSION file fallback

Wired into every packaging path:

  * zygisk/build-zip.sh and portshide/build-zip.sh now stage a copy of
    module/ and sed-patch `version=` in the staging copy, so committed
    module.prop files stay at the last-released version.
  * kmod/build-zip.sh now builds into a staging copy too.
  * The kmod CI step runs build-version.sh and sed-patches module.prop
    before zipping (git installed in the DDK container).
  * lsposed/app/build.gradle.kts exec's build-version.sh at configure
    time and assigns the result to `versionName` (versionCode stays
    static, still bumped by release.py).

All actions/checkout@v6 gained `fetch-depth: 0` so git describe sees
the full tag history inside CI containers.

Result: a locally built or CI-from-main APK shows up in Android
Settings as e.g. `0.6.1-16-gf86e5e5`, and the zip inside carries the
same string in module.prop; the Magisk/KSU manager displays it in the
update list. Release tag builds are indistinguishable from before —
clean `X.Y.Z`. Diagnostic bug reports now carry the exact commit in
the App version line of device_info.txt.
This commit is contained in:
okhsunrog 2026-04-17 14:42:40 +03:00
parent f86e5e5a4c
commit 3fc735572a
10 changed files with 127 additions and 6 deletions

View file

@ -23,6 +23,7 @@ jobs:
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Mark workspace safe
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
@ -67,6 +68,8 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build kernel module
run: |
@ -84,9 +87,13 @@ jobs:
- name: Package KSU module zip
run: |
apt-get update -qq && apt-get install -y -qq zip git >/dev/null
git config --global --add safe.directory "$GITHUB_WORKSPACE"
BUILD_VERSION=$(./scripts/build-version.sh)
echo "Stamping kmod module.prop version=v${BUILD_VERSION}"
cp kmod/vpnhide_kmod.ko kmod/module/
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" kmod/module/module.prop
echo "updateJson=https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-kmod-${{ matrix.kmi }}.json" >> kmod/module/module.prop
apt-get update -qq && apt-get install -y -qq zip >/dev/null
(cd kmod/module && zip -qr "$GITHUB_WORKSPACE/vpnhide-kmod-${{ matrix.kmi }}.zip" .)
- name: Upload artifact
@ -108,6 +115,7 @@ jobs:
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Mark workspace safe
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
@ -144,6 +152,8 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Mark workspace safe
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
@ -178,6 +188,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Package ports module zip
run: |
@ -200,6 +212,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v8

View file

@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Save button next to Share for full-system logcat recordings — writes the capture via the Storage Access Framework to a user-picked location (previously only Share was available, which didn't always persist the file when sharing to a file manager).
### Changed
- Module zips and APK now include git provenance in the version string. Official release builds from a tag stay at the clean version (e.g. 0.6.2); intermediate builds from main or a feature branch show up as 0.6.2-5-gabc1234 (or -dirty) in the Magisk/KSU manager and Android Settings, so debug-log submissions identify the exact commit.
## v0.6.1
### Added

View file

@ -103,6 +103,17 @@ ktlint "lsposed/**/*.kt"
cd lsposed && ./gradlew :app:lint
```
## Build versions
Every module zip and the APK carry a version string derived from git at build time:
- on a release tag `vX.Y.Z``X.Y.Z`
- otherwise → `X.Y.Z-N-gSHA` (commits since the nearest tag + short hash, plus `-dirty` if the working tree has uncommitted changes)
So a locally-built dev APK shows up in Android Settings as e.g. `0.6.1-5-gabc1234-dirty`, and the same string lands in `module.prop` inside the zip. The committed `module.prop` files themselves stay at the last release number — the version is stamped into a staging copy per build.
See [releasing.md](releasing.md#build-versions) for details.
## More docs
- [releasing.md](releasing.md) — version bump, tag, release flow

View file

@ -4,7 +4,7 @@
- `VERSION` file = **the last released version** on `main`. It is only modified by `release.py`.
- `changelog.json.unreleased` = work in progress. Entries accumulate here via `./scripts/changelog.py` during normal development. See [changelog.md](changelog.md).
- Intermediate builds (main, feature branches, local) get a version string like `0.6.1-5-gabc1234` derived from `git describe` — propagated into `module.prop` / APK `versionName` at build time. (This is Phase 2 — tracked separately.)
- Intermediate builds (main, feature branches, local) get a version string derived from `git describe` — propagated into `module.prop` / APK `versionName` at build time. See [build versions](#build-versions) below.
## Cutting a release
@ -46,3 +46,22 @@ Update-json **must** be committed *after* the GitHub release is live. Magisk and
- `versionCode` is derived automatically by `release.py` as `major*10000 + minor*100 + patch` (e.g. `0.6.2``602`).
- If `unreleased` has no entries when you run `release.py`, it warns but proceeds — useful for version-only bumps.
- `release.py` refuses to release a version that already exists in `history[]`.
## Build versions
Every packaging step runs `./scripts/build-version.sh` to compute the version string stamped into the artifact:
- **On a release tag `vX.Y.Z`:** `X.Y.Z`
- **N commits after the nearest tag:** `X.Y.Z-N-gSHA` (the git describe format)
- **Working tree dirty:** additional `-dirty` suffix
- **No git / no tags:** falls back to the `VERSION` file
This string goes into:
- `module.prop` `version=...` (visible in the Magisk/KSU manager app)
- APK `versionName` (visible in Android Settings → Apps, diagnostic debug zip, `BuildConfig.VERSION_NAME`)
- Inside the zip filenames (only for release tags; dev artifacts in CI keep a stable name)
The committed `module.prop` files are **not** modified — `build-zip.sh` stages a copy, patches the version there, and zips. `lsposed/app/build.gradle.kts` evaluates `build-version.sh` at configure time and sets `versionName` dynamically.
`versionCode` stays at the value baked in by the last `release.py` run (monotonically increasing integer required by Android/Magisk).

View file

@ -9,11 +9,22 @@ if [ ! -f vpnhide_kmod.ko ] || [ vpnhide_kmod.c -nt vpnhide_kmod.ko ]; then
make
fi
cp vpnhide_kmod.ko module/vpnhide_kmod.ko
# Assemble the module staging directory so the committed module.prop
# stays at its release version while the zip carries the actual build
# version (git describe).
STAGING="module-staging"
rm -rf "$STAGING"
cp -a module "$STAGING"
cp vpnhide_kmod.ko "$STAGING/vpnhide_kmod.ko"
BUILD_VERSION="$(../scripts/build-version.sh)"
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
echo "Stamped module.prop version=v${BUILD_VERSION}"
OUT="vpnhide-kmod.zip"
rm -f "$OUT"
(cd module && zip -qr "../$OUT" .)
(cd "$STAGING" && zip -qr "../$OUT" .)
rm -rf "$STAGING"
echo
echo "Built: $OUT"

View file

@ -11,12 +11,27 @@ android {
namespace = "dev.okhsunrog.vpnhide"
compileSdk = 35
// Effective build version from ../scripts/build-version.sh:
// release tag -> "0.6.2"
// dev build -> "0.6.1-5-gabc1234" (+"-dirty" if uncommitted)
// no git -> VERSION file
val buildVersion: String =
providers
.exec {
commandLine(
"bash",
rootProject.projectDir.parentFile.resolve("scripts/build-version.sh").absolutePath,
)
}.standardOutput.asText
.get()
.trim()
defaultConfig {
applicationId = "dev.okhsunrog.vpnhide"
minSdk = 29
targetSdk = 35
versionCode = 601
versionName = "0.6.1"
versionName = buildVersion
ndk {
abiFilters += listOf("arm64-v8a")

View file

@ -9,6 +9,15 @@
"ru": "Кнопка «Сохранить» рядом с «Поделиться» для записи полного системного logcat — сохраняет файл через Storage Access Framework в выбранное место (раньше была только «Поделиться», и через диалог «Сохранить в файлы» файл не всегда сохранялся)."
}
]
},
{
"type": "changed",
"items": [
{
"en": "Module zips and APK now include git provenance in the version string. Official release builds from a tag stay at the clean version (e.g. 0.6.2); intermediate builds from main or a feature branch show up as 0.6.2-5-gabc1234 (or -dirty) in the Magisk/KSU manager and Android Settings, so debug-log submissions identify the exact commit.",
"ru": "Zip-модули и APK теперь включают git-провенанс в строку версии. Официальные релизные сборки с тега остаются с чистой версией (например, 0.6.2); промежуточные сборки с main или feature-ветки отображаются как 0.6.2-5-gabc1234 (или -dirty) в Magisk/KSU-менеджере и настройках Android — по логам сразу видно, какой именно коммит у пользователя."
}
]
}
]
},

View file

@ -3,9 +3,21 @@ set -euo pipefail
cd "$(dirname "$0")"
# Assemble the module staging directory so the committed module.prop
# stays at its release version while the zip carries the actual build
# version (git describe).
STAGING="module-staging"
rm -rf "$STAGING"
cp -a module "$STAGING"
BUILD_VERSION="$(../scripts/build-version.sh)"
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
echo "Stamped module.prop version=v${BUILD_VERSION}"
OUT="vpnhide-ports.zip"
rm -f "$OUT"
(cd module && zip -qr "../$OUT" .)
(cd "$STAGING" && zip -qr "../$OUT" .)
rm -rf "$STAGING"
echo
echo "Built: $OUT"

20
scripts/build-version.sh Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Print the effective build version for vpnhide artifacts.
#
# - HEAD on a tag vX.Y.Z -> "X.Y.Z" (release build)
# - N commits after tag vX.Y.Z -> "X.Y.Z-N-gSHA" (dev build)
# - working tree dirty -> additional "-dirty" suffix
# - no git / no matching tag -> falls back to VERSION file
#
# Used by every packaging step (module.prop, APK versionName, CI artifact
# names) so dev builds are unambiguously identifiable at a glance.
set -euo pipefail
cd "$(dirname "$0")/.."
if git rev-parse --git-dir >/dev/null 2>&1 \
&& raw=$(git describe --tags --match 'v*' --dirty 2>/dev/null); then
echo "${raw#v}"
else
cat VERSION
fi

View file

@ -40,6 +40,13 @@ cp -a module "$STAGING"
mkdir -p "$STAGING/zygisk"
cp "$SO_SRC" "$STAGING/zygisk/arm64-v8a.so"
# Stamp the effective build version into the staging module.prop without
# touching the committed file. On a release tag this matches VERSION; on
# any other commit the git suffix makes dev builds identifiable.
BUILD_VERSION="$(../scripts/build-version.sh)"
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
echo "Stamped module.prop version=v${BUILD_VERSION}"
# Zip it
OUT_ZIP="target/vpnhide-zygisk.zip"
rm -f "$OUT_ZIP"