github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/internal/bzlmod/semver.bzl (about) 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 15 visibility([ 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 25 def _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 ident == COMPARES_HIGHEST_SENTINEL: 38 return (ident,) 39 elif numeric_only: 40 fail("Expected a numeric identifier, got: " + ident) 41 else: 42 # 11.4.2: 43 # "Identifiers with letters or hyphens are compared lexically in ASCII sort order." 44 return (ident,) 45 46 def _semver_to_comparable(v, *, relaxed = False): 47 """ 48 Parses a string representation of a semver version into an opaque comparable object. 49 50 Args: 51 v: The string representation of the version. 52 relaxed: If true, the release version string is allowed to have an arbitrary number of 53 dot-separated components, each of which is allowed to contain the same set of characters 54 as a pre-release segment. This is the version string format used by Bazel modules. 55 """ 56 57 # Strip build metadata as it is not relevant for comparisons. 58 v, _, _ = v.partition("+") 59 60 release_str, _, prerelease_str = v.partition("-") 61 if prerelease_str: 62 # 11.4.4: 63 # "A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding 64 # identifiers are equal." 65 prerelease = [_identifier_to_comparable(ident, numeric_only = False) for ident in prerelease_str.split(".")] 66 else: 67 # 11.3: 68 # "When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version." 69 prerelease = [(COMPARES_HIGHEST_SENTINEL,)] 70 71 release = release_str.split(".") 72 if not v == COMPARES_HIGHEST_SENTINEL and not relaxed and len(release) != 3: 73 fail("Semantic version strings must have exactly three dot-separated components, got: " + v) 74 75 return ( 76 tuple([_identifier_to_comparable(s, numeric_only = not relaxed) for s in release]), 77 tuple(prerelease), 78 ) 79 80 semver = struct( 81 to_comparable = _semver_to_comparable, 82 )