contrib/genosxversion.py
branchstable
changeset 33594 283a7da602ae
child 38734 25880ddf9a86
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/genosxversion.py	Mon Jul 24 20:38:09 2017 -0400
@@ -0,0 +1,130 @@
+#!/usr/bin/env python2
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+
+# Always load hg libraries from the hg we can find on $PATH.
+hglib = json.loads(subprocess.check_output(
+    ['hg', 'debuginstall', '-Tjson']))[0]['hgmodules']
+sys.path.insert(0, os.path.dirname(hglib))
+
+from mercurial import util
+
+ap = argparse.ArgumentParser()
+ap.add_argument('--paranoid',
+                action='store_true',
+                help=("Be paranoid about how version numbers compare and "
+                      "produce something that's more likely to sort "
+                      "reasonably."))
+ap.add_argument('--selftest', action='store_true', help='Run self-tests.')
+ap.add_argument('versionfile', help='Path to a valid mercurial __version__.py')
+
+def paranoidver(ver):
+    """Given an hg version produce something that distutils can sort.
+
+    Some Mac package management systems use distutils code in order to
+    figure out upgrades, which makes life difficult. The test case is
+    a reduced version of code in the Munki tool used by some large
+    organizations to centrally manage OS X packages, which is what
+    inspired this kludge.
+
+    >>> paranoidver('3.4')
+    '3.4.0'
+    >>> paranoidver('3.4.2')
+    '3.4.2'
+    >>> paranoidver('3.0-rc+10')
+    '2.9.9999-rc+10'
+    >>> paranoidver('4.2+483-5d44d7d4076e')
+    '4.2.0+483-5d44d7d4076e'
+    >>> paranoidver('4.2.1+598-48d1e1214d8c')
+    '4.2.1+598-48d1e1214d8c'
+    >>> paranoidver('4.3-rc')
+    '4.2.9999-rc'
+    >>> paranoidver('4.3')
+    '4.3.0'
+    >>> from distutils import version
+    >>> class LossyPaddedVersion(version.LooseVersion):
+    ...     '''Subclass version.LooseVersion to compare things like
+    ...     "10.6" and "10.6.0" as equal'''
+    ...     def __init__(self, s):
+    ...             self.parse(s)
+    ...
+    ...     def _pad(self, version_list, max_length):
+    ...         'Pad a version list by adding extra 0 components to the end'
+    ...         # copy the version_list so we don't modify it
+    ...         cmp_list = list(version_list)
+    ...         while len(cmp_list) < max_length:
+    ...             cmp_list.append(0)
+    ...         return cmp_list
+    ...
+    ...     def __cmp__(self, other):
+    ...         if isinstance(other, str):
+    ...             other = MunkiLooseVersion(other)
+    ...         max_length = max(len(self.version), len(other.version))
+    ...         self_cmp_version = self._pad(self.version, max_length)
+    ...         other_cmp_version = self._pad(other.version, max_length)
+    ...         return cmp(self_cmp_version, other_cmp_version)
+    >>> def testver(older, newer):
+    ...   o = LossyPaddedVersion(paranoidver(older))
+    ...   n = LossyPaddedVersion(paranoidver(newer))
+    ...   return o < n
+    >>> testver('3.4', '3.5')
+    True
+    >>> testver('3.4.0', '3.5-rc')
+    True
+    >>> testver('3.4-rc', '3.5')
+    True
+    >>> testver('3.4-rc+10-deadbeef', '3.5')
+    True
+    >>> testver('3.4.2', '3.5-rc')
+    True
+    >>> testver('3.4.2', '3.5-rc+10-deadbeef')
+    True
+    >>> testver('4.2+483-5d44d7d4076e', '4.2.1+598-48d1e1214d8c')
+    True
+    >>> testver('4.3-rc', '4.3')
+    True
+    >>> testver('4.3', '4.3-rc')
+    False
+    """
+    major, minor, micro, extra = util.versiontuple(ver, n=4)
+    if micro is None:
+        micro = 0
+    if extra:
+        if extra.startswith('rc'):
+            if minor == 0:
+                major -= 1
+                minor = 9
+            else:
+                minor -= 1
+            micro = 9999
+            extra = '-' + extra
+        else:
+            extra = '+' + extra
+    else:
+        extra = ''
+    return '%d.%d.%d%s' % (major, minor, micro, extra)
+
+def main(argv):
+    opts = ap.parse_args(argv[1:])
+    if opts.selftest:
+        import doctest
+        doctest.testmod()
+        return
+    with open(opts.versionfile) as f:
+        for l in f:
+            if l.startswith('version = '):
+                # version number is entire line minus the quotes
+                ver = l[len('version = ') + 1:-2]
+                break
+    if opts.paranoid:
+        print(paranoidver(ver))
+    else:
+        print(ver)
+
+if __name__ == '__main__':
+    main(sys.argv)