#!/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 "Release ${VERSION}" echo "==> committed: $(git log --oneline -1)" fi else echo "==> not committed (pass --commit to commit)" fi