gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]