...
1# Copyright 2023 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15visibility([
16 "//tests/bzlmod/...",
17])
18
19# Compares lower than any non-numeric identifier.
20_COMPARES_LOWEST_SENTINEL = ""
21
22# Compares higher than any valid non-numeric identifier (containing only [A-Za-z0-9-]).
23_COMPARES_HIGHEST_SENTINEL = "{"
24
25def _identifier_to_comparable(ident, *, numeric_only):
26 if not ident:
27 fail("Identifiers in semantic version strings must not be empty")
28 if ident.isdigit():
29 if ident[0] == "0" and ident != "0":
30 fail("Numeric identifiers in semantic version strings must not include leading zeroes")
31
32 # 11.4.1:
33 # "Identifiers consisting of only digits are compared numerically."
34 # 11.4.3:
35 # "Numeric identifiers always have lower precedence than non-numeric identifiers."
36 return (_COMPARES_LOWEST_SENTINEL, int(ident))
37 elif numeric_only:
38 fail("Expected a numeric identifier, got: " + ident)
39 else:
40 # 11.4.2:
41 # "Identifiers with letters or hyphens are compared lexically in ASCII sort order."
42 return (ident,)
43
44def _semver_to_comparable(v, *, relaxed = False):
45 """
46 Parses a string representation of a semver version into an opaque comparable object.
47
48 Args:
49 v: The string representation of the version.
50 relaxed: If true, the release version string is allowed to have an arbitrary number of
51 dot-separated components, each of which is allowed to contain the same set of characters
52 as a pre-release segment. This is the version string format used by Bazel modules.
53 """
54
55 # Strip build metadata as it is not relevant for comparisons.
56 v, _, _ = v.partition("+")
57
58 release_str, _, prerelease_str = v.partition("-")
59 if prerelease_str:
60 # 11.4.4:
61 # "A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding
62 # identifiers are equal."
63 prerelease = [_identifier_to_comparable(ident, numeric_only = False) for ident in prerelease_str.split(".")]
64 else:
65 # 11.3:
66 # "When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version."
67 prerelease = [(_COMPARES_HIGHEST_SENTINEL,)]
68
69 release = release_str.split(".")
70 if not relaxed and len(release) != 3:
71 fail("Semantic version strings must have exactly three dot-separated components, got: " + v)
72
73 return (
74 tuple([_identifier_to_comparable(s, numeric_only = not relaxed) for s in release]),
75 tuple(prerelease),
76 )
77
78semver = struct(
79 to_comparable = _semver_to_comparable,
80)
View as plain text