wix: more robust normalization of RC version components
MSI has strict version requirements where the format is
`A.B.C[.D]` and all fields must be numeric
(https://docs.microsoft.com/en-us/windows/win32/msi/productversion?redirectedfrom=MSDN).
Only the first 3 are used by the installer itself.
Mercurial's version strings can have `rcN` and an optional
`+<commit>-<date>` fragment at the end.
This commit teaches the MSI version normalization to handle
both of these more robustly.
Before, we would throw away the `.rcN` component completely.
e.g. `5.3rc1` would get normalized to `5.3.0`. And worse,
`5.3rc0+5-abcdef` would get normalized to `5.3.5`.
After this commit, presence of an `.rcN` provides the
value for a 4th field. e.g. `5.3rc1` -> `5.3.0.1`. In
addition, the commit count from the `+` suffix gets
normalized into the 4th version component, but only if
the original version string didn't have a 4th version
component or if no `rcN` is present. e.g. `5.3+5-abcdef`
is `5.3.0.5`.
Differential Revision: https://phab.mercurial-scm.org/D8003
--- a/contrib/packaging/hgpackaging/wix.py Sat Jan 25 00:16:04 2020 -0500
+++ b/contrib/packaging/hgpackaging/wix.py Fri Jan 24 20:21:53 2020 -0800
@@ -74,9 +74,36 @@
def normalize_version(version):
"""Normalize Mercurial version string so WiX accepts it.
- Version strings have to be numeric X.Y.Z.
+ Version strings have to be numeric ``A.B.C[.D]`` to conform with MSI's
+ requirements.
+
+ We normalize RC version or the commit count to a 4th version component.
+ We store this in the 4th component because ``A.B.C`` releases do occur
+ and we want an e.g. ``5.3rc0`` version to be semantically less than a
+ ``5.3.1rc2`` version. This requires always reserving the 3rd version
+ component for the point release and the ``X.YrcN`` release is always
+ point release 0.
+
+ In the case of an RC and presence of ``+`` suffix data, we can't use both
+ because the version format is limited to 4 components. We choose to use
+ RC and throw away the commit count in the suffix. This means we could
+ produce multiple installers with the same normalized version string.
+
+ >>> normalize_version("5.3")
+ '5.3.0'
+
+ >>> normalize_version("5.3rc0")
+ '5.3.0.0'
+
+ >>> normalize_version("5.3rc1")
+ '5.3.0.1'
+
+ >>> normalize_version("5.3rc1+2-abcdef")
+ '5.3.0.1'
+
+ >>> normalize_version("5.3+2-abcdef")
+ '5.3.0.2'
"""
-
if '+' in version:
version, extra = version.split('+', 1)
else:
@@ -84,19 +111,24 @@
# 4.9rc0
if version[:-1].endswith('rc'):
+ rc = int(version[-1:])
version = version[:-3]
+ else:
+ rc = None
+ # Ensure we have at least X.Y version components.
versions = [int(v) for v in version.split('.')]
while len(versions) < 3:
versions.append(0)
- major, minor, build = versions[:3]
+ if len(versions) < 4:
+ if rc is not None:
+ versions.append(rc)
+ elif extra:
+ # <commit count>-<hash>+<date>
+ versions.append(int(extra.split('-')[0]))
- if extra:
- # <commit count>-<hash>+<date>
- build = int(extra.split('-')[0])
-
- return '.'.join('%d' % x for x in (major, minor, build))
+ return '.'.join('%d' % x for x in versions[0:4])
def ensure_vc90_merge_modules(build_dir):