# HG changeset patch # User Gregory Szorc # Date 2020-01-25 04:21:53 # Node ID 8d653abed8618a47bb5a99e18ad6e7f46b0a0ab7 # Parent 0ab651b5f77c41489e78010bc601fd821fba7c80 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 `+-` 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 diff --git a/contrib/packaging/hgpackaging/wix.py b/contrib/packaging/hgpackaging/wix.py --- a/contrib/packaging/hgpackaging/wix.py +++ b/contrib/packaging/hgpackaging/wix.py @@ -74,9 +74,36 @@ def find_version(source_dir: pathlib.Pat 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 @@ def normalize_version(version): # 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: + # -+ + versions.append(int(extra.split('-')[0])) - if extra: - # -+ - 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):