taler-deployment

Deployment scripts and configuration files
Log | Files | Refs | README

commit 1d543c5286cc8832946571c6f7490f16833c4cc4
parent b2cd3e535234df4d9e7642509ad843223a5b0aeb
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 27 Jul 2026 16:38:19 +0200

packaging/ng: add "upgrade" subcommand, fix tag to debian version mapping

Diffstat:
Mpackaging/ng/README.md | 97++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpackaging/ng/buildscripts/generic | 6+++---
Mpackaging/ng/taler-pkg | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 232 insertions(+), 6 deletions(-)

diff --git a/packaging/ng/README.md b/packaging/ng/README.md @@ -53,11 +53,76 @@ apt-get install qemu-user-static binfmt-support Also contains a `Packages.gz` metadata file generated by `dpkg-scanpackages` so that this folder can be directly consumed as a trusted package source. +* `buildconfig/*`: Per-component configuration, see "Versions" below. * `buildscripts/*`: Build scripts used during the package build steps. * `distros/*`: Files for building for a specific distro. * `server-side/*.sh`: Scripts that must be installed in taler-packaing@taler.net:$HOME +## Versions + +Each component is configured by these files in `buildconfig/`: + +* `$component.giturl`: the upstream repository. +* `$component.tag`: the git tag that is built. It determines the version of the +resulting debian package. +* `$component.debpath`: the directory within the repository that contains the +`debian/` folder, relative to the repository root. Optional, defaults to the +repository root itself. + +`debpath` exists so that one repository can provide several packages that are +built independently. Their tag files are separate, but the tags all come from +the same repository, so `upgrade` moves all of them to the same tag. This +affects every component built from `taler-typescript-core` +(`taler-wallet-cli`, `taler-harness` and the `*-webui` packages), each of which +has a `packages/$component` debpath. + +Supported tag syntax (see `buildscripts/generic`): + +* `v$maj.$min.$patch`, e.g. `v1.2.3`: release version. +* `v$maj.$min.$patch-dev.$n`, e.g. `v1.2.3-dev.4`: dev version. +* `deb-v$maj.$min.$patch-$revision`, e.g. `deb-v1.2.3-1`: release version with a +debian revision, for rebuilds that only change `debian/`. + +Debian revisions of dev versions are *not* supported. Other tag formats occur +in some of the repositories (`debian-1.2.3`, `v1.2.3-debian-1`, `v1.2.3a`, ...) +and are ignored by `taler-pkg`. + +Tags are ordered by major, minor and patch first. Within one release, dev +versions sort before the release and debian revisions after it: + +``` +v1.2.3-dev.1 < v1.2.3-dev.2 < v1.2.3 < deb-v1.2.3-1 < v1.2.4-dev.1 +``` + +### From git tag to debian version + +`buildscripts/generic` derives the debian version of a package from the tag in +two steps. `get_tag_debver` strips the `v` / `deb-v` prefix and rewrites +`-dev.$n` into `~dev$n`, which sorts before the corresponding release. +`make_codename_version` then appends the distro codename. If the version has no +debian revision at that point, `-0` is inserted first: a plain `1.2.3+trixie` +would be *newer* than `1.2.3`, and `1.2.3~trixie` would be older than any real +`1.2.3-$revision`, so both would break version requirements in dependencies. + +Examples, for codename `trixie`: + +``` +v1.2.3 => 1.2.3-0+trixie +v1.2.3-dev.4 => 1.2.3~dev4-0+trixie +v1.2.3-dev.10 => 1.2.3~dev10-0+trixie +deb-v1.2.3 => 1.2.3-0+trixie +deb-v1.2.3-1 => 1.2.3-1+trixie +``` + +The resulting versions sort the same way as the tags they come from, so bumping +a tag file always produces a package that apt considers newer: + +``` +1.2.3~dev1-0+trixie < 1.2.3~dev10-0+trixie < 1.2.3-0+trixie < 1.2.3-1+trixie +``` + + ## Recipes ### Building for a distribution @@ -66,16 +131,42 @@ so that this folder can be directly consumed as a trusted package source. ./taler-pkg build $DISTRO ``` -### Updating component versions +### Bumping component versions ``` -# will output current vs latest version +# Show current vs. latest version ./taler-pkg show-latest -# Update component version +# Show which tag files would change +./taler-pkg upgrade --dry + +# Bump all components to the latest release tag +./taler-pkg upgrade + +# Bump only some components +./taler-pkg upgrade gnunet taler-exchange + +# Also consider dev tags +./taler-pkg upgrade --dev + +# Set a version manually echo $desired_version > buildconfig/$component.tag ``` +`upgrade` looks at every `buildconfig/*.tag` file, queries the corresponding +repository for its tags and writes back the newest one. Without `--dev`, only +release and `deb-v` tags are considered, with `--dev` also dev tags. + +Components are only bumped forwards. A tag file that already points to a +version newer than the newest candidate is left alone, which is what happens to +components pinned to a dev tag when running without `--dev`. Tag files with +unsupported syntax and components without a `.giturl` are also left alone. + +Components that share a repository, i.e. those distinguished by a `debpath`, +always end up on the same tag. + +`upgrade` only changes tag files; run `./taler-pkg build $DISTRO` afterwards. + ### Forcing a rebuild ``` diff --git a/packaging/ng/buildscripts/generic b/packaging/ng/buildscripts/generic @@ -40,14 +40,14 @@ def get_tag_debver(tag): if d < 0: return tag[1:] else: - return tag[1:d] + "~dev" + tag[d + len(devsuff)] + return tag[1:d] + "~dev" + tag[d + len(devsuff):] if tag.startswith("deb-v"): tag = tag[5:] if "-" in tag: a, b = tag.split("-") - return a + ":" + b + return a + "-" + b return tag - raise Error("unexpected tag format") + raise ValueError(f"unexpected tag format: {tag}") diff --git a/packaging/ng/taler-pkg b/packaging/ng/taler-pkg @@ -8,6 +8,7 @@ import argparse import subprocess import platform import os +import re import sys from pathlib import Path @@ -520,6 +521,119 @@ def print_latest(cfg): check_version(name, giturl) +# Tag syntax variants supported by buildscripts/generic: +# v$maj.$min.$patch => release version +# v$maj.$min.$patch-dev.$n => dev version +# deb-v$maj.$min.$patch-$revision => release version with debian revision +# Debian revisions of dev versions are *not* supported +tag_re_release = re.compile(r"v(\d+)\.(\d+)\.(\d+)") +tag_re_dev = re.compile(r"v(\d+)\.(\d+)\.(\d+)-dev\.(\d+)") +tag_re_deb = re.compile(r"deb-v(\d+)\.(\d+)\.(\d+)(?:-(\d+))?") + + +def tag_sortkey(tag): + """Get a sort key for a tag, or None if the tag syntax isn't supported. + + Dev versions sort before the corresponding release version, debian + revisions sort after it. + """ + m = tag_re_release.fullmatch(tag) + if m: + return (int(m.group(1)), int(m.group(2)), int(m.group(3)), 1, 0) + m = tag_re_dev.fullmatch(tag) + if m: + return (int(m.group(1)), int(m.group(2)), int(m.group(3)), 0, int(m.group(4))) + m = tag_re_deb.fullmatch(tag) + if m: + rev = m.group(4) + return (int(m.group(1)), int(m.group(2)), int(m.group(3)), 1, int(rev or 0)) + return None + + +def list_remote_tags(url): + """Get all tags from the git repo""" + cmd = ["git", "ls-remote", "--exit-code", "--refs", "--tags", url] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + tags = [] + for line in result.stdout.strip().split("\n"): + parts = line.split() + if len(parts) < 2: + continue + # refs/tags/v1.0.0 -> v1.0.0 + tags.append(parts[1].split("/")[-1]) + return tags + + +def latest_tag(tags, dev): + """Find the newest supported tag, only considering dev tags if dev is set""" + best = None + bestkey = None + # Iterate in sorted order, so that the result doesn't depend on + # the order in which the remote lists its refs. + for tag in sorted(tags): + if not dev and tag_re_dev.fullmatch(tag): + continue + key = tag_sortkey(tag) + if key is None: + continue + if bestkey is None or key > bestkey: + best = tag + bestkey = key + return best + + +def upgrade(cfg): + """Upgrade tag files in buildconfig to the latest upstream tag""" + names = cfg.components + if not names: + names = sorted(p.stem for p in Path("buildconfig").glob("*.tag")) + # Multiple components can share a repo, only ask each remote once. + remote_tags = {} + upgraded = [] + for name in names: + tag_file = Path("buildconfig") / f"{name}.tag" + url_file = Path("buildconfig") / f"{name}.giturl" + if not url_file.exists(): + print(f"[?] {name} has no giturl, skipping", file=sys.stderr) + continue + giturl = url_file.read_text().strip() + if giturl not in remote_tags: + remote_tags[giturl] = list_remote_tags(giturl) + latest = latest_tag(remote_tags[giturl], cfg.dev) + if latest is None: + print(f"[?] {name} has no usable tag in {giturl}, skipping", file=sys.stderr) + continue + curr = None + if tag_file.exists(): + curr = tag_file.read_text().strip() + currkey = tag_sortkey(curr) + if currkey is None: + print( + f"[?] {name} tag {curr} has unsupported syntax, skipping", + file=sys.stderr, + ) + continue + latestkey = tag_sortkey(latest) + if currkey > latestkey: + # Happens when the tag file pins a dev version but only + # production tags are considered. + print(f" {name} {curr} (newer than latest {latest})") + continue + if currkey == latestkey: + print(f" {name} {curr} (up to date)") + continue + print(f"[!] {name} {curr or '(none)'} -> {latest}") + upgraded.append(name) + if not cfg.dry: + tag_file.write_text(latest + "\n") + if not upgraded: + print("nothing to upgrade") + elif cfg.dry: + print("would upgrade:", " ".join(upgraded)) + else: + print("upgraded:", " ".join(upgraded)) + + def main(): parser = argparse.ArgumentParser( prog="taler-pkg", description="Taler Packaging Helper" @@ -576,6 +690,27 @@ def main(): ) parser_show_latest.set_defaults(func=print_latest) + # subcommand upgrade + + parser_upgrade = subparsers.add_parser( + "upgrade", help="Upgrade component tags to the latest upstream version." + ) + parser_upgrade.set_defaults(func=upgrade) + parser_upgrade.add_argument( + "components", + nargs="*", + help="Components to upgrade (default: all components in buildconfig)", + ) + parser_upgrade.add_argument( + "--dev", + help="Also consider dev tags, not just production tags", + action="store_true", + default=False, + ) + parser_upgrade.add_argument( + "--dry", help="Dry run", action="store_true", default=False + ) + # subcommand show-order parser_show_order = subparsers.add_parser("show-order", help="Show build order.")