[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-deployment] branch master updated: packaging: python-only version
From: |
Admin |
Subject: |
[taler-deployment] branch master updated: packaging: python-only version comparison |
Date: |
Tue, 10 Jun 2025 20:51:22 +0200 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository taler-deployment.
The following commit(s) were added to refs/heads/master by this push:
new 97d59d1 packaging: python-only version comparison
97d59d1 is described below
commit 97d59d1d3caf32f72607d4fa1d0ab1ef0abf7d47
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Jun 10 20:33:55 2025 +0200
packaging: python-only version comparison
---
packaging/ng/.gitignore | 1 +
packaging/ng/taler-pkg | 65 +++++++++++----
packaging/ng/util/__init__.py | 188 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 239 insertions(+), 15 deletions(-)
diff --git a/packaging/ng/.gitignore b/packaging/ng/.gitignore
index 681bab4..2db7beb 100644
--- a/packaging/ng/.gitignore
+++ b/packaging/ng/.gitignore
@@ -1,2 +1,3 @@
packages/
cache/
+*.pyc
diff --git a/packaging/ng/taler-pkg b/packaging/ng/taler-pkg
index d1c2586..0a809fe 100755
--- a/packaging/ng/taler-pkg
+++ b/packaging/ng/taler-pkg
@@ -8,8 +8,16 @@ import argparse
import subprocess
from dataclasses import dataclass
import os
+import sys
from pathlib import Path
+# Make local util package available
+file = Path(__file__).resolve()
+parent, root = file.parent, file.parents[1]
+sys.path.append(str(root))
+
+from util import Dpkg
+
mydir = os.path.dirname(os.path.realpath(__file__))
host = "taler.net"
@@ -26,8 +34,8 @@ components = [
"taler-harness",
"taler-merchant",
"robocop",
- #"taler-mdb",
- #"taler-merchant-demos",
+ # "taler-mdb",
+ # "taler-merchant-demos",
"taler-wallet-cli",
"taler-directory",
"taler-mailbox",
@@ -38,7 +46,7 @@ deps = {
"anastasis": ["gnunet", "taler-merchant"],
"anastasis-gtk": ["anastasis", "gnunet-gtk"],
"taler-merchant": ["gnunet", "taler-exchange"],
- #"taler-mdb": ["gnunet", "taler-exchange", "taler-merchant"],
+ # "taler-mdb": ["gnunet", "taler-exchange", "taler-merchant"],
"sync": ["taler-merchant", "taler-exchange", "gnunet"],
"gnunet-gtk": ["gnunet"],
}
@@ -51,6 +59,7 @@ for n1, d in deps.items():
if n1 not in rd:
rd.append(n1)
+
def buildsort(roots):
"""Toposort transitive closure of roots based on deps"""
out = []
@@ -182,11 +191,9 @@ def show_published(cfg):
listfmt = "${package}_${version}_${architecture}.deb\n"
subprocess.run(["ssh", f"taler-packaging@{host}", f"reprepro -b
/home/taler-packaging/www/apt/{vendor}/ --list-format '{listfmt}' list
{codename}"], check=True)
-
def publish(cfg):
distro = cfg.distro
vendor, codename = distro.split("-")
- #debs = list(Path(f"./packages/{distro}/").glob("*.deb"))
debs = []
for component in components:
current = None
@@ -205,13 +212,23 @@ def publish(cfg):
if cfg.dry:
return
debs = [Path(f"./packages/{distro}/") / x for x in debs]
- subprocess.run(["ssh", f"taler-packaging@{host}", f"rm -f
'{distro}/*.deb'"], check=True)
- subprocess.run(["scp", "--", *debs, f"taler-packaging@{host}:{distro}/"],
check=True)
+ subprocess.run(
+ ["ssh", f"taler-packaging@{host}", f"rm -f '{distro}/*.deb'"],
check=True
+ )
+ subprocess.run(
+ ["scp", "--", *debs, f"taler-packaging@{host}:{distro}/"], check=True
+ )
# FIXME: This fails when packages of the same version are already present.
# That's a problem since builds are not bit-reproducible.
- subprocess.run(["ssh", f"taler-packaging@{host}",
f"./include-{distro}.sh"], check=True)
- subprocess.run(["ssh", f"taler-packaging@{host}",
f"./export-{distro}.sh"], check=True)
- subprocess.run(["ssh", f"taler-packaging@{host}", f"./show-{distro}.sh"],
check=True)
+ subprocess.run(
+ ["ssh", f"taler-packaging@{host}", f"./include-{distro}.sh"],
check=True
+ )
+ subprocess.run(
+ ["ssh", f"taler-packaging@{host}", f"./export-{distro}.sh"], check=True
+ )
+ subprocess.run(
+ ["ssh", f"taler-packaging@{host}", f"./show-{distro}.sh"], check=True
+ )
def main():
@@ -227,9 +244,23 @@ def main():
parser_build.set_defaults(func=build)
parser_build.add_argument("distro")
# Keep for backwards compat
- parser_build.add_argument("--no-transitive", help="Do not build transitive
deps of changed components (default)", action="store_true", dest="transitive",
default=None)
- parser_build.add_argument("--transitive", help="Build transitive deps of
changed components", action="store_false", dest="transitive", default=None)
- parser_build.add_argument("--dry", help="Dry run", action="store_true",
default=False)
+ parser_build.add_argument(
+ "--no-transitive",
+ help="Do not build transitive deps of changed components (default)",
+ action="store_true",
+ dest="transitive",
+ default=None,
+ )
+ parser_build.add_argument(
+ "--transitive",
+ help="Build transitive deps of changed components",
+ action="store_false",
+ dest="transitive",
+ default=None,
+ )
+ parser_build.add_argument(
+ "--dry", help="Dry run", action="store_true", default=False
+ )
# subcommand show-latest
@@ -245,14 +276,18 @@ def main():
parser_show_order.add_argument("roots", nargs="+")
# subcommand show-published
- parser_show_published = subparsers.add_parser("show-published", help="Show
published packages on deb.taler.net")
+ parser_show_published = subparsers.add_parser(
+ "show-published", help="Show published packages on deb.taler.net"
+ )
parser_show_published.add_argument("distro")
parser_show_published.set_defaults(func=show_published)
# subcommand publish
parser_publish = subparsers.add_parser("publish", help="Publish to
deb.taler.net")
- parser_publish.add_argument("--dry", help="Dry run", action="store_true",
default=False)
+ parser_publish.add_argument(
+ "--dry", help="Dry run", action="store_true", default=False
+ )
parser_publish.add_argument("distro")
parser_publish.set_defaults(func=publish)
diff --git a/packaging/ng/util/__init__.py b/packaging/ng/util/__init__.py
new file mode 100644
index 0000000..b2ae2d6
--- /dev/null
+++ b/packaging/ng/util/__init__.py
@@ -0,0 +1,188 @@
+# Class "Dpkg" adapted in modified form from python-dpkg:
+#
https://github.com/TheClimateCorporation/python-dpkg/blob/master/pydpkg/__init__.py
+#
+# License:
+#
+# Copyright [2017] The Climate Corporation (https://climate.com)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+class Dpkg:
+ @staticmethod
+ def get_upstream(version_str):
+ """Given a version string that could potentially contain both an
upstream
+ revision and a debian revision, return a tuple of both. If there is no
+ debian revision, return 0 as the second tuple element."""
+ try:
+ d_index = version_str.rindex("-")
+ except ValueError:
+ # no hyphens means no debian version, also valid.
+ return version_str, "0"
+ return version_str[0:d_index], version_str[d_index + 1 :]
+
+ @staticmethod
+ def get_epoch(version_str):
+ """Parse the epoch out of a package version string.
+ Return (epoch, version); epoch is zero if not found."""
+ try:
+ # there could be more than one colon,
+ # but we only care about the first
+ e_index = version_str.index(":")
+ except ValueError:
+ # no colons means no epoch; that's valid, man
+ return 0, version_str
+ try:
+ epoch = int(version_str[0:e_index])
+ except ValueError:
+ raise Exception(
+ "Corrupt dpkg version %s: epochs can only be ints, and "
+ "epochless versions cannot use the colon character." %
version_str
+ )
+ return epoch, version_str[e_index + 1 :]
+
+ @staticmethod
+ def split_full_version(version_str):
+ """Split a full version string into epoch, upstream version and
+ debian revision."""
+ epoch, full_ver = Dpkg.get_epoch(version_str)
+ upstream_rev, debian_rev = Dpkg.get_upstream(full_ver)
+ return epoch, upstream_rev, debian_rev
+
+ @staticmethod
+ def get_alphas(revision_str):
+ """Return a tuple of the first non-digit characters of a revision
(which
+ may be empty) and the remaining characters."""
+ # get the index of the first digit
+ for i, char in enumerate(revision_str):
+ if char.isdigit():
+ if i == 0:
+ return "", revision_str
+ return revision_str[0:i], revision_str[i:]
+ # string is entirely alphas
+ return revision_str, ""
+
+ @staticmethod
+ def get_digits(revision_str):
+ """Return a tuple of the first integer characters of a revision (which
+ may be empty) and the remains."""
+ # If the string is empty, return (0,'')
+ if not revision_str:
+ return 0, ""
+ # get the index of the first non-digit
+ for i, char in enumerate(revision_str):
+ if not char.isdigit():
+ if i == 0:
+ return 0, revision_str
+ return int(revision_str[0:i]), revision_str[i:]
+ # string is entirely digits
+ return int(revision_str), ""
+
+ @staticmethod
+ def listify(revision_str):
+ """Split a revision string into a list of alternating between strings
and
+ numbers, padded on either end to always be "str, int, str, int..." and
+ always be of even length. This allows us to trivially implement the
+ comparison algorithm described at
+ http://debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
+ """
+ result = []
+ while revision_str:
+ rev_1, remains = Dpkg.get_alphas(revision_str)
+ rev_2, remains = Dpkg.get_digits(remains)
+ result.extend([rev_1, rev_2])
+ revision_str = remains
+ return result
+
+ @staticmethod
+ def dstringcmp(a, b):
+ """debian package version string section lexical sort algorithm
+
+ "The lexical comparison is a comparison of ASCII values modified so
+ that all the letters sort earlier than all the non-letters and so that
+ a tilde sorts before anything, even the end of a part."
+ """
+
+ if a == b:
+ return 0
+ try:
+ for i, char in enumerate(a):
+ if char == b[i]:
+ continue
+ # "a tilde sorts before anything, even the end of a part"
+ # (emptyness)
+ if char == "~":
+ return -1
+ if b[i] == "~":
+ return 1
+ # "all the letters sort earlier than all the non-letters"
+ if char.isalpha() and not b[i].isalpha():
+ return -1
+ if not char.isalpha() and b[i].isalpha():
+ return 1
+ # otherwise lexical sort
+ if ord(char) > ord(b[i]):
+ return 1
+ if ord(char) < ord(b[i]):
+ return -1
+ except IndexError:
+ # a is longer than b but otherwise equal, hence greater
+ # ...except for goddamn tildes
+ if char == "~":
+ return -1
+ return 1
+ # if we get here, a is shorter than b but otherwise equal, hence lesser
+ # ...except for goddamn tildes
+ if b[len(a)] == "~":
+ return 1
+ return -1
+
+ @staticmethod
+ def split_full_version(version_str):
+ """Split a full version string into epoch, upstream version and
+ debian revision."""
+ epoch, full_ver = Dpkg.get_epoch(version_str)
+ upstream_rev, debian_rev = Dpkg.get_upstream(full_ver)
+ return epoch, upstream_rev, debian_rev
+
+ @staticmethod
+ def compare_versions(ver1, ver2):
+ """Function to compare two Debian package version strings,
+ suitable for passing to list.sort() and friends."""
+ if ver1 == ver2:
+ return 0
+
+ # note the string conversion: the debian policy here explicitly
+ # specifies ASCII string comparisons, so if you are mad enough to
+ # actually cram unicode characters into your package name, you are on
+ # your own.
+ epoch1, upstream1, debian1 = Dpkg.split_full_version(str(ver1))
+ epoch2, upstream2, debian2 = Dpkg.split_full_version(str(ver2))
+
+ # if epochs differ, immediately return the newer one
+ if epoch1 < epoch2:
+ return -1
+ if epoch1 > epoch2:
+ return 1
+
+ # then, compare the upstream versions
+ upstr_res = Dpkg.compare_revision_strings(upstream1, upstream2)
+ if upstr_res != 0:
+ return upstr_res
+
+ debian_res = Dpkg.compare_revision_strings(debian1, debian2)
+ if debian_res != 0:
+ return debian_res
+
+ # at this point, the versions are equal, but due to an interpolated
+ # zero in either the epoch or the debian version
+ return 0
+
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-deployment] branch master updated: packaging: python-only version comparison,
Admin <=