diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index 20b205240..d67d0c682 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -254,12 +254,12 @@ def _go_deps_impl(module_ctx): replace_map.update(go_mod_replace_map) else: # Register this Bazel module as providing the specified Go module. It participates - # in version resolution using its registry version, which is assumed to be an - # actual semver. An empty version string signals an override, which is assumed to - # be newer than any other version. - # TODO: Decide whether and how to handle non-semver versions. + # in version resolution using its registry version, which uses a relaxed variant of + # semver that can however still be compared to strict semvers. + # An empty version string signals an override, which is assumed to be newer than any + # other version. raw_version = _canonicalize_raw_version(module.version) - version = semver.to_comparable(raw_version) if raw_version else _HIGHEST_VERSION_SENTINEL + version = semver.to_comparable(raw_version, relaxed = True) if raw_version else _HIGHEST_VERSION_SENTINEL if module_path not in bazel_deps or version > bazel_deps[module_path].version: bazel_deps[module_path] = struct( module_name = module.name, diff --git a/internal/bzlmod/semver.bzl b/internal/bzlmod/semver.bzl index e8629c4db..0f6354eb1 100644 --- a/internal/bzlmod/semver.bzl +++ b/internal/bzlmod/semver.bzl @@ -25,32 +25,37 @@ def _identifier_to_comparable(ident): # "Identifiers with letters or hyphens are compared lexically in ASCII sort order." return (ident,) -def _semver_to_comparable(v): +def _semver_to_comparable(v, *, relaxed = False): """ Parses a string representation of a semver version into an opaque comparable object. + + Args: + v: The string representation of the version. + relaxed: If true, the release version string is allowed to have an arbitrary number of + dot-separated components, each of which is allowed to contain the same set of characters + as a pre-release segment. This is the version string format used by Bazel modules. """ # Strip build metadata as it is not relevant for comparisons. v, _, _ = v.partition("+") - major_minor_patch_str, _, prerelease_str = v.partition("-") + release_str, _, prerelease_str = v.partition("-") if prerelease_str: # 11.4.4: # "A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding # identifiers are equal." - prerelease = tuple([_identifier_to_comparable(ident) for ident in prerelease_str.split(".")]) + prerelease = [_identifier_to_comparable(ident) for ident in prerelease_str.split(".")] else: # 11.3: # "When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version." - prerelease = ((_COMPARES_HIGHEST_SENTINEL,),) - - major_str, minor_str, patch_str = major_minor_patch_str.split(".") - return ( - int(major_str), - int(minor_str), - int(patch_str), - prerelease, - ) + prerelease = [(_COMPARES_HIGHEST_SENTINEL,)] + + release = release_str.split(".") + if not relaxed and len(release) != 3: + fail("Semantic version strings must have exactly three dot-separated components, got: " + v) + + release_segment_parser = _identifier_to_comparable if relaxed else int + return (tuple([release_segment_parser(s) for s in release]), tuple(prerelease)) semver = struct( to_comparable = _semver_to_comparable, diff --git a/tests/bzlmod/semver_test.bzl b/tests/bzlmod/semver_test.bzl index f0c1ac55b..6672124ff 100644 --- a/tests/bzlmod/semver_test.bzl +++ b/tests/bzlmod/semver_test.bzl @@ -13,8 +13,8 @@ _SORTED_TEST_VERSIONS = [ "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-x.7.z.92", - "1.0.0-x-y-z.–", - "1.0.0+21AF26D3—-117B344092BD", + "1.0.0-x-y-z.--", + "1.0.0+21AF26D3----117B344092BD", "1.0.0+20130313144700", "1.0.0", "2.0.0", @@ -26,11 +26,11 @@ _SCRAMBLED_TEST_VERSIONS = [ "2.1.1", "2.1.0", "2.0.0", - "1.0.0+21AF26D3—-117B344092BD", + "1.0.0+21AF26D3----117B344092BD", "1.0.0+20130313144700", "1.0.0", "1.0.0-x.7.z.92", - "1.0.0-x-y-z.–", + "1.0.0-x-y-z.--", "1.0.0-rc.1", "1.0.0-beta.11", "1.0.0-beta.2", @@ -54,8 +54,72 @@ def _semver_test_impl(ctx): semver_test = unittest.make(_semver_test_impl) +_SORTED_RELAXED_TEST_VERSIONS = [ + "1.0.0-0.3.7", + "1.0.0-alpha", + "1.0.0-alpha+001", + "1.0.0-alpha.1", + "1.0.0-alpha.beta", + "1.0.0-beta+exp.sha.5114f85", + "1.0.0-beta", + "1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-rc.1", + "1.0.0-x.7.z.92", + "1.0.0-x-y-z.--", + "1.0.0+21AF26D3----117B344092BD", + "1.0.0+20130313144700", + "1.0.0", + "2.0.0", + "2.1.0", + "2.1.1-0", + "2.1.1", + "2.1.1.0", + "2.1.1.1", + "2.1.1.a", + "2.1.2", +] + +_SCRAMBLED_RELAXED_TEST_VERSIONS = [ + "2.1.2", + "2.1.1.a", + "2.1.1.1", + "2.1.1.0", + "2.1.1", + "2.1.1-0", + "2.1.0", + "2.0.0", + "1.0.0+21AF26D3----117B344092BD", + "1.0.0+20130313144700", + "1.0.0", + "1.0.0-x.7.z.92", + "1.0.0-x-y-z.--", + "1.0.0-rc.1", + "1.0.0-beta.11", + "1.0.0-beta.2", + "1.0.0-beta+exp.sha.5114f85", + "1.0.0-beta", + "1.0.0-alpha.beta", + "1.0.0-alpha.1", + "1.0.0-alpha", + "1.0.0-alpha+001", + "1.0.0-0.3.7", +] + +def _semver_relaxed_test_impl(ctx): + env = unittest.begin(ctx) + asserts.equals( + env, + _SORTED_RELAXED_TEST_VERSIONS, + sorted(_SCRAMBLED_RELAXED_TEST_VERSIONS, key = lambda x: semver.to_comparable(x, relaxed = True)), + ) + return unittest.end(env) + +semver_relaxed_test = unittest.make(_semver_relaxed_test_impl) + def semver_test_suite(name): unittest.suite( name, semver_test, + semver_relaxed_test, )