From 598833da69c68acc9f81d38518212942b4761d20 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sat, 17 Oct 2026 13:03:18 +0300 Subject: [PATCH] fix(packaging): .deb and .rpm of a beta sort before the final release (0.5.0~beta.1) (#1494) --- scripts/linux/build_deb.sh | 5 +- scripts/linux/build_rpm.sh | 7 +-- scripts/linux/package_version.sh | 22 +++++++++ test/packaging/package_version_test.dart | 60 ++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100755 scripts/linux/package_version.sh create mode 100644 test/packaging/package_version_test.dart diff --git a/scripts/linux/build_deb.sh b/scripts/linux/build_deb.sh index 5427f546..3cdd273e 100755 --- a/scripts/linux/build_deb.sh +++ b/scripts/linux/build_deb.sh @@ -24,6 +24,9 @@ if ! command -v dpkg-deb >/dev/null 2>&1; then fi VERSION="$(grep '^version:' "$ROOT/pubspec.yaml" | sed 's/^version: //; s/+.*//')" +# The file keeps the tag form; the package version sorts a beta before its +# final release (0.5.0~beta.1, #1494). +DEB_VERSION="$("$ROOT/scripts/linux/package_version.sh" "$VERSION")" OUT="${2:-$ROOT/Querya-Desktop-${VERSION}-linux.deb}" ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)" @@ -61,7 +64,7 @@ INSTALLED_SIZE="$(du -sk "$PKG" | awk '{print $1}')" cat > "$PKG/DEBIAN/control" </dev/null 2>&1; then fi VERSION="$(grep '^version:' "$ROOT/pubspec.yaml" | sed 's/^version: //; s/+.*//')" -# RPM Version must not contain '-' (it separates Version from Release). -# Keep artifact filename as the pubspec/marketing version (e.g. 0.4.11-b). -RPM_VERSION="${VERSION//-/.}" +# RPM Version must not contain '-' (it separates Version from Release), and a +# beta must sort before its final release: 0.5.0-beta.1 -> 0.5.0~beta.1 (#1494). +# The artifact file name keeps the pubspec / tag form. +RPM_VERSION="$("$ROOT/scripts/linux/package_version.sh" "$VERSION")" OUT="${2:-$ROOT/Querya-Desktop-${VERSION}-linux.rpm}" WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/querya-rpm.XXXXXX")" diff --git a/scripts/linux/package_version.sh b/scripts/linux/package_version.sh new file mode 100755 index 00000000..009d09e9 --- /dev/null +++ b/scripts/linux/package_version.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# The version string a .deb / .rpm carries for a pubspec version (#1494). +# +# ./scripts/linux/package_version.sh 0.5.0-beta.1 # -> 0.5.0~beta.1 +# +# or `source` it and call `package_version`. +# +# A SemVer pre-release must sort *before* its final release. As is, dpkg reads +# `0.5.0-beta.1` as upstream 0.5.0 with revision `beta.1`, newer than `0.5.0`, +# and rpm's `0.5.0.beta.1` is newer too, so beta users would never be upgraded +# to the final package. Both sort `~` before anything, even the end of the +# string: `0.5.0~beta.1 < 0.5.0`. Any further `-` becomes `.`, since `-` +# separates the Debian revision and the RPM release. + +package_version() { + printf '%s\n' "$1" | sed 's/-/~/; s/-/./g' +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + set -euo pipefail + package_version "${1:?usage: package_version.sh }" +fi diff --git a/test/packaging/package_version_test.dart b/test/packaging/package_version_test.dart new file mode 100644 index 00000000..954dd28d --- /dev/null +++ b/test/packaging/package_version_test.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +/// `.deb` / `.rpm` versions sort a beta before its final release (#1494). +void main() { + final skipNonPosix = Platform.isWindows ? 'needs bash' : null; + + String packageVersion(String version) { + final r = Process.runSync( + 'bash', + ['scripts/linux/package_version.sh', version], + ); + expect(r.exitCode, 0, reason: '${r.stderr}'); + return (r.stdout as String).trim(); + } + + bool has(String tool) => + Process.runSync('sh', ['-c', 'command -v $tool']).exitCode == 0; + + test('a pre-release gets ~ before its label; a final version is unchanged', + () { + expect(packageVersion('0.5.0'), '0.5.0'); + expect(packageVersion('0.5.0-beta.1'), '0.5.0~beta.1'); + expect(packageVersion('0.5.0-rc-2'), '0.5.0~rc.2'); + }, skip: skipNonPosix); + + // Pairs that must sort as given: older first. + final ordered = [ + ('0.4.19', '0.5.0-beta.1'), + ('0.5.0-beta.1', '0.5.0-beta.2'), + ('0.5.0-beta.2', '0.5.0-beta.10'), + ('0.5.0-beta.2', '0.5.0'), + ('0.5.0', '0.5.1'), + ]; + + test('dpkg upgrades a beta to the final release', () { + for (final (older, newer) in ordered) { + final r = Process.runSync('dpkg', [ + '--compare-versions', + packageVersion(older), + 'lt', + packageVersion(newer), + ]); + expect(r.exitCode, 0, reason: '$older should sort before $newer'); + } + }, skip: skipNonPosix ?? (has('dpkg') ? null : 'dpkg is not installed')); + + test('rpm upgrades a beta to the final release', () { + for (final (older, newer) in ordered) { + final a = packageVersion(older), b = packageVersion(newer); + final r = Process.runSync( + 'rpm', + ['--eval', '%{lua: print(rpm.vercmp("$a", "$b"))}'], + ); + expect((r.stdout as String).trim(), '-1', + reason: '$older should sort before $newer'); + } + }, skip: skipNonPosix ?? (has('rpm') ? null : 'rpm is not installed')); +}