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  )