diff --git a/docker/android/package.sh b/docker/android/package.sh index a90a69c..0839038 100755 --- a/docker/android/package.sh +++ b/docker/android/package.sh @@ -27,6 +27,26 @@ CACHE="${XDG_CACHE_HOME:-${HOME}/.cache}/darkroom-android" OUT="${CACHE}/target/apk" APK="${OUT}/darkroom.apk" +# The version, taken from the workspace rather than restated here. +# +# `AndroidManifest.xml` deliberately declares none: a manifest that states a +# version is a second place for one to be wrong, and it was — the APK reported +# `versionName=null` and `versionCode=0` on the device while the binary inside +# it knew perfectly well what it was. +# +# `versionCode` must be a single increasing integer, which a semantic version +# is not, so it is packed: MAJOR*10000 + MINOR*100 + PATCH. That keeps the +# ordering Android needs (it refuses to install an APK whose code is lower than +# the installed one) and stays readable — 0.4.0 is 400. It allows 99 minors and +# 99 patches per major, which is a limit worth knowing about and a long way off. +VERSION_NAME="$(sed -n 's/^version = "\(.*\)"$/\1/p' "${REPO}/Cargo.toml" | head -1)" +if [[ -z "${VERSION_NAME}" ]]; then + echo "error: no version in Cargo.toml" >&2 + exit 1 +fi +VERSION_CODE="$(awk -F. '{ print $1 * 10000 + $2 * 100 + $3 }' <<< "${VERSION_NAME}")" +echo "==> version ${VERSION_NAME} (code ${VERSION_CODE})" + INSTALL=0 [[ "${1:-}" == "--install" ]] && INSTALL=1 @@ -100,6 +120,8 @@ echo "==> packaging APK" -R "${OUT}/res.zip" \ --min-sdk-version 28 \ --target-sdk-version 36 \ + --version-name "'"${VERSION_NAME}"'" \ + --version-code "'"${VERSION_CODE}"'" \ -o "${OUT}/base.apk" \ --auto-add-overlay diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index 5b29c8f..df12363 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -4,7 +4,7 @@ # makes `makepkg -si` in this directory install what you are actually working # on. Swap `source` for a tagged tarball when there is something to release. pkgname=darkroom -pkgver=0.3.0 +pkgver=0.4.0 pkgrel=1 pkgdesc="Non-destructive RAW photo library and editor" arch=('x86_64') diff --git a/tools/set-version.sh b/tools/set-version.sh new file mode 100755 index 0000000..d1fc2ad --- /dev/null +++ b/tools/set-version.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Set the project's version in the few places that must state it, from one +# source, and optionally commit the result. +# +# tools/set-version.sh --from-tag # take it from the latest git tag +# tools/set-version.sh 0.5.0 # or state it +# tools/set-version.sh --from-tag --commit +# +# Why this exists: the workspace read 0.1.0 for two releases, so every binary +# reported a version two releases stale, and the APK reported none at all. A +# version that has to be edited in several files by hand is a version that will +# be wrong in at least one of them. +# +# The number of places is kept deliberately small. Only two files state the +# version; everything else derives it: +# +# Cargo.toml the source of truth for every crate in the workspace +# packaging/PKGBUILD pacman needs it before anything is built +# +# AndroidManifest.xml states NO version on purpose — `package.sh` passes +# it to `aapt2 link` from Cargo.toml, so the APK cannot +# drift from the binary inside it. +set -euo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${REPO}" + +COMMIT=0 +VERSION="" +for arg in "$@"; do + case "${arg}" in + --commit) COMMIT=1 ;; + --from-tag) VERSION="$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//')" ;; + -h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}"; exit 0 ;; + -*) echo "error: unknown flag ${arg}" >&2; exit 2 ;; + *) VERSION="${arg#v}" ;; + esac +done + +if [[ -z "${VERSION}" ]]; then + echo "error: no version given, and no tag to take one from" >&2 + echo "usage: tools/set-version.sh [--from-tag | ] [--commit]" >&2 + exit 2 +fi + +# Rejected rather than coerced. A version is a contract with whoever reads a +# bug report, and "0.4" or "v0.4.0-wip" silently becoming something else is +# worse than being told to type it again. +if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "error: '${VERSION}' is not MAJOR.MINOR.PATCH" >&2 + exit 2 +fi + +CURRENT="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -1)" +echo "==> ${CURRENT:-unknown} -> ${VERSION}" + +# 1. The workspace. Every member inherits this through `version.workspace`. +# Anchored to the [workspace.package] table so a dependency's version +# string cannot be hit by accident. +python3 - "${VERSION}" <<'PY' +import re, sys, pathlib +version = sys.argv[1] +p = pathlib.Path("Cargo.toml") +s = p.read_text() +m = re.search(r"(\[workspace\.package\][^\[]*?\nversion = \")([^\"]+)(\")", s, re.S) +if not m: + sys.exit("error: no version under [workspace.package] in Cargo.toml") +p.write_text(s[:m.start(2)] + version + s[m.end(2):]) +print(f" Cargo.toml {m.group(2)} -> {version}") +PY + +# 2. pacman. `pkgrel` returns to 1: it counts packaging revisions *of one +# version*, so carrying it across a version bump would claim this is the +# second package of a version nobody has packaged yet. +python3 - "${VERSION}" <<'PY' +import re, sys, pathlib +version = sys.argv[1] +p = pathlib.Path("packaging/PKGBUILD") +s = p.read_text() +old = re.search(r"^pkgver=(.*)$", s, re.M) +s = re.sub(r"^pkgver=.*$", f"pkgver={version}", s, count=1, flags=re.M) +s = re.sub(r"^pkgrel=.*$", "pkgrel=1", s, count=1, flags=re.M) +p.write_text(s) +print(f" packaging/PKGBUILD {old.group(1) if old else '?'} -> {version}") +PY + +# 3. Cargo.lock. The workspace members appear in it by version, so leaving it +# alone makes `--locked` builds fail — which is exactly what CI uses. +cargo update --workspace --offline >/dev/null 2>&1 \ + || cargo update --workspace >/dev/null 2>&1 \ + || echo " warning: could not refresh Cargo.lock; run 'cargo update --workspace'" >&2 +echo " Cargo.lock refreshed" + +echo "==> Android takes its version from Cargo.toml at link time; nothing to edit" + +if [[ "${COMMIT}" == "1" ]]; then + git add Cargo.toml Cargo.lock packaging/PKGBUILD + if git diff --cached --quiet; then + echo "==> nothing changed; no commit made" + else + git commit -q -m "Say which version this is: ${VERSION} + +Set by tools/set-version.sh, which is the only thing that should. The +workspace, the pacman package and — through Cargo.toml at link time — the +APK all state ${VERSION}, so a bug report naming a version names one commit. + +Co-Authored-By: Claude Opus 5 (1M context) " + echo "==> committed: $(git log --oneline -1)" + fi +else + echo "==> not committed (pass --commit to commit)" +fi