diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index ff05a7d5d..9eb1c7473 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -341,6 +341,14 @@ def _go_deps_impl(module_ctx): # in the module resolutions and swapping out the entry. for path, replace in replace_map.items(): if path in module_resolutions: + + # If the replace directive specified a version then we only + # apply it if the versions match. + if replace.from_version: + comparable_from_version = semver.to_comparable(replace.from_version) + if module_resolutions[path].version != comparable_from_version: + continue + new_version = semver.to_comparable(replace.version) module_resolutions[path] = with_replaced_or_new_fields( module_resolutions[path], diff --git a/internal/bzlmod/go_mod.bzl b/internal/bzlmod/go_mod.bzl index 3733c0e0d..59e134670 100644 --- a/internal/bzlmod/go_mod.bzl +++ b/internal/bzlmod/go_mod.bzl @@ -141,16 +141,32 @@ def _parse_directive(state, directive, tokens, comment, path, line_no): elif directive == "replace": # A replace directive might use a local file path beginning with ./ or ../ # These are not supported with gazelle~go_deps. - if len(tokens) == 3 and tokens[2][0] == ".": + if (len(tokens) == 3 and tokens[2][0] == ".") or (len(tokens) > 3 and tokens[3][0] == "."): fail("{}:{}: local file path not supported in replace directive: '{}'".format(path, line_no, tokens[2])) - if len(tokens) != 4 or tokens[1] != "=>": - fail("{}:{}: replace directive must follow pattern: 'replace from_path => to_path version' ".format(path, line_no)) + # replacements key off of the from_path from_path = tokens[0] - state["replace"][from_path] = struct( - to_path = tokens[2], - version = _canonicalize_raw_version(tokens[3]), - ) + + # pattern: replace from_path => to_path to_version + if len(tokens) == 4 and tokens[1] == "=>": + state["replace"][from_path] = struct( + from_version = None, + to_path = tokens[2], + version = _canonicalize_raw_version(tokens[3]), + ) + # pattern: replace from_path from_version => to_path to_version + elif len(tokens) == 5 and tokens[2] == "=>": + state["replace"][from_path] = struct( + from_version = _canonicalize_raw_version(tokens[1]), + to_path = tokens[3], + version = _canonicalize_raw_version(tokens[4]), + ) + else: + fail( + "{}:{}: replace directive must follow pattern: ".format(path, line_no) + + "'replace from_path from_version => to_path to_version' or " + + "'replace from_path => to_path to_version'" + ) # TODO: Handle exclude. diff --git a/tests/bzlmod/go_mod_test.bzl b/tests/bzlmod/go_mod_test.bzl index ba7519ee2..c4287074a 100644 --- a/tests/bzlmod/go_mod_test.bzl +++ b/tests/bzlmod/go_mod_test.bzl @@ -13,6 +13,7 @@ github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect ) replace github.com/go-fsnotify/fsnotify => github.com/fsnotify/fsnotify v1.4.2 +replace github.com/bmatcuk/doublestar/v4 v4.0.2 => github.com/bmatcuk/doublestar/v4 v4.0.3 module github.com/bazelbuild/bazel-gazelle @@ -28,7 +29,10 @@ require golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect _EXPECTED_GO_MOD_PARSE_RESULT = struct( go = (1, 18), module = "github.com/bazelbuild/bazel-gazelle", - replace_map = {"github.com/go-fsnotify/fsnotify": struct(to_path = "github.com/fsnotify/fsnotify", version = "1.4.2")}, + replace_map = { + "github.com/go-fsnotify/fsnotify": struct(from_version = None, to_path = "github.com/fsnotify/fsnotify", version = "1.4.2"), + "github.com/bmatcuk/doublestar/v4": struct(from_version = "4.0.2", to_path = "github.com/bmatcuk/doublestar/v4", version = "4.0.3"), + }, require = ( struct(indirect = False, path = "github.com/bazelbuild/buildtools", version = "v0.0.0-20220531122519-a43aed7014c8"), struct(indirect = False, path = "github.com/bazelbuild/rules_go", version = "v0.n\\\"33.0"),