github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/internal/bzlmod/go_deps.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  load("//internal:go_repository.bzl", "go_repository")
    16  load(
    17      ":default_gazelle_overrides.bzl",
    18      "DEFAULT_BUILD_EXTRA_ARGS_BY_PATH",
    19      "DEFAULT_BUILD_FILE_GENERATION_BY_PATH",
    20      "DEFAULT_DIRECTIVES_BY_PATH",
    21  )
    22  load(":go_mod.bzl", "deps_from_go_mod", "go_work_from_label", "sums_from_go_mod", "sums_from_go_work")
    23  load(":semver.bzl", "COMPARES_HIGHEST_SENTINEL", "semver")
    24  load(
    25      ":utils.bzl",
    26      "drop_nones",
    27      "extension_metadata",
    28      "format_rule_call",
    29      "get_directive_value",
    30      "with_replaced_or_new_fields",
    31  )
    32  
    33  visibility("//")
    34  
    35  _HIGHEST_VERSION_SENTINEL = semver.to_comparable("999999.999999.999999")
    36  
    37  _FORBIDDEN_OVERRIDE_TAG = """\
    38  Using the "go_deps.{tag_class}" tag in a non-root Bazel module is forbidden, \
    39  but module "{module_name}" requests it.
    40  
    41  If you need this override for a Bazel module that will be available in a public \
    42  registry (such as the Bazel Central Registry), please file an issue at \
    43  https://github.com/bazelbuild/bazel-gazelle/issues/new or submit a PR adding \
    44  the required directives to the "default_gazelle_overrides.bzl" file at \
    45  https://github.com/bazelbuild/bazel-gazelle/tree/master/internal/bzlmod/default_gazelle_overrides.bzl.
    46  """
    47  
    48  _GAZELLE_ATTRS = {
    49      "build_file_generation": attr.string(
    50          default = "on",
    51          doc = """One of `"auto"`, `"on"` (default), `"off"`.
    52  
    53          Whether Gazelle should generate build files for the Go module.
    54  
    55          Although "auto" is the default globally for build_file_generation,
    56          if a `"gazelle_override"` or `"gazelle_default_attributes"` tag is present
    57          for a Go module, the `"build_file_generation"` attribute will default to "on"
    58          since these tags indicate the presence of `"directives"` or `"build_extra_args"`.
    59  
    60          In `"auto"` mode, Gazelle will run if there is no build file in the Go
    61          module's root directory.
    62  
    63          """,
    64          values = [
    65              "auto",
    66              "off",
    67              "on",
    68          ],
    69      ),
    70      "build_extra_args": attr.string_list(
    71          default = [],
    72          doc = """
    73          A list of additional command line arguments to pass to Gazelle when generating build files.
    74          """,
    75      ),
    76      "directives": attr.string_list(
    77          doc = """Gazelle configuration directives to use for this Go module's external repository.
    78  
    79          Each directive uses the same format as those that Gazelle
    80          accepts as comments in Bazel source files, with the
    81          directive name followed by optional arguments separated by
    82          whitespace.""",
    83      ),
    84  }
    85  
    86  def _fail_on_non_root_overrides(module_ctx, module, tag_class):
    87      if module.is_root:
    88          return
    89  
    90      # Isolated module extension usages only contain tags from a single module, so we can allow
    91      # overrides. This is a new feature in Bazel 6.3.0, earlier versions do not allow module usages
    92      # to be isolated.
    93      if getattr(module_ctx, "is_isolated", False):
    94          return
    95  
    96      if getattr(module.tags, tag_class):
    97          fail(_FORBIDDEN_OVERRIDE_TAG.format(
    98              tag_class = tag_class,
    99              module_name = module.name,
   100          ))
   101  
   102  def _fail_on_duplicate_overrides(path, module_name, overrides):
   103      if path in overrides:
   104          fail("Multiple overrides defined for Go module path \"{}\" in module \"{}\".".format(path, module_name))
   105  
   106  def _fail_on_unmatched_overrides(override_keys, resolutions, override_name):
   107      unmatched_overrides = [path for path in override_keys if path not in resolutions]
   108      if unmatched_overrides:
   109          fail("Some {} did not target a Go module with a matching path: {}".format(
   110              override_name,
   111              ", ".join(unmatched_overrides),
   112          ))
   113  
   114  def _check_directive(directive):
   115      if directive.startswith("gazelle:") and " " in directive and not directive[len("gazelle:"):][0].isspace():
   116          return
   117      fail("Invalid Gazelle directive: \"{}\". Gazelle directives must be of the form \"gazelle:key value\".".format(directive))
   118  
   119  def _get_override_or_default(specific_overrides, gazelle_default_attributes, default_path_overrides, path, default_value, attribute_name):
   120      # 1st: Check for user-provided specific overrides. If a specific override is found,
   121      # all of its attributes will be applied (even if left to the tag's default). This is to allow
   122      # users to override the gazelle_default_attributes tag back to the tag's default.
   123      #
   124      # This will also cause "build_file_generation" to default to "on" if a specific override is found.
   125      specific_override = specific_overrides.get(path)
   126      if specific_override and hasattr(specific_override, attribute_name):
   127          return getattr(specific_override, attribute_name)
   128  
   129      # 2nd. Check for default attributes provided by the user. This must be done before checking for
   130      # gazelle's defaults path overrides to prevent Gazelle from overriding a user-specified flag.
   131      #
   132      # This will also cause "build_file_generation" to default to "on" if default attributes are found.
   133      global_override_value = getattr(gazelle_default_attributes, attribute_name, None)
   134      if global_override_value:
   135          return global_override_value
   136  
   137      # 3rd: Check for default overrides for specific path.
   138      default_path_override = default_path_overrides.get(path)
   139      if default_path_override:
   140          return default_path_override
   141  
   142      # 4th. Return the default value if no override was found.
   143      # This will cause "build_file_generation" to default to "auto".
   144      return default_value
   145  
   146  def _get_directives(path, gazelle_overrides, gazelle_default_attributes):
   147      return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_DIRECTIVES_BY_PATH, path, [], "directives")
   148  
   149  def _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes):
   150      # The default value for build_file_generation is "auto" if no override is found, but will default to "on" if an override is found.
   151      return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_FILE_GENERATION_BY_PATH, path, "auto", "build_file_generation")
   152  
   153  def _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes):
   154      return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_EXTRA_ARGS_BY_PATH, path, [], "build_extra_args")
   155  
   156  def _get_patches(path, module_overrides):
   157      return _get_override_or_default(module_overrides, struct(), {}, path, [], "patches")
   158  
   159  def _get_patch_args(path, module_overrides):
   160      override = _get_override_or_default(module_overrides, struct(), {}, path, None, "patch_strip")
   161      return ["-p{}".format(override)] if override else []
   162  
   163  def _repo_name(importpath):
   164      path_segments = importpath.split("/")
   165      segments = reversed(path_segments[0].split(".")) + path_segments[1:]
   166      candidate_name = "_".join(segments).replace("-", "_")
   167      return "".join([c.lower() if c.isalnum() else "_" for c in candidate_name.elems()])
   168  
   169  def _is_dev_dependency(module_ctx, tag):
   170      if hasattr(tag, "_is_dev_dependency"):
   171          # Synthetic tags generated from go_deps.from_file have this "hidden" attribute.
   172          return tag._is_dev_dependency
   173  
   174      # This function is available in Bazel 6.2.0 and later. This is the same version that has
   175      # module_ctx.extension_metadata, so the return value of this function is not used if it is
   176      # not available.
   177      return module_ctx.is_dev_dependency(tag) if hasattr(module_ctx, "is_dev_dependency") else False
   178  
   179  def _intersperse_newlines(tags):
   180      return [tag for p in zip(tags, len(tags) * ["\n"]) for tag in p]
   181  
   182  # This function processes the gazelle_default_attributes tag for a given module and returns a struct
   183  # containing the attributes from _GAZELLE_ATTRS that are defined in the tag.
   184  def _process_gazelle_default_attributes(module_ctx):
   185      for module in module_ctx.modules:
   186          _fail_on_non_root_overrides(module_ctx, module, "gazelle_default_attributes")
   187  
   188      for module in module_ctx.modules:
   189          tags = module.tags.gazelle_default_attributes
   190          if not tags:
   191              continue
   192  
   193          if len(tags) > 1:
   194              fail(
   195                  "go_deps.gazelle_default_attributes: only one tag can be specified per module, got:\n",
   196                  *[t for p in zip(module.tags.gazelle_default_attributes, len(module.tags.gazelle_default_attributes) * ["\n"]) for t in p]
   197              )
   198  
   199          tag = tags[0]
   200          return struct(**{
   201              attr: getattr(tag, attr)
   202              for attr in _GAZELLE_ATTRS.keys()
   203              if hasattr(tag, attr)
   204          })
   205  
   206      return None
   207  
   208  # This function processes a given override type for a given module, checks for duplicate overrides
   209  # and inserts the override returned from the process_override_func into the overrides dict.
   210  def _process_overrides(module_ctx, module, override_type, overrides, process_override_func, additional_overrides = None):
   211      _fail_on_non_root_overrides(module_ctx, module, override_type)
   212      for override_tag in getattr(module.tags, override_type):
   213          _fail_on_duplicate_overrides(override_tag.path, module.name, overrides)
   214  
   215          # Some overrides conflict with other overrides. These can be specified in the
   216          # additional_overrides dict. If the override is in the additional_overrides dict, then fail.
   217          if additional_overrides:
   218              _fail_on_duplicate_overrides(override_tag.path, module.name, additional_overrides)
   219  
   220          overrides[override_tag.path] = process_override_func(override_tag)
   221  
   222  def _process_gazelle_override(gazelle_override_tag):
   223      for directive in gazelle_override_tag.directives:
   224          _check_directive(directive)
   225  
   226      return struct(**{
   227          attr: getattr(gazelle_override_tag, attr)
   228          for attr in _GAZELLE_ATTRS.keys()
   229          if hasattr(gazelle_override_tag, attr)
   230      })
   231  
   232  def _process_module_override(module_override_tag):
   233      return struct(
   234          patches = module_override_tag.patches,
   235          patch_strip = module_override_tag.patch_strip,
   236      )
   237  
   238  def _process_archive_override(archive_override_tag):
   239      return struct(
   240          urls = archive_override_tag.urls,
   241          sha256 = archive_override_tag.sha256,
   242          strip_prefix = archive_override_tag.strip_prefix,
   243          patches = archive_override_tag.patches,
   244          patch_strip = archive_override_tag.patch_strip,
   245      )
   246  
   247  def _go_repository_config_impl(ctx):
   248      repos = []
   249      for name, importpath in sorted(ctx.attr.importpaths.items()):
   250          repos.append(format_rule_call(
   251              "go_repository",
   252              name = name,
   253              importpath = importpath,
   254              module_name = ctx.attr.module_names.get(name),
   255              build_naming_convention = ctx.attr.build_naming_conventions.get(name),
   256          ))
   257  
   258      ctx.file("WORKSPACE", "\n".join(repos))
   259      ctx.file("BUILD.bazel", "exports_files(['WORKSPACE', 'config.json'])")
   260      ctx.file("go_env.bzl", content = "GO_ENV = " + repr(ctx.attr.go_env))
   261  
   262      # For use by @rules_go//go.
   263      ctx.file("config.json", content = json.encode_indent({
   264          "go_env": ctx.attr.go_env,
   265          "dep_files": ctx.attr.dep_files,
   266      }))
   267  
   268  _go_repository_config = repository_rule(
   269      implementation = _go_repository_config_impl,
   270      attrs = {
   271          "importpaths": attr.string_dict(mandatory = True),
   272          "module_names": attr.string_dict(mandatory = True),
   273          "build_naming_conventions": attr.string_dict(mandatory = True),
   274          "go_env": attr.string_dict(mandatory = True),
   275          "dep_files": attr.string_list(),
   276      },
   277  )
   278  
   279  def check_for_version_conflict(version, previous, module_tag, module_name_to_go_dot_mod_label, conflict_printer):
   280      """
   281      Check if duplicate modules have different versions, and fail with a useful error message if they do.
   282  
   283      Args:
   284          version: The version of the module.
   285          previous: The previous module object.
   286          module_tag: The module tag.
   287          module_name_to_go_dot_mod_label: A dictionary mapping module paths to go.mod labels.
   288          conflict_printer: a printer function to use for printing the error message, generally either print or fail.
   289      """
   290  
   291      if not previous or version == previous.version:
   292          # no previous module, so no possible error OR
   293          # version is the same, skip because we won't error
   294          return
   295  
   296      if hasattr(module_tag, "local_path"):
   297          # overrides are not considered for version conflicts
   298          return
   299  
   300      # When using go.work, duplicate dependency versions are possible.
   301      # This can cause issues, so we fail with a hopefully actionable error.
   302      current_label = module_tag._parent_label
   303  
   304      previous_label = previous.module_tag._parent_label
   305  
   306      corrective_measure = """To correct this:
   307      1. ensure that '{}' in all go.mod files is the same version.
   308      2. in the folders where you made changes run: bazel run @rules_go//go -- mod tidy
   309      3. at the workspace root run: bazel run @rules_go//go -- work sync.""".format(module_tag.path)
   310  
   311      message = """Multiple versions of {} found:
   312      - {} contains: {}
   313      - {} contains: {}
   314  {}""".format(module_tag.path, current_label, module_tag.version, previous_label, previous.module_tag.version, corrective_measure)
   315  
   316      conflict_printer(message)
   317  
   318  def _noop(_):
   319      pass
   320  
   321  # These repos are shared between the isolated and non-isolated instances of go_deps as they are
   322  # referenced directly by rules (go_proto_library) and would result in linker errors due to duplicate
   323  # packages if they were resolved separately.
   324  # When adding a new Go module to this list, make sure that:
   325  # 1. The corresponding repository is visible to the gazelle module via a use_repo directive.
   326  # 2. All transitive dependencies of the module are also in this list. Avoid adding module that have
   327  #    a large number of transitive dependencies.
   328  _SHARED_REPOS = [
   329      "github.com/golang/protobuf",
   330      "google.golang.org/protobuf",
   331  ]
   332  
   333  def _go_deps_impl(module_ctx):
   334      module_resolutions = {}
   335      sums = {}
   336      replace_map = {}
   337      bazel_deps = {}
   338  
   339      gazelle_default_attributes = _process_gazelle_default_attributes(module_ctx)
   340      archive_overrides = {}
   341      gazelle_overrides = {}
   342      module_overrides = {}
   343  
   344      root_versions = {}
   345      root_module_direct_deps = {}
   346      root_module_direct_dev_deps = {}
   347  
   348      first_module = module_ctx.modules[0]
   349      if first_module.is_root and first_module.name in ["gazelle", "rules_go"]:
   350          root_module_direct_deps["bazel_gazelle_go_repository_config"] = None
   351  
   352      outdated_direct_dep_printer = print
   353      go_env = {}
   354      dep_files = []
   355      debug_mode = False
   356      for module in module_ctx.modules:
   357          if len(module.tags.config) > 1:
   358              fail(
   359                  "Multiple \"go_deps.config\" tags defined in module \"{}\":\n".format(module.name),
   360                  *_intersperse_newlines(module.tags.config)
   361              )
   362  
   363          # Parse the go_deps.config tag of the root module only.
   364          for mod_config in module.tags.config:
   365              if not module.is_root:
   366                  continue
   367              check_direct_deps = mod_config.check_direct_dependencies
   368              if check_direct_deps == "off":
   369                  outdated_direct_dep_printer = _noop
   370              elif check_direct_deps == "warning":
   371                  outdated_direct_dep_printer = print
   372              elif check_direct_deps == "error":
   373                  outdated_direct_dep_printer = fail
   374              go_env = mod_config.go_env
   375              debug_mode = mod_config.debug_mode
   376  
   377          _process_overrides(module_ctx, module, "gazelle_override", gazelle_overrides, _process_gazelle_override)
   378          _process_overrides(module_ctx, module, "module_override", module_overrides, _process_module_override, archive_overrides)
   379          _process_overrides(module_ctx, module, "archive_override", archive_overrides, _process_archive_override, module_overrides)
   380  
   381          if len(module.tags.from_file) > 1:
   382              fail(
   383                  "Multiple \"go_deps.from_file\" tags defined in module \"{}\": {}".format(
   384                      module.name,
   385                      ", ".join([str(tag.go_mod) for tag in module.tags.from_file]),
   386                  ),
   387              )
   388  
   389          additional_module_tags = []
   390          from_file_tags = []
   391          module_name_to_go_dot_mod_label = {}
   392  
   393          for from_file_tag in module.tags.from_file:
   394              if bool(from_file_tag.go_work) == bool(from_file_tag.go_mod):
   395                  fail("go_deps.from_file tag must have either go_work or go_mod attribute, but not both.")
   396  
   397              if from_file_tag.go_mod:
   398                  from_file_tags.append(from_file_tag)
   399              elif from_file_tag.go_work:
   400                  if module.is_root != True:
   401                      fail("go_deps.from_file(go_work = '{}') tag can only be used from a root module but: '{}' is not a root module.".format(from_file_tag.go_work, module.name))
   402  
   403                  go_work = go_work_from_label(module_ctx, from_file_tag.go_work)
   404  
   405                  # this ensures go.work replacements are considered
   406                  additional_module_tags += [
   407                      with_replaced_or_new_fields(tag, _is_dev_dependency = False)
   408                      for tag in go_work.module_tags
   409                  ]
   410  
   411                  for entry, new_sum in sums_from_go_work(module_ctx, from_file_tag.go_work).items():
   412                      _safe_insert_sum(sums, entry, new_sum)
   413  
   414                  replace_map.update(go_work.replace_map)
   415                  from_file_tags = from_file_tags + go_work.from_file_tags
   416              else:
   417                  fail("Either \"go_mod\" or \"go_work\" must be specified in \"go_deps.from_file\" tags.")
   418  
   419          for from_file_tag in from_file_tags:
   420              module_path, module_tags_from_go_mod, go_mod_replace_map, module_name = deps_from_go_mod(module_ctx, from_file_tag.go_mod)
   421              module_name_to_go_dot_mod_label[module_name] = from_file_tag.go_mod
   422  
   423              # Collect the relative path of the root module's go.mod file if it lives in the main
   424              # repository.
   425              if module.is_root and not from_file_tag.go_mod.workspace_name:
   426                  go_mod = "go.mod"
   427                  if from_file_tag.go_mod.package:
   428                      go_mod = from_file_tag.go_mod.package + "/" + go_mod
   429                  dep_files.append(go_mod)
   430  
   431              is_dev_dependency = _is_dev_dependency(module_ctx, from_file_tag)
   432              additional_module_tags += [
   433                  with_replaced_or_new_fields(tag, _is_dev_dependency = is_dev_dependency)
   434                  for tag in module_tags_from_go_mod
   435              ]
   436  
   437              if module.is_root or getattr(module_ctx, "is_isolated", False):
   438                  # for the replace_map, first in wins
   439                  for mod_path, mod in go_mod_replace_map.items():
   440                      if not mod_path in replace_map:
   441                          replace_map[mod_path] = mod
   442              else:
   443                  # Register this Bazel module as providing the specified Go module. It participates
   444                  # in version resolution using its registry version, which uses a relaxed variant of
   445                  # semver that can however still be compared to strict semvers.
   446                  # An empty version string signals an override, which is assumed to be newer than any
   447                  # other version.
   448                  raw_version = _canonicalize_raw_version(module.version)
   449                  version = semver.to_comparable(raw_version, relaxed = True) if raw_version else _HIGHEST_VERSION_SENTINEL
   450                  if module_path not in bazel_deps or version > bazel_deps[module_path].version:
   451                      bazel_deps[module_path] = struct(
   452                          module_name = module.name,
   453                          repo_name = "@" + from_file_tag.go_mod.workspace_name,
   454                          version = version,
   455                          raw_version = raw_version,
   456                      )
   457  
   458              # Load all sums from transitively resolved `go.sum` files that have modules.
   459              if len(module_tags_from_go_mod) > 0:
   460                  for entry, new_sum in sums_from_go_mod(module_ctx, from_file_tag.go_mod).items():
   461                      _safe_insert_sum(sums, entry, new_sum)
   462  
   463          # Load sums from manually specified modules separately.
   464          for module_tag in module.tags.module:
   465              if module_tag.build_naming_convention:
   466                  fail("""The "build_naming_convention" attribute is no longer supported for "go_deps.module" tags. Use a "gazelle:go_naming_convention" directive via the "gazelle_override" tag's "directives" attribute instead.""")
   467              if module_tag.build_file_proto_mode:
   468                  fail("""The "build_file_proto_mode" attribute is no longer supported for "go_deps.module" tags. Use a "gazelle:proto" directive via the "gazelle_override" tag's "directives" attribute instead.""")
   469              sum_version = _canonicalize_raw_version(module_tag.version)
   470              _safe_insert_sum(sums, (module_tag.path, sum_version), module_tag.sum)
   471  
   472          # Parse the go_dep.module tags of all transitive dependencies and apply
   473          # Minimum Version Selection to resolve importpaths to Go module versions
   474          # and sums.
   475          #
   476          # Note: This applies Minimum Version Selection on the resolved
   477          # dependency graphs of all transitive Bazel module dependencies, which
   478          # is not what `go mod` does. But since this algorithm ends up using only
   479          # Go module versions that have been explicitly declared somewhere in the
   480          # full graph, we can assume that at that place all its required
   481          # transitive dependencies have also been declared - we may end up
   482          # resolving them to higher versions, but only compatible ones.
   483          paths = {}
   484  
   485          for module_tag in module.tags.module + additional_module_tags:
   486              if module_tag.path in bazel_deps:
   487                  continue
   488  
   489              raw_version = _canonicalize_raw_version(module_tag.version)
   490  
   491              # For modules imported from a go.sum, we know which ones are direct
   492              # dependencies and can thus only report implicit version upgrades
   493              # for direct dependencies. For manually specified go_deps.module
   494              # tags, we always report version upgrades unless users override with
   495              # the "indirect" attribute.
   496              if module.is_root and not module_tag.indirect:
   497                  root_versions[module_tag.path] = raw_version
   498                  if _is_dev_dependency(module_ctx, module_tag):
   499                      root_module_direct_dev_deps[_repo_name(module_tag.path)] = None
   500                  else:
   501                      root_module_direct_deps[_repo_name(module_tag.path)] = None
   502  
   503              version = semver.to_comparable(raw_version)
   504              previous = paths.get(module_tag.path)
   505  
   506              fail_on_version_conflict = any([x.fail_on_version_conflict for x in module.tags.from_file])
   507  
   508              conflict_printer = fail if fail_on_version_conflict else print
   509              check_for_version_conflict(version, previous, module_tag, module_name_to_go_dot_mod_label, conflict_printer)
   510              paths[module_tag.path] = struct(version = version, module_tag = module_tag)
   511  
   512              if module_tag.path not in module_resolutions or version > module_resolutions[module_tag.path].version:
   513                  to_path = None
   514                  local_path = None
   515  
   516                  if module_tag.path in replace_map:
   517                      replacement = replace_map[module_tag.path]
   518  
   519                      to_path = replacement.to_path
   520                      local_path = replacement.local_path
   521  
   522                  module_resolutions[module_tag.path] = struct(
   523                      repo_name = _repo_name(module_tag.path),
   524                      version = version,
   525                      raw_version = raw_version,
   526                      to_path = to_path,
   527                      local_path = local_path,
   528                  )
   529  
   530      _fail_on_unmatched_overrides(archive_overrides.keys(), module_resolutions, "archive_overrides")
   531      _fail_on_unmatched_overrides(gazelle_overrides.keys(), module_resolutions, "gazelle_overrides")
   532      _fail_on_unmatched_overrides(module_overrides.keys(), module_resolutions, "module_overrides")
   533  
   534      # All `replace` directives are applied after version resolution.
   535      # We can simply do this by checking the replace paths' existence
   536      # in the module resolutions and swapping out the entry.
   537      for path, replace in replace_map.items():
   538          if path in module_resolutions:
   539              # If the replace directive specified a version then we only
   540              # apply it if the versions match.
   541              if replace.from_version:
   542                  comparable_from_version = semver.to_comparable(replace.from_version)
   543                  if module_resolutions[path].version != comparable_from_version:
   544                      continue
   545  
   546              new_version = semver.to_comparable(replace.version)
   547              module_resolutions[path] = with_replaced_or_new_fields(
   548                  module_resolutions[path],
   549                  replace = replace.to_path,
   550                  version = new_version,
   551                  raw_version = replace.version,
   552              )
   553              if path in root_versions:
   554                  if replace != replace.to_path:
   555                      # If the root module replaces a Go module with a completely different one, do
   556                      # not ever report an implicit version upgrade.
   557                      root_versions.pop(path)
   558                  else:
   559                      root_versions[path] = replace.version
   560  
   561      for path, bazel_dep in bazel_deps.items():
   562          # We can't apply overrides to Bazel dependencies and thus fall back to using the Go module.
   563          if path in archive_overrides or path in gazelle_overrides or path in module_overrides or path in replace_map:
   564              continue
   565  
   566          # Only use the Bazel module if it is at least as high as the required Go module version.
   567          if path in module_resolutions and bazel_dep.version < module_resolutions[path].version:
   568              outdated_direct_dep_printer(
   569                  "Go module \"{path}\" is provided by Bazel module \"{bazel_module}\" in version {bazel_dep_version}, but requested at higher version {go_version} via Go requirements. Consider adding or updating an appropriate \"bazel_dep\" to ensure that the Bazel module is used to provide the Go module.".format(
   570                      path = path,
   571                      bazel_module = bazel_dep.module_name,
   572                      bazel_dep_version = bazel_dep.raw_version,
   573                      go_version = module_resolutions[path].raw_version,
   574                  ),
   575              )
   576              continue
   577  
   578          # TODO: We should update root_versions if the bazel_dep is a direct dependency of the root
   579          #   module. However, we currently don't have a way to determine that.
   580          module_resolutions[path] = bazel_dep
   581  
   582      for path, root_version in root_versions.items():
   583          if semver.to_comparable(root_version) < module_resolutions[path].version:
   584              outdated_direct_dep_printer(
   585                  "For Go module \"{path}\", the root module requires module version v{root_version}, but got v{resolved_version} in the resolved dependency graph.".format(
   586                      path = path,
   587                      root_version = root_version,
   588                      resolved_version = module_resolutions[path].raw_version,
   589                  ),
   590              )
   591  
   592      for path, module in module_resolutions.items():
   593          if hasattr(module, "module_name"):
   594              # Do not create a go_repository for a Go module provided by a bazel_dep.
   595              root_module_direct_deps.pop(_repo_name(path), default = None)
   596              root_module_direct_dev_deps.pop(_repo_name(path), default = None)
   597              continue
   598          if getattr(module_ctx, "is_isolated", False) and path in _SHARED_REPOS:
   599              # Do not create a go_repository for a dep shared with the non-isolated instance of
   600              # go_deps.
   601              continue
   602          go_repository_args = {
   603              "name": module.repo_name,
   604              "importpath": path,
   605              "build_directives": _get_directives(path, gazelle_overrides, gazelle_default_attributes),
   606              "build_file_generation": _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes),
   607              "build_extra_args": _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes),
   608              "patches": _get_patches(path, module_overrides),
   609              "patch_args": _get_patch_args(path, module_overrides),
   610              "debug_mode": debug_mode,
   611          }
   612  
   613          archive_override = archive_overrides.get(path)
   614          if archive_override:
   615              go_repository_args.update({
   616                  "urls": archive_override.urls,
   617                  "strip_prefix": archive_override.strip_prefix,
   618                  "sha256": archive_override.sha256,
   619                  "patches": _get_patches(path, archive_overrides),
   620                  "patch_args": _get_patch_args(path, archive_overrides),
   621              })
   622          elif module.local_path:
   623              go_repository_args.update({
   624                  # the version is now meaningless
   625                  "version": None,
   626                  "local_path": module.local_path,
   627              })
   628          else:
   629              go_repository_args.update({
   630                  "sum": _get_sum_from_module(path, module, sums),
   631                  "replace": getattr(module, "replace", None),
   632                  "version": "v" + module.raw_version,
   633              })
   634  
   635          go_repository(**go_repository_args)
   636  
   637      # Create a synthetic WORKSPACE file that lists all Go repositories created
   638      # above and contains all the information required by Gazelle's -repo_config
   639      # to generate BUILD files for external Go modules. This skips the need to
   640      # run generate_repo_config. Only "importpath" and "build_naming_convention"
   641      # are relevant.
   642      _go_repository_config(
   643          name = "bazel_gazelle_go_repository_config",
   644          importpaths = {
   645              module.repo_name: path
   646              for path, module in module_resolutions.items()
   647          },
   648          module_names = {
   649              info.repo_name: info.module_name
   650              for path, info in bazel_deps.items()
   651          },
   652          build_naming_conventions = drop_nones({
   653              module.repo_name: get_directive_value(
   654                  _get_directives(path, gazelle_overrides, gazelle_default_attributes),
   655                  "go_naming_convention",
   656              )
   657              for path, module in module_resolutions.items()
   658          }),
   659          go_env = go_env,
   660          dep_files = dep_files,
   661      )
   662  
   663      return extension_metadata(
   664          module_ctx,
   665          root_module_direct_deps = root_module_direct_deps.keys(),
   666          # If a Go module appears as both a dev and a non-dev dependency, it has to be imported as a
   667          # non-dev dependency.
   668          root_module_direct_dev_deps = {
   669              repo_name: None
   670              for repo_name in root_module_direct_dev_deps.keys()
   671              if repo_name not in root_module_direct_deps
   672          }.keys(),
   673          reproducible = True,
   674      )
   675  
   676  def _get_sum_from_module(path, module, sums):
   677      entry = (path, module.raw_version)
   678      if hasattr(module, "replace"):
   679          entry = (module.replace, module.raw_version)
   680  
   681      if entry not in sums:
   682          if module.raw_version == COMPARES_HIGHEST_SENTINEL:
   683              # replacement have no sums, so we can skip this
   684              return None
   685          elif module.local_path == None:
   686              fail("No sum for {}@{} from {} found. You may need to run: bazel run @rules_go//go -- mod tidy".format(path, module.raw_version, "parent-label-todo"))  #module.parent_label))
   687  
   688      return sums[entry]
   689  
   690  def _safe_insert_sum(sums, entry, new_sum):
   691      if entry in sums and new_sum != sums[entry]:
   692          fail("Multiple mismatching sums for {}@{} found: {} vs {}".format(entry[0], entry[1], new_sum, sums[entry]))
   693      sums[entry] = new_sum
   694  
   695  def _canonicalize_raw_version(raw_version):
   696      if raw_version.startswith("v"):
   697          return raw_version[1:]
   698      return raw_version
   699  
   700  _config_tag = tag_class(
   701      attrs = {
   702          "check_direct_dependencies": attr.string(
   703              values = ["off", "warning", "error"],
   704          ),
   705          "go_env": attr.string_dict(
   706              doc = "The environment variables to use when fetching Go dependencies or running the `@rules_go//go` tool.",
   707          ),
   708          "debug_mode": attr.bool(doc = "Whether or not to print stdout and stderr messages from gazelle", default = False),
   709      },
   710  )
   711  
   712  _from_file_tag = tag_class(
   713      attrs = {
   714          "go_mod": attr.label(mandatory = False),
   715          "go_work": attr.label(mandatory = False),
   716          "fail_on_version_conflict": attr.bool(
   717              default = True,
   718              doc = "Fail if duplicate modules have different versions",
   719          ),
   720      },
   721  )
   722  
   723  _module_tag = tag_class(
   724      attrs = {
   725          "path": attr.string(mandatory = True),
   726          "version": attr.string(mandatory = True),
   727          "sum": attr.string(),
   728          "indirect": attr.bool(
   729              doc = """Whether this Go module is an indirect dependency.""",
   730              default = False,
   731          ),
   732          "build_naming_convention": attr.string(doc = """Removed, do not use""", default = ""),
   733          "build_file_proto_mode": attr.string(doc = """Removed, do not use""", default = ""),
   734          "local_path": attr.string(
   735              doc = """For when a module is replaced by one residing in a local directory path """,
   736              mandatory = False,
   737          ),
   738      },
   739  )
   740  
   741  _archive_override_tag = tag_class(
   742      attrs = {
   743          "path": attr.string(
   744              doc = """The Go module path for the repository to be overridden.
   745  
   746              This module path must be defined by other tags in this
   747              extension within this Bazel module.""",
   748              mandatory = True,
   749          ),
   750          "urls": attr.string_list(
   751              doc = """A list of HTTP(S) URLs where an archive containing the project can be
   752              downloaded. Bazel will attempt to download from the first URL; the others
   753              are mirrors.""",
   754          ),
   755          "strip_prefix": attr.string(
   756              doc = """If the repository is downloaded via HTTP (`urls` is set), this is a
   757              directory prefix to strip. See [`http_archive.strip_prefix`].""",
   758          ),
   759          "sha256": attr.string(
   760              doc = """If the repository is downloaded via HTTP (`urls` is set), this is the
   761              SHA-256 sum of the downloaded archive. When set, Bazel will verify the archive
   762              against this sum before extracting it.""",
   763          ),
   764          "patches": attr.label_list(
   765              doc = "A list of patches to apply to the repository *after* gazelle runs.",
   766          ),
   767          "patch_strip": attr.int(
   768              default = 0,
   769              doc = "The number of leading path segments to be stripped from the file name in the patches.",
   770          ),
   771      },
   772      doc = "Override the default source location on a given Go module in this extension.",
   773  )
   774  
   775  _gazelle_override_tag = tag_class(
   776      attrs = {
   777          "path": attr.string(
   778              doc = """The Go module path for the repository to be overridden.
   779  
   780              This module path must be defined by other tags in this
   781              extension within this Bazel module.""",
   782              mandatory = True,
   783          ),
   784      } | _GAZELLE_ATTRS,
   785      doc = "Override Gazelle's behavior on a given Go module defined by other tags in this extension.",
   786  )
   787  
   788  _gazelle_default_attributes_tag = tag_class(
   789      attrs = _GAZELLE_ATTRS,
   790      doc = "Override Gazelle's default attribute values for all modules in this extension.",
   791  )
   792  
   793  _module_override_tag = tag_class(
   794      attrs = {
   795          "path": attr.string(
   796              doc = """The Go module path for the repository to be overridden.
   797  
   798              This module path must be defined by other tags in this
   799              extension within this Bazel module.""",
   800              mandatory = True,
   801          ),
   802          "patches": attr.label_list(
   803              doc = "A list of patches to apply to the repository *after* gazelle runs.",
   804          ),
   805          "patch_strip": attr.int(
   806              default = 0,
   807              doc = "The number of leading path segments to be stripped from the file name in the patches.",
   808          ),
   809      },
   810      doc = "Apply patches to a given Go module defined by other tags in this extension.",
   811  )
   812  
   813  go_deps = module_extension(
   814      _go_deps_impl,
   815      tag_classes = {
   816          "archive_override": _archive_override_tag,
   817          "config": _config_tag,
   818          "from_file": _from_file_tag,
   819          "gazelle_override": _gazelle_override_tag,
   820          "gazelle_default_attributes": _gazelle_default_attributes_tag,
   821          "module": _module_tag,
   822          "module_override": _module_override_tag,
   823      },
   824  )